1、useState 的使用
-
useState只是预置了reducer的useReducer。 -
useState 的参数
- 非函数:作为初始化的值
- 函数:只在初始化时执行一次
- 计算内容:每次 reander 时都执行、返回值始终为首次执行后的值
-
setState 的调用
- 更新是异步的、在当前执行上下文中是获取不到最新的
state - 传入的值会与上次执行后的值进行 Object.is() 浅比较、来判断是否进行重新 render 组件
- 参数可以为函数,接受的参数是最新的
state
- 更新是异步的、在当前执行上下文中是获取不到最新的
import React, {useState} from 'react';
// 定义全局变量,只在初始化时加载一次
let num = 0;
// 每次点击 handleClick 都会调用
function getNum() {
// 虽然 num 每次都会 +1,但是每次执行 getNum() 始终返回 1
num = num + 1;
return num;
}
const App = () => {
// useState函数可以是函数、非函数
// 1、参数为非函数、作为 count 的初始值
const [count, setCount] = React.useState(0);
// 2、参数为函数,只在初始化时执行一次
const [count, setCount] = useState(()=>{ return 0; });
// 3、参数是一个计算内容,每次组件渲染都会执行 getNum 函数,但是每次返回的都是第一次调用后的值
const [count2, setCount2] = useState(getNum() * 10);
// 4、如果初始化时参数为对象 a、set 时传入另一个对象 b(则返回值为b并非 a&b)
const [person, setPerson] = useState({ name: 'LinDaiDai', sex: 'boy' });
function changeCount() {
// 进行 setState 时会使用类似于 `Object.is()` 这样浅比较,而不是 `===`
// 1、`Object.is(+0, -0)的结果为false`,会重新渲染组件
// 2、`Object.is(NaN, NaN)的结果为true`;不会重新渲染组件
// 3、`Object.is({a:'a'}, {a:'a'})的结果为true`;不会重新渲染组件
setCount(count + 1);
// 多次同步调用 setCount 实际上只会执行一次
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// setCount的更新是异步的、获取的是上次更新后的值
console.log('我是点击时的count:', count);
}
function changePerson(){
// 更新后的 person 值为 { name: "LiMei" }
setPerson({ name: "LiMei" })
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={changeCount}>
Click me
</button>
<button onClick={changePerson}>
Click me
</button>
</div>
);
}
export default App;
2、useReducer 的使用
import React, { useReducer } from 'react'
const initialState = {firstCounter: 0}
const reducer = (state:{firstCounter: number}, action:{type: string}) => {
switch (action.type) {
case 'increment':
return {firstCounter: state.firstCounter + 1}
case 'decrement':
return {firstCounter: state.firstCounter - 1}
case 'reset':
return initialState
default:
return state
}
}
function CounterTwo() {
// useReducer 接收两个参数,参数1:reducer、参数2:init
const [count, dispatch] = useReducer(reducer, initialState)
return (
<div>
<div>Count - {count.firstCounter}</div>
<button onClick={() => dispatch({ type: 'increment' })}>
Increment
</button>
<button onClick={() => dispatch({ type: 'decrement' })}>
Decrement
</button>
<button onClick={() => dispatch({ type: 'reset' })}>
Reset
</button>
</div>
)
}
export default CounterTwo;
3、类组件 state 的使用
this.setState(stateChange, [callback])
- 特点:
- 在
生命周期、合成事件中是异步批量更新 - 在
setTimeout、原⽣事件中都是同步更新
- 在
- stateChange:可以为对象、函数
- 对象: this.setState({ isHot: !isHot })
- 函数:this.setState( (state, props)=> ({count:state.count+1}))
- callback:
- 界面也更新后才被调用(获取更新后的值)
异步、同步更新策略
在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates 判断是直接更新 this.state 还是放到队列中延时更新。isBatchingUpdates 默认是 false,表示 setState 会同步更新 this.state,batchedUpdates 函数会把 isBatchingUpdates 修改为 true,而当 React 在调用 合成事件处理函数之前 就会先调用 batchedUpdates 函数。
- setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
- setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的 调用顺序在组件更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果
- setState 的批量更新优化也是 建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新
4、扩展 useState 的极简实现
<script>
let isMount = true; // 是否为首次挂载阶段
let workInProgressHook = null; // 表示正在执行的 hook
// 极简的 fiber 结构
const fiber = {
memoizedState: null, // 保存函数组件的 hooks
stateNode: App,
};
// 创建 update 对象,重新 render
function dispatchAction(queue, action) {
// 每个触发更新的动作,都会产生一个update对象
// 以环状链表的形式存在queue中
const update = {
action,
next: null,
};
if (queue.pending === null) {
// 首次,自己指向自己
update.next = update;
} else {
// 中插入一个新的update
// 例如:3 -> 0 -> 1 -> 2 -> 3 ===> 4 -> 0 -> 1 -> 2 -> 3 -> 4
// 1、update 表示当前要插入的 update 4
// 1、因为 update.pending 表示最后一个 update 3
// 2、所以 update.pending.next 表示了第一个 update 0
// 3、第一步 让 4 要指向 0, 第二步 让 3 指向 4
update.next = queue.pending.next;
queue.pending.next = update;
}
// queue.pending 指向最新值
queue.pending = update;
// 重新 render
run();
}
// 1、创建一个 hook
// 2、以单链表形式存在
// 3、返回[baseState, dispatchAction]
function useState(initialState) {
let hook;
if (isMount) {
// 挂在过程
hook = {
queue: {
pending: null, // 存储 update
},
memoizedState: initialState,
next: null, // 同时执行多个 useState 会以链表形式保存
};
if (!fiber.memoizedState) {
fiber.memoizedState = hook;
} else {
workInProgressHook.next = hook;
}
workInProgressHook = hook;
} else {
// 更新过程
hook = workInProgressHook;
workInProgressHook = workInProgressHook.next;
}
// 计算state
let baseState = hook.memoizedState;
if (hook.queue.pending) {
// 更新阶段存在 update 连接的环状链表
// hook.queue.pending:指向末尾 update
// hook.queue.pending.next:指向第一个 update
let firstUpdate = hook.queue.pending.next;
do {
const action = firstUpdate.action;
baseState = action(baseState);
firstUpdate = firstUpdate.next;
} while (firstUpdate !== hook.queue.pending.next);
// 指向完,重置 pending
hook.queue.pending = null;
}
hook.memoizedState = baseState;
return [baseState, dispatchAction.bind(null, hook.queue)];
}
function App() {
// 1、新的 state 由 baseState 和 update 计算得出
// 2、调用 updateNum 产生新的 update
const [num, updateNum] = useState(0);
const [status, triggerStatus] = useState(false);
console.log("num", num);
console.log("status", status);
return {
onClick() {
// 同一个setState执行多次,会以链表形式挂在到 hook.queue 上
// updateNum(num=>num + 1)
// updateNum(num=>num + 1)
updateNum((num) => num + 1);
},
trigger() {
triggerStatus((status) => !status);
},
};
}
// 入口函数
function run() {
workInProgressHook = fiber.memoizedState;
// 模拟render阶段
const app = fiber.stateNode();
isMount = false;
return app;
}
window.app = run();
</script>