当前位置: 首页 > Web前端 > JavaScript

悄悄告诉你:React18文档错误的地方

时间:2023-03-27 01:29:07 JavaScript

大家好,我已经发布了卡森React18正式版一段时间了,如果你升级到v18后仍然使用ReactDOM.render创建应用,你会收到如下信息alarm:大意是:v18使用createRoot代替render创建应用,如果你仍然使用render创建应用,那么应用的行为会和v17一样。React团队之所以有信心让大家升级到v18并使用createRoot,是因为他们做了一个承诺:大意是如果升级到v18,只要不使用并发特性(比如useTransition),React会和之前的版本一样表现(更新会同步且不间断)今天这篇文章我想说的是:在某些情况下,上面的说法是错误的。欢迎加入人类优质前端框架群。飞行时不要胡说八道。在上面的例子中,有两个状态a和b。第一次渲染后,2秒后会触发a和b的更新。其中,触发b更新的方式比较特殊:模拟点击,间接触发b更新:functionApp(){const[a,setA]=useState(0);const[b,setB]=useState(0);constBtnRef=useRef(null);useEffect(()=>{setTimeout(()=>{setA(9000);BtnRef.current?.click();},2000);},[]);返回(

setB(1)}>b:{b}{Array(a).fill(0).map((_,i)=>{return{a}
;})}
);}完整示例地址现在我们有两种方式挂载。v18之前的方式:constrootElement=document.getElementById("root");//v18之前创建应用的方式ReactDOM.render(,rootElement);v18提供的方式:constroot=ReactDOM.createRoot(rootElement);//v18??创建应用的方式root.render();为了看出两者的区别,有两种方法:增加setA(9000)中的值,让页面渲染更多的item。页面渲染时的卡顿越明显,渲染顺序的差异越明显setTimeout(()=>{setA(9000);BtnRef.current?.click();},2000);在react-dom.development.js的commitRootImpl方法中断点该方法是React渲染时调用的方法,可以在断点处看到页面渲染的顺序。对于ReactDOM.render创建的应用,触发更新后的渲染顺序如下:first:second:对于ReactDOM.createRoot创建的应用,触发更新后的渲染顺序如下:first:second:渲染order明显变了,这和React文档中的声明是矛盾的。其背后的原因是什么?更新的优先级,无处不在先解释一下为什么例子中的b通过触发onClick事件间接触发更新:BtnRef.current?.click();这是因为:不同方法触发的更新有不同的优先级,onClick回调中触发的更新是最高优先级,即同步优先级。那么问题来了,v18没有使用并发特性,不应该所有的更新都是同步不间断的吗?确实如此,更新本身是同步的,不可中断的。但是需要安排更新。示例中,如果使用ReactDOM.createRoot创建应用,触发更新时的优先级如下:setTimeout(()=>{//触发更新,优先级为“默认优先级”setA(9000);//触发更新,优先级为“同步优先级”BtnRef.current?.click();},2000);接下来React的执行流程如下:a触发更新,优先级为“默认优先级”来调度a的更新,优先级为“默认优先级”b触发更新,优先级为“同步”priority”来调度b的更新,优先级为“同步优先级”。这时发现已经有更新调度(a更新),优先级Low(默认优先级<同步优先级)取消a的更新调度,转而开始调度b的更新。调度过程结束,b的更新开始同步和不间断执行。b的相应更新呈现到页面。一个update(a的更新),调度调度过程结束,开始同步不间断地执行a的更新。a的相应更新已呈现并在页面上可见。只要应用是用ReactDOM.createRoot创建的,优先级的影响就会一直存在,与使用并发特性的区别在于只有默认优先级和同步优先级优先级只会影响调度,不会中断更新的执行旧版本React的历史包袱。那么用ReactDOM.render创建的应用程序的执行顺序是怎样的呢?怎么了?记住经典的(毫无意义的)React面试问题:React更新是同步的还是异步的?下面两种情况,打印出来的结果是1吗?//案例1onClick(){this.setState({a:1});console.log(a);}//Case2onClick(){setTimeout(()=>{this.setState({a:1});console.log(a);})}其中,打印结果为情况2中的a为1。造成这种情况的原因是React早期实现批处理的缺陷造成的,并非有意为之。当使用Fiber架构重构React时,完全可以避免这个缺陷。但是为了和老版本的行为保持一致,特意这样实现。因此,在我们的示例中,这两个更新不会受到优先级的影响,但会受到与旧版本兼容性的影响:setTimeout(()=>{setA(9000);BtnRef.current?.click();},2000);React的执行过程如下:a触发更新,因为是在setTimeout中触发的,所以后续的更新过程会同步执行a相应的更新渲染到页面b触发更新,因为是在setTimeout中触发的,所以后续更新过程会同步执行b相应的更新会渲染到页面总结React作为一个已经维护了将近10年的框架,在大版本更新后保持框架行为一致并不容易。更新顺序的变化对一般应用影响不大。但是,如果你的应用是依赖于更新页面中的当前值来进行后续判断的,升级到v18后需要注意这些细微的变化。