前言
本文新手向解释了 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
依赖更新,缓存effect2
function 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
依赖更新,缓存effect2
function 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
依赖更新,缓存effect2
function 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
的碰撞结果。