(原创)深入解读s React 中的useState Hook 修改了值,但是不重新渲染,不刷新的问题

本文深入探讨了React类组件和函数组件修改状态的机制。在类组件中,`setState`会合并新数据与旧数据;而在函数组件中,`useState`则直接替换原数据。当涉及引用类型数据时,浅拷贝导致的意外状态更新问题,需通过深拷贝解决,如使用`[...data]`或`JSON.parse(JSON.stringify(data))`。文章以实际代码示例说明了这个问题,并给出了修正方案。
摘要由CSDN通过智能技术生成

React类组件和函数组件修改自身状态的设计机制

class组件

 export default class App extends Component {
  state = {
    list: {
       name:'zhangsan'
     }
  };

  render() {
  
    return (
      <div>
          ....
      </div>
    );
  }
}

如上代码,如果想修改class组件的list状态值:

    this.setState({list:{name:'lisi})
    
    // 修改之后的list状态值为
    {
       name:'lisi
     }

如果,你不想修改name字段的值,只是想新加一个属性值,则直接:

    this.setState({ list:{age:18}})
    
    // 修改之后的list状态值为
    {
       name:'lisi,
       age:18
    }

很多人这时候就纳闷了,为何修改了list为age,可name属性还存在。

结论:因为React底层设计setState用于修改类组件的自身状态时,规定新数据会与原来的数据进行合并操作,而非替换

函数组件

  • 函数组件通过useState Hook来声明自身状态及,修改状态的方法函数,如下:
import React, { useState, useEffect, useCallback, useMemo } from "react";
import Header from "./components/Header";
import List from "./components/List";
import Footer from "./components/Footer";
import "./App.css";
import { myContext } from "./context";

export default function App() {

  let arr = [
    {
      id: 1,
      checked: true,
      title: "打球",
    },
    {
      id: 2,
      checked: false,
      title: "看美女",
    },
    {
      id: 3,
      checked: true,
      title: "唱歌",
    },
  ]

  const [data, setData] = useState(arr);

  // 将data作为value值传入context.Provider
  const contextValue = { data, setData };

  return (
    <myContext.Provider value={ contextValue }>
      <div className="todo-container">
        <div className="todo-wrap">
          <Header ></Header>
          <List></List>
          <Footer></Footer>
        </div>
      </div>
    </myContext.Provider>
  );
}

可能有小伙伴纳闷,这里myContext.Provider是什么,这里解释一下,myContext是通过React.createContext()创建的一个Context上下文,在这个组件中通过myContext.Provider标签包裹,value属性传递值,下面被包裹的所有子组件都能获取到value传递下去的值,并且下面的子组件也都会随着value内的值的改变而触发重新渲染。

myContext.Provider内有个输入框添加任务的组件:

import React, {
  useState,
  useEffect,
  useCallback,
  useMemo,
  useContext,
} from "react";
import { nanoid } from "nanoid";
import { myContext } from "../../context";
import "./index.css";

export default function Header(props) {
  const { } = props
  const [inputV,setInputV] = useState('')

  //获取祖先组件的Context
  const { data,setData } = useContext(myContext);
  console.log("Header  data :>> ", data);

  //输入框触发修改事件
  let changeMethod = useCallback((evt) => {
    console.log("Header  changeMethod :>> ", evt.target.value);
    setInputV(evt.target.value)
  });

  //输入框触发键盘按键抬起事件 =》新增数据
  let keyUpMethod = useCallback((evt) => {
    console.log("Header  keyUpMethod :>> ", evt.target.value);
    if (evt.keyCode === 13) {
      let tempData = data; // 注意这里...................
      tempData.unshift({
        id: nanoid(),
        checked: false,
        title: evt.target.value,
      });
      console.log(`tempData`, tempData);
      setData(tempData);
      setInputV('')
      console.log(data);
    }
  });

  return (
    <div className="todo-header">
      <input
        type="text"
        placeholder="请输入你的任务名称,按回车键确认"
        value={ inputV }
        onChange={changeMethod}
        onKeyUp={keyUpMethod}
      />
    </div>
  );
}

以上代码块内有个keyUpMethod事件,当输入框键盘抬起的时候会触发,并且当抬起的按键是13=>Enter键位的时候,会先获取到通过useContext Hook获取到的祖先组件传递的data值和修改值的方法setData。接下来就是先声明一个临时变量获取之前的值,然后往数组前面追加一条新加入的数据,然后调用setData修改数据。从而使下面的后代组件重新渲染。

但是,事与愿违,注意上面打标记的一行let tempData = data;。效果并不是我们预期的那样,数据添加,并且触发组件重新渲染。而是下面这样:

b841745e58e5f6dbc9f5b8b5a25c8e9.png

我们通过react调试工具就可以直观的看到,data数组已经被我添加到了6条数据,而列表就是不触发重新渲染。注意,这里就是本文的重点,为什么会这样呢???不科学…

不卖关子,结论:React官方在设计Hook时候,规定使用useState创建的数据,修改时,不像React类组件中那天,去合并原来的数据,而是直接完全替换原数据。

既然知道这样原理了。那我们离真相已经不远了。考虑一下let tempData = data;是什么?如果data是引用类型数据,那么我们这么写其实只是做了一个浅拷贝的操作。

这里再废话一下***浅拷贝的原理:***

image.png

也就是说,我们这句代码的意思就是tempData获取到了data数据的引用地址而已。并没有完全生成一块新的堆内存去存放之前的数据。所以就算你是修改了数据,也同样只是修改了原来的堆内存中存放的数据。React不认为这需要触发页面重新渲染。
改成深拷贝就能解决这个坑了!!!

let tempData = [...data]; // 利用...和数组的解构赋值可以深拷贝数组,对应的对象深拷贝是 {...data}
// 这里经过和小伙伴的一翻讨论,发现...扩展符仅可以深拷贝一维数组或者是一层的对象解构,
// 所以遇到多层结构时,大家可以使用 JSON.parse(JSON.stringify('引用类型变量' )) 进行数据的深拷贝

兄弟姐妹们,点波关注吧,一起分享有趣的技术!

掘金: https://juejin.cn/user/3034307824456296/posts 全部原创好文

CSDN: https://blog.csdn.net/qq_42753705?type=lately 全部原创好文

segmentfault 思否: https://segmentfault.com/u/jasonma1995/articles 全部原创好文

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值