「React Hooks」手写 useState & useEffect
React Hooks 不是魔法,仅仅是数组(链表)。
前言
React Hooks 是 2019 年由 Facebook 开发团队在 React v16 版本中推出的新特性,比起类组件,他们认为函数组件更加适合前端 UI 开发,更适合 React 未来的发展方向。那么,如何在函数组件里使用状态和副作用,成为了亟待解决的问题,于是, React Hooks 在这种趋势下应运而生。
1. 闭包函数
手写一个叫做 useState
的函数,函数内部会形成一个闭包,外部可以通过 setState
设置闭包内部的局部变量,并通过运行 state()
获取闭包内的最新的状态值 _val
。
function useState<T>(initVal: T): [() => T, (v: T) => void] {
let _val = initVal;
let state = () => _val;
let setState = (newVal: T) => {
_val = newVal;
};
return [state, setState];
}
const [count, setCount] = useState<number>(1);
console.log(count()); // -> 1
setCount(2);
console.log(count()); // -> 2
运行以上代码,可以看到控制台能先后打印出 1
和 2
。
2. 作为 React 的一个方法
使用立即执行函数生成一个 React 对象,将 function useState()
放入其中并返回,模拟 React 库:
const React = (() => {
function useState<T>(initVal: T): [() => T, (v: T) => void] {
let _val = initVal;
let state = () => _val;
let setState = (newVal: T) => {
_val = newVal;
};
return [state, setState];
}
return {useState}
})();
const [count, setCount] = React.useState<number>(1);
console.log(count()); // -> 1
setCount(2);
console.log(count()); // -> 2
3. 手写 Component 渲染
手动实现一个 React.render
,它能够打印出 count
,在控制台模拟实现渲染的功能。
const React = (() => {
let _val: any;
function useState<T>(initVal: T): [T, (v: T) => void] {
let state = _val || initVal;
let setState = (newVal: T) => {
_val = newVal;
};
return [state, setState];
}
function render(Com: () => any) {
let C = Com();
C.render();
return C;
}
return { useState, render };
})();
function Component() {
const [count, setCount] = React.useState<number>(1);
return {
render: () => {
console.log(count);
},
click: () => {
setCount(count + 1);
}
};
}
var app = React.render(Component); // => 1
app.click();
var app = React.render(Component); // => 2
4. 多个 useState
当我们需要使用多个 useState
的时候,发现会打印混乱,因为多个 useState
共享了同一个变量 let _val;
,这显然不是我们想要的。
const React = (() => {
let _val: any;
function useState<T>(initVal: T): [T, (v: T) => void] {
let state = _val || initVal;
let setState = (newVal: T) => {
_val = newVal;
};
return [state, setState];
}
function render(Com: () => any) {
let C = Com();
C.render();
return C;
}
return { useState, render };
})();
function Component() {
const [count, setCount] = React.useState<number>(1);
const [text, setText] = React.useState<string>("React");
return {
render: () => {
console.log({ count, text });
},
click: () => {
setCount(count + 1);
},
type: (word: string) => {
setText(word);
}
};
}
var app = React.render(Component); // => {count: 1, text: "React"}
app.click();
var app = React.render(Component); // => {count: 2, text: 2}
app.type("Vue");
app.type("Vue");
var app = React.render(Component); // => {count: "Vue", text: "Vue"}
5. 使用数组存储 state
使用数组能够稳定的保存多个 useState
的状态,当每次组件 render
时,将数组下标恢复至 0
const React = (() => {
let hooks: any[] = [];
let idx = 0;
function useState<T>(initVal: T): [T, (v: T) => void] {
let state = hooks[idx] || initVal;
let _idx = idx;
let setState = (newVal: T) => {
hooks[_idx] = newVal;
console.log(`hooks[${_idx}]`, newVal);
};
idx++;
return [state, setState];
}
function render(Com: () => any) {
idx = 0;
let C = Com();
C.render();
return C;
}
return { useState, render };
})();
function Component() {
const [count, setCount] = React.useState<number>(1);
const [text, setText] = React.useState<string>("React");
return {
render: () => {
console.log({ count, text });
},
click: () => {
setCount(count + 1);
},
type: (word: string) => {
setText(word);
}
};
}
var app = React.render(Component); // => {count: 1, text: "React"}
app.click(); // => hooks[0] 2
var app = React.render(Component); // => {count: 2, text: 2}
app.type("Vue"); // => hooks[1] Vue
app.type("Vue"); // => hooks[1] Vue
app.click(); // => hooks[0] 3
app.type("Angular"); // => hooks[1] Angular
var app = React.render(Component); // => {count: 3, text: "Angular"}
总结
本文章采用关注点分离的方式,目的在于描述 useState
是如何通过闭包和数组遍历在组件中实现多个状态共存,并稳定的存取的。演示代码屏蔽了 React Diff 和 React.render 渲染 DOM 的细节,采用 console.log()
一笔带过,实际上 React 的渲染过程没那么简单。
正因为 React Hooks 的实现机制是基于闭包和数组遍历,因此使用 React Hooks 需要遵循以下规则:
- 不要在循环、条件或嵌套函数中调用 Hook;
- 仅在 React 的函数组件中调用 Hook;
参考资源
[1] How do React hooks really work Under The Hood ? By Engineers.SG - Youtube
转载自:https://juejin.cn/post/6927698033798807560