自己实现一个useState

作者 likaiqiang 日期 2020-08-22
自己实现一个useState

前言

推荐一本书learn react hooks,这本书详细介绍了useState的原理,然后通过一个博客例子介绍了常用的几个hooks、React.lazy、react-router以及自定义hooks。目前没有中文版,不过有代码。鄙人是个英文渣,不过功夫不负有心人,有个app叫拍读英语,可以拍照翻译,对照着代码可以一章一章看下去。这本书的经典之处是作者实现了一个useState,通过这个介绍官方的useState为什么那么奇怪,为什么不能在条件语句中使用hooks。

useState的奇怪之处

useState的参数只在初次调用时起作用(存储)

举个例子:

import React from "react";
import Bar from './Bar.js'
import "./styles.css";

export default function App() {
const [value,setValue] = React.useState(0)
return (
<div className="App">
<button onClick={e=>{
setValue(value+1)
}}>add</button>
<Bar value={value}/>
</div>
);
}

Bar.js

import React from 'react'

const Bar = (props)=>{
const result = React.useState(props.value)[0]
console.log('render',result)
console.log('props',props.value)
return (
<div>
{`result: ${result}`}
</div>
)
}

export default Bar

结果:

image.png

每次点击add,Bar都会重新render,但是result永远都是0,可事实上props.value变了。这不符合常理。

useState不能放到条件语句中

众所周知,useState是不能放在条件语句中的。

if(xxx){
const [value,setValue] = React.useState('')
}

如果开发过程中使用eslint这样的工具,是不允许写这样的代码的.

自己实现一个useState

useState的原理

useState是内部是利用数组来存储值的,而且是利用数组的下标来区分不同的值得。如果把useState用在条件语句中,数组的下标就会产生混乱。

初次读这句话我是不理解的,不过看了实现就彻底明白了。

手写useState

高手可以直接看书中的实现,其核心是用了闭包,初次调用useState函数时,同时初始化了hookIndex和setState函数,hookIndex会一直存在于setState的闭包中,当setState被调用时,会通过hookIndex找待更新值的位置,所以hookIndex一旦错误,就会出错。

文字的解释永远是乏味的,如果能看懂代码,根本无需多言。

借用一下书中的实现:

import React from 'react'
import ReactDOM from 'react-dom'

let values = [] // 首先创建了一个用来存useState值的values变量,是一个数组。
let currentHook = 0 // useState函数被调用一次,currentHook值就加一

function useState (initialState) {
if (typeof values[currentHook] === 'undefined') values[currentHook] = initialState
// 这句话解了为什么props.value变了,而result没变。

let hookIndex = currentHook
function setState (nextValue) {
values[hookIndex] = nextValue
ReactDOM.render(<MyName />, document.getElementById('root'))
}
// 经典代码,hookIndex默认等于currentHook,但是它不会随着currentHook的变化而变化,在useState执行完毕后一直存在于setState函数的闭包中。setState函数执行完毕后,会重新渲染组件,useState会重新执行,所以hookIndex的值很重要(useState不能写在条件语句中),不然会张冠李戴。
return [ values[currentHook++], setState ] // 我们用的最多的代码,其实最不重要。
}

function MyName () {
currentHook = 0

const [ name, setName ] = useState('')
const [ lastName, setLastName ] = useState('')

function handleChange (evt) {
setName(evt.target.value)
}

function handleLastNameChange (evt) {
setLastName(evt.target.value)
}

return (
<div>
<h1>My name is: {name} {lastName}</h1>
<input type="text" value={name} onChange={handleChange} />
<input type="text" value={lastName} onChange={handleLastNameChange} />
</div>
)
}

export default MyName

用数组来记录useState的值看起来很奇怪,但却是最好的方法。如果用对象,我们每次调用useState时都必须传一个hookName,而且绝对不能重复,这其实很难。

PS: 最后说一句,虽然我写的很烂,但万一哪位不开眼的copy了我的文章,麻烦注明出处。或者直接买书。