jtwang7 / React-Note

React 学习笔记
8 stars 2 forks source link

React - Hooks (useState) #6

Open jtwang7 opened 3 years ago

jtwang7 commented 3 years ago

React - Hooks (useState)

参考文章:

useState

// useState 用法
const [mystate, setMystate] = useState(initialState)

(函数组件外定义的方法不会重新定义,因此我们可以利用闭包特性保留一些值,当然也可用后续提及的 useMemo, useCallback 等 Hooks)

  • useState 接收唯一的参数:初始 state 值,initialState 仅在组件被创建时使用,后续组件重新渲染均使用上一个 state 记录。
  • useState 返回一个数组:一个 state,一个对应更新该 state 的函数。通常用 ES6 数组解构的方式将其分离出来。
  • 在初始化渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。
  • 更新方法与 setState() 类似,你可以在事件处理函数中或其他一些地方调用这个函数来更新 state。它与 setState() 区别在于:它不会把新的 state 和旧的 state 进行合并,而是直接替换。 useState 只能用于 React 函数组件或自定义 Hook 的最外层,但它返回的修改 state 的方法可以用于其他位置或多层嵌套中。

state 更新机制

useState 返回的用于更新 state 状态的方法,在更新机制上与 setState 类似,setState 的更新机制可参考我的另一篇文章:React - setState 学习笔记

useState 返回的更新方法,也是异步的。它会等待同步代码执行完毕后,再更新 state,然后触发 render 和生命周期函数,重新渲染组件并执行定义的副作用。

Hook state 更新特点

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

初始化 state 惰性求值

Hooks 独立性 & 顺序性

独立性

每一个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 的执行顺序与存储到链表结构中的顺序一致。