react Effect副作用 - 避免滥用Effect

react Effect副作用 - 避免滥用Effect

  • react Effect副作用
    • 基础概率
      • 什么是纯函数? 什么是副作用函数?
        • 纯函数
        • 副作用函数
      • 什么时候使用Effect
      • 如何使用Effect
    • 避免滥用Effect
      • 根据 props 或 state 来更新 state
      • 当 props 变化时重置所有 state
      • 将数据传递给父组件
      • 获取异步数据

react Effect副作用

基础概率

阅读文章
React新文档:不要滥用effect哦
react 中文文档

什么是纯函数? 什么是副作用函数?

纯函数

仅执行计算操作,不做其他操作,这类函数通常被称为纯函数

纯函数的特征
1.只负责自己的任务,它不会更改在该函数调用前就已存在的对象或变量。
2.输入相同,则输出相同,给定相同的输入,纯函数应总是返回相同的结果。

React 便围绕着这个概念进行设计,假设编写的所有组件都是纯函数。

React渲染过程必须自始至终是纯粹的,不改变在渲染前,就已存在的任何对象或变量。 – 这将会使其变得不纯粹,也就是我们说的产生副作用

/*
 案例1:不纯粹组件的写法
 该组件正在读写其外部声明的 guest 变量。这意味着 多次调用这个组件会产生不同的 JSX!并且,如果 其他 组件读取 guest ,它们也会产生不同的 JSX,其结果取决于它们何时被渲染!这是无法预测的。
*/
let guest = 0;

function Cup() {
  // Bad:正在更改预先存在的变量!
  guest = guest + 1;
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup />
      <Cup />
      <Cup />
    </>
  );
}

// 案例2:纯粹组件的写法
function Cup({ guest }) {
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup guest={1} />
      <Cup guest={2} />
      <Cup guest={3} />
    </>
  );
}

React 提供了 “严格模式”,在严格模式下开发时,会调用每个组件函数两次。通过重复调用组件函数,严格模式有助于找到违反这些规则的组件。严格模式在生产环境下不生效,因此不会降低应用程序的速度。如需引入严格模式,可以用 <React.StrictMode> 包裹根组件。

副作用函数

在 React 中,副作用通常属于 事件处理程序。事件处理程序是 React 在你执行某些操作(如单击按钮)时运行的函数。即使事件处理程序是在组件内部定义的,它们也不会在渲染期间运行! 因此事件处理程序无需是纯函数

可以理解副作用为:额外发生的事情,与渲染过程无关。

如果无法为副作用找到合适的事件处理程序,可以选择使用useEffect

什么时候使用Effect

React中有两个重要的概念

  • Rendering code渲染代码是不带副作用的纯函数,开发者编写的组件渲染逻辑,最终会返回一段JSX。
    // 渲染代码
    function App() {
    const [age, setAge] = useState(10);
    return <div>{age}</div>;
    }
    
    // 包含副作用:在 React 中,JSX 的渲染必须是纯粹操作,不应该包含任何像修改 DOM 的副作用。
    import { useState, useRef, useEffect } from 'react';
    function VideoPlayer({ src, isPlaying }) {
     const ref = useRef(null);
    if (isPlaying) {
    	ref.current.play();  // 渲染期间不能调用 `play()`,获取不到ref.current的值。
    } else {
    	ref.current.pause(); // 同样,调用 `pause()` 也不行。
    }
    return <video ref={ref} src={src} loop playsInline />;
    }
    
  • Event handlers事件处理器是组件内部的函数,用于执行用户操作,可以包含副作用。
    function App() {
    	const [age, setAge] = useState(10);
    	const changAge = () => {
    		setAge(11);
    	}
    return <div onClick={changAge }>{age}</div>;
    }
    

但是并不是所有副作用都能在Event handlers事件处理器中解决,比如初始化进入页面之后需要请求数据,也就是说不是由用户触发的可以让useEffect处理。

所以使用Event handlers还是useEffect的一个思路是:判断需求是否由用户行为触发

如何使用Effect

每个React组件都经历相同的生命周期
1.当组件被添加到屏幕上时,会进行组件的 挂载
2.当组件接收到新的 props state 时,通常是作为对交互的响应,它会进行组件的更新
3.当组件从屏幕上移除时,它会进行组件的卸载

使用说明
1.useEffect的参数只能是一般函数,不能是异步函数(async)。如果在useEffect里使用异步函数请求数据,需要其外部包装一个一般函数并调用。
2.默认情况下,Effect会在每次渲染后都会执行。如果添加依赖,当依赖发生变化时Effect会执行。
3.useEffect参数函数会在组件每次渲染完毕(dom渲染完毕)后执行。
在这里插入图片描述
4.在useEffect的回调函数中,可以返回一个函数,该函数被称为清理函数,该函数会在下次Effect执行前调用。可以在清理函数中,清除上一次Effect执行所带来的影响。

// 初始化 先Effect回调再清理函数
// 其他情况,先清理函数再Effect
const [keyword,setKeyword] = useState();
  useEffect(()=>{ 
      const timer = setTimeout();//初始化时,先设置一个定时器A
      // 清理函数
      return ()=>{
         /*
         这里形成了一个闭包,timer是定时器A的值。
         下一次Effect执行前,先清理定时器A再生成新的定时器
         */
         clearTimeout(timer);     
     }
  },[keyword])
}

避免滥用Effect

核心:Effect通常用于暂时“跳出” React 代码并与一些外部系统进行同步。
思路
1.能使用Event handlers的优先使用Event handlersuseEffect是最后的选择。
2.可以在渲染时期进行的计算,就在渲染期间执行。

根据 props 或 state 来更新 state

先是用 fullName 的旧值执行了整个渲染流程,然后useEffect修改了fullName立即使用更新后的值又重新渲染了一遍。

function Form() {
  const [firstName, setFirstName] = useState('Taylor');
  const [lastName, setLastName] = useState('Swift');

  // 🔴 避免:多余的 state 和不必要的 Effect
  const [fullName, setFullName] = useState('');
  useEffect(() => {
    setFullName(firstName + ' ' + lastName);
  }, [firstName, lastName]);
  // ...
}

如果一个值可以基于现有的 propsstate 计算得出,不要把它作为一个 state,而是在渲染期间直接计算这个值。

function Form() {
  const [firstName, setFirstName] = useState('Taylor');
  const [lastName, setLastName] = useState('Swift');
  // ✅ 非常好:在渲染期间进行计算
  const fullName = firstName + ' ' + lastName;
  // ...
}

当 props 变化时重置所有 state

一个ProfilePage组件,它接收一个userId代表当前正在操作的用户,里面有一个评论输入框,用一个state来记录输入框中的内容。为了防止切换用户后,原用户输入的内容被当前的用户发出这种误操作,有必要在userId改变时置空state,包括ProfilePage组件的所有子组件中的评论state

export default function ProfilePage({ userId }) {
  const [comment, setComment] = useState('');

  // 🔴 避免:当 prop 变化时,在 Effect 中重置 state
  useEffect(() => {
    setComment('');
  }, [userId]);
  // ...
}

当在相同的位置渲染相同的组件时,React 会保留状态。通过组件的key来判断当前的组件是否相同,每当 key(这里是 userId)变化时,React 将重新加载组件。

export default function ProfilePage({ userId }) {
  return (
    <Profile
      userId={userId}
      key={userId}
    />
  );
}

function Profile({ userId }) {
  // ✅ 当 key 变化时,该组件内的 comment 或其他 state 会自动被重置
  const [comment, setComment] = useState('');
  // ...
}

将数据传递给父组件

父组件:将修改state的方法传递给子组件
子组件:调用修改state的方法

在 React 中,数据从父组件流向子组件,当子组件在Effect 中更新其父组件的 state 时,数据流变得非常难以追踪。

// 不是很理解怎么会有这种写法??
function Parent() {
  const [data, setData] = useState(null);
  // ...
  return <Child onFetched={setData} />;
}

function Child({ onFetched }) {
  const data = useSomeAPI();
  // 🔴 避免:在 Effect 中传递数据给父组件
  useEffect(() => {
    if (data) {
      onFetched(data);
    }
  }, [onFetched, data]);
  // ...
}

如果组件和父组件都需要相同的数据,那么可以让父组件获取那些数据,并将其向下传递给子组件

function Parent() {
  const data = useSomeAPI();
  // ...
  // ✅ 非常好:向子组件传递数据
  return <Child data={data} />;
}

function Child({ data }) {
  // ...
}

获取异步数据

非常常见的一种写法是在effect中异步获取数据,但这种代码存在一个问题
假设快速地输入 “hello”。那么 query 会从 “h” 变成 “he”,“hel”,“hell” 最后是 “hello”。这会触发一连串不同的数据获取请求,但无法保证对应的返回顺序。例如,“hell” 的响应可能在 “hello” 的响应 之后 返回。这种情况被称为 竞态条件:两个不同的请求 “相互竞争”,并以与你预期不符的顺序返回。

function SearchResults({ query }) {
  const [results, setResults] = useState([]);
  const [page, setPage] = useState(1);

  useEffect(() => {
    // 🔴 避免:没有清除逻辑的获取数据
    fetchResults(query, page).then(json => {
      setResults(json);
    });
  }, [query, page]);

  function handleNextPageClick() {
    setPage(page + 1);
  }
  // ...
}

可以给Effect添加一个清理函数,来忽略较早的返回结果。下面的案例采用一个变量ignore来控制这个Effect回调的"有效性",只要是执行了下一个Effect回调,上一个的ignore就变成了true,此时如果刚好上一个Effect的请求结束,由于ignore=true会跳过setResults

function SearchResults({ query }) {
  const [results, setResults] = useState([]);
  const [page, setPage] = useState(1);
  useEffect(() => {
    let ignore = false;
    fetchResults(query, page).then(json => {
      if (!ignore) {
        setResults(json);
      }
    });
    return () => {
      ignore = true;
    };
  }, [query, page]);

  function handleNextPageClick() {
    setPage(page + 1);
  }
  // ...
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/622845.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

持续集成-Git

重要步骤命令 git init (初始化一个仓库) git add [文件名] (添加新的文件) git commit -m [关于本次提交的相关说明] (提交) git status (查看文件状态) git diff (如果文件改变&#xff0c;比较两个文件内容) git add[文件名] || git commit -a -m [关于本次提交的相关说…

RiProV2主题美化【支付页弹窗增加价格提示语】Ritheme主题美化RiProV2-网站WordPress美化二开

背景: 楼主的网站是用WordPress搭建的,并使用了正版主题RiProV2,但RiProV2在支付弹窗页没有价格,只在文章详情页会展示价格。本文就是美化这个支付弹窗,在支付弹窗页把价格字段加上,如下图所示: 美化前: 美化后 美化步骤: (1)定位到文件:/www/wwwroot/www.uu2i…

免费思维13招之九:时间型思维

免费思维13招之九:时间型思维 免费思维的另一大战略思维——时间型思维。 什么是时间型思维呢?就是在某一个规定的时间内对消费者进行免费,比如一个月中的某一天,或一周中的某一天或一天中的某一个时间段对消费者进行免费。 就在去年,有一个电影院老板弟子,他的电影院营…

增强型植被指数EVI、ndvi数据、NPP数据、GPP数据、土地利用数据、植被类型数据、降雨量数据

引言 多种卫星遥感数据反演增强型植被指数&#xff08;EVI&#xff09;产品是地理遥感生态网推出的生态环境类数据产品之一&#xff0c;产品包括1986-2021年度月度数据&#xff0c;数据类型tif栅格数据。该产品经过专家组验证&#xff0c;质量良好。 正文 栅格数据源 数据名…

【JavaEE】Web服务器与请求响应流程:深入了解如何处理Web请求

目录 Web服务器请求响应流程分析小结 Web服务器 浏览器和服务器两端进⾏数据交互, 使⽤的就是HTTP协议 前⾯我们已经学习了 HTTP 协议, 知道了 HTTP 协议就是 HTTP 客⼾端和 HTTP 服务器之间的交互数据的格式. Web 服务器就是对HTTP协议进⾏封装, 程序员不需要直接对协议进⾏…

开关电源功率测试方法:输入、输出功率测试步骤

在现代电子设备中&#xff0c;开关电源扮演着至关重要的角色&#xff0c;其效率和稳定性直接影响到整个系统的性能。因此&#xff0c;对开关电源进行功率测试成为了电源管理的重要环节。本文将详细介绍如何使用DC-DC电源模块测试系统对开关电源的输入输出功率进行准确测量&…

快速对比 找出2个名单不同之处

import pandas as pd# 读取两个Excel文件 df1 pd.read_excel(1.xlsx) df2 pd.read_excel(2.xlsx)# 检查两个DataFrame的列是否相同 if list(df1.columns) ! list(df2.columns):print("两个Excel文件的列不一致。")print("文件1的列&#xff1a;", df1.co…

【C++】可变参数模板简单介绍

前言 可变参数模板是C11中的新特性&#xff0c;它能够让我们创建可以接收可变参数的函数模板和类模板&#xff0c;相比C98/03&#xff0c;类模版和函数模版中只能含固定数量的模版参数&#xff0c;可变模版参数是一个巨大的改进&#xff0c;通过系统系统推演数据的类型&#xf…

OpenCL

一、OpenCL host开发流程 建立Platform环境&#xff08;Platform、Device、contest&#xff09; 平台&#xff1a;一台服务器可以有GPU和FPGA多个平台 cl_platform_id XfindPlatform("Intel(R) FPGA");或clGetPlatformIDs(1, &myp, NULL); 设备&#xff1a;通过…

SpringBoot集成Seata分布式事务OpenFeign远程调用

Docker Desktop 安装Seata Server seata 本质上是一个服务&#xff0c;用docker安装更方便&#xff0c;配置默认&#xff1a;file docker run -d --name seata-server -p 8091:8091 -p 7091:7091 seataio/seata-server:2.0.0与SpringBoot集成 表结构 项目目录 dynamic和dyna…

【MQTT】paho.mqtt.c 库的“介绍、下载、交叉编译” 详解,以及编写MQTT客户端例子源码

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a;2024-05-13 1…

ubuntu 22.04 安装 RTX 4090 显卡驱动 GPU Driver(PyTorch准备)

文章目录 1. 参考文章2. 检查GPU是Nvidia3. 卸载已有驱动3.1. 命令删除3.2. 老驱动包 4. 官网下载驱动5. 运行5.1. 远程安装关闭交互界面5.2. 运行5.3. 打开交互界面 6. 检测与后续安装 1. 参考文章 https://blog.csdn.net/JineD/article/details/129432308 2. 检查GPU是Nvid…

02-WPF_基础(二)

3、控件学习 控件学习 布局控件&#xff1a; panel、Grid 内容空间&#xff1a;Context 之恶能容纳一个控件或布局控件 代表提内容控件&#xff1a;内容控件可以设置标题 Header 父类&#xff1a;HeaderContextControl。 条目控件&#xff1a;可以显示一列数据&#xf…

计算机网络复习-应用层

概述 传输层以及以下的层提供完整的通信服务&#xff0c;不需要管传输&#xff0c;只需要往上对接用户即可。应用层是面向用户的一层 定义应用间通信的规则 应用进程的报文类型 (请求报文、应答报文)报文的语法、格式应用进程发送数据的时机、规则 DNS详解 DNS&#xff1a…

js基础-数组-事件对象-日期-本地存储

一、大纲 一、获取元素位置 在JavaScript中&#xff0c;获取一个元素在页面上的位置可以通过多种方法实现。以下是一些常见的方法&#xff1a; getBoundingClientRect() getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。它提供了元素的left、top、right和bo…

[Linux][网络][高级IO][一][五种IO模型][同步通信][异步通信][非阻塞IO]详细讲解

目录 0.预备知识 && 思考问题1.五种IO模型0.形象理解五种模型1.阻塞IO2.非阻塞IO3.信号驱动IO4.多路转接/多路复用5.异步IO 2.高级IO重要概念1.同步通信 vs 异步通信2.阻塞 vs 非阻塞 3.非阻塞IO1.fcntl()2.实现SetNonBlock 0.预备知识 && 思考问题 网络通信本…

Poe是什么?怎样订阅Poe?

Poe&#xff08;全称“开放探索平台”&#xff0c;Platform for Open Exploration&#xff09;是一款由Quora开发的移动应用程序&#xff0c;于2022年12月推出。该应用程序内置建基于AI技术的聊天机器人&#xff0c;可供用户向机器人询问专业知识、食谱、日常生活&#xff0c;甚…

懒人网址导航源码v3.9

测试环境 宝塔Nginx -Tengine2.2.3的PHP5.6 MySQL5.6.44 为防止调试错误&#xff0c;建议使用测试环境运行的php与mysql版本 首先用phpMyAdmin导入数据库文件db/db.sql 如果导入不行&#xff0c;请直接复制数据库内容运行sql语句也可以 再修改config.php来进行数据库配置…

解决SpringBoot整合MyBatis和MyBatis-Plus,请求后不打印sql日志

问题发现 在整合springBootmyBatis时&#xff0c;发现请求不打印sql日志&#xff0c;示例代码如下&#xff1a; RestController public class MyController {AutowiredProductMapper productMapper;GetMapping("/test")public void test() {System.out.println(&qu…

使用Dockerfile配置Springboot应用服务发布Docker镜像-16

创建Docker镜像 springboot-docker模块 这个应用可以随便找一个即可&#xff0c;这里不做详细描述了。 pom.xml 依赖版本可参考 springbootSeries 模块中pom.xml文件中的版本定义 <dependencies><dependency><groupId>com.alibaba.cloud</groupId>…