实现一个简单的hook,mini-useState

1,025 阅读4分钟

俺学习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供你参考