React16.8+版本的发布,hooks函数是新增的一大功能,其实useState函数是让函数组件有了状态,那么我们就来详细了解一下useState这个函数。
用法:
const [state, setState] = useState(initialState);
因为react是函数式编程,所以上面的代码就很好理解了,useState函数接收一个初始化参数initialState,其返回值用数组解构俩个参数,state是初始化的state,以及更新state的函数setState. 在初始渲染期间,返回的状态 (state
) 与传入的第一个参数 (initialState
) 值相同。setState
函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。
setState(newState);
在后续的重新渲染中,useState
返回的第一个值将始终是更新后最新的 state。
注意: React 会确保 setState
函数的标识是稳定的,并且不会在组件重新渲染时发生变化。这就是为什么可以安全地从 useEffect
或 useCallback
的依赖列表中省略 setState
。
函数式更新
如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState
。该函数将接收先前的 state,并返回一个更新后的值。
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
</>
);
}
“+” 和 “-” 按钮采用函数式形式,因为被更新的 state 需要基于之前的 state。但是“重置”按钮则采用普通形式,因为它总是把 count 设置回初始值。
如果你的更新函数返回值与当前 state 完全相同,则随后的重渲染会被完全跳过。
注意
与 class 组件中的 setState
方法不同,useState
不会自动合并更新对象。你可以用函数式的 setState
结合展开运算符来达到合并更新对象的效果。
setState(prevState => {
// 也可以使用 Object.assign
return {...prevState, ...updatedValues};
});
useReducer
是另一种可选方案,它更适合用于管理包含多个子值的 state 对象。
惰性初始 state
initialState
参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用:
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
跳过 state 更新
调用 State Hook 的更新函数并传入当前的 state 时,React 将跳过子组件的渲染及 effect 的执行。(React 使用 Object.is 来比较 state。)
需要注意的是,React 可能仍需要在跳过渲染前渲染该组件。不过由于 React 不会对组件树的“深层”节点进行不必要的渲染,所以大可不必担心。如果你在渲染期间执行了高开销的计算,则可以使用 useMemo
来进行优化。
以上部分是官方文档给出的内容。。。
下面,我们来解析useState的一些相关问题:
1、当组件中出现多个状态的时候该怎么办?
组件中出现多个状态这是太正常不过了。那也就是意味着useState函数会被调用多次.
import React, { useState } from 'react'
function Example () {
const [name, setName] = useState('cat')
const [age, setAge] = useState(0)
const [gender, setGender] = useState('male')
}
useState()函数接收的参数并没有规定接收什么类型的参数,它只是被作为初始值。需要注意的是,我们之前调用setState方发,是将现在的数据与之前的state数据进行合并然后返回一个新状态,而hooks调用setState方法是将之前的state数据进行替换,返回一个新状态,当然hooks也提供了useReducer()函数,以redux的方式来管理state.
在开发中我们可以发现,无论useState()函数被调用了多少次,他们之前都是互相不影响,都是相互独立的。这一点很重要。那么这是如何做到的呢?
react会根据useState的顺序来规定的。在react实现的内部声明了一个数组,按照其useState()函数调用的顺序,来保存其数据,所以,一定要在函数的外层调用useState(),不要在if...else,for循环,子函数中调用useState(),所以,因为react函数共享一个保存数据的数组,那么也会对其他的函数组件有影响。Capture Value” 特性的产生是在于每一次 ReRender 的时候,都是重新去执行函数组件了,对于之前已经执行过的函数组件,并不会做任何操作。
来实现一个简单的useState函数:
let memoizedState = []; // hooks 存放在这个数组
let cursor = 0; // 当前 memoizedState 下标
function useState(initialValue) {
memoizedState[cursor] = memoizedState[cursor] || initialValue;
const currentCursor = cursor;
function setState(newState) {
memoizedState[currentCursor] = newState;
render();
}
return [memoizedState[cursor++], setState]; // 返回当前 state,并把 cursor 加 1
}
下面来看看react是如何实现的:
(1)React 中是通过类似单链表的形式来代替数组的。通过 next 按顺序串联所有的 hook。
type Hooks = {
memoizedState: any, // 指向当前渲染节点 Fiber
baseState: any, // 初始化 initialState, 已经每次 dispatch 之后 newState
baseUpdate: Update<any> | null,// 当前需要更新的 Update ,每次更新完之后,会赋值上一个 update,方便 react 在渲染错误的边缘,数据回溯
queue: UpdateQueue<any> | null,// UpdateQueue 通过
next: Hook | null, // link 到下一个 hooks,通过 next 串联每一 hooks
}
memoizedState,cursor 是存在哪里的?如何和每个函数组件一一对应的?
我们知道,react 会生成一棵组件树(或Fiber 单链表),树中每个节点对应了一个组件,hooks 的数据就作为组件的一个信息,存储在这些节点上,伴随组件一起出生,一起死亡。
useState完结。。。