react获取上一轮的props和state(接用 useEffect, useRef实现)

问题描述:

          react获取上一轮的props和state ,有的时候 需要 获取 改变前的 state,和props 做个对比处理,或者其它处理。下面就是实现步骤。

实现过程

 效果图:

react获取上一轮的props和state(接用 useEffect, useRef实现)_第1张图片

如果只是 想实现 这个效果 下面的代码 也行 。就不用借助其它的了。 这个思路就是,在 改变 state之前 就 备份一下 值 。

 hook

import React, { useState, useEffect } from 'react'
import { Select } from 'antd';
const { Option } = Select;
function Index() {

    
    const [val, setVal] = useState("--")//name
    const [val2, setVal2] = useState("--")//name
   
    const handleChange = (value) => {
        console.log(val,value)
        setVal2(val)
        setVal(value);
    };


    return (
        

现在的值: {val}, 之前的值: {val2}

) } export default Index

class 

  不借助生命周期

import React, { Component } from 'react';
import { Select } from 'antd';
const { Option } = Select;
class Index extends Component {
    constructor(props) {
        super(props)
        this.state = {
            val: "--",
            val2: "--"
        }
    }
    componentDidMount() {

    }

    handleChange = (value) => {
       
        this.setState({
            val: value,
            val2:this.state.val
        })
    }
   
    render() {
        let { val,val2 } = this.state;
        return (
            

现在的值: {val}, 之前的值: {val2}

); } } export default Index

借助生命周期 componentDidUpdate

import React, { Component } from 'react';
import { Select } from 'antd';
const { Option } = Select;
class Index extends Component {
    constructor(props) {
        super(props)
        this.state = {
            val: "--",
            val2:"--"
        }
    }
    componentDidMount() {

    }

    handleChange = (value) => {
        this.setState({
            val: value
        })
    }
    componentDidUpdate(prevProps, prevState, snapshot){
        console.log(prevProps, prevState, snapshot,"10")
        if(prevState.val!=this.state.val){
            this.setState({
                val2:prevState.val
            })
        }
        
    }
    // getSnapshotBeforeUpdate(prevProps, prevState) {
    //     console.log(prevProps, prevState,"09")
    //     return null;
    //   }
    render() {
        let { val ,val2} = this.state;
        return (
            

现在的值: {val}, 之前的值: { val2}

); } } export default Index

下面的方法是 获取到整个 state 和 props 

react 类组(class)件实现 

react Class 组件 很好实现因为有生命周期,可以用 componentDidUpdate(prevProps, prevState, snapshot) 和 getSnapshotBeforeUpdate(prevProps, prevState) 获取到

componentDidUpdate()介绍

componentDidUpdate(prevProps, prevState, snapshot)

componentDidUpdate() 会在更新后会被立即调用。首次渲染不会执行此方法。

当组件更新后,可以在此处对 DOM 进行操作。如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。(例如,当 props 未发生变化时,则不会执行网络请求)。

componentDidUpdate(prevProps) {
  // 典型用法(不要忘记比较 props):
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}

你也可以在 componentDidUpdate() 中直接调用 setState(),但请注意它必须被包裹在一个条件语句里,正如上述的例子那样进行处理,否则会导致死循环。它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。不要将 props “镜像”给 state,请考虑直接使用 props。 欲了解更多有关内容,请参阅为什么 props 复制给 state 会产生 bug。

如果组件实现了 getSnapshotBeforeUpdate() 生命周期(不常用),则它的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined。

注意

如果 shouldComponentUpdate() 返回值为 false,则不会调用 componentDidUpdate()


getSnapshotBeforeUpdate 介绍

getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()

此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。

应返回 snapshot 的值(或 null)。

例如:

class ScrollingList extends React.Component {
  constructor(props) {
    super(props);
    this.listRef = React.createRef();
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // 我们是否在 list 中添加新的 items ?
    // 捕获滚动​​位置以便我们稍后调整滚动位置。
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
    // 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
    //(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
    if (snapshot !== null) {
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }

  render() {
    return (
      
{/* ...contents... */}
); } }

在上述示例中,重点是从 getSnapshotBeforeUpdate 读取 scrollHeight 属性,因为 “render” 阶段生命周期(如 render)和 “commit” 阶段生命周期(如 getSnapshotBeforeUpdate 和 componentDidUpdate)之间可能存在延迟。

   上面参考于:react 官网 -生命周期 

    实现代码如下:

import React, { Component } from 'react';
import { Select } from 'antd';
const { Option } = Select;
class Index extends Component {
    constructor(props) {
        super(props)
        this.state = {
            val: "--"
        }
    }
    componentDidMount() {

    }

    handleChange = (value) => {
        this.setState({
            val: value
        })
    }
    componentDidUpdate(prevProps, prevState, snapshot){
        console.log(prevProps, prevState, snapshot,"10")
    }
    getSnapshotBeforeUpdate(prevProps, prevState) {
        console.log(prevProps, prevState,"09")
        return null;
      }
    render() {
        let { val } = this.state;
        return (
            

现在的值: {val}, 之前的值: { }

); } } export default Index

函数组件(react Hook实现)

 借用useEffect, useRef 实现 。

useEffect

useEffect(didUpdate);

该 Hook 接收一个包含命令式、且可能有副作用代码的函数。

在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性。

使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。

默认情况下,effect 将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候 才执行。

清除 effect

通常,组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源。要实现这一点,useEffect 函数需返回一个清除函数。以下就是一个创建订阅的例子:

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // 清除订阅
    subscription.unsubscribe();
  };
});

为防止内存泄漏,清除函数会在组件卸载前执行。另外,如果组件多次渲染(通常如此),则在执行下一个 effect 之前,上一个 effect 就已被清除。在上述示例中,意味着组件的每一次更新都会创建新的订阅。若想避免每次更新都触发 effect 的执行,请参阅下一小节。

effect 的执行时机

与 componentDidMountcomponentDidUpdate 不同的是,传给 useEffect 的函数会在浏览器完成布局与绘制之后,在一个延迟事件中被调用。这使得它适用于许多常见的副作用场景,比如设置订阅和事件处理等情况,因为绝大多数操作不应阻塞浏览器对屏幕的更新。

然而,并非所有 effect 都可以被延迟执行。例如,一个对用户可见的 DOM 变更就必须在浏览器执行下一次绘制前被同步执行,这样用户才不会感觉到视觉上的不一致。(概念上类似于被动监听事件和主动监听事件的区别。)React 为此提供了一个额外的 useLayoutEffect Hook 来处理这类 effect。它和 useEffect 的结构相同,区别只是调用时机不同。

此外,从 React 18 开始,当它是离散的用户输入(如点击)的结果时,或者当它是由 flushSync 包装的更新结果时,传递给 useEffect 的函数将在屏幕布局和绘制之前同步执行。这种行为便于事件系统或 flushSync 的调用者观察该效果的结果。

注意

这只影响传递给 useEffect 的函数被调用时 — 在这些 effect 中执行的更新仍会被推迟。这与 useLayoutEffect 不同,后者会立即启动该函数并处理其中的更新。

即使在 useEffect 被推迟到浏览器绘制之后的情况下,它也能保证在任何新的渲染前启动。React 在开始新的更新前,总会先刷新之前的渲染的 effect。

effect 的条件执行

默认情况下,effect 会在每轮组件渲染完成后执行。这样的话,一旦 effect 的依赖发生变化,它就会被重新创建。

然而,在某些场景下这么做可能会矫枉过正。比如,在上一章节的订阅示例中,我们不需要在每次组件更新时都创建新的订阅,而是仅需要在 source prop 改变时重新创建。

要实现这一点,可以给 useEffect 传递第二个参数,它是 effect 所依赖的值数组。更新后的示例如下:

useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],
);

此时,只有当 props.source 改变后才会重新创建订阅。

注意

如果你要使用此优化方式,请确保数组中包含了所有外部作用域中会发生变化且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。请参阅文档,了解更多关于如何处理函数 以及数组频繁变化时的措施 的内容。

如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循输入数组的工作方式。

如果你传入了一个空数组([]),effect 内部的 props 和 state 就会一直持有其初始值。尽管传入 [] 作为第二个参数有点类似于 componentDidMount 和 componentWillUnmount 的思维模式,但我们有 更好的 方式 来避免过于频繁的重复调用 effect。除此之外,请记得 React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect,因此会使得处理额外操作很方便。

我们推荐启用 eslint-plugin-react-hooks 中的 exhaustive-deps 规则。此规则会在添加错误依赖时发出警告并给出修复建议。

依赖项数组不会作为参数传给 effect 函数。虽然从概念上来说它表现为:所有 effect 函数中引用的值都应该出现在依赖项数组中。未来编译器会更加智能,届时自动创建数组将成为可能。

useRef

const refContainer = useRef(initialValue);

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。

一个常见的用例便是命令式地访问子组件:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      
      
    
  );
}

本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。

你应该熟悉 ref 这一种访问 DOM 的主要方式。如果你将 ref 对象以 

 形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。

然而,useRef() 比 ref 属性更有用。它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。

这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。

请记住,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。

实现代码如下:


import React, { useState, useEffect, useRef } from 'react'
import { Select } from 'antd';
const { Option } = Select;
function Index() {

    const prevCountRef = useRef();
    const [val, setVal] = useState("--")//name
    useEffect(() => {
        prevCountRef.current = val;
    });

    const handleChange = (value) => {
        setVal(value);
    };


    return (
        

现在的值: {val}, 之前的值: {prevCountRef.current}

) } export default Index

如何获取上一轮的 props 或 state?官方的示例

function Counter() {
  const [count, setCount] = useState(0);

  const prevCountRef = useRef();
  useEffect(() => {
    prevCountRef.current = count;
  });
  const prevCount = prevCountRef.current;

  return 

Now: {count}, before: {prevCount}

; }

 参考于:Hook API 索引 – React

 React 官网 如何获取上一轮的 props 或 state?

你可能感兴趣的:(React,react.js)