State Hook
State Hook是一个在函数组件中使用的函数, 该函数名字是useState, 用于在函数组件中提供状态
让React的函数组件能够像类组件一样拥有state
useState
函数有一个参数, 这个参数的值表示状态的默认值
在我们引入react的时候顺带先引入一下stateHook
import React, { useState } from 'react';
useState
函数的返回值是一个数组, 该数组一定包含两项
- 数组的第一项为状态的值
- 数组的第二项是函数
useState的基本使用方法如下
import React, { useState } from 'react';
export default function App(props) {
// 在函数组件中state可以是一个原始类型
// 使用一个状态, 该状态的默认值是0, 调用useState后返回一个数组
// 数组的第一项是默认值, 数组的第二项是一个函数用来改变这个state
const arr = useState(0);
const number = arr[0];
const setNumber = arr[1];
return (
<div>
{/* 点击了按钮以后调用setNumber, setNumber接收一个参数就是要将number改变为何值 */}
<span onClick = { () => { setNumber(number + 1) } }> + </span>
<span>{ number }</span>
<span onClick = { () => { setNumber(number - 1) } }> - </span>
</div>
)
}
我们是否有发现用hook来给函数组件装饰了以后实现同样得功能函数组件会比类组件写法简洁很多
其实我们还可以更加简洁, 如果大家学习了ES6的解构的话, 我相信对上方代码的某一块能够思考到这块
const arr = useState(0);
const number = arr[0];
const setNumber = arr[1];
// 上方代码的这一块如果我们用解构的话是可以浓缩为一行如下
const [number, setNumber] = useState(0); // 效果是一模一样的, 未来我们也会普遍使用这种方式进行书写
一个函数组件可以拥有多个state, 这种写法非常有利于横向切分关注点.
这是什么意思呢, 也就是说在函数组件中我们可以多次调用useState从而返回多个不同的state, 而在类组件中我们必须所有的状态都书写在state这个对象中, 如果数据多了会让state解构变得特别复杂且不容易阅读
下方我们就通过useState生成了两个state
import React, { useState } from 'react';
export default function App(props) {
const [number, setNumber] = useState(0); // 定义一个状态默认值为0 来控制页面中展示的数字值
const [isVisible, setVisible] = useState(true); // 定义一个状态默认值为true, 来控制类名为wrapper的div是否显示
return (
<div>
<div style = {{
display: isVisible ? 'block' : 'none'
}} className = 'wrapper'>
{/* 点击了按钮以后调用setNumber, setNumber接收一个参数就是要将number改变为何值 */}
<span onClick={() => { setNumber(number + 1) }}> + </span>
<span>{number}</span>
<span onClick={() => { setNumber(number - 1) }}> - </span>
</div>
<button onClick = { () => { setVisible(!isVisible) } }>显示/隐藏</button>
</div>
)
}
了解了useState的基本操作, 我们来看看他的原理
不知道朋友们有没有一个疑问, 就是函数组件每次渲染都会重新进行调用把函数体重新走一次, 但是useState的值却没有一直被赋予初值, 这是为什么呢? 我们带着这个问题来看看原理
看了上方的图以后, 大家可能想值得那这个状态怎么被清空呢, 清空方式只有一个那就是该函数组件被卸载, 所以注意: 如果函数组件被卸载则表格被清空那么调用useState会被赋初值, 所以为了避免出现bug, 根据react渲染原理, 我们要尽量用style来控制元素的消失和隐藏
使用useState的注意点
- useState最好写到函数的起始位置, 主要是便于阅读
- useState严禁出现在代码块(判断和循环等)中
- useState返回的函数(数组的第二项), 这个函数的引用是不会变化的(优化性能)
- 如果使用函数改变数据, 若数据和之前的数据完全相等(使用Object.is), 则不会重新渲染, 由于Object.is是浅比较, 所以如果状态是一个对象的时候要小心操作了
- 如果使用函数改变数据, 传入的值不会和原来的数据进行合并而是直接进行替换(跟setState完全不一样), 所以在修改对象的时候, 我们要先将之前的对象保存下来
export default function App(props) {
const [obj, setObj] = useState({
name: '小明',
age: 18
})
return (
<>
{obj.name}, {obj.age}
<button onClick={() => {
setObj({
...obj, // 先将之前的对象进行展开
age: 20
})
}}>change</button>
</>
)
}
- 不要直接去改变state的值
- 如果要实现强制刷新组件的情况: 如果是类组件我们都会使用forceUpdate, 在函数组件中, 我们可以用useState来实现, 使用useState的改变state的函数传入一个空对象, 因为每次传入一个空对象的地址不一样所以一定会刷新
export default function App(props) {
// const [number, setNumber] = useState(0); // 定义一个状态默认值为0 来控制页面中展示的数字值
// const [isVisible, setVisible] = useState(true); // 定义一个状态默认值为true, 来控制类名为wrapper的div是否显示
// let element = isVisible ? <ChildA /> : null;
const [, forceUpdate] = useState({})
return (
<>
<button onClick={() => {
forceUpdate({})
}}>强制刷新</button>
</>
)
}
- 如果某些状态之间没有必然的联系, 应该分化为不同的状态而非合并成一个对象
- 和类组件一样, 函数组件的状态更改在某些时候是一步的(dom事件下), 如果是异步的更改, 则多个状态的更改会合并, 此时不能信任之前的状态, 而应该使用回调函数的方式改变状态
export default function App(props) {
const [num, setNum] = useState(0);
return (
<>
<span>{num}</span>
<button onClick={() => {
setNum(num + 1) // 不会立马运行会等到事件完全走完以后一起运行, 同时多个更改会合并到一起执行
setNum(num + 1) // 因为是一步的, 运行到第二个setNum的时候num的基础值还是0
}}>num++</button>
</>
)
}
// 如果我们确实需要进行对num的两次更改请务必使用回调函数的方式
export default function App(props) {
const [num, setNum] = useState(0);
return (
<>
<span>{num}</span>
<button onClick={() => {
setNum(curNum => curNum + 1) // 传入的函数会在事件走完以后按照顺序依次执行
setNum(curNum => curNum + 1)
}}>num++</button>
</>
)
}
- 就是之前标红的, 因为组件被卸载会导致状态表清空, 所以当我们需要频繁的隐藏显示一个组件的时候最好使用style的display的方式来进行操作, 同时这种操作从渲染原理层面来说也更利于效率的提升