起因
起初项目中有一些google,bing 的脚本需要提前放置,领英,机器人等脚本后置的需求
然后找到了 有关script 标签 async , defer 的属性与行为;
第一次做法
不改html 在provider 层逻辑注入,因为觉得写在html中太捞了,也没有抽离的思想
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet" />
/// 其他逻辑
</head>
<body>
/// spa 位置
<div id="root"></div>
</body>
</html>
在provider 层注入 但写在了useEffect 中,获取插入head节点第一个
/**
* 该文件为脚本注入文件provider
* 2024-4-20需求
* google/bing 等用户统计资源需要 async拿脚本(即便会阻塞)
* 页脚加载:paypal - 领英 - salesmartly - tiktok defer加载
* 众所周知,async defer 参数需要在head中添加,否则不会生效
**/
import React, { useEffect } from 'react';
function bingScript() {
// 获取 <head> 元素
var head = document.head || document.getElementsByTagName('head')[0];
// 获取 <head> 元素的第一个子节点作为参考节点
var firstChild = head.firstChild;
const bingHtml = `(function (w, d, t, r, u) {
//logic 省略
})(window, document, 'script', '//bat.bing.com/bat.js', 'uetq');`;
const scriptele = document.createElement('script');
scriptele.innerHTML = bingHtml;
head.insertBefore(scriptele, firstChild);
}
const ScriptProvider = ({ children }: { children: React.ReactNode }) => {
// const location = useLocation();
// const history = createBrowserHistory();
useEffect(() => {
// bing
bingScript();
}, []);
return <>{children}</>;
};
export default ScriptProvider;
结论 : 等js包和脚本执行后才执行到provider 层,哪怕注入到head 顶层,也已经慢了
单独建个项目看下async defer 的脚本在什么时候执行,react 在什么时候执行(DCL 前后?)
这次放在html页面中测试,自己启动node搭建
<!DOCTYPE html>
<html lang="en">
<head>
<script>
// 注入监听
window.addEventListener('DOMContentLoaded',()=>{
console.log('DOMContentLoaded done')
})
window.addEventListener('load',()=>{
console.log('load done')
})
console.log('html head 开始 ')</script>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rspack + React</title>
// defer 的脚本
<script defer src="http://localhost:3000/1" ></script>
// async 的脚本
<script async src="http://localhost:3000/2" ></script>
<script> console.log('html head 执行结束')</script>
</head>
<body>
<script>console.log('body 开始')</script>
<div id="root"></div>
<script> console.log('body 结束')</script>
// 放在body 尾部的脚本
<script defer src="http://localhost:3000/3" ></script>
</body>
</html>
在main.js 中打印开始状态
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
console.log('main js 开始')
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
在app中打印状态
import React, { useEffect } from "react";
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import "./App.css";
console.log('app js 执行')
function App() {
useEffect(()=>{
console.log('app js mounted')
},[])
return (
<div className="App">
</div>
);
}
export default App;
启动个express 模拟延时脚本
const express = require('express');
const fs = require('fs');
const app = express();
const port = 3000;
app.get('/1', (req, res) => {
// 读取 index.js 文件内容
fs.readFile('js1.js', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
res.status(500).send('Internal Server Error');
return;
}
// 返回 index.js 文件内容
res.setHeader('Content-Type', 'application/javascript');
res.send(data);
});
});
app.listen(port, () => {
console.log(`Proxy server listening at http://localhost:${port}`);
});
js1 的实际内容
(function blockExecution(ms) {
globalThis.console.log('外部1脚本执行开始')
const start = Date.now();
console.log(new Date(),'start');
while (Date.now() - start < ms) {
// 这里什么也不做,只是阻塞执行
}
console.log(new Date(),'end');
})(1000)
console.log('外部1脚本执行完毕')
启动!
没有测量出async 和 defer 的行为,因为同步代码阻塞9秒,但看到了react执行竟然在DCL之前
openai =》
React 的具体实现是开源的,你可以在 React 的 GitHub 仓库中找到源代码。React 通过调度器(scheduler)来控制任务的执行顺序,从而实现了在 DOMContentLoaded 之前开始执行的效果。
以下是 React 中一些关键的源码片段,用于解释 React 如何在 DOMContentLoaded 之前执行:
- 调度器(Scheduler) :React 使用调度器来控制任务的优先级和执行顺序。调度器会根据任务的优先级决定何时执行任务,并且可以中断和恢复任务的执行。
- 任务调度(Task Scheduling) :React 将需要执行的任务(如渲染组件、处理事件等)添加到调度器中,并根据任务的优先级安排执行顺序。
- 异步执行(Asynchronous Execution) :React 利用浏览器提供的异步 API(如 requestAnimationFrame、setTimeout 等)来延迟任务的执行,从而使得任务可以在 DOMContentLoaded 之前开始执行。
以下是伪代码示例,展示了 React 中任务调度的简化实现:
// 任务调度器
const Scheduler = {
scheduleTask(task, priority) {
// 将任务添加到调度队列中,按优先级排序
// 省略具体实现
},
runTasks() {
// 执行调度队列中的任务
// 省略具体实现
}
};
// React 组件
class Component {
constructor() {
this.state = {};
}
// 更新组件状态
setState(newState) {
// 更新组件状态并调度重新渲染
this.state = newState;
Scheduler.scheduleTask(() => this.render(), Scheduler.Priority.UPDATE);
}
// 渲染组件
render() {
// 渲染组件并将结果插入到 DOM 中
// 省略具体实现
}
}
// 页面加载时初始化 React
document.addEventListener('DOMContentLoaded', () => {
// 创建根组件并渲染到页面中
const rootComponent = new RootComponent();
rootComponent.render();
// 开始执行任务调度器中的任务
Scheduler.runTasks();
});
在这个示例中,Scheduler.scheduleTask 方法用于将任务添加到调度队列中,并根据任务的优先级进行排序。在页面加载完毕后,React 创建根组件并将其渲染到页面中,然后开始执行调度器中的任务。由于调度器会根据任务的优先级决定执行顺序,并利用异步 API 来延迟任务的执行,因此 React 可以在 DOMContentLoaded 之前开始执行任务,从而实现更快的页面加载和渲染。
总结
自己试了下有关DCL事件 在html解析后的执行顺序,没有给async 和 defer 测实际行为,但发现了react的异步处理会在DCL事件之前,也算是优先于白屏时间。
参考: blog.csdn.net/upgrade_bro…
回复 map ={ 1: 一坨, 2: 下次别写了, 3: 求断更 }