1.为函数组件提供状态
const TestHook = () => {
// 1.使用 useState 创建一个新的状态,返回值是一个数组,包含了两个元素
// - 第一个元素:状态
// - 第二个元素:更新状态的方法
// const countState = useState(0)
// 第一个元素:就是状态
// const count = countState[0]
// 第二个元素:用来更新状态的函数,相当于类组件中的 this.setState()
// const setCount = countState[1]
// 用结构语法简化写法
const [count, setCount] = useState(0)
const handleClick = () => {
// 2.使用状态更新函数,进行状态的更新操作
setCount(count + 1)
}
return (
<div>
<h1>计数:{count}</h1>
<button onClick={handleClick}>累加</button>
</div>
)
}
useState
的参数可以有两种形式:
useState(普通数据)
:比如useState(0)
/useState('abc')
useState(回调函数)
: 比如useState(() => { return 123 })
如果参数为回调函数:
- 该回调函数的返回值就是创建的状态的初始值
- 该回调函数在整个组件生命周期中只执行一次
import { useState } from "react"
const TestState = () => {
// 当我们明确知道初始值是什么值的时候,用以下形式
const [count, setCount] = useState(222)
// 当我们需要经过计算,得出一个不固定的初始值的时候,用一下形式
const [msg, setMsg] = useState(() => {
const txt = '初始消息1:' + Date.now()
console.log('>>>>>', txt);
return txt
})
// 不要写成下面的形式,这种写法每次组件渲染都会执行,浪费性能
// const [message, setMessage] = useState('初始消息2' + Date.now()) 等价于下面的代码
const txt = '初始消息2:' + Date.now()
console.log('======', txt);
const [message, setMessage] = useState(txt)
return (
<div>
<h1>{count}|{msg}|{message}</h1>
<button onClick={() => {
setMsg("更新1:" + Date.now())
setMessage("更新2:" + Date.now())
}}>更新</button>
</div>
)
}
export default TestState
那么 useState
的两种参数形式,到底应该用哪种?
- 如果初始状态是一个已知数据,则用
useState(普通数据)
- 如果初始状态是要经过一些计算才能得到的,则用
useState(回调函数)
2.状态的读取和修改:
读取状态
在函数组件中用 useState
创建的状态,其实就是一个函数内的局部变量,所以它可在组件函数内的任意位置访问。
修改状态
-
setXxx(newState)
是个函数,参数为 新的状态值 -
一定要创建 新状态 来替换 旧状态(和类组件中的 this.setState 的原则一样)
const [person, setPerson] = useState({ name: '张三', age: 18 }) const updatePersonInfo = () => { // 错误方式,修改了老的状态 // person.name = '王五' // 正确的方式,新建状态,替换老的状态 setPerson({ ...person, name: '李四' }) return( <div> <p>{person.name}|{person.age}</p> <button onClick={updatePersonInfo}>更新用户信息</button> </div> )
-
通过
setXxx
修改状态后,组件函数会重新执行,进行界面的重新渲染
总结:
- 修改状态时,一定要用新的状态替换旧的状态,不要直接修改旧的状态,
尤其是引用类型
的状态
3.函数组件的执行过程(面试题)
初次渲染:
- 从头开始执行该组件函数中的逻辑
- 当遇到
useState
调用时,就创建状态(比如useState(0)
创建一个初始值为 0 的状态) - 使用当前的状态值,进行界面渲染(此时的状态值为 0)
第二次及以后的渲染:
- 调用
setXXX
函数更新状态(比如setCount(count + 1)
),此时会触发组件重新渲染 - 重新渲染时,会再次执行组件函数中的所有代码逻辑
- 当再次遇到
useState
调用时,不会创建新状态,而是取之前已有状态值(比如之前更新的 1) - 使用当前的状态值,进行界面渲染(此时的状态值为 1)
总结:
useState 并不是每次都创建新的状态
- 第一次渲染才创建
- 后续则是取之前的状态
React框架内部会收集每一useState()调用后生成或更新的状态到一个链表(类数组,在内存中不连续)中,以供组件更新时获取之前已有状态值。
相当于函数组件外部放了一个存放状态的地方- 函数执行完之后,会释放掉内部所有的东西,不会被保存下来。
4.使用说明和注意事项
useState
的注意事项
-
不能在
函数组件
和自定义 Hooks 函数
以外的地方调用! -
不能在 含有条件判断、循环遍历的代码块中调用(如 if、for、while 等)!
【原因说明】
React Hooks 是按照组件第一次渲染时的调用顺序来存储状态的,在后续渲染中,会按这个固定顺序来操作状态。
所以,一旦用了 if/for 等语句,就可能会导致顺序对应不上的情况发生。
import {useState} from 'react' //相当于在函数组件的 外面 放了一个存放状态的地方 const arr = [message,count,message2] // 链表(类数组,元素在内存中不连续)。单项链表,下一个元素知道上一个元素的位置;双向链表,每个元素都知道上一个和下一个元素的位置。 const TestComp = () => { const [message,setMessage] = useState('init value') if(true){ // 如果函数组件第一次渲染时,若判断条件为true,链表中就会在第二个位置存放 cuont 。 //当组件第二次渲染时,若判断条件为false,会导致if语句后面的状态 message2 按存放顺序从链表中取出 count 的值。 const [count,setCount] = useState(0) } const [message2,setMessage2] = useState('init value') }
通过 React 开发者工具,查看组件中的状态和顺序:
总结
以上提到的这些规则,同样也适用于任意的 React Hooks 函数