API之 preconnect
preconnect
函数向浏览器提供一个提示,告诉它应该打开到给定服务器的连接。如果浏览器选择这样做,则可以加快从该服务器加载资源的速度。
preconnect(href)
一、使用例子
import { preconnect } from 'react-dom';
function AppRoot() {
preconnect("https://example.com");
return ...;
}
import { preconnect } from 'react-dom';
function CallToAction() {
const onClick = () => {
preconnect('http://example.com');
startWizard();
}
return (
<button onClick={onClick}>Start Wizard</button>
);
}
二、源码
function preconnect(href: string, options?: ?PreconnectOptions) {
if (typeof href === 'string') {
const crossOrigin = options
? getCrossOriginString(options.crossOrigin)
: null;
ReactDOMSharedInternals.d /* ReactDOMCurrentDispatcher */
.C(/* preconnect */ href, crossOrigin);
}
}
ReactDOMSharedInternals.d.C
preconnect
函数的主要功能是发起一个 preconnect
操作,用于提前建立与指定 href
对应的服务器连接。
function preconnect(href: string, crossOrigin?: ?CrossOriginEnum) {
previousDispatcher.C(/* preconnect */ href, crossOrigin);
preconnectAs('preconnect', href, crossOrigin);
}
API之 prefetchDNS
prefetchDNS
函数向浏览器提供一个提示,告诉它应该查找给定服务器的 IP 地址。如果浏览器选择这样做,则可以加快从该服务器加载资源的速度。
一、使用示范
prefetchDNS
允许提前查找期望从中加载资源的服务器的 IP。
prefetchDNS("https://example.com");
二、源码分析
function prefetchDNS(href: string) {
if (typeof href === 'string') {
ReactDOMSharedInternals.d /* ReactDOMCurrentDispatcher */
.D(/* prefetchDNS */ href);
}
}
ReactDOMSharedInternals.d.D
prefetchDNS
函数的主要作用是预获取指定 href
对应的 DNS 信息。
function prefetchDNS(href: string) {
previousDispatcher.D(/* prefetchDNS */ href);
preconnectAs('dns-prefetch', href, null);
}
preconnectAs
preconnectAs
函数用于创建 rel="preconnect"
或 rel="dns-prefetch"
的 link
元素,以提前建立与目标服务器的连接或解析 DNS,从而优化后续资源加载的性能。它会检查是否已存在相同配置的预连接标签,避免重复创建。
function preconnectAs(
rel: 'preconnect' | 'dns-prefetch',
href: string,
crossOrigin: ?CrossOriginEnum,
) {
// 获取document
const ownerDocument = getGlobalDocument();
if (ownerDocument && typeof href === 'string' && href) {
const limitedEscapedHref =
escapeSelectorAttributeValueInsideDoubleQuotes(href);
//
let key = `link[rel="${rel}"][href="${limitedEscapedHref}"]`;
if (typeof crossOrigin === 'string') {
key += `[crossorigin="${crossOrigin}"]`;
}
if (!preconnectsSet.has(key)) {
preconnectsSet.add(key);
const preconnectProps = {rel, crossOrigin, href};
if (null === ownerDocument.querySelector(key)) {
const instance = ownerDocument.createElement('link');
// 设置link元素的属性
setInitialProperties(instance, 'link', preconnectProps);
// 标记可提升
markNodeAsHoistable(instance);
// 往head元素中添加子元素link标签
(ownerDocument.head: any).appendChild(instance);
}
}
}
}
类型 | 作用 | 使用场景 |
preconnect | 提前建立完成连接。(包括dns解析、tcp握手和 TLS协商) | 确定会理解加载跨域资源(如字体、API) |
dns-prefetch | 仅提前解析dns,不建立TCP、TLS连接。 | 可能会在未来加载的资源(如广告、第三方内容),减少dns解析延迟。 |
API之 preinit
preinit
函数向浏览器提供一个提示,告诉它应该开始下载并执行给定的资源,这可以节省时间。preinit
的脚本在下载完成后执行。预初始化的样式表被插入到文档中,这会使它们立即生效。
preinit(href, options)
参数
href
:字符串,要下载并执行的资源的 URL。options
:对象,可以包含以下属性:as
:必需的字符串,表示资源的类型,可能的值包括script
与style
。precedence
:字符串,与样式表一起使用时必需。指定样式表相对于其他样式表的插入位置。具有较高优先级的样式表可以覆盖具有较低优先级的样式表,可能的值包括reset
、low
、medium
与high
。crossOrigin
:字符串,表示要使用的 CORS 策略,可能的值为anonymous
与use-credentials
。integrity
:字符串,为资源的加密哈希,用于 验证其真实性。nonce
:字符串,表示使用严格内容安全策略时允许资源的 加密随机数。fetchPriority
:字符串,表示建议获取资源的相对优先级,可能的值为auto
(默认值)、high
与low
。
一、使用例子
1、加载script资源
import { preinit } from 'react-dom';
function AppRoot() {
preinit("https://example.com/script.js", {as: "script"});
return ...;
}
2、加载style样式资源
import { preinit } from 'react-dom';
function AppRoot() {
preinit("https://example.com/style.css", {as: "style", precedence: "medium"});
return ...;
}
3、在事件程序中使用加载资源
import { preinit } from 'react-dom';
function CallToAction() {
const onClick = () => {
preinit("https://example.com/wizardStyles.css", {as: "style"});
startWizard();
}
return (
<button onClick={onClick}>Start Wizard</button>
);
}
二、源码
function preinit(href: string, options: PreinitOptions) {
if (typeof href === 'string' && options && typeof options.as === 'string') {
// 获取要下载的资源类型
const as = options.as;
// 获取crossorigin属性的值,可能为use-credentials、空字符串或undefined
const crossOrigin = getCrossOriginStringAs(as, options.crossOrigin);
// 资源加密哈希
const integrity =
typeof options.integrity === 'string' ? options.integrity : undefined;
// 资源优先级
const fetchPriority =
typeof options.fetchPriority === 'string'
? options.fetchPriority
: undefined;
// 资源为样式表
if (as === 'style') {
ReactDOMSharedInternals.d /* ReactDOMCurrentDispatcher */
.S(
/* preinitStyle */
href,// 资源链接
typeof options.precedence === 'string'
? options.precedence
: undefined,// 资源优先级
{
crossOrigin,//
integrity,//
fetchPriority,//
},
);
// 资源为script脚本
} else if (as === 'script') {
ReactDOMSharedInternals.d /* ReactDOMCurrentDispatcher */
.X(/* preinitScript */ href, {
crossOrigin,
integrity,
fetchPriority,
nonce: typeof options.nonce === 'string' ? options.nonce : undefined,//安全策略随机数
});
}
}
}
ReactDOMSharedInternals.d.S(preinitStyle)
preinitStyle
函数的主要功能是预处理样式资源。它会检查指定的样式资源(通过 href
标识)是否已存在于全局文档的样式资源中。如果不存在,会创建一个新的样式资源实例(link
元素),设置其属性,并插入到文档中,同时缓存该资源的相关信息。
function preinitStyle(
href: string,
precedence: ?string,
options?: ?PreinitStyleOptions,
) {
previousDispatcher.S(/* preinitStyle */ href, precedence, options);
// 获取document对象
const ownerDocument = getGlobalDocument();
if (ownerDocument && href) {
// 获取样式表信息,map类型
const styles = getResourcesFromRoot(ownerDocument).hoistableStyles;
// 将href解析返回 格式为‘href=xxxxx’
const key = getStyleKey(href);
precedence = precedence || 'default';
// Check if this resource already exists
let resource = styles.get(key);
if (resource) {
// We can early return. The resource exists and there is nothing
// more to do
return;
}
// 初始化资源
const state = {
loading: NotLoaded,
preload: null,
};
// Attempt to hydrate instance from DOM
// 查询选取器
let instance: null | Instance = ownerDocument.querySelector(
// `link[rel="stylesheet"][${key}]`
getStylesheetSelectorFromKey(key),
);
if (instance) {
// 资源已加载并插入
state.loading = Loaded | Inserted;
} else {
// Construct a new instance and insert it
// 处理link相关属性
const stylesheetProps = Object.assign(
({
rel: 'stylesheet',
href,
'data-precedence': precedence,
}: StylesheetProps),
options,
);
const preloadProps = preloadPropsMap.get(key);
if (preloadProps) {
// 设置stylesheetProps对象中的属性,如title,crossOrigin,refferPolicy
adoptPreloadPropsForStylesheet(stylesheetProps, preloadProps);
}
// 创建link标签
const link = (instance = ownerDocument.createElement('link'));
//标记
markNodeAsHoistable(link);
// 设置属性
setInitialProperties(link, 'link', stylesheetProps);
// 为link设置promise
(link: any)._p = new Promise((resolve, reject) => {
link.onload = resolve;
link.onerror = reject;
});
// 处理 load 和 error 事件
link.addEventListener('load', () => {
state.loading |= Loaded;
});
link.addEventListener('error', () => {
state.loading |= Errored;
});
state.loading |= Inserted;
// 插入文档
insertStylesheet(instance, precedence, ownerDocument);
}
// Construct a Resource and cache it
resource = {
type: 'stylesheet',
instance,
count: 1,
state,
};
// 缓存资源
styles.set(key, resource);
return;
}
}
const preloadPropsMap: Map<string, PreloadProps | PreloadModuleProps> =
new Map();
type LoadingState = number;
const NotLoaded = /* */ 0b000;//0 未加载
const Loaded = /* */ 0b001;//1 加载
const Errored = /* */ 0b010;//2 出错
const Settled = /* */ 0b011;//3
const Inserted = /* */ 0b100;//4 插入
type StylesheetProps = {
rel: 'stylesheet',
href: string,
'data-precedence': string,
[string]: mixed,
};
function adoptPreloadPropsForStylesheet(
stylesheetProps: StylesheetProps,
preloadProps: PreloadProps | PreloadModuleProps,
): void {
if (stylesheetProps.crossOrigin == null)
stylesheetProps.crossOrigin = preloadProps.crossOrigin;
if (stylesheetProps.referrerPolicy == null)
stylesheetProps.referrerPolicy = preloadProps.referrerPolicy;
if (stylesheetProps.title == null) stylesheetProps.title = preloadProps.title;
}
ReactDOMSharedInternals.d.X(preinitScript)
preinitScript
函数的主要功能是预初始化 JavaScript 脚本资源。它会检查指定 src
的脚本是否已存在于文档中,如果不存在则创建并插入一个新的 script
标签,同时缓存该资源的相关信息。该函数支持从现有 DOM 中复用脚本实例,也能处理预加载属性,并确保脚本以异步方式加载。
function preinitScript(src: string, options?: ?PreinitScriptOptions) {
previousDispatcher.X(/* preinitScript */ src, options);
// 获取document对象
const ownerDocument = getGlobalDocument();
if (ownerDocument && src) {
// 获取资源
const scripts = getResourcesFromRoot(ownerDocument).hoistableScripts;
// src=xxxx
const key = getScriptKey(src);
// Check if this resource already exists
let resource = scripts.get(key);
if (resource) {
// We can early return. The resource exists and there is nothing
// more to do
return;
}
// Attempt to hydrate instance from DOM
let instance: null | Instance = ownerDocument.querySelector(
getScriptSelectorFromKey(key),
);
if (!instance) {
// Construct a new instance and insert it
const scriptProps = Object.assign(
({
src,
async: true,// 异步加载
}: ScriptProps),
options,
);
// Adopt certain preload props
const preloadProps = preloadPropsMap.get(key);
if (preloadProps) {
// 设置属性integrity、referrerPolicy、crossOrigin
adoptPreloadPropsForScript(scriptProps, preloadProps);
}
// 创建script标签
instance = ownerDocument.createElement('script');
// 打标记
markNodeAsHoistable(instance);
// 设置属性
setInitialProperties(instance, 'link', scriptProps);
// 添加
(ownerDocument.head: any).appendChild(instance);
}
// Construct a Resource and cache it
resource = {
type: 'script',
instance,
count: 1,
state: null,
};
scripts.set(key, resource);
return;
}
}
function getScriptSelectorFromKey(key: string): string {
return 'script[async]' + key;
}
function adoptPreloadPropsForScript(
scriptProps: ScriptProps,
preloadProps: PreloadProps | PreloadModuleProps,
): void {
if (scriptProps.crossOrigin == null)
scriptProps.crossOrigin = preloadProps.crossOrigin;
if (scriptProps.referrerPolicy == null)
scriptProps.referrerPolicy = preloadProps.referrerPolicy;
if (scriptProps.integrity == null)
scriptProps.integrity = preloadProps.integrity;
}
API之 preinitModule
preinitModule
函数向浏览器提供一个提示,告诉它应该开始下载并执行给定的模块,这可以节省时间。预初始化的模块在下载完成后执行。
preinitModule(href, options)
参数
href
:字符串,要下载并执行的模块的 URL。options
:对象,可以包含以下属性:
一、使用示范
import { preinitModule } from 'react-dom';
function AppRoot() {
preinitModule("https://example.com/module.js", {as: "script"});
return ...;
}
import { preinitModule } from 'react-dom';
function CallToAction() {
const onClick = () => {
preinitModule("https://example.com/module.js", {as: "script"});
startWizard();
}
return (
<button onClick={onClick}>Start Wizard</button>
);
}
二、源码分析
function preinitModule(href: string, options?: ?PreinitModuleOptions) {
if (typeof href === 'string') {
if (typeof options === 'object' && options !== null) {
if (options.as == null || options.as === 'script') {
const crossOrigin = getCrossOriginStringAs(
options.as,
options.crossOrigin,
);
ReactDOMSharedInternals.d /* ReactDOMCurrentDispatcher */
.M(/* preinitModuleScript */ href, {
crossOrigin,
integrity:
typeof options.integrity === 'string'
? options.integrity
: undefined,
nonce:
typeof options.nonce === 'string' ? options.nonce : undefined,
});
}
} else if (options == null) {
ReactDOMSharedInternals.d /* ReactDOMCurrentDispatcher */
.M(/* preinitModuleScript */ href);
}
}
}
ReactDOMSharedInternals.d.M
preinitModuleScript
函数的核心功能是预初始化 ES6 模块脚本资源。它会检查指定 src
的模块脚本是否已存在于文档中,如果不存在则创建并插入一个新的 script
标签(设置为 type="module"
),同时缓存该资源的相关信息。
function preinitModuleScript(
src: string,
options?: ?PreinitModuleScriptOptions,
) {
previousDispatcher.M(/* preinitModuleScript */ src, options);
// 获取document对象
const ownerDocument = getGlobalDocument();
if (ownerDocument && src) {
// 获取资源
const scripts = getResourcesFromRoot(ownerDocument).hoistableScripts;
const key = getScriptKey(src);
// Check if this resource already exists
let resource = scripts.get(key);
if (resource) {
// We can early return. The resource exists and there is nothing
// more to do
return;
}
// Attempt to hydrate instance from DOM
let instance: null | Instance = ownerDocument.querySelector(
getScriptSelectorFromKey(key),
);
if (!instance) {
// Construct a new instance and insert it
const scriptProps = Object.assign(
({
src,
async: true,//异步加载
type: 'module',// 模块
}: ScriptProps),
options,
);
// Adopt certain preload props
const preloadProps = preloadPropsMap.get(key);
if (preloadProps) {
// 如果存在预加载属性 preloadProps,则将其应用到脚本属性中(如 integrity、referrerPolicy、crossOrigin 等)。
adoptPreloadPropsForScript(scriptProps, preloadProps);
}
instance = ownerDocument.createElement('script');
// 标记可提升
markNodeAsHoistable(instance);
setInitialProperties(instance, 'link', scriptProps);
(ownerDocument.head: any).appendChild(instance);
}
// Construct a Resource and cache it
resource = {
type: 'script',
instance,
count: 1,
state: null,
};
scripts.set(key, resource);
return;
}
}
API之 preload
preload
函数的主要作用是对资源进行预加载操作。它会先对传入的参数进行有效性检查,确保参数类型符合要求,然后触发资源的预加载。
preload(href, options)
参数
- href:字符串,要下载的资源的 URL。
- options:对象,可以包含以下属性:
as
:必需的字符串,表示资源的类型,可能的值 包括 audio、document、embed、fetch、font、image、object、script、style、track、video 与 worker。crossOrigin
:字符串,表示要使用的 CORS 策略,可能的值为 anonymous 与 use-credentials。当 as 设置为 "fetch" 时是必需的。referrerPolicy
:字符串,表示在获取时发送的 referer 请求头,可能的值为 no-referrer-when-downgrade(默认值)、no-referrer、origin、origin-when-cross-origin 与 unsafe-url。integrity
:字符串,为资源的加密哈希,用于 验证其真实性。type
:字符串,表示资源的 MIME 类型。nonce
:字符串,表示使用严格内容安全策略时允许资源的 加密随机数。fetchPriority
:字符串,为获取资源建议的相对优先级,可能的值为 auto(默认值)、high 与 low。imageSrcSet
:字符串,仅与 as: "image" 一起使用,用于指定 图像的源集。imageSizes
:字符串,仅与 as: "image" 一起使用,用于指定 图像的尺寸。
一、使用示范
- 渲染时进行预加载
- 在事件处理程序中预加载
preload 可以预获取期望使用的资源,比如样式表、字体或外部脚本。
preload("https://example.com/font.woff2", {as: "font"});
import { preload } from 'react-dom';
function AppRoot() {
preload("https://example.com/style.css", {as: "style"});
preload("https://example.com/font.woff2", {as: "font"});
return ...;
}
二、源码
function preload(href: string, options: PreloadOptions) {
if (
typeof href === 'string' &&
typeof options === 'object' &&
options !== null &&
typeof options.as === 'string'
) {
// as:从 options 对象中提取 as 属性的值,该属性通常用于指定预加载资源的类型,如 'script'、'style' 等。
const as = options.as;
// 调用 getCrossOriginStringAs 函数,根据 as 和 options.crossOrigin 生成跨域相关的字符串。
const crossOrigin = getCrossOriginStringAs(as, options.crossOrigin);
ReactDOMSharedInternals.d /* ReactDOMCurrentDispatcher */
.L(/* preload */ href, as, {
crossOrigin,
integrity:
typeof options.integrity === 'string' ? options.integrity : undefined,
nonce: typeof options.nonce === 'string' ? options.nonce : undefined,
type: typeof options.type === 'string' ? options.type : undefined,
fetchPriority:
typeof options.fetchPriority === 'string'
? options.fetchPriority
: undefined,
referrerPolicy:
typeof options.referrerPolicy === 'string'
? options.referrerPolicy
: undefined,
imageSrcSet:
typeof options.imageSrcSet === 'string'
? options.imageSrcSet
: undefined,
imageSizes:
typeof options.imageSizes === 'string'
? options.imageSizes
: undefined,
media: typeof options.media === 'string' ? options.media : undefined,
});
}
}
ReactDOMSharedInternals.d.L
preload
函数主要用于预加载资源,通过创建 rel="preload"
的 link
元素来实现。它会根据资源的类型(as
)和相关选项(options
)生成合适的选择器和属性,检查资源是否已存在于文档中,若不存在则插入新的 link
元素进行预加载,同时处理 Safari 中关于图片 imageSrcSet
的特殊情况。
function preload(href: string, as: string, options?: ?PreloadImplOptions) {
previousDispatcher.L(/* preload */ href, as, options);
// 获取document对象
const ownerDocument = getGlobalDocument();
if (ownerDocument && href && as) {
// 预加载选择器
let preloadSelector = `link[rel="preload"][as="${escapeSelectorAttributeValueInsideDoubleQuotes(
as,
)}"]`;
// 当资源类型为image时,处理
if (as === 'image') {
if (options && options.imageSrcSet) {
preloadSelector += `[imagesrcset="${escapeSelectorAttributeValueInsideDoubleQuotes(
options.imageSrcSet,
)}"]`;
if (typeof options.imageSizes === 'string') {
preloadSelector += `[imagesizes="${escapeSelectorAttributeValueInsideDoubleQuotes(
options.imageSizes,
)}"]`;
}
} else {
preloadSelector += `[href="${escapeSelectorAttributeValueInsideDoubleQuotes(
href,
)}"]`;
}
} else {
// 对于非图片类型的资源,直接向选择器中添加 href 属性值。
preloadSelector += `[href="${escapeSelectorAttributeValueInsideDoubleQuotes(
href,
)}"]`;
}
let key = preloadSelector;
switch (as) {
case 'style':
// 当资源类型为 style 时,将 key 更新为 getStyleKey(href) 的返回值
key = getStyleKey(href);
break;
case 'script':
// 当资源类型为 script 时,将 key 更新为 getScriptKey(href) 的返回值
key = getScriptKey(href);
break;
}
if (!preloadPropsMap.has(key)) {
// 创建预加载属性
const preloadProps = Object.assign(
({
rel: 'preload',
href:
as === 'image' && options && options.imageSrcSet ? undefined : href,
as,
}: PreloadProps),
options,
);
preloadPropsMap.set(key, preloadProps);
if (null === ownerDocument.querySelector(preloadSelector)) {
if (
as === 'style' &&
ownerDocument.querySelector(getStylesheetSelectorFromKey(key))
) {
// 若资源类型为 style 且已存在对应样式表(通过 getStylesheetSelectorFromKey(key) 检查),则返回,不进行预加载。
// We already have a stylesheet for this key. We don't need to preload it.
return;
} else if (
as === 'script' &&
ownerDocument.querySelector(getScriptSelectorFromKey(key))
) {
// 若资源类型为 script 且已存在对应脚本(通过 getScriptSelectorFromKey(key) 检查),则返回,不进行预加载。
// We already have a stylesheet for this key. We don't need to preload it.
return;
}
// 创建link元素
const instance = ownerDocument.createElement('link');
// 设置初始属性
setInitialProperties(instance, 'link', preloadProps);
// 标记可提升
markNodeAsHoistable(instance);
// 往head标签中插入子节点
(ownerDocument.head: any).appendChild(instance);
}
}
}
}
API之 preloadModule
preloadModule
可以急切地预获取期望使用的 ESM 模块。
preloadModule("https://example.com/module.js", {as: "script"});
参考语法
preloadModule(href, options)
参数
- href:字符串,要下载的资源的 URL。
- options:对象,可以包含以下属性:
as
:必需的字符串,表示资源的类型,可能的值 包括 audio、document、embed、fetch、font、image、object、script、style、track、video 与 worker。crossOrigin
:字符串,表示要使用的 CORS 策略,可能的值为 anonymous 与 use-credentials。当 as 设置为 "fetch" 时是必需的。integrity
:字符串,为资源的加密哈希,用于 验证其真实性。nonce
:字符串,表示使用严格内容安全策略时允许资源的 加密随机数。
一、使用示范
import { preloadModule } from 'react-dom';
function CallToAction() {
const onClick = () => {
preloadModule("https://example.com/module.js", {as: "script"});
startWizard();
}
return (
<button onClick={onClick}>Start Wizard</button>
);
}
二、源码
function preloadModule(href: string, options?: ?PreloadModuleOptions) {
if (typeof href === 'string') {
if (options) {
const crossOrigin = getCrossOriginStringAs(
options.as,
options.crossOrigin,
);
ReactDOMSharedInternals.d /* ReactDOMCurrentDispatcher */
.m(/* preloadModule */ href, {
as:
typeof options.as === 'string' && options.as !== 'script'
? options.as
: undefined,
crossOrigin,
integrity:
typeof options.integrity === 'string'
? options.integrity
: undefined,
});
} else {
ReactDOMSharedInternals.d /* ReactDOMCurrentDispatcher */
.m(/* preloadModule */ href);
}
}
}
ReactDOMSharedInternals.d.m
preloadModule
函数用于预加载 ES6 模块资源,通过创建 rel="modulepreload"
的 link
元素来实现。它会根据资源类型(as
)和相关选项生成合适的选择器和属性,检查资源是否已存在于文档中,若不存在则插入新的 link
元素进行预加载。
function preloadModule(href: string, options?: ?PreloadModuleImplOptions) {
previousDispatcher.m(/* preloadModule */ href, options);
// 获取document对象
const ownerDocument = getGlobalDocument();
if (ownerDocument && href) {
const as =
options && typeof options.as === 'string' ? options.as : 'script';
// 生成预加载选择器 preloadSelector,包含 rel="modulepreload"、资源类型 as 和资源链接 href。
const preloadSelector = `link[rel="modulepreload"][as="${escapeSelectorAttributeValueInsideDoubleQuotes(
as,
)}"][href="${escapeSelectorAttributeValueInsideDoubleQuotes(href)}"]`;
let key = preloadSelector;
switch (as) {
case 'audioworklet':
case 'paintworklet':
case 'serviceworker':
case 'sharedworker':
case 'worker':
case 'script': {
key = getScriptKey(href);
break;
}
}
if (!preloadPropsMap.has(key)) {
const props: PreloadModuleProps = Object.assign(
({
rel: 'modulepreload',
href,
}: PreloadModuleProps),
options,
);
preloadPropsMap.set(key, props);
if (null === ownerDocument.querySelector(preloadSelector)) {
switch (as) {
case 'audioworklet':
case 'paintworklet':
case 'serviceworker':
case 'sharedworker':
case 'worker':
case 'script': {
if (ownerDocument.querySelector(getScriptSelectorFromKey(key))) {
return;
}
}
}
const instance = ownerDocument.createElement('link');
setInitialProperties(instance, 'link', props);
markNodeAsHoistable(instance);
(ownerDocument.head: any).appendChild(instance);
}
}
}
}
工具函数之 getCrossOriginStringAs
返回值只有三种情况:use-credentials、空字符串、undefined。
function getCrossOriginStringAs(
as: ?string,// 资源类型
input: ?string,// crossorigin属性值
): ?CrossOriginString {
// 字体类型返回空字符串
if (as === 'font') {
return '';
}
if (typeof input === 'string') {
// 如果crossorigin值为use-credentials,否则返回空字符串
return input === 'use-credentials' ? input : '';
}
return undefined;
}
工具函数之 getCrossOriginString
返回值只有三种情况:use-credentials、空字符串、undefined。
function getCrossOriginString(input: ?string): ?CrossOriginString {
if (typeof input === 'string') {
return input === 'use-credentials' ? input : '';
}
return undefined;
}
工具函数之 getGlobalDocument
获取document对象。
const globalDocument = typeof document === 'undefined' ? null : document;
function getGlobalDocument(): ?Document {
return globalDocument;
}
工具函数之 getResourcesFromRoot
获取资源,当资源不存在时进行初始化操作后返回。
function getResourcesFromRoot(root: HoistableRoot): RootResources {
// 获取资源
let resources = (root: any)[internalRootNodeResourcesKey];
if (!resources) {
// 设置资源
resources = (root: any)[internalRootNodeResourcesKey] = {
hoistableStyles: new Map(),
hoistableScripts: new Map(),
};
}
return resources;
}
const internalRootNodeResourcesKey = '__reactResources$' + randomKey;
export type HoistableRoot = Document | ShadowRoot;
export type HoistableRoot = Document | ShadowRoot;
export type RootResources = {
hoistableStyles: Map<string, StyleResource>,
hoistableScripts: Map<string, ScriptResource>,
};
工具函数之 markNodeAsHoistable
设置节点为可提升。
function markNodeAsHoistable(node: Node) {
(node: any)[internalHoistableMarker] = true;
}
const internalHoistableMarker = '__reactMarker$' + randomKey;
工具函数之 getStyleKey
function getStyleKey(href: string) {
const limitedEscapedHref =
escapeSelectorAttributeValueInsideDoubleQuotes(href);
return `href="${limitedEscapedHref}"`;
}
工具函数之 getScriptKey
function getScriptKey(src: string): string {
const limitedEscapedSrc = escapeSelectorAttributeValueInsideDoubleQuotes(src);
return `[src="${limitedEscapedSrc}"]`;
}
工具函数之 escapeSelectorAttributeValueInsideDoubleQuotes
// 正则表达式用于匹配字符串中的换行符(\n)、双引号(")和反斜杠(\\)。g 标志表示全局匹配
const escapeSelectorAttributeValueInsideDoubleQuotesRegex = /[\n\"\\]/g;
function escapeSelectorAttributeValueInsideDoubleQuotes(
value,
) {
return value.replace(
escapeSelectorAttributeValueInsideDoubleQuotesRegex,
ch => '\\' + ch.charCodeAt(0).toString(16) + ' ',
);
}
const input = 'This is a string with "quotes" and \n newlines and \\ backslashes.';
const escaped = escapeSelectorAttributeValueInsideDoubleQuotes(input);
console.log(escaped);
// This is a string with \22 quotes\22 and \a newlines and \5c backslashes.
工具函数之 getStylesheetSelectorFromKey
function getStylesheetSelectorFromKey(key: string) {
return `link[rel="stylesheet"][${key}]`;
}
工具函数之 insertStylesheet
insertStylesheet
函数的主要作用是将样式元素(link
或 style
)按照特定的优先级规则插入到文档中。它会根据元素的 data-precedence
属性值来确定插入位置,确保相同优先级的样式元素相邻排列,并且按照优先级从高到低的顺序排列在文档中。
function insertStylesheet(
instance: Element,
precedence: string,// 优先级
root: HoistableRoot,
): void {
// 使用 querySelectorAll 查找根节点 root 下所有带有 data-precedence 属性的样式元素(包括 link 和 style)。
const nodes = root.querySelectorAll(
'link[rel="stylesheet"][data-precedence],style[data-precedence]',
);
// 最后一个
const last = nodes.length ? nodes[nodes.length - 1] : null;
let prior = last;
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
// 获取优先级
const nodePrecedence = node.dataset.precedence;
// 如果当前元素的 data-precedence 与目标优先级 precedence 相同,则更新 prior 为该元素。
if (nodePrecedence === precedence) {
prior = node;
} else if (prior !== last) {
// 退出循环
break;
}
}
if (prior) {
// 新元素插入到 prior 的下一个兄弟节点之前
((prior.parentNode: any): Node).insertBefore(instance, prior.nextSibling);
} else {
const parent =
root.nodeType === DOCUMENT_NODE
// 将元素插入到文档的 head 中作为第一个子元素
? ((((root: any): Document).head: any): Element)
// 插入到 Shadow DOM 的根节点中作为第一个子元素
: ((root: any): ShadowRoot);
parent.insertBefore(instance, parent.firstChild);
}
}