初识React Hooks原理
前言
记录一下在学习React Hooks原理过程中的心得。
一、useState原理
useState函数接受一个初识状态,返回一个元组,包含当前状态和设置状态的函数。
其原理是通过使用数组保存按顺序调用useState得到的状态。主要依靠cursor指针。在第一次初始化时按顺序将state存入数组中,每存一个cursor++。当调用setState后,将cursor = 0
,并调用render
函数。于是当前组件函数会按顺序重新执行useState函数,然后得到对应的state,这也是为什么只能在函数顶层调用hooks的原因。
简单实现:
function render() {
ReactDOM.render(<App />, document.getElementById("root"));
}
let state: any;
function useState<T>(initialState: T): [T, (newState: T) => void] {
state = state || initialState;
function setState(newState: T) {
state = newState;
render();
}
return [state, setState];
}
render(); // 首次渲染
过程图
二、useEffect原理
和useState原理相似,还是使用Array + cursor来实现。
下面是一个不包括销毁副作用功能的 useEffect 的 TypeScript 实现
// 还是利用 Array + Cursor的思路
const allDeps: any[][] = [];
let effectCursor: number = 0;
function useEffect(callback: () => void, deps: any[]) {
if (!allDeps[effectCursor]) {
// 初次渲染:赋值 + 调用回调函数
allDeps[effectCursor] = deps;
++effectCursor;
callback();
return;
}
const currenEffectCursor = effectCursor;
const rawDeps = allDeps[currenEffectCursor];
// 检测依赖项是否发生变化,发生变化需要重新render
const isChanged = rawDeps.some(
(dep: any, index: number) => dep !== deps[index]
);
if (isChanged) {
callback();
allDeps[effectCursor] = deps;
}
++effectCursor;
}
function render() {
ReactDOM.render(<App />, document.getElementById("root"));
effectCursor = 0; // 注意将 effectCursor 重置为0
}
!补充
以上的方法是轻量级react hook 实现的方法,官方的方法是使用链表来实现的
/**
* 简单实现useSate
*
*/
// 记录是否挂载
let isMount = true;
// 记录当前hook
let workInProgressHook;
// fiber对象
const fiber = {
memorizedState: null,
targetApp: App
};
function schedule() {
// 执行后,workInProgressHook应该是第一个hook
workInProgressHook = fiber.memorizedState;
// 每次调用该函数返回app执行结果
const app = fiber.targetApp;
isMount = false;
return app();
}
// 每次调用dispatchAction就是为了调用action,更新state
function dispatchAction(queue, action) {
const update = {
action,
next: null
};
// 若queue.pending为空=>还没有update,把当前action添加进去
if (!queue.pending) {
update.next = update;
} else { // 若不为空=>添加到环形链表最前面
update.next = queue.pending.next;
queue.pending.next = update;
}
queue.pending = update;
// 调用schedule进行组件render,在下次render的时候在useState中计算获取最新的state
schedule();
}
function useState(initialState) {
let hook;
// 若是挂载,创建新hook对象,挂载到fiber对象的hooks链表中
if (isMount) {
hook = {
queue: {
pending: null
},
state: initialState,
next: null
};
if (!fiber.memorizedState) {
fiber.memorizedState = hook;
} else {
workInProgressHook.next = hook;
}
workInProgressHook = hook;
} else { // 若是render,要拿到对应顺序的hook,依靠workInProgressHook指针
hook = workInProgressHook;
workInProgressHook = workInProgressHook.next;
}
let baseState = hook.state;
// 拿到hook后,如果有update对象了,就要执行完,得到最新的state
if (hook.queue.pending) {
let firstUpdate = hook.queue.pending.next;
do {
baseState = firstUpdate.action(baseState);
firstUpdate = firstUpdate.next;
} while (firstUpdate !== hook.queue.pending);
hook.queue.pending = null;
}
hook.state = baseState;
// 返回可以执行所有update对象的函数
return [baseState, dispatchAction.bind(null, hook.queue)];
}
function App() {
const [num, updateNum] = useState(0);
const [str, updateStr] = useState("0");
console.log(`${isMount ? 'mount' : 'update'} num: `, num);
console.log(`${isMount ? 'mount' : 'update'} str: `, str);
return {
click() {
updateNum(num => num + 1);
updateStr(str => str + 1);
}
}
}
const app = App();
app.click();
二、useReducer
useReducer是useState的代替用法const [state, dispatch] = useReducer(reducer, initialArg, init);
useState的原理调用useReducer,只是在mount阶段,useStatelastRenderedReducer
为basicStateReducer
。
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
区别就是,useReducer的dispatch中会使用自定义的reducer,而useState会使用basicStateReducer
。
对hooks功能的总结
- 相比类组件,函数组件是无状态组件,使用hooks可以让函数组件拥有和类组件相似的状态功能。
- 在类组件中,每个组件有很强的封装性,有自己的生命周期函数。而函数组件可以通过hooks来实现相似的生命周期函数。
- 但是hooks还是和函数组件有一定割裂感,并不是像类组件一样有很强的封装性。如:在不使用hooks时函数组件就是一个普通函数,这也体现了其面向函数编程的思想。