前言
本文新手向解释了 react useState 的更新逻辑和基本使用方法,需要了解真实且具体的实现原理,请移步Fiber。
useState
ts
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];
基本Demo
tsx
function FunctionComponent(props: any) {
const [count1, setCount1] = useState()
const [count2, setCount2] = useState(() => props.count || 0)
useEffect(() => {
setCount1(prevState => prevState + 1)
}, [])
return <div>{count1}</div>
}
没什么好说的,基本用法就这些。注意点:
-
基本使用规范:仅可在组件或hook顶层调用
useState来声明一个变量。不可在循环或条件语句中调用,这里不做过多说明。 -
set函数仅会影响下一次渲染中state返回的内容, -
多次执行
set仅会返回最后一次执行结果。 -
其它要点请仔细看下面示例,以时间线的形式描述了state更新时机、浏览器更新时机及副作用执行时机,同时解释了小白常见两个问题:
- state更新缺无法获取最新值;
- state更新死循环:
tsxfunction FunctionComponent() { const [count, setCount] = useState(0) const handleClick1 = () => { setCount(count + 1) setCount(count + 1) console.log('handleClick1: ', count) setCount(count + 1) } const handleClick2 = () => { setCount(count => count + 1) setCount(count => { console.log('setCount: ', count) return count + 1 }) console.log('handleClick2: ', count) setCount(count => count + 1) } useEffect(() => { setCount(1) console.log('effect1', count) }, []) useEffect(() => console.log('effect2', count), [count]) console.log('function body: ', count) return <div> <button type='button' onClick={handleClick1}>button1: {count}</button> <button type='button' onClick={handleClick2}>button2: {count}</button> </div> }- 注:Fiber真实逻辑与下文有差异,这里仅为更方便理解。
- 初始化执行函数
- 第一次执行函数
- 初始化
count,useState 返回状态值0 - 定义
handleClick1,handleClick2, - 缓存
effect1,缓存effect2 function body:打印状态值为0- 浏览器渲染 count 状态值为
0 - 执行
effect1回调,记录 count 更新值 为1 effect1打印当前状态值为0- 执行
effect2回调 effect2打印当前状态值为0- 副作用执行完毕,更新状态值为记录值:
1 - 第二次执行函数
- useState 返回状态值
1 - 定义
handleClick1,handleClick2 effect1无动作,effect2依赖更新,缓存effect2function body:打印状态值为1- 浏览器渲染 count 状态值为
1 - 执行
effect2回调 effect2打印当前状态值为1- 副作用执行完毕,无需更新渲染层。
- 点击 button1
- 执行
handleClick1,当前状态值为1- 记录 count 更新新值 为
1 + 1 - 记录 count 更新新值 为
1 + 1 handleClick1打印当前状态值为1- 记录 count 更新新值 为
1 + 1
- 记录 count 更新新值 为
- 顺序更新三次记录值
- count 记录值更新值为
2 - count 记录值更新值为
2 - count 记录值更新值为
2
- count 记录值更新值为
- 更新状态值为记录值:
2 - 第三次执行函数
- useState 返回状态值
2 - 定义
handleClick1,handleClick2, effect1无动作,effect2依赖更新,缓存effect2function body:打印状态值为2- 浏览器渲染 count 状态值为
2 - 执行
effect2回调 effect2打印当前状态值为2- 副作用执行完毕,无需更新渲染层。
- useState 返回状态值
- 执行
- 点击 button2
- 执行
handleClick2,当前状态值为2- 记录 count 更新函数:
count => count + 1 - 记录 count 更新函数:
count => { console.log('setCount: ', count); return count + 1 } handleClick2打印当前状态值为2- 记录 count 更新函数:
count => count + 1
- 记录 count 更新函数:
- 顺序执行三个更新函数,将记录值作为入参,将返回值作为新记录值
- 第一次:当前记录值为
2,count 记录值更新值为2 + 1 - 第二次:
setCount打印当前记录值为3,count 记录值更新值为3 + 1 - 第一次:当前记录值为
4, count 记录值更新值为4 + 1
- 第一次:当前记录值为
- 更新状态值为记录值:
5 - 第四次执行函数
- useState 返回状态值
5 - 定义
handleClick1,handleClick2, effect1无动作,effect2依赖更新,缓存effect2function body:打印状态值为5- 浏览器渲染 count 状态值为
5 - 执行
effect2回调 effect2打印当前状态值为5- 副作用执行完毕,无需更新渲染层。
- useState 返回状态值
- 执行
useContext
ts
function createContext<T>(defaultValue: T): Context<T>;
function useContext<T>(context: Context<T>): T;
基本DEMO
tsx
const Context = createContext()
function Demo() {
return (
<Context.Provider value={sameValue}>
<Child/>
</CountContext2.Provider>
)
}
function Child() {
const value = useContext(Context)
return <div>{value}</div>
}
没什么好说的,基本用法就这些。存值取值。后续更新状态管理会涉及到 useContext和useState的碰撞结果。