每天对自己多问几个为什么,总是有着想象不到的收获。 一个菜鸟小白的成长之路(copyer)
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
useState的基本使用
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>点击</button>
</div>
);
}
function render() {
ReactDOM.render(<Example />,document.getElementById('root'))
}
render()
上面的代码中,定义一个函数组件,名为Example
,通过调用 useState
Hook 声明了一个新的 state 变量。它返回一对值给到我们命名的变量上。count
记录点击的次数,setCount
更新count的函数。
useState
函数应该具备以下三个功能:
- 接受一个参数
- 返回一个数组,第一个值为最新的状态值(
count
),第二个参数为改变状态值的函数(setCount
) - 执行改变状态值的函数后需要重新渲染
useState的初步实现
根据上面useState的三个功能
,我们可以简单的写个useState函数。
let _state;
function useState(initState) {
_state = _state || initState
function _setState(newState) {
_state = newState
render()
}
return [_state, _setState]
}
分析代码:
为什么在函数的外面定义一个变量? 原因就是每次执行render
之后,就会重新执行一遍useState函数,那么就会造成重新赋值,造成没有缓存的效果。
useState内部定义一个函数,来改变_state
的值,并且触发render函数。
聪明的人,就会发生上面的代码是存在问题的。上面的代码只是针对一个useState,是没有问题的。但是如果有多个useState,就会造成后面的useState 会覆盖前面的useState。
let _state; //useState中保存的值的变量
function Example() {
const [count, setCount] = useState(0)
const [name, setName] = useState('james')
}
// 执行第一个useState: _state 保存的是count的值
// 执行第二个useState: _state 就是保存的name的值咯
后面的useState,覆盖了前面的useState保存的值,导致一个useState就是出于无效的状态。所以,就需要对代码进一步的改正。
useState的改进完善
let memorialArr = [] //具有记忆的数组
let index = 0 // 定义初始化的下标
function useState(initState) {
let currentIndex = index //保存当前的index
memorialArr[currentIndex] = memorialArr[currentIndex] || initState
function _setState(newState) {
memorialArr[currentIndex] = newState
render()
}
index += 1 //index
return [memorialArr[currentIndex], _setState]
}
function render() {
index = 0
ReactDOM.render(<Example />,document.getElementById('root'))
}
代码实现思路:
- 在外面定义一个数组,用来保存各个useState的值,
- 定义
index
来表示当前是第几个useState - useState根据当前的索引值保存在数组中
- 每次调用useState,就把 index 加 1,为了下次的useState的索引值有所不一样
- 返回值也是根据当前的索引来进行返回的
- 最后一点的就是(重要):在重新进行render的时候,把index的值改为0,这样保证useState重新渲染的时候,索引值依然一一对应
上面的最主要的思想就是:保证每个useState的数组对应的index保持一致。
const [count, setCount] = useState(0)
const [name, setName] = useState('james')
无论渲染多少次,count对应的索引值为0, name对应的索引值为1
这也能充分的解释 hooks 为什么不能处在if while等判断中(超级重要
)
function Example() {
const [count, setCount] = useState(0)
if(count === 0) {
const [name, setName] = useState('james')
}
const [num, setNum] = useState(0)
}
- 在初始化渲染的时候,
memorialArr = [0, 'james', 0]
- count 对应的index为 0,name对应的index为 1,num对应的index 为2
- 在第二次渲染的时候,count的条件不满足,那么不会执行
useState('name')
- count对应的index为0,num对应的值就为1
通过上面的分析,在判断中使用useState时,当条件不满足时,就会造成useState对应的index不一致,就会造成取指的混乱,从而报错.
额外补充
1、修改之后,拿不到最新的值
function Example() {
const [count, setCount] = React.useState(0)
const btn = () => {
setCount(count + 1)
console.log(count); // 0
}
return (
<div>
<p>count: {count}</p>
<button onClick={btn}>count</button>
</div>
);
}
在上面的代码中,已经修改了count的值,为什么打印的时候还是0
, 这就涉及到了 函数组件特性的 capture value
特性。每次修改count的之后,会创建一个新的render状态
,但是打印count
的代码是在上个render状态中,上个render的状态中的count为0,所以打印出0.
const btn = () => {
setCount(count+1)
setCount(count+2)
}
执行上面的代码,界面上只是显示2, 而不是3? 按照理想的状态: 0 + 1
, 1 + 2
, 那么最后的结果应该是3
。但是实际上,并不是这样。这里还是涉及到了capture value
特性。这里修改两次count的值,那就会生成两次新的render状态, 这两次新的render状态都是在最开始render状态基础上新增的,最开始的基础上 render中的 count 为 0,渲染最后一次render状态
,所以状态为2
2、useState的函数形式的写法
setCount((prev) => {
return prev + 1
})
//简写为
setCount(prev => prev + 1)
3、useState处理复杂数据类型
修改的时候,一定要注意,返回一个新的地址,界面才会更新
//处理数组
const [list, setList] = useState(['james', 'kobe'])
//函数形式修改
setList((prev) => {
prev.push('aa')
return [...prev]
})
//直接修改
list.push('aa')
setList([...list])
//处理对象
const [obj, setObj] = useState({name: 'james'})
//函数形式修改
setObj((prev) => {
return {
...prev,
name: 'kobe'
}
})
//直接修改
setObj({name: 'kobe'})