手写 hooks
手写 useState
javascript
var _state;
function useState(initialValue) {
_state = _state || initialValue;
function setState(newState) {
_state = newState;
render();
}
return [_state, setState];
}
useEffect
使用方法:
jsx
useEffect(() => {
console.log(count);
}, [count]);
useEffect 有几个特点:
- 有两个参数 callback 和 dependencies 数组
- 如果 dependencies 不存在,那么 callback 每次 render 都会执行
- 如果 dependencies 存在,只有当它发生了变化,callback 才会执行
javascript
let _deps;
function useEffect(callback, depArray) {
const hasNoDeps = !depArray;
const hasChangedDeps = _deps
? !depArray.every((el, i) => el === _deps[i])
: true;
if (hasNoDeps || hasChangedDeps) {
callback();
_deps = depArray;
}
}
Q:为什么第二个参数是空数组,相当于 componentDidMount
A:因为依赖一直不变化,callback 不会二次执行
现在手写的 useState 和 useEffect 只能用一次,因为只有一个 _state 和 一个 _deps
有什么方法解决呢?做成数组
代码关键在于:
- 初次渲染的时候,按照 useState,useEffect 的顺序,把 state,deps 等按顺序塞到 memoizedState 数组中
- 更新的时候,按照顺序,从 memoizedState 中把上次记录的值拿出俩
javascript
let memoizedState = []; // 存放 hooks 的数组
let cursor = 0; // 当前 memoizedState 下标
function useState(initialState) {
memoizedState[cursor] = memoizedState[cursor] || initialState;
const currentCursor = cursor;
function setState(newState) {
memoizedState[currentCursor] = newState;
render();
}
return [memoizedState[currentCursor++], setState];
}
function useEffect(callback, depArray) {
const hasNoDeps = !depArray;
const deps = memoizedState[cursor];
const hasChangedDeps = deps
? !depArray.every((el, i) => el === deps[i])
: true;
if (hasNoDeps || hasChangedDeps) {
callback();
memoizedState[cursor] = depArray;
}
cursor++;
}
现在可以回答几个问题
Q:为什么只能在函数最外层调用 Hook?为什么不要在循环、条件判断或子函数中调用?
A:memoizedState 数组是按 hook 定义的顺序来放置数据的,如果 hook 顺序变化,memoizedState 并不会感知到
Q:"Capture Value" 特性是如何产生的?
A:每次 ReRender 的时候,都是重新去执行函数组件了,对于之前已经执行过的函数组件,并不会做任何操作
数组也许比连边更好解释其原理的模型
javascript
let hooks, i;
function useState() {
i++
if(hoos[i]) {
// 再次渲染时
return hooks[i]
}
// 第一次渲染
hooks.push(...)
}
// 准备渲染
i = -1
hooks = fiber.hooks || [];
// 调用组件
YourComponent()
// 缓存 Hooks 的状态
fiber.hooks = hooks;
简易的 Hooks
javascript
let memorizedState = []; // 存放hooks
let cursor = 0;
let lastRef;
function useState(intialState) {
memorizedState[cursor] = memeorizedState[cursor] || initialState;
const currentCursor = cursor;
function setState(newState) {
memorizedState[currentCursor] = newState;
render();
}
return [memorizedState[cursor++], setState];
}
function useEffect(callback, depArr) {
const noDepArr = !depArr;
const deps = memorizedState[cursor];
const hasDepsChanged = deps
? !depArr.every((el, i) => el === deps[i])
: true;
if (noDepArr || hasDepsChanged) {
callback();
memorizedState[cursor] = depArr;
}
cursor++;
}
function useRef(value) {
lastRef = lastRef || { current: value };
return lastRef;
}
所以产生 Capture Value 的原因,正是每一次 rerender 的时候,都会去重新执行函数组件,
而使用 useRef,就相当于把数据存在在内存中