文章目录
- useState 用法详解
- 基本用法
- initData 为非函数的情况
- initData 为函数的情况
- state 变化监听
- 过时状态问题
- 更新引用数据类型
- useState 实现原理
useState 用法详解
React-hooks
正式发布以后, useState
可以使函数组件像类组件一样拥有 state
,也就说明函数组件可以通过 useState
改变 UI 视图。那么 useState
到底应该如何使用,底层又是怎么运作的呢,首先一起看一下 useState
。
基本用法
[ state , dispatch ] = useState(initData)
- state,目的提供给 UI ,作为渲染视图的数据源。
- dispatch 改变 state 的函数,可以理解为推动函数组件渲染的渲染函数。
- initData 有两种情况,第一种情况是非函数,将作为 state 初始化的值。 第二种情况是函数,函数的返回值作为 useState 初始化的值。
initData 为非函数的情况
/* 此时将把 0 作为初使值 */
const [ num , setNum ] = useState(0)
initData 为函数的情况
每当 React
重新渲染组件时,都会执行useState(initData)
。 如果初始状态是原始值(数字,布尔值等),则不会有性能问题。
当初始状态需要昂贵的性能方面的操作时,可以通过为useState(computeInitialState)
提供一个函数来使用状态的延迟初始化,如下所示:
function MyComponent({ bigJsonData }) {
const [value, setValue] = useState(function getInitialData() {
const object = JSON.parse(bigJsonData);
return object.initialValue;
});
}
getInitialData()
仅在初始渲染时执行一次,以获得初始状态。在以后的组件渲染中,不会再调用getInitialData()
,从而跳过昂贵的操作。
state 变化监听
类组件 setState
中,有第二个参数 callback
或者是生命周期 componentDidUpdat
可以检测监听到 state
改变或是组件更新。
那么在函数组件中,如何怎么监听 state
变化呢?这个时候就需要 useEffect
出场了,通常可以把 state
作为依赖项传入 useEffect
第二个参数 deps
,但是注意 useEffect
初始化会默认执行一次。
具体可以参考如下 Demo :
import { useState, useEffect } from "react";
export default function App() {
const [num, setNum] = useState(0);
/* 监听 num 变化 */
useEffect(() => {
console.log("监听num变化,此时的num是: " + num);
}, [num]);
const handerClick = () => {
setNum(1);
setTimeout(() => {
setNum(3);
});
};
console.log(num);
return (
<div>
<span> {num}</span>
<button onClick={handerClick}>num++</button>
</div>
);
}
点击按钮后输出:
0
监听num变化,此时的num是: 0
1
监听num变化,此时的num是: 1
3
监听num变化,此时的num是: 3
我们再把上面的demo改成这样,看看会输出什么:
const [ num , setNum ] = React.useState(0)
const handleClick = ()=>{
setNum(1)
console.log(num)
setTimeout(()=>{
setNum(3)
console.log(num)
})
}
结果:0 0 0
原因很简单,函数组件更新就是函数的执行,在函数一次执行过程中,函数内部所有变量重新声明,所以改变的 state ,只有在下一次函数组件执行时才会被更新。所以在如上同一个函数执行上下文中,number 一直为0,无论怎么打印,都拿不到最新的 state 。
我们只需要记住:在本次函数执行上下文中,是获取不到最新的 state 值的就可以了
过时状态问题
闭包是一个从外部作用域捕获变量的函数。
闭包(例如事件处理程序,回调)可能会从函数组件作用域中捕获状态变量。 由于状态变量在渲染之间变化,因此闭包应捕获具有最新状态值的变量。否则,如果闭包捕获了过时的状态值,则可能会遇到过时的状态问题。
来看看一个过时的状态是如何表现出来的。组件<DelayedCount>
延迟3秒计数按钮点击的次数
import React, { useState } from 'react';
function DelayedCount() {
const [count, setCount] = useState(0);
const handleClickAsync = () => {
setTimeout(function delay() {
setCount(count + 1);
}, 3000);
}
return (
<div>
{count}
<button onClick={handleClickAsync}>Increase async</button>
</div>
);
}
快速多次点击按钮。count
变量不能正确记录实际点击次数,有些点击被吃掉。delay()
是一个过时的闭包,它从初始渲染(使用0初始化时)中捕获了过时的count变量。
为了解决这个问题,使用函数方法来更新count
状态:
import React, { useState } from 'react';
function DelayedCount() {
const [count, setCount] = useState(0);
const handleClickAsync = () => {
setTimeout(function delay() {
setCount(count => count + 1);
}, 3000);
}
return (
<div>
{count}
<button onClick={handleClickAsync}>Increase async</button>
</div>
);
}
现在setCount(count => count + 1)
在delay()
中正确更新计数状态。React
确保将最新状态值作为参数提供给更新状态函数,过时闭包的问题解决了。
快速单击按钮。 延迟过去后,count
能正确表示点击次数。
更新引用数据类型
在使用 useState
的 dispatchAction
更新 state
的时候,记得不要传入相同的 state
,这样会使视图不更新:
const textObj = {name:'yinjie'}
const [useState1, setUseState1] = useState(textObj)
setUseState1((oldUseState1) => {
oldUseState1.name = 'xxx'
return oldUseState1
}
useEffect(() => {
console.log(useState1)
},[useState1])
//结果是没有任何反应
为什么会造成这个原因呢?
在 useState
的 dispatchAction
处理逻辑中,会浅比较两次 state
,发现 state
相同,不会开启更新调度任务;demo 中两次 state 指向了相同的内存空间,所以默认为 state 相等,就不会发生视图更新了。
解决方法:
const textObj = {name:'yinjie'}
const [useState1, setUseState1] = useState(textObj)
setUseState1((oldUseState1) => {
oldUseState1.name = 'xxx'
/** 返回一个新的对象,useEffectc才能检测得到 */
return {...oldUseState1}
}
useEffect(() => {
console.log(useState1) // {name: "xxx"}
},[useState1])
在上面的 demo 中我们浅拷贝了对象,重新申请了一个内存空间。
useState 实现原理
下面简单写一下useState的实现原理:
function useState(init) {
let state;
// useState无法保存函数
if(typeof init === 'function') {
state = init()
} else {
state = init
}
const setState = (change) => {
// 判断一下是否传递过来的是函数
if(typeof change === 'function') {
// 如果是函数,调用,并将之前的state传过去,接收到的返回值作为新的state并赋值
state = change(state)
} else {
// 如果不是函数,直接赋值
state = change;
}
}
return [state, setState]
}