携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第15天,点击查看活动详情
useState()
一句话解释 useState() hook:管理函数式组件的状态。
无状态函数组件
无状态函数式组件没有state,一般负责展示。
function NoStateComponent() {
return <div>no state</div>
}
我们给这个组件添加一个“空调遥控”的相关操作,使用 useState() 给它添加状态。
使用 useState() 进行“空调“组件状态管理
使用 useState() 将 NoStateComponent 转为有状态的组件。
在组件内部调用 hook 使其成为有状态的函数式组件。
const AirConditioning = () => {
const [/* ... */] = useState(/* ... */)
return <div>关闭中</div>
}
开关
useState(false)返回一个数组。第一项是状态值,当前为 false;第二项是修改状态值的函数。
const AirConditioning = () => {
const [go, setGo] = useState(false)
return <div>{go ? '运行中' : '已关闭'}</div>
}
go 就是我们这个组件的状态,可以通过添加一个按钮来修改 go。
const AirConditioning = () => {
const [go, setGo] = useState(false)
const toggleGo = () => {
setGo(go => !go)
}
return <>
<button onClick="toggleGo">{go ? '关闭' : '启动'}</button>
<div>{go ? '运行中' : '已关闭'}</div>
</>
}
一旦状态发生变化,React 就会重新渲染组件。go 变量获取新的状态值。
项目开发中一般在按钮的点击交互、数据请求的回调中使用 setXxx() 去更新状态
setGo 里面写了一个函数 go => !go,箭头函数参数是前一个 state 的值,返回值作为更新值,这里可以简写成 !go,但是有些地方是不能这么写的,可以看下面的例子。
旧state与新state
点击一个按钮,每次点击1秒后,将数值增加
const Count = () => {
const [count, setCount] = useState(0)
const handleClick = () => {
setTimeout(() => {
setCount(count + 1)
}, 1000)
}
return <div>
<button onClick={handleClick}>{count}</button>
</div>
}
这么写好像没问题,但是点击几次之后会发现,它仅仅显示了1。
问题表现在,闭包每次获取的 count 都是一个初始化的state,也就是0,所以 setCount 每次只是把 count 更新为 0+1=1。
我们应该使用函数式的方式来更新 count 状态
const handleClick = () => {
setTimeout(() => {
setCount(count => count + 1) // 改了这里
}, 1000)
}
使用 setCount(count => count + 1) 能够正确更新内部的 count 状态
React 确保了最新的状态值作为参数传递给 setCount 更新状态函数。
在合适的地方初始化
使用 useState() 初始化时,必须遵守:
- useState() 不能 在循环、条件、嵌套函数等中调用。多次useState()调用时,渲染之间的调用顺序必须相同。
- useState() 仅在函数式组件中或者自定义 hook 内使用。
在自定义 hook 内使用
function customHook(initValue) {
const [go, setGo] = useState(initValue);
return [go, () => setOn(!go)];
}
function AirConditioning() {
const [go, setGo] = toggleHook(false);
}
复杂状态管理:useReducer()
useState() 主要管理简单的状态。
为了复杂状态的管理,使用 useReducer() hook 比较合适,它为需要多个操作的状态提供了更好的支持。
实现一个 mini todo
const Todo = () => {
const [todos, setTodos] = useState([{ title: '123' }])
const add = (todo) => setTodos([...todos, todo])
const remove = (name) => {
setTodos([
...todos.slice(0, index),
...todos.slice(index + 1)
])
}
return <ul>
{
todos.map(i => {
// ...
})
}
</ul>
}
状态列表需要几个操作:添加、删除、或者更新。状态管理细节使组件更混乱。
更好的解决方案是将复杂的状态管理提取到reducer中:
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'add':
return [...state, action.item];
case 'remove':
return [
...state.slice(0, action.index),
...state.slice(action.index + 1)
];
default:
throw new Error();
}
}
const Todo = () => {
const [state, dispatch] = useReducer(reducer, [{ name: '123' }]);
return (
{
// ...
}
)
}
可以将 reducer 提取到一个单独的模块中,然后在其他组件中重新使用它。
组件呈现 UI 和对事件的响应,而 reducer 执行状态操作。