Open jtwang7 opened 3 years ago
参考文章:
// useState 用法 const [mystate, setMystate] = useState(initialState)
类组件每次重新渲染,都会调用 render 方法以及生命周期函数。同理,在函数组件中,每次重新渲染都会调用函数组件(本身就是构造 UI 的 render 方法),所以定义在函数组件内的方法都会被重新定义并执行,按理来说 state 状态会被覆盖重置,但 React Hooks 能在重复渲染时保留上一个 state 状态,并作为下一次改变的初始值。
(函数组件外定义的方法不会重新定义,因此我们可以利用闭包特性保留一些值,当然也可用后续提及的 useMemo, useCallback 等 Hooks) useState 接收唯一的参数:初始 state 值,initialState 仅在组件被创建时使用,后续组件重新渲染均使用上一个 state 记录。 useState 返回一个数组:一个 state,一个对应更新该 state 的函数。通常用 ES6 数组解构的方式将其分离出来。 在初始化渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。 更新方法与 setState() 类似,你可以在事件处理函数中或其他一些地方调用这个函数来更新 state。它与 setState() 区别在于:它不会把新的 state 和旧的 state 进行合并,而是直接替换。 useState 只能用于 React 函数组件或自定义 Hook 的最外层,但它返回的修改 state 的方法可以用于其他位置或多层嵌套中。
(函数组件外定义的方法不会重新定义,因此我们可以利用闭包特性保留一些值,当然也可用后续提及的 useMemo, useCallback 等 Hooks)
setState()
useState 返回的用于更新 state 状态的方法,在更新机制上与 setState 类似,setState 的更新机制可参考我的另一篇文章:React - setState 学习笔记。
setState
useState 返回的更新方法,也是异步的。它会等待同步代码执行完毕后,再更新 state,然后触发 render 和生命周期函数,重新渲染组件并执行定义的副作用。
Object.is
这主要与 React Hooks 的闭包特性有关,可参考 超性感的React Hooks(二)再谈闭包。useState 会将 state 保存,形成一个闭包,当接受的值与保存的 state 经过 Object.is 比较相等时,useState 会直接使用原有的 state。 // useState 简单实现 let state = null;
这主要与 React Hooks 的闭包特性有关,可参考 超性感的React Hooks(二)再谈闭包。useState 会将 state 保存,形成一个闭包,当接受的值与保存的 state 经过 Object.is 比较相等时,useState 会直接使用原有的 state。
// useState 简单实现 let state = null;
export const useState = (value: number) => { // 第一次调用时没有初始值,因此使用传入的初始值赋值 state = state || value;
function dispatch(newValue) { if (!Object.is(state, newValue)) { state = newValue; // 假设此方法能触发页面渲染 render(); } } return [state, dispatch]; }
* useState 的更新方法会直接用接收参数替换原有参数。 > 与 `setState` 不同点在于,setState 会将接收的对象合并到实例 state 对象上,并覆盖同名属性。 ## 函数式更新 如果新的 state 需要通过使用先前的 state 计算得出,那么可以将回调函数当做参数传递给 setState。该回调函数将接收先前的 state,并返回一个更新后的值。 ```js let [mystate, setMystate] = useState(false); ... setMystate((prev) => (prev + 1));
与 setState 函数式更新区别在于,它不接受当前 props 作为参数,因为在函数组件内部的嵌套函数本身就可以通过作用域获取 props,不需要将其作为形参传入回调函数。 推荐使用函数式更新,详细原因见文章:函数式 setState
即仅作用于组件创建,不作用于组件重新渲染。
function getInitState() { // do something return xxx } // getInitState 在其所在组件首次被创建中调用。 let [counter,setCounter] = useState(getInitState);
每一个hook都是相互独立的,这就意味着不同组件调用同一个hook也能保证各自状态的独立性。
// 组件 A let [a, setA] = useState(false) // 组件 B let [b, setB] = useState(123)
如上例,不同组件内都调用了 useState 这一个 Hook,但由于每个 Hook 都是独立的,因此每个 Hook 调用都返回独立的结果,保证了不同组件调用同一个hook也能保证各自状态的独立性。
React Hooks 实现了独立性,但是 React 是怎么知道组件 A 调用的 useState 对应的是数组 [a, setA] 呢? 这就涉及了 React Hooks 定义的顺序性问题。 实际上,react 是根据 Hooks 出现的顺序来定的,以 useState 为例:
//第一次渲染 useState(42); //将age初始化为42 useState('banana'); //将fruit初始化为banana useState([{ text: 'Learn Hooks' }]); //... //第二次渲染 useState(42); //读取状态变量age的值(这时候传的参数42直接被忽略) // useState('banana'); useState([{ text: 'Learn Hooks' }]); //读取到的却是状态变量fruit的值,导致报错
在组件初始化(创建)时,React 会根据 useState 书写顺序存储对应的值,并构成一个链表结构,后续组件重新渲染时,会按照链表顺序遍历移动指针,并调用相应的 Hook。
React Hooks 对顺序性的要求,也约束了开发者书写 Hooks 的行为。我们只能将 Hooks 写在函数的最外层,不能写在 if ... else ... 或循环等语句中,确保 Hooks 的执行顺序与存储到链表结构中的顺序一致。
if ... else ...
React - Hooks (useState)
参考文章:
useState
state 更新机制
useState 返回的用于更新 state 状态的方法,在更新机制上与
setState
类似,setState
的更新机制可参考我的另一篇文章:React - setState 学习笔记。Hook state 更新特点
Object.is
来比较新/旧 state 是否相等。export const useState = (value: number) => { // 第一次调用时没有初始值,因此使用传入的初始值赋值 state = state || value;
function dispatch(newValue) { if (!Object.is(state, newValue)) { state = newValue; // 假设此方法能触发页面渲染 render(); } } return [state, dispatch]; }
初始化 state 惰性求值
Hooks 独立性 & 顺序性
独立性
每一个hook都是相互独立的,这就意味着不同组件调用同一个hook也能保证各自状态的独立性。
如上例,不同组件内都调用了 useState 这一个 Hook,但由于每个 Hook 都是独立的,因此每个 Hook 调用都返回独立的结果,保证了不同组件调用同一个hook也能保证各自状态的独立性。
顺序性
React Hooks 实现了独立性,但是 React 是怎么知道组件 A 调用的 useState 对应的是数组 [a, setA] 呢? 这就涉及了 React Hooks 定义的顺序性问题。 实际上,react 是根据 Hooks 出现的顺序来定的,以 useState 为例:
在组件初始化(创建)时,React 会根据 useState 书写顺序存储对应的值,并构成一个链表结构,后续组件重新渲染时,会按照链表顺序遍历移动指针,并调用相应的 Hook。