useState
1. 基本用法
useState,能让函数组件拥有自己的状态,因此,它是一个管理状态的 hooks API
。通过 useState 可以实现状态的初始化、读取、更新。
基本语法格式如下:
const [状态名, set函数] = useState(初始值)
其中:状态名所代表的数据,可以被函数组件使用;如果要修改状态名所代表的数据,需要调用 set 函数 进行修改。例如:
import { useState } from 'react'
export function Count() { // 定义状态 count,其初始值为 0 // 如果要修改 count 的值,需要调用 setCount(新值) 函数 const [count, setCount] = useState(0) return ( <> <!-- 在函数组件内,使用名为 count 的状态 --> <h1>当前的 count 值为:{count}</h1> <!-- 点击按钮时,调用 setCount() 函数,为 count 赋新值 --> <button onClick={() => setCount(count + 1)}>点我+1</button> </> )
}
2. 状态变化时,会触发函数组件的重新执行
在函数组件中使用 setState 定义状态之后,每当状态发生变化,都会触发函数组件的重新执行,从而根据最新的数据更新渲染 DOM 结构。
例如:
import { useState } from 'react'
export function Count() { // 定义状态 count,其初始值为 0 // 如果要修改 count 的值,需要调用 setCount(新值) 函数 const [count, setCount] = useState(0) // 每次 count 值发生变化,都会打印下面的这句话: console.log('组件被重新渲染了') const add = () => { setCount(count + 1) } return ( <> <!-- 在函数组件内,使用名为 count 的状态 --> <h1>当前的 count 值为:{count}</h1> <!-- 点击按钮时,在 add 处理函数中,调用 setCount() 函数,为 count 赋新值 --> <button onClick={add}>+1</button> </> )
}
注意:当函数式组件被重新执行时,不会重复调用
useState()
给数据赋初值,而是会复用上次的state
值。
3. 以【函数】形式为状态赋初始值
在使用 useState 定义状态时,除了可以直接给定初始值,还可以通过函数返回值的形式,为状态赋初始值,语法格式如下:
const [value, setValue] = useState(() => 初始值)
例如:
export const DateCom: React.FC = () => { // const [date] = useState({ year: 2023, month: 9, day: 11 }) const [date, setDate] = useState(() => { const dt = new Date() return { year: dt.getFullYear(), month: dt.getMonth() + 1, day: dt.getDate() } }) return ( <> <h1>今日信息:</h1> <p>年份:{date.year}年</p> <p>月份:{date.month}月</p> <p>日期:{date.day}日</p> </> )
}
注意:以函数的形式为状态赋初始值时,只有组件首次被渲染才会执行 fn 函数;当组件被更新时,会以更新前的值作为状态的初始值,赋初始值的函数不会执行。
4. useState 是异步变更状态的
调用 useState() 会返回一个变更状态的函数,这个函数内部是以异步的形式修改状态的,所以修改状态后无法立即拿到最新的状态,例如:
export const Count: React.FC = () => { const [count, setCount] = useState(() => 0) const add = () => { // 1. 让数值自增+1 setCount(count + 1) // 2. 打印 count 的值 console.log(count) } return ( <> <h1>当前的 count 值为:{count}</h1> <button onClick={add}>+1</button> </> )
}
在上述代码的第8行,打印出来的 count 值是更新前的旧值,而非更新后的新值。证明 useState 是异步变更状态的。
5. 结合 useEffect 监听状态的变化
为了能够监听到状态的变化,react 提供了 useEffect 函数。它能够监听依赖项状态的变化,并执行对应的回调函数。基本语法格式如下:
useEffect(() => { /* 依赖项变化时,要触发的回调函数 */ }, [依赖项])
例如:
export const Count: React.FC = () => { const [count, setCount] = useState(() => 0) const add = () => { setCount(count + 1) } // 当 count 变化后,会触发 useEffect 指定的回调函数 useEffect(() => { console.log(count) }, [count]) return ( <> <h1>当前的 count 值为:{count}</h1> <button onClick={add}>+1</button> </> )
}
注意:useEffect 也是 React 提供的 Hooks API,后面的课程中会对它进行详细的介绍。
6. 注意事项
6.1 更新对象/数组类型的值
知识回顾:判断对象的值是否改变,看的是地址是否改变
如果要更新对象类型的值,并触发组件的重新渲染,则必须使用展开运算符或 Object.assign()
生成一个新对象,用新对象覆盖旧对象,才能正常触发组件的重新渲染。
示例代码如下:
export const UserInfo: React.FC = () => { const [user, setUser] = useState({ name: 'zs', age: 12, gender: '男' }) const updateUserInfo = () => { user.name = 'Jesse Pinkman' // 下面的写法是错误的,因为 set 函数内部,会对更新前后的值进行对比; // 由于更新前后的 user,原值的引用和新值的引用相同, // 所以 react 认为值没有发生变化,不会触发组件的重新渲染。 // setUser(user) // 【解决方案】:用新对象的引用替换旧对象的引用,即可正常触发组件的重新渲染。 // 1、setUser({ ...user }) // 2、setUser(Object.assign({}, user)) // 通常在实际开发中,经常结合【展开运算符 + 属性值覆盖】的形式更新对象的属性值: setUser({...user, name: 'Jesse Pinkman'}) } return ( <> <h1>用户信息:</h1> <p>姓名:{user.name}</p> <p>年龄:{user.age}</p> <p>性别:{user.gender}</p> <button onClick={updateUserInfo}>更新用户信息</button> </> )
}
6.2 解决值更新不及时的 Bug
当连续多次以相同的操作更新状态值时,React 内部会对传递过来的新值进行比较,如果值相同,则会屏蔽后续的更新行为,从而防止组件频繁渲染的问题。这虽然提高了性能,但也带来了一个使用误区,例如:
export const Count: React.FC = () => { const [count, setCount] = useState(() => 0) const add = () => { // 1. 希望让 count 值从 0 自增到 1 setCount(count + 1) // 2. 希望让 count 值从 1 自增到 2 setCount(count + 1) } return ( <> <h1>当前的 count 值为:{count}</h1> <button onClick={add}>+1</button> </> )
}
经过测试,我们发现上述代码执行的结果,只是让 count 从 0 变成了 1,最终的 count 值并不是 2。Why?
因为 setCount
是异步地更新状态值的,所以前后两次调用 setCount
传递进去的新值都是 1
。React 内部如果遇到两次相同的状态,则会默认阻止组件再次更新。
为了解决上述的问题,我们可以使用函数的方式给状态赋新值。当函数执行时才通过函数的形参,拿到当前的状态值,并基于它返回新的状态值。
示例代码如下:
export const Count: React.FC = () => { const [count, setCount] = useState(() => 0) const add = () => { // 传递了更新状态的函数进去 setCount((c) => c + 1) setCount((c) => c + 1) } return ( <> <h1>当前的 count 值为:{count}</h1> <button onClick={add}>+1</button> </> )
}
【总结】setState 更新值的两种方式
1、setCount(新值)
2、setCount((pre) => 基于pre计算并返回的新值)
tips
:当我们修改 state 时,如果新值依赖旧值,应选择第二种方式更新状态
6.3 使用 setState 模拟组件的强制刷新
在函数组件中,我们可以通过 useState
来模拟 forceUpdate
的强制刷新操作。因为只要 useState 的状态发生了变化,就会触发函数组件的重新渲染,从而达到强制刷新的目的。具体的代码示例如下:
export const FUpdate: React.FC = () => { const [, forceUpdate] = useState({}) // 每次调用 onRefresh 函数,都会给 forceUpdate 传递一个新对象 // 从而触发组件的重新渲染 const onRefresh = () => forceUpdate({}) return ( <> <button onClick={onRefresh}>点击强制刷新 --- {Date.now()}</button> </> )
}
注意:因为每次传入的
对象的地址
不同,所以一定会使组件刷新。