React Hooks原理:为什么不能写在if里?揭开Hook的“魔法”面纱
前言Hooks刚出的时候大家都觉得是“黑魔法”一个函数组件居然能记住自己的状态还能模拟生命周期很多人用了很久却不知道原理。导致遇到奇怪的问题比如无限循环、状态不更新时只能靠试。今天我们不背源码用最简单的代码模拟React Hooks的核心机制。学完你不仅能解释“为什么不能在条件语句里调用Hook”还能自己手写一个迷你useState。一、函数组件为啥需要Hooks类组件有this.state和生命周期但函数组件每次渲染都会重新执行里面的变量都会重新创建。那怎么保存状态React用了闭包链表。每个函数组件对应一个“虚拟节点”Fiber节点它上面有个memoizedState属性用来保存该组件的Hooks链表。二、模拟React Hooks手写迷你useState我们先不管React的实现细节用纯JS模拟一个最简单的useState。lethooksnull;// 当前组件的hooks链表letcurrentHook0;// 当前正在执行的hook索引functionuseState(initialValue){// 如果是第一次渲染初始化这个hook的值if(!hooks[currentHook]){hooks[currentHook]{state:initialValue};}consthookhooks[currentHook];constsetState(newValue){hook.statenewValue;scheduleRender();// 触发重新渲染伪代码};currentHook;return[hook.state,setState];}functionrender(component){hooks[];// 重置hooks链表currentHook0;// 重置索引constvdomcomponent();// 执行组件收集hooks// 渲染vdom...}关键点hooks数组按调用顺序存储每个useState的状态。每次渲染currentHook重置为0依次取出对应的状态。所以必须保证每次渲染时Hook的调用顺序和数量完全一致。这就是不能在if或循环里调用的根本原因。三、为什么顺序必须不变假设第一次渲染时你在if里调用了useState第二次渲染时条件不成立那个Hook被跳过了。那么后续Hook的对应关系就会错位本来应该取第二个状态结果取了第三个的。React就会报错。// 错误示例functionMyComponent({flag}){if(flag){const[a,setA]useState(1);// 第一次有第二次没有}const[b,setB]useState(2);// 第一次是第二个hook第二次变成了第一个}React团队之所以这样设计是为了在保证性能的同时简化实现。用数组/链表存储比用Map key查找快得多。四、多个Hook是怎么串联的React实际用的是单向链表每个Hook节点有next指针指向下一个。这样即使组件不渲染链表也保留在Fiber节点上。// 简化的链表结构consthook{memoizedState:null,// 当前状态next:null,// 下一个hook// 还有queue等用于更新的字段};每次渲染React根据上次的链表和本次调用的顺序把新状态赋给对应的Hook。五、useEffect的原理等渲染完再执行useEffect的回调不会阻塞浏览器绘制它是在渲染提交到屏幕之后异步执行的。它的存储也类似但多了清除函数的管理。functionuseEffect(callback,deps){consthookhooks[currentHook];constprevDepshook?.deps;consthasChanged!prevDeps||deps.some((dep,i)dep!prevDeps[i]);if(hasChanged){// 将callback放到待执行队列等渲染完成后执行scheduleEffect(callback);}hook.depsdeps;currentHook;}六、为什么不能在循环里调用Hook跟if同理循环次数变了Hook的顺序就变了。即使你保证循环次数不变也没法阻止以后的维护者改代码。所以React直接禁止这种写法。七、useCallback和useMemo本质是缓存它们也存储在Hook链表里只是memoizedState里存的是缓存的值和依赖。functionuseMemo(factory,deps){consthookhooks[currentHook];constprevDepshook?.deps;consthasChanged!prevDeps||deps.some((d,i)d!prevDeps[i]);if(hasChanged){hook.valuefactory();hook.depsdeps;}currentHook;returnhook.value;}八、自定义Hook为什么没有特殊待遇自定义Hook只是调用了内置Hook的普通函数它不会新增链表节点只是把调用的内置Hook顺序归入组件的链表。所以自定义Hook的规则也遵循“只在顶层调用”。九、总结Hooks的“交通规则”Hooks用链表存储顺序就是调用顺序。每次渲染必须保持完全相同的调用顺序和数量。所以禁止在条件、循环、嵌套函数里调用Hook。useState返回的setter之所以能拿到最新值是因为闭包引用了Hook对象而Hook对象上的状态会被更新。理解了这个原理你再也不会害怕Hooks的诡异报错。下次同事问“为什么不能if里写useState”你可以拍拍他肩膀“因为React用数组存状态你跳过一个后面的全对不上了。”
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2552518.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!