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

2,322 阅读4分钟

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('引用类型变量' )) 进行数据的深拷贝

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

掘金juejin.cn/user/303430… 全部原创好文

CSDNblog.csdn.net/qq_42753705… 全部原创好文

segmentfault 思否segmentfault.com/u/jasonma19… 全部原创好文