useState() 使用详解

262 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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>
    </>
}

chrome-capture-2022-7-9.gif

一旦状态发生变化,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 执行状态操作。