React useState 进阶实践指南
admin
2024-04-13 11:19:52
0

文章目录

  • 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 (
{num}
); }

点击按钮后输出:

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 值的就可以了

过时状态问题

闭包是一个从外部作用域捕获变量的函数。
闭包(例如事件处理程序,回调)可能会从函数组件作用域中捕获状态变量。 由于状态变量在渲染之间变化,因此闭包应捕获具有最新状态值的变量。否则,如果闭包捕获了过时的状态值,则可能会遇到过时的状态问题。
来看看一个过时的状态是如何表现出来的。组件延迟3秒计数按钮点击的次数

import React, { useState } from 'react';function DelayedCount() {const [count, setCount] = useState(0);const handleClickAsync = () => {setTimeout(function delay() {setCount(count + 1);}, 3000);}return (
{count}
); }

快速多次点击按钮。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 (
{count}
); }

现在setCount(count => count + 1)delay()中正确更新计数状态。React 确保将最新状态值作为参数提供给更新状态函数,过时闭包的问题解决了。
快速单击按钮。 延迟过去后,count 能正确表示点击次数。

更新引用数据类型

在使用 useStatedispatchAction 更新 state 的时候,记得不要传入相同的 state,这样会使视图不更新:

const textObj = {name:'yinjie'}
const [useState1, setUseState1] = useState(textObj)setUseState1((oldUseState1) => {oldUseState1.name = 'xxx'return oldUseState1
}
useEffect(() => {console.log(useState1)  
},[useState1])
//结果是没有任何反应

为什么会造成这个原因呢?

useStatedispatchAction 处理逻辑中,会浅比较两次 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]
}

相关内容

热门资讯

罗晓明 | 乐云游,旅游漫记之... (上) 如果说古埃及是古代文明的代表,那么,迪拜便是体现奇迹、现代文明的典型。有个说法,迪拜豪华,一...
白酒“小时代”,谁在共舞? “小时代”的浪潮,正把消费与文化推向“小而美、小而精、小而真”的全新阶段。而转向“价值重构”的白酒“...
香菇焖饭:一锅搞定的鲜香主食 香菇焖饭 忙碌的工作日,谁不想快速做出一顿美味又管饱的饭菜?香菇焖饭就是这样一道贴心的存在 —— 米...
椒盐南瓜:外酥里糯的家常小零嘴 椒盐南瓜 在众多南瓜的做法中,椒盐南瓜以其独特的口感占据一席之地。外皮带着椒盐的咸香与微脆,内里却是...
原创 血... “医生,我最近血糖控制得挺好,但是人是不是感觉有点不对劲,是不是药吃太多了?”在内分泌科门诊,医生们...
这才是月饼界“良心担当”!中华... ▲+艺姐wx,领五张优惠券 最多优惠50元(全场通用) 中秋还没到,各式各样的月饼已经让人眼花缭乱了...
秋季疯狂掉发?先补气血、养肝肾... 很多人一到秋天,头发掉得比树叶还快。虽说秋天脱发是正常的新陈代谢,但如果脱落得太厉害,那就要注意了!...
秋季养肺食疗 秋 季 养 肺 秋季养肺是中医养生的一大重点,非常值得关注。秋天气候干燥,中医认为“燥邪当令”,最容...
秋季进补黄金期!不可错过的8类... 秋季,大自然慷慨地为我们送来了各种滋补食材。这个时节,人体也正需要营养的滋养来应对逐渐转凉的天气。以...
秋天必吃的八道家常菜,简单快手... 秋天,是个收获的季节,各种新鲜食材纷纷上市。在这个秋高气爽的时节,为家人烹制几道美味的家常菜,无疑是...
中秋家宴,少不了这8道“硬菜”... 中秋佳节,明月如盘,家人齐聚。在这个充满温情的节日里,一顿丰盛的家宴必不可少。下面就为大家带来八道好...
初秋时节,遇到这8种秋菜别手软... 初秋的风,带着丝丝凉意,轻轻拂过脸颊,仿佛在提醒我们季节的更迭。在这个美好的时节,大自然慷慨地馈赠了...
国庆去安徽四日游经典路线,学生... 黄山,这座自然与时间的联合作品,一直是我心中向往的旅游胜地。这次,我决定带着父母一起踏上这场探索之旅...
秋季孩子补钙长高季,多吃这8样... 秋天是孩子长高的黄金季节,充足的钙摄入对于孩子的骨骼发育至关重要。今天就为大家带来 8 道美味又补钙...
广东人能接受的“预制菜”居然是... 这几天 “预制菜”的话题备受关注 有网友提出 广东人无法接受大部分的预制菜 但唯独豆豉鲮鱼罐头是个例...
原创 杨... 杨蓉,43岁的她,没有被娱乐圈的喧嚣所吞没,反而以一种近乎诗意的方式,诠释着属于自己的生活哲学。 在...
“银发团”扎堆“错峰游” 在四川省成都市铁路安靖站,“银发专列”上的旅客们在上车前合影。 新华社发 □金陵晚报/紫金山新闻记...
没逛过早市,等于没来过东北!全... 如果问一座城市最先苏醒的地方,早市便是其中之一。天还未亮,早市早已沸腾。如今,早市早已不再是“中老年...
罗定文旅惊艳广东旅博会 “农文... 9月12日,第16届广东国际旅游产业博览会(以下简称“旅博会”)在中国进出口商品交易会展馆A区开幕。...
原创 赵... 前言近日,赵丽颖和她的儿子想想在上海世纪公园露营的活动引起了广大网友的关注。这次露营活动不仅展示了赵...