HooK是react16.8的新增特性,它可以让你在不编写class的情况下使用state以及其他的react的特性
状态是隐藏在组件中的信息,组件可以在父组件不知道的情况下修改其状态。函数组件默认是没有状态的,要使函数组件具有状态管理,可以使用useState() hook进行状态管理。
将状态添加到函数组件需要四个步骤:启用状态、初始化、读取和更新。(下面使用checkbox的选中和取消来展示案例)
启用状态
要讲函数组件转换为有状态的组件,需要告诉React:从’react’包中导入useState钩子,然后再组件函数的顶部调用。
大致如下:
import React, { useState } from 'react';
function App() {
... = useState(...);
return (
<div className="App"><input type="checkbox" /></div>
);
}
export default App;
在App组件函数的第一行调用 useState()(暂时不考虑参数和返回值)。重要的是,在组件内部调用HooK会使该函数成为有状态的函数组件。
启用状态后,下一步就是初始化
初始化状态
开始时,checkbox没有选中,使用状态为false来初始化Hook:
import React, { useState } from 'react';
function App() {
// useState(false) 使用false初始化状态
... = useState(false);
return (
<div className="App"><input type="checkbox" /></div>
);
}
export default App;
启用和初始化状态之后,如何读取它?来看看useState(false) 返回什么。
读取状态
当useState(false)被调用时,它返回一个数组,该数组的第一项是状态值
const stateArray = useState(false);
stateArray[0] // => 状态值
读取组件的状态:
import React, { useState } from 'react';
function App() {
const stateArray = useState(false);
return (
<input type="checkbox" checked={stateArray[0]} />
);
}
export default App;
useState(false) 返回一个数组,第一项包含状态值,该值当前为false(因为状态已用false初始化)。
可以使用解构赋值将状态赋值到变量checked上。
import React, { useState } from 'react';
function App() {
const [checked] = useState(false);
return (
<input type="checkbox" checked={checked} />
);
}
export default App;
checked状态变量保存状态值。
状态已经启用并初始化,现在可以读取了,但是如何更新呢,再看看useState() 返回什么。
更新状态
在读取的时候我们已经知道,useState() 返回一个数组。其中第一项是一个状态值,第二项是一个更新状态的函数。
const [state, setState] = useState(initState);
// 将状态更改为 ‘newState’ 并触发重新渲染
setState(newState);
// 重新渲染 ‘state’ 后的值为 ‘newState’
要更新组件的状态,需使用新状态调用更新器函数setState(newState)。组件重新渲染后,状态接收新值 newState。
当点击选中按钮时将状态更新为true,点击取消时更新为false。
import React, { useState } from 'react';
function App() {
const [checked, setChecked] = useState(false);
return (
<div className="App">
<input type="checkbox" checked={checked} />
<button onClick={() => setChecked(true)}>选中</button>
<button onClick={() => setChecked(false)}>取消</button>
</div>
);
}
export default App;
单击选中按钮时,setChecked() 函数将 checked 更新为true。单击取消时会将 checked 更新为 false。
状态一旦改变,React就会重新渲染组件,checked 变量获取新的状态值。
状态更新作为对提供一些新信息的事件和响应。这些事件包括按钮单击、http请求完成等,确保在事件回调或其他HooK回调中调用状态更新函数。
使用回调更新状态(函数式更新)
// const [state, setState] = useState(initState);
// setState(prevState => nextState);
import React, { useState } from 'react';
function App() {
const [checked, setChecked] = useState(false);
return (
<div className="App">
<input type="checkbox" checked={checked} />
<button onClick={() => setChecked(checked => !checked)}>选中/取消</button>
</div>
);
}
export default App;
setChecked(checked => !checked) 使用函数更新状态
小结
- 调用useState() HooK 来启用函数组件中的状态。
- useState(initState) 的第一个参数 initState 是状态的初始值。
- [state, setState] = useState(initState); 返回一个包含2个元素的数组:状态值和状态更新函数。
- 使用新值调用状态更新器函数 setState(newState) 更新状态。或者可以使用一个回调 setState(prevState => nextState)来调用状态更新器,该回调将返回基于先去状态的新状态。
- 调用状态更新器后,React确保重新渲染组件,使新状态变为当前状态。
多种状态
通过多次调用useState(), 一个函数组件可以拥有多个状态
import React, { useState } from 'react';
function App() {
const [checked, setChecked] = useState(false);
const [count, setCount] = useState(1);
const ipt = Array(count).fill(<input type="checkbox" checked={checked} />)
return (
<div className="App">
<div>{ipt}</div>
<button onClick={() => setChecked(chec => !chec)}>选中/取消</button>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}
export default App;
// const [checked, setChecked] = useState(false); 管理checkbox的选中和取消
// const [count, setCount] = useState(1); 管理checkbox 的数量
多个状态可以在一个组件中正确工作。
状态的延迟初始化(惰性初始state)
initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始state需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的state,此函数只在初始渲染时被调用:
const [state, setState] = useState(() => {
const initialState = someExpensiveComoutation(props);
return initialState;
});
过时状态
闭包是一个从外部作用域捕获变量的函数。
闭包(例如时间处理程序,回调)可能会从函数组件作用域中捕获状态变量。由于状态变量在渲染之间变化,因此闭包应捕获具有最新状态值的变量。否则,如果闭包捕获了过时的状态值,则可能会遇到过时的状态问题。
来看看一个过时的状态是如何表现出来的。组件<DelayedCount>延迟3秒计数按钮点击的次数
import React, { useState } from 'react';
function DelayedCount() {
const [count, setCount] = useState(0);
const handleClickAsync = () => {
setTimeout(() => {
setCount(count + 1);
}, 3000);
}
return (
<div>
{count}
<button onClick={handleClickAsync}>Increase async</button>
</div>
);
}
快速多次点击按钮,count变量不能正确记录实际点击次数,有些点击被吃掉。定时器中的回调函数是一个过时的闭包,它从初始渲染(使用0初始化时)中捕获了过时的count变量。为了解决这个问题,使用函数方法来更新count状态:
...
const handleClickAsync = () => {
setTimeout(() => {
setCount(count => count + 1);
}, 3000);
}
...
现在setCount(count => count + 1) 在回调函数中正确更新计数状态。React确保将最新状态值作为参数提供给更新状态函数,过时闭包的问题解决了。
快速单击按钮。延迟过去后,count 能正确表示点击次数。
HooK 使用规则
在使用useState() Hook 时,必须遵循 Hook 的规则
- 仅顶层调用 Hook :不能在循环,条件,嵌套函数等中调用useState()。在多个useState()调用中,渲染之间的调用顺序必须相同。
- 仅从React 函数调用 Hook:必须仅在函数组件或自定义钩子内部调用useState()。