动手实现 useState

293 阅读2分钟

useState

这是 React Hooks 中的一个函数,一般用来声明组件的状态,返回值是数组,第一项为数据,第二项为改变数据的方法,同时可以更新 UI,也就是重新执行函数组件

先准备一个自制的 useState

let _state
const useState2=(initialValue)=>{
    _state=_state||initialValue
    const setState=(newState)=>{
        _state=newState
        render()
    }
    return [_state,setState]
}

const App = () => {
    const [n, setN] = useState2(0)
    return (
        <>
            <h2>n:{n}</h2>
            <button onClick={() => setN(n + 1)}>+1</button>
        </>
    )
}

const render = () => {
    ReactDOM.render(
            <App/>,
        document.getElementById('root')
    )
};
render()

在更新了 state 后,我们需要更新界面,所以我们调用 render,可以发现,可以使用,虽然将整个组件重新执行了一遍

但是我们发现,如果出现第二个 useState2 的使用者,就会引起覆盖的问题,所以我们需要记录下来每次存储的值,最好用数组的顺序来存储,用一个变量来记录当前的位置

let _state=[]
let index=0
const useState2=(initialValue:any)=>{
    _state[index]=_state[index]||initialValue
    const setState=(newState:any)=>{
        _state[index]=newState
        render()
    }
    return [_state[index++],setState]
}

结果还是不能正常使用,原因是 index 的值不会像我们想象的那样,最终为 1,由于我们粗暴地重新执行了组件,那么组件会再调用 2 次 useState2,所以 index 的值会一直变大,造成读取错位的问题

let _state=[]
let index=0
const useState2=(initialValue:any)=>{
    let currentIndex=index
    _state[currentIndex]=_state[currentIndex]||initialValue
    const setState=(newState:any)=>{
        _state[currentIndex]=newState
        render()
    }
    index++
    return [_state[currentIndex],setState]
}

我们可以利用一个变量来记住变量的位置,每次调用 setState 的时候,它还可以访问到其作用域中保留下来的下标,也就是 currentIndex

但是由于 render 会再次执行组件,也就是会再次执行 useState2,那么 index 会一直变大,造成变量的值一直为初始值

换句话说,如果你声明了 2 个变量,数组中存储的值会越来越多,从而在更新数据的时候,每次都会生成新的值,而不是在原来的基础上修改,所以我们要在更新界面之前,将 index 恢复到最初的状态,也就是 0

我们虽然不能防止 render 不去再次调用 useState2,但是我们能够保证数据的下标是正确的

最后贴上完整例子

let _state=[]
let index=0
const useState2=(initialValue:any)=>{
    let currentIndex=index
    _state[currentIndex]=_state[currentIndex]||initialValue
    const setState=(newState:any)=>{
        _state[currentIndex]=newState
        render()
        console.log(_state);
    }
    index++
    return [_state[currentIndex],setState]
}

const App = () => {
    const [n, setN] = useState2(0)
    const [m, setM] = useState2(0)
    return (
        <>
            <h2>n:{n}</h2>
            <button onClick={() => setN(n + 1)}>+1</button>
            <hr/>
            <h2>m:{m}</h2>
            <button onClick={() => setM(m + 1)}>+1</button>
        </>
    )
}

const render = () => {
    index=0
    ReactDOM.render(
            <App/>,
        document.getElementById('root')
    )
};
render()