在 React 的世界中,有容器组件和 UI 组件之分,在 React Hooks 出现之前,UI 组件我们可以使用函数,无状态组件来展示 UI,而对于容器组件,函数组件就显得无能为力,我们依赖于类组件来获取数据,处理数据,并向下传递参数给 UI 组件进行渲染。使用 React Hooks 相比于从前的类组件有以下几点好处:
Hook 是 React 16.8.0 版本增加的新特性,可以在函数组件中使用 state
以及其他的 React 特性
。
Hooks只能在函数式组件中使用,既无状态组件(所有钩子在用时都要先引入)。
Hook 就是JavaScript 函数
,但是使用它们会有两个额外的规则:
1、只能在函数最外层调用 Hook
。不要在循环、条件判断
或者嵌套函数(子函数)
中调用。
(参考:https://blog.csdn.net/weixin_34413802/article/details/91475789)
2、只能在 React 的函数组件
中调用 Hook
。不要在其他 JavaScript 函数中调用。
3、在多个useState()
调用中,渲染之间的调用顺序
必须相同
。
useState() 在函数组件引入状态的钩子,Hook 的每次调用都有一个完全独立的 state —— 因此你可以在单个组件中多次调用同一个 Hook。
const [xxx, setXxx] = useState(initValue); // 解构赋值
例如:
function Counter({
initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {
count}
<button onClick={
() => setCount(initialCount)}>Reset</button>
<button onClick={
() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={
() => setCount(prevCount => prevCount + 1)}>+</button>
</>
);
}
count 只在组件首次渲染
的时候被创建,通过调用 setCount 来更新当前的 count。
组件重新渲染,useState 返回给我们当前最新的count值。
第一次初始化指定的值在内部作缓存。可以按照需要使用数字
或字符串
对其进行赋值
,而不一定是对象
。没有规定一定要是string/number/boolean
这种简单数据类型,它完全可以接收对象
或者数组
作为参数。
包含 2 个元素的数组,第 1 个为内部当前状态值
,第 2 个为更新状态值的函数
,一般直接采用es6里面的解构赋值
获取。
如果想要在state中存储多个不同的变量
,只需调用 useState() 多次
即可。
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{
text: 'Learn Hooks' }]);
useState无论调用多少次,相互之间是独立的
这一点至关重要,那么react怎么保证多个useState之间是相互独立的?
比如上面的例子,我们调用了三次useState,每次我们传的参数只是一个值(如42,‘banana’),我们根本没有告诉react这些值对应的key是哪个,那react是怎么保证这三个useState找到它对应的state呢?
答案是:react是根据useState出现的顺序来定的
//第一次渲染
useState(42); //将age初始化为42
useState('banana'); //将fruit初始化为banana
useState([{
text: 'Learn Hooks' }]); //...
//第二次渲染
useState(42); //读取状态变量age的值(这时候传的参数42直接被忽略)
useState('banana'); //读取状态变量fruit的值(这时候传的参数banana直接被忽略)
useState([{
text: 'Learn Hooks' }]); //...
假如我们改一下代码:
let showFruit = true;
function ExampleWithManyStates() {
const [age, setAge] = useState(42);
if(showFruit) {
const [fruit, setFruit] = useState('banana');
showFruit = false;
}
const [todos, setTodos] = useState([{
text: 'Learn Hooks' }]);
这样一来
//第一次渲染
useState(42); //将age初始化为42
useState('banana'); //将fruit初始化为banana
useState([{
text: 'Learn Hooks' }]); //...
//第二次渲染
useState(42); //读取状态变量age的值(这时候传的参数42直接被忽略)
// useState('banana');
useState([{
text: 'Learn Hooks' }]); //读取到的却是状态变量fruit的值,导致报错
所以,react有规定:我们必须把hooks写在函数的最外层,不能写在ifelse等条件语句当中,来确保hooks的执行顺序一致
。
1、setXxx(newValue)
:参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值。
2、setXxx(value => newValue)
:参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值
initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。
如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用:
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
function App() {
const [obj, setObject] = useState({
count: 0,
name: "alife"
});
return (
<div className="App">
Count: {
obj.count}
<button onClick={
() => setObject({
...obj, count: obj.count + 1 })}>+</button>
<button onClick={
() => setObject({
...obj, count: obj.count - 1 })}>-</button>
</div>
);
}
useState 会返回一对值:当前状态
和一个让你更新它的函数
,你可以在事件处理函数中或其他一些地方调用这个函数。
它类似
class 组件的 this.setState,但是useState 不会自动合并更新对象
,不会把新的 state 和旧的 state 进行合并
,更新 state 变量是完全替换
。
如果你想,官网上给出思路,利用展开运算符(···)
来达到合并更新对象的效果
const [state, setState] = useState({
});
setState(prevState => {
// 也可以使用 Object.assign
return {
...prevState, ...updatedValues};
});
1、useState(initValue),初始值可以是基本数据类型,而不一定是对象
;而this.state必须是一个对象
2、创建多个状态
,需要多次调用
useState();this.state只哟领调用一次即可
3、在useState()中,state和改变状态的函数需要成对获取
,如const [count, setCount] = useState();而this.state的改变需要用this.setState
改变
闭包
是一个从外部作用域捕获变量的函数。
闭包(例如事件处理程序,回调)可能会从函数组件作用域中捕获状态变量。
由于状态变量在渲染之间变化,因此闭包应捕获具有最新状态值的变量。
否则,如果闭包捕获了过时的状态值,则可能会遇到过时的状态问题。
过时状态:
import React ,{
useState}from 'react';
const App2 = () => {
const [count , setCount] = useState(0)
const handleClick = () => {
setTimeout(()=>{
setCount(count+1)
console.log('111')
},3000)
console.log('222')
}
return (
<div>
{
count}
<button onClick={
handleClick}>点击我</button>
</div>
);
};
export default App2;
可以看出,这是一个过时的闭包,它从初始渲染(使用0初始化时)中捕获了过时的count变量。
为了解决这个问题,我们使用函数方法来更新count的状态。
更新状态
import React ,{
useState}from 'react';
const App2 = () => {
const [count , setCount] = useState(0)
const handleClick = () => {
setTimeout(()=>{
setCount(count=>count+1)
console.log('111')
},3000)
console.log('222')
}
return (
<div>
{
count}
<br/>
<button onClick={
handleClick}>点击我</button>
</div>
);
};
export default App2;
效果图
不难看出,count=>count+1
正确的更新计数状态。React 确保将最新状态值作为参数提供给更新状态函数,过时闭包的问题解决了。