俺学习react的时候,看到了hooks这一章节,觉得很有趣,首先我并不会立马着手于源码的阅读,因为我看不懂~而且我觉得自己思考过后再去看源码能取得更大的收获!
下面开始吧~我的实现完全没有基于react这个库(如果能实现mini-react搭配实现hook当然是学习最佳),在这里只探讨hook的简单实现,学习其思想和原理。
首先应该有个函数(在react是做函数式组件,须知hook只能搭配函数式组件使用)
我们来定义个函数,并给予他还未实现的useState方法。当前的函数没有任何返回值,(再次强调react中的函数组件应该返回ui,这里的实现是模拟)
function component1(){
let [num,setNum] = useState(10)
console.log(num,'num') // 10
}
//首次默认执行
component1()
接下来应该去定义useState方法,这个方法的返回值显而易见,是一个数组[],数组里的两个元素分别是状态以及修改状态的方法。
function useState(initalize){
let state = initalize
function setState(value){
state = value
component1() // setState是会重新渲染ui 既函数组件重新执行
}
return [state,setState]
}
useState的雏形已经有了,但是很显然他并没有灵魂~
当第一轮事件循环执行完后,假设我们会超能力,我们用超能力去执行一次setNum(20)会发生什么呢?并不会那么轻松的在函数执行的第二次就轻易得到20。因为有两个问题,第一,当执行setNum(20)的时候,state = 10 已经返回出去了,再去改state这个函数作用域下的变量已经没有任何意义。第二,component1()再次执行,这段代码let [num,setNum] = useState(10)也重新执行,所以num永远是10,setNum这个方法是不起效果的。
解决的办法聪明的你一定想到了,那就是将其提升为全局变量并且判断是否有状态,
let state
function useState(initalize){
if(!state){
state = initalize
}
function setState(value){
state = value
component1() // setState是会重新渲染ui 既函数组件重新执行
}
return [state,setState]
}
为了方便测试,这次我们不使用超能力,可以将这段代码复制到html,我们可以执行一下完整代码看看结果
//html
<button id="num">num</button>
//js
let handleSet;
let collet = true;
function component1() {
let [num, setNum] = useState(10);
if (collet) {
handleSet = setNum;
}
console.log(num, "num");
}
let state;
function useState(initalize) {
if (!state) {
state = initalize;
}
function setState(value) {
state = value;
component1(); // setState是会重新渲染ui 既函数组件重新执行
}
return [state, setState];
}
component1();
collet = false;
document.getElementById("num").addEventListener("click", function () {
handleSet(20);
});
结果和预期一样,他已经能从10变成20了,但是...如果再增加多个useState呢?
请把这段替换到上面函数中。
let [num, setNum] = useState(10);
let [num1, setNum1] = useState(100);
let [num2, setNum2] = useState(1000);
显然易见,全局变量state已经无法管理了,既然是多个,我会想到用数组去集中管理这些状态,那么我们改造一下state吧。
let stateQueue = [] 首先state改名为stateQueue(这很重要), 接着赋予他空数组。(这个在react源码中的事先是挂在在fiber上的,我们这里用全局变量)
既然stateQueue数组里存储的状态要和我们useState里的状态一一对应,那我们我们还需要一个桥梁,oh没那么复杂,就是一个简单的index!let indexForState = 0
有了这两个东西,我们存储状态就很方便了,接着改造useState函数。
function useState(initialize) {
let oldState = stateQueue[indexForState] && stateQueue[indexForState].state
let hook = {
state:initialize,
set:setState
}
if(oldState){
hook.state = stateQueue[indexForState].state
}else{
stateQueue.push(hook)
}
function setState(value) {
indexForState = 0
hook.state = value
component1()
}
console.log(stateQueue)
let index = indexForState++
console.log(index)
return [stateQueue[index].state,stateQueue[index].set]
}
这里的改动稍微有那么一丢丢丢丢大。但是带着接下来我要说的思路去看,so easy啦。
每次执行usetate都会产生一个新状态,我们用indexForState记录下当前是第几个状态
我们把当前hook(包含一个状态,以及修改的方法)push进stateQueue,并返回这个hook的state和setState
到目前为止,mini-hook已经实现咯,这里有完整的 demo供你参考