Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React - Hooks (useState) #6

Open
jtwang7 opened this issue May 25, 2021 · 0 comments
Open

React - Hooks (useState) #6

jtwang7 opened this issue May 25, 2021 · 0 comments

Comments

@jtwang7
Copy link
Owner

jtwang7 commented May 25, 2021

React - Hooks (useState)

参考文章:

useState

// useState 用法
const [mystate, setMystate] = useState(initialState)
  • useState 为函数组件提供了类似类组件中的 state 声明以及 setState 修改方法。
  • useState 在函数组件每次重新渲染时,都会重新定义。React 会在重复渲染时保留前一个 state 值。

类组件每次重新渲染,都会调用 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 的方法可以用于其他位置或多层嵌套中。

state 更新机制

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

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

Hook state 更新特点

  • Hook 内部使用 Object.is 来比较新/旧 state 是否相等。
  • 若状态修改时,useState 接收的状态值没有变化,则不会触发重新渲染。

这主要与 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,并返回一个更新后的值。

let [mystate, setMystate] = useState(false);
...
setMystate((prev) => (prev + 1));

setState 函数式更新区别在于,它不接受当前 props 作为参数,因为在函数组件内部的嵌套函数本身就可以通过作用域获取 props,不需要将其作为形参传入回调函数。
推荐使用函数式更新,详细原因见文章:函数式 setState

初始化 state 惰性求值

  • useState 的 initialState 参数只会在组件的初始化渲染中起作用,后续渲染时会被忽略。

即仅作用于组件创建,不作用于组件重新渲染。

  • initialState 被使用的时机是在组件创建的时候。因此如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用。
function getInitState() {
  // do something
  return xxx
}
// getInitState 在其所在组件首次被创建中调用。
let [counter,setCounter] = useState(getInitState);

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant