目录
11、相比较传统的用超链接进行页面的切换与跳转,Vue使用的是路由,不用刷新页面。
15、一个网页文件有很多个js要加载,加载过程会比较慢,如何保证用户想要触发某个功能时相应的js已经加载完毕
19、关闭 / 退出页面时上报(文档卸载期间发ajax请求)
1、React和Vue区别
大体上是相同的,支持组件化,虚拟DOM,数据驱动视图,都有router库实现url到组件的映射,都有状态管理...
(1)DOM更新策略
二者虚拟dom相同,都是用JS对象来模拟真实DOM,然后用虚拟DOM的diff来最小化更新真实DOM。按颗粒度分为tree diff, component diff, element diff. tree diff 比较同层级dom节点,进行增、删、移操作。
dom更新策略不同:
react采用自顶向下的全diff,在react中,当状态发生改变时,组件树就会自顶向下的全diff, 重新render页面, 重新生成新的虚拟dom树, 新旧dom树进行比较, 进行patch打补丁方式进行增、删、移操作,局部更新dom.。(所以react为了避免父组件更新而引起不必要的子组件更新, 可以在shouldComponentUpdate做逻辑判断,减少没必要的render, 以及重新生成虚拟dom,做差量对比过程)
vue是局部订阅的模式。 通过Object.defineProperty 将data 属性全部转为 getter/setter。同时watcher实例对象会在组件渲染时,将属性记录为dep, 当dep项中的 setter被调用时,通知watch重新计算,使得关联组件更新。
(2)数据驱动视图
Vue通过MVVM框架实现数据驱动视图,通过中间件ViewModel监听中间件变化,通知双方进行相应的动作。
同时Vue在实例化对象时会(使用 Object.defineProperty )把data的属性全部转为 getter/setter。每个对象有自身的监听器,可以用getter访问自身属性。若(setter被调用)数据依赖发生改变,water会对比数值是否变化,决定是否通知视图重新渲染。
React通过setState实现数据驱动视图,通过setState来引发组件的更新,实现页面重新渲染。
(3)单向、双向数据流
2、大文件分片上传
- 发送&&切片过程
- 大文件切片上传
先切片,后发送。关键:formData数据处理,promise()并发请求
获取文件后,设置切片体积,通过file.slice(start,end)进行切片,将切片转为二进制形式后,传给后端。
前端读取文件,创建切片,上传切片;后端转换formData数据接收切片,上传成功后通知后端合并切片。
// 创建切片 存储在数组中
function createChunk(file, size) {
//file是大文件,size是切片的大小(也就是小文件的大小)
const chunkList = []
let cur = 0
while (cur < file.size) {
chunkList.push({
file: file.slice(cur, cur + size)//使用slice()进行切片
})
cur += size
}
return chunkList;
}
chunkList = createChunk(files, 2 * 1024 * 1024)//在读取文件的回调中调用
console.log(chunkList);
<!DOCTYPE html>
<html lang="en">
<body>
<div class="container">
<h1>大文件上传</h1>
<input type="file" id="fileInput" accept="image/*">
<button id="uploadButton">切片后直接上传</button>
<br>
</div>
<script>
//chunk就是一个切片,也就是小文件
async function uploadChunk(chunk) {
const formData = new FormData();
formData.append('file', chunk);
//这里的地址可以替换为你的后端地址
const response = await fetch('https://file.io', {
method: 'POST',
body: formData
});
const result = await response.json();
return result;
}
document.getElementById('uploadButton').addEventListener('click', async function() {
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];// 获取文件
const chunkSize = 100 * 1024; // 设置文件大小为100KB
const totalChunks = Math.ceil(file.size / chunkSize);// 计算文件被切成多少块
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);// 判断,当文件大于切割的块
const chunk = file.slice(start, end);
//上传一个切片
const result = await uploadChunk(chunk);// 上传,转为FormData二进制,发给后端
}
});
</script>
</body>
</html>
3、图片上传
- 组件二次封装
对于upload组件的二次封装,主要是维护图片列表fileList,实现图片的预览展示功能。
(1)通过上传按钮,将图片添加到fileList内;对于图片预览,通过fileReader将图片文件转换为字符串后绑定给src。
(2)数据可通过formData转换成二进制后,通过fetch或axios发请求传给后端。
(3)限制上传文件的类型可通过accept属性设置,大小可通过file.size属性限制。
- 原生JS
(1)上传部分,设置input标签有multiple属性,允许选择多个文件。之后监听onChange事件获取多个文件,存在列表files中,并使用FormData进行格式转换,便于发送给后端。
(2)删除部分,绑定事件,从files列表删除。
(3)预览部分,为选择的图片绑定img标签,并建立弹出层展示图片。
(4)回显部分,后端根据图片唯一标识构建url,传回给前端,前端将url绑定到src上。
4、Fiber架构
JS引擎和页面渲染引擎两个线程是互斥的,React在diff的计算过程中,递归遍历
组件树成本高,会造成主线程被持续占⽤,页面就不能及时渲染更新,造成卡顿
,影响⽤户体验。
(1) Fiber节点对应DOM节点,形成fiber树结构;
(2)把渲染更新过程拆分成多个子任务,实现任务切片与异步渲染与任务的优先级调度;
任务拆分:在渲染更新过程中,React Fiber 将整个更新过程分解为多个小任务,每个任务被称为 Fiber 节点。每个 Fiber 节点代表一个组件或 DOM 节点,并包含了该节点的相关信息,如 props、state 等。Fiber 节点之间形成了一个树状结构,反映了组件的层次结构。
任务切片:为了避免长时间的阻塞,React Fiber 将任务切片成多个时间片(Time Slice),每个时间片执行一小部分任务。任务切片的大小是根据浏览器的空闲时间和优先级等因素来动态调整的。通过任务切片,React 可以在执行一小部分任务后,将控制权交还给浏览器,以响应用户输入或处理其他高优先级的任务。
异步渲染:Fiber 架构支持异步渲染,即将渲染任务分解为多个时间片,并根据任务的优先级和时间片信息来调度任务的执行。通过异步渲染,React 可以将高优先级的任务优先执行,以提高用户体验。例如,对用户交互事件的响应可以具有更高的优先级,以确保及时的用户反馈。
任务优先级调度:React Fiber 使用优先级调度算法来决定任务的执行顺序。每个任务都有一个优先级,React 根据任务的优先级来决定任务的执行顺序。例如,高优先级的任务会优先执行,而低优先级的任务可能会被推迟或暂停。优先级调度算法可以根据任务的类型、时间紧迫性和用户体验等因素来动态地调整任务的优先级。
5、Diff算法
- React
Diff算法特点:只对(sibling连接的)同级元素进行diff比较,新旧元素同key不同type会销毁重建(若仅是顺序改变,使用key可实现复用)
Diff实现:
1. 单节点:key相同(证明存在oldFiber节点)—>type相同—>可复用
2. 多节点:两轮遍历
(1)遍历对比,从newChildren[0]与oldFiber开始,不可复用or遍历完时跳出。
(2)i>一方遍历完,依据情况增删节点。 ii>若均没有遍历完,而节点位置改变,遍历newChildren记录lastplacedIndex,当前节点旧索引小于lastplacedIndex,节点向右移动。
(逐个遍历新列表,设置一个参考值lastPlaceIndex记录“目前可复用节点列表中的最后一个”,(初始值为0),若当前遍历节点的旧索引大于lastPlaceIndex,他在旧列表位置不变;反之若旧索引小于lastPlaceIndex,证明该节点被移到后边了,则改变位置)
// 之前
abcd
// 之后
acdb
===第一轮遍历开始===
a(之后)vs a(之前)
key不变,可复用
此时 a 对应的oldFiber(之前的a)在之前的数组(abcd)中索引为0
所以 lastPlacedIndex = 0;
继续第一轮遍历...
c(之后)vs b(之前)
key改变,不能复用,跳出第一轮遍历
此时 lastPlacedIndex === 0;
===第一轮遍历结束===
===第二轮遍历开始===
newChildren === cdb,没用完,不需要执行删除旧节点
oldFiber === bcd,没用完,不需要执行插入新节点
将剩余oldFiber(bcd)保存为map
// 当前oldFiber:bcd
// 当前newChildren:cdb
继续遍历剩余newChildren
key === c 在 oldFiber中存在
const oldIndex = c(之前).index;
此时 oldIndex === 2; // 之前节点为 abcd,所以c.index === 2
比较 oldIndex 与 lastPlacedIndex;
如果 oldIndex >= lastPlacedIndex 代表该可复用节点不需要移动
并将 lastPlacedIndex = oldIndex;
如果 oldIndex < lastplacedIndex 该可复用节点之前插入的位置索引小于这次更新需要插入的位置索引,代表该节点需要向右移动
在例子中,oldIndex 2 > lastPlacedIndex 0,
则 lastPlacedIndex = 2;
c节点位置不变
继续遍历剩余newChildren
// 当前oldFiber:bd
// 当前newChildren:db
key === d 在 oldFiber中存在
const oldIndex = d(之前).index;
oldIndex 3 > lastPlacedIndex 2 // 之前节点为 abcd,所以d.index === 3
则 lastPlacedIndex = 3;
d节点位置不变
继续遍历剩余newChildren
// 当前oldFiber:b
// 当前newChildren:b
key === b 在 oldFiber中存在
const oldIndex = b(之前).index;
oldIndex 1 < lastPlacedIndex 3 // 之前节点为 abcd,所以b.index === 1
则 b节点需要向右移动
===第二轮遍历结束===
最终acd 3个节点都没有移动,b节点被标记为移动
-
Vue
1. 只有新节点:createElm 创建新的节点
2. 只有旧节点:删除旧节点
3. 新旧节点都存在:通过 sameVnode 判断节点是否相同:
(1)相同:直接调用 patchVnode 去处理这两个节点
Vnode 是文本节点,则更新文本(文本节点不存在子节点)
Vnode 有子节点,则处理比较更新子节点.
新旧节点都有子节点,而且不一样,那就执行updateChildren
updateChildren 维持新旧节点首尾的四个指针进行遍历对比,遵循的原则是:能不移动,尽量不移动。不行就移动,实在不行就新建
Vue采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。
6、优化策略手段
- CDN(内容分发网络)静态资源加载
原理:将Web资源(文本、图片和脚本等),可下载资源(媒体文件、软件、文档等),应用程序(门户网站等),放在后端CDN服务器上,实现资源高效访问。
使用:CDN外链,把静态资源放在后端的CDN服务器上,请求资源时候,资源经过Gzip压缩后传输,因此能更快的请求到首屏的资源,提高渲染速度。
<script src="https://cdn.example.com/js/script.js"></script>
body {
background-image: url('https://cdn.example.com/images/background.jpg');
}
const script = document.createElement('script');
script.src = 'https://cdn.example.com/js/library.js';
拓展:可以把用CDN分发项目资源,让该开源项目使用时更高速;
直播本质上是使用流媒体进行传送,CDN也是支持流媒体传送的,所以直播完全可以使用CDN来提高访问速度。(普通静态文件回源方式向上层查找;流媒体通过主动推送进行)
- 懒加载(按需加载)
懒加载为了避免,在图片多、网页长的情况下,用户只能看到可视窗口的那一部分,但所有的图片均被加载出来,浪费性能。
使用懒加载实现在滚动屏幕时才加载,提高网页的加载速度,减少了服务器的负载。
实现:图片url保存在自定义属性,图片出现在可视区时获取图片url赋值给src即可。
- 路由懒加载
把路由对应的组件分割成不同的代码块,被访问的时候才加载。
import VueRouter from 'vue-router'
const Login = ()=> import('@/views/login/index.vue')
const Home = ()=> import('@/views/home/home.vue')
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{ path: '/login', component: Login },
{ path: '/home', component: Home }
]
export default router
7、formily特点
开源表单方案 Formily 的核心设计思路-阿里云开发者社区
formily表单数据管理更细致,当填写表单的某一表项时,Formily 只会更新相关的表单项和相关的状态,而不会重新渲染整个页面。
而el-form是基于双向绑定实现的,填写某一表项会触发Vue的响应式机制,可能导致整个表单的渲染。
8、react的render阶段
整个流程虽然看起来繁琐,但就做了2件事:
在这个过程中如果遇到兄弟节点,又重复步骤1,直到最终又回到根Fiber,完成整棵树的创建与遍历
9、setState执行机制
setState
更新数据的时候,setState
的更新类型分成同步更新和异步更新。
同时setState存在合并机制,直接传递对象的setstate
会被合并成一次,而使用函数传递state
不会被合并
- 在组件生命周期或合成事件中,setState是异步
(1)调用setState后
不会立即更新;
(2)所有组件使用的是同一套更新机制,当所有组件挂载
后(钩子函数中调用setState),父组件挂载,然后才执行更新
(3)更新时会把每个组件的更新合并,每个组件只会触发一次更新的生命周期。
- 在setTimeout异步函数或原生dom事件中,setState是同步
(1)在(钩子函数中调用setState并用定时器包裹)父组件先didmount挂载
(2)各组件的setState会
同步更新
- 更新过程步骤
(1)接收状态参数,判断组件是否有待执行的state队列,有则加入状态参数,无则创建后加入。(不确定对不对,先忽略)
(2)确保当前React处于批量更新状态后,入队,将当前组件加入待更新状态的组件队列。(这里用isBatchingUpdates判断,只有当事件监听或定时器时会是false且不可修改,这时候会立刻更新不入队)
(3)调用事物的waper方法,遍历待更新状态的组件,依次执行更新。
(4)执行componentWillRecieveProps后,合并state状态,清空待更新队列。
(5)执行shouldUpdate、will..、render、Did...等钩子更新。
10、异步async/await理解
async
声明function是一个异步函数,返回一个promise
对象,可以使用 then 方法添加回调函数。async
函数内部return
语句返回的值,会成为then
方法回调函数的参数。
await 操作符只能在异步函数 async function 内部使用。如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果,也就是说它会阻塞后面的代码,等待 Promise 对象结果。如果等待的不是 Promise 对象,则返回该值本身。
11、相比较传统的用超链接进行页面的切换与跳转,Vue使用的是路由,不用刷新页面。
12、import和require
import/export(置顶性) | require/exports |
ES6,react | commonJS,node |
(仅webpack)异步加载,编译时执行 | 同步加载,运行时执行 |
引用,不可修改 | 浅拷贝,可修改对象第二层 |
13、从输入url到页面渲染过程
(1)URL解析:确定协议域名端口号合法;
(2)查找缓存:强缓存和协商缓存;
(3)无本地缓存,发送DNS查询:查询域名对应的IP地址(递归+迭代)
(4)发送请求前,建立TCP连接(三次握手)、发送请求、接收响应
客户端建立SSL连接,服务端返回证书和公钥,客户端产生会话密钥,用公钥加密会话密钥,发给服务端,服务端用自己的私钥解密出会话密钥。连接建立成功。
连接建立成功之后,浏览器向服务器发送HTTP请求报文,来获取自己想要的数据。
服务器对请求报文进行解析,并给客户端发送HTTP响应报文。
浏览器拿到我们服务器返回的HTML文件,可以开始解析渲染页面。
(5)浏览器渲染页面、TCP连接断开(四次挥手)
浏览器解析HTML,构建DOM树,同时并行的下载CSS解析CSS,二者结合构建Render树,进行布局和绘制。
四次挥手:i)客户端发送FIN终止信号,并进入终止等待状态。
ii)服务器发送ACK,进入关闭等待状态。
iii)服务器发送FIN终止信号,进入关闭状态。
iv)客户端发送ACK应答,进入时间等待状态后关闭。
14、监测页面白屏
页面白屏是指:由于资源加载问题、执行错误、渲染时间过长等技术异常,导致页面长时间无法正常展示与响应的现象。
(1)监测根节点是否渲染+onerror监听:发生白屏后通常是根节点下所有 DOM 被卸载。
// JS 错误监听
window.onerror = function (msg, url, lineNo, columnNo, error) {
const rootElement = document.querySelector('#root')
if (!rootElement.firstChild) {
console.log('#root节点不存在内容,判断为白屏!')
}
}
(2)MutationObserver 监听 DOM 变化:监听时间段内 DOM 是否变化,从而判断页面是否白屏。
Mutation Observe有可判断渲染问题,不受资源加载影响,支持问题回溯等优势。
注意:为避免用户长时间未操作产生的误判,可以在页面中插入一些隐藏的元素,然后定时更新这些元素的样式或内容;一些自动化的数据推送、广告展示等行为也会引起 DOM 的变化,这些行为也可以被 Mutation Observer 监听到,从而避免误判。
let timeoutId=null
const observer = new MutationObserver(callback);
function callback(mutationsList, observer) {
// 有 DOM 变化,说明页面还没有白屏,重置一个定时器
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
// 一段时间内没有任何 DOM 变化,说明页面已经白屏
console.log('页面白屏');
}, 3000);
}
const targetNode = document.body;
const config = {
childList: true,
attributes: true,
subtree: true,
characterData: true,
};
observer.observe(targetNode, config);
(3)关键点采样对比
在浏览器可视区定一个坐标轴,把可视区域中心点作为坐标轴的中心,然后在X,Y轴上平均分配10个点,页面onload事件触发后,通过坐标找出这20个坐标点上的DOM元素,如果这些元素是你自定义的标签,则该点非空白。如果所有点是Html,Body,App,Root等非自定义元素,就代表着此时还没有渲染出元素,则判定为白屏,此时白屏点加1,最终如果白屏点等于20,则上报白屏日志。
15、一个网页文件有很多个js要加载,加载过程会比较慢,如何保证用户想要触发某个功能时相应的js已经加载完毕
(1)对script标签使用async/defer属性
(2)使用回调函数,先加载js文件,所有文件加载完毕再触发事件
function loadScript(url, callback) {
var script = document.createElement('script');
script.src = url;
script.onload = callback;
document.head.appendChild(script);
}
function loadMultipleScripts(urls, finalCallback) {
var loadedCount = 0;
function scriptLoaded() {
loadedCount++;
if (loadedCount === urls.length) {
finalCallback();
}
}
for (var i = 0; i < urls.length; i++) {
loadScript(urls[i], scriptLoaded);
}
}
var scriptsToLoad = [
'路径/至/script1.js',
'路径/至/script2.js',
'路径/至/script3.js'
];
loadMultipleScripts(scriptsToLoad, function() {
// 在所有 JavaScript 文件加载完成后要执行的功能代码
});
(3)使用Promise:Promise内部加载js文件,使用all监听全部加载成功,.then方法内触发事件。
function loadScript(url) {
return new Promise(function(resolve, reject) {
var script = document.createElement('script');
script.src = url;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
function loadMultipleScripts(urls) {
var promises = urls.map(function(url) {
return loadScript(url);
});
return Promise.all(promises);
}
var scriptsToLoad = [
'路径/至/script1.js',
'路径/至/script2.js',
'路径/至/script3.js'
];
loadMultipleScripts(scriptsToLoad)
.then(function() {
// 在所有 JavaScript 文件加载完成后要执行的功能代码
})
.catch(function(error) {
// 处理加载错误
});
16、如何让外界不能随意调用函数里的成员跟属性
将需要隐藏的成员和属性定义在包含它们的函数内部,组成闭包,可以阻止外部代码直接访问它们。(只暴露函数接口或返回一个包含公共方法的对象,外界可以通过这些方法间接地访问和操作内部成员。)
17、如何及时判断输入框中用户输入数据的合法性
使用onInput属性,给该属性添加相应的回调函数,并传递this.value
<input type="text" id="inputField" oninput="validateInput(this.value)" />
<script>
function validateInput(value) {
if (value.length < 5) {
// 输入不合法的情况
} else {}
}
</script>
拓展:input标签事件
onfocus、onblur、onchange、onclick、onselect、oninput、onkeydown、onkeyup
18、ajax原理是什么、如何实现
(1)原理:浏览器通过(XHR
对象)来向服务器发异步请求,从服务器获得数据,然后用js来操作DOM
更新页面
(2)实现方式:i)XHR:原生的js API实现面试官:ajax原理是什么?如何实现? | web前端面试 - 面试官系列
const request = new XMLHttpRequest() /* 1<<创建XHR对象实例>> */
/* 4<<绑定onreadystatechange 方法,监听服务器端的通信状态>> */
request.onreadystatechange = function(e){
if(request.readyState === 4){ // 整个请求过程完毕readystate判断状态
if(request.status >= 200 && request.status <= 300){
console.log(request.responseText) // 服务端返回的结果
}else if(request.status >=400){
console.log("错误信息:" + request.status)
}
}
}
request.open('POST','http://xxxx') /* 2<<open方法与服务器建立连接>> */
// xhr.open(method, url, [async][, user][, password])
request.send() /* 3<<send方法给服务端发送数据>> */
ii)Axios:可以在node.js中使用、提供了并发请求的接口、支持Promise API;
可以通过.all方法实现并发请求;面试官:Vue项目中有封装过axios吗?主要是封装哪方面的? | web前端面试 - 面试官系列
// 简单使用
axios({
method: 'GET',
url: url,
})
.then(res => {console.log(res)})
.catch(err => {console.log(err)})
iii)fetch:基于Promise设计的API,底层,需要手动拼接参数
// fetch使用
fetch(url)
.then(response => {
if (response.ok) {
return response.json();
}
})
.then(data => console.log(data))
.catch(err => console.log(err))
// fetch封装
fetch(url, {
method: 'POST',
body: Object.keys({name: 'test'}).map((key) => { // 手动将参数拼接成'name=test'的格式
return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
}).join('&')
})
iv)jquery:体积大
19、关闭 / 退出页面时上报(文档卸载期间发ajax请求)
(1)事件监听
beforeunload:
在文档和资源将要关闭的时候调用的(场景:退出时弹窗询问)
unload:
在页面已经正在被卸载时发生
(2)发送请求方法
i)发送同步的ajax请求
ii)发送异步请求,并且在服务端忽略ajax的abort中断
iii)使用navigator.sendBeacon
发送异步请求(beason: 指引;)
20、用户反馈APP内打开H5页面白屏时间过长,你应该怎么处理?
(1)针对 WebView 初始化: 当客户端刚启动时,可以先提前初始化一个全局的 WebView 待用并隐藏
(2)针对向后端发送接口请求:在客户端初始化 WebView 的同时,直接由 Native 开始网络请求数据,当页面初始化完成后,向 Native 获取其代理请求的数据。
(3)针对加载的 js 动态拼接 html(单页面应用):可采用多页面打包, 服务端渲染,以及构建时预渲染等方式。
(4)针对加载页面资源的大小:可采用懒加载等方式,将需要较大资源的部分分离出来,等整体页面渲染完成后再异步请求分离出来的资源,以提升整体页面加载速度。
21、前端性能优化
(1)体积层面(打包)
- 使用webpack的压缩插件Plugin,压缩代码(html、css、js)进行Gzip压缩;
- 打包时通过Tree Shaking消除死代码;
(2)网络层面(传输)
- 使用CDN加载资源
- 使用HTTP缓存策略(强缓存,协商缓存)
- 使用http2,多路复用的特性,头部压缩,请求优先级
(3)渲染层面(浏览器)
- 使用缓存避免短期内切换页面反复请求接口(客户端缓存、CDN缓存等)
- 设置<link>标签对浏览器关键资源进行preload预加载,对不关键资源使用async异步加载、defer延迟加载
- 设置精灵图、雪碧图,将多个小图标合并到大图中,减少HTTP请求次数,提高页面的渲染性能。(通过CSS的背景定位技术,将需要显示的部分定位到相应的位置。)
22、React各个版本特点区别
- React18React18 新特性解读 & 完整版升级指南 - 掘金
(1)render API:引入新的root API,支持并发模式(concurrent mode)的渲染。
(Tips:传统的同步模式中,React 会一次性地计算组件的更新,更新完成后才重新渲染整个组件树。这意味着在进行大量计算或更新较大组件树时,应用程序可能会出现阻塞,导致用户界面无响应。
而并发模式是一种异步的渲染模式,React 会将工作拆分成多个小任务,并根据任务的优先级动态地分配时间片进行处理。这样可以不阻塞主线程,使得渲染和更新过程更加平滑,提高了应用程序的响应性能。)(更快的初始加载渲染、优先级管理)
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
const root = document.getElementById('root')!;
// React 17
ReactDOM.render(<App />, root);
// React 18
ReactDOM.createRoot(root).render(<App />);
(2)setState自动批处理:
在 18 之前,只有在react事件处理函数中,才会自动执行批处理,其它情况会多次更新
在 18 之后,任何情况都会自动执行批处理,多次更新始终合并为一次
(3)flushSync:可以精准控制React18中不需要的批量更新。
import React, { useState } from 'react';
import { flushSync } from 'react-dom';
const App: React.FC = () => {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
return (
<div onClick={() => {
flushSync(() => { setCount1(count => count + 1); });// 第一次更新
flushSync(() => { setCount2(count => count + 1); });// 第二次更新
}} >
<div>count1: {count1}</div>
<div>count2: {count2}</div>
</div>
);
};
export default App;
(4)suspense组件:不再需要fallback捕获。以往会跳过fallback为空的suspense边界,继续向上搜索,没有的话将不显示任何备用内容。
现在会默认fallback为null,继续使用它对应的suspense。
(Tips:suspense组件用于标记异步加载的内容,而fallback是指定在等待异步加载时显示的备用内容)
const App = () => { // React 17(18)
return (
<Suspense fallback={<Loading />}> // <--- 这个边界被使用,显示 Loading 组件。(不使用
<Suspense>// <--- 这个边界被跳过,没有 fallback 属性。(被使用,将 fallback 渲染为 null。
<Page />
</Suspense>
</Suspense>
);
};
export default App;
(5)新的API:useId、useInsertionEffect(一般用于提前注入<style>脚本)、useSyncExternalStore