当前位置: 首页 > news >正文

React+TS 从零开始教程(4):useEffect

上一节传送门:React+TS 从零开始教程(3):useState

源码链接:https://pan.quark.cn/s/c6fbc31dcb02

上一节,我们已经学会了React的第一个Hook:useState。 这一节,我们要学习的是另一个非常重要的Hook:useEffect。 Effect在这里,意为副作用? 不论是哪一个Hook,都是写在函数里面的,比如上个章节中useEffect,就是写在一个函数中。

import React, { useState } from "react";const Switch = () => {//每一个变量都要单独做一个State//useState解构出两个变量,一个是变量名,一个是赋值这个变量的函数const [on,setOn] = useState(false)return (<><h2 onClick={ () => {setOn(!on)}}>{on?'关闭':'开启'}</h2></>)
}export default Switch;

所以,这个副作用,就是函数的副作用。 怎么理解呢?我们知道,函数就是你给我参数,我给你一个确定的返回,这个叫函数。但是呢,有一些操作其实跟函数的返回没有太大的关系,却出现在了函数体中,比如网络请求,订阅事件等,这些跟函数渲染不相关,就可以被认为是副作用。

两种常见的副作用。

不需要清除的副作用

我们希望在React更新DOM之后,发送一些网络请求,手动变更DOM,记录日志等,这些操作在我们函数返回之后,完全可以忽略掉。 这些情形,就是不需要清除的副作用。 总之,当React函数式组件return了一个DOM,并成功在页面更新之后,如果你还想做一些快活的事情,就写在useEffect的第一个回调参数里。 在Switch.tsx中,写上如下代码

//以下的effect操作会在DOM更新之后发生
useEffect(() => {document.title = 'DOM更新完毕,Effect发生!'
});

useEffect接收一个回调函数,里面定义了DOM更新完成之后的操作,或者说给组件添加了一个渲染后的副作用。 App.tsx

image.png

刷新页面

image.png

页面的标题发生了变化,符合预期。 注意,useEffect会在每次渲染后都执行1次。 之前用Class组件的时候,分挂载和更新两个钩子,所以我们想用这种DOM更新后的操作的话,就得在这两个钩子中各写一遍逻辑,很麻烦。 现在有了useEffect,就化繁为简了。 而且,非常好懂。

需要清除的副作用

有些副作用是需要被清除的,比如事件的添加。 在 之前做法中,我们是在componentDitMount中添加事件,然后在componentWillUnmount中解绑事件。 现在我们用useEffect,该如何实现呢? 我们新创建一个组件,叫MouseTracker。

image.png

顾名思义,就是一个鼠标位置的追踪器。 然后,引入React依赖

import React, { useEffect, useState } from "react";

声明一下函数组件

const MouseTracker : React.FC = () => {}

然后定义坐标状态

//定义坐标const [pos,setPos] = useState({x:0,y:0})

直接return

return (<p>X: {pos.x} , Y:{pos.y}</p>
)

导出去

export default MouseTracker;

然后在App.tsx中引入这个组件

image.png

目前这个x和y还是静态的,都是0,我们希望每次鼠标点击的时候,就更新这个坐标。 什么时候更新呢,首先是鼠标点击之后,然后触发x和y的更新,也就是dom的更新。 让我们在MouseTracker中添加一个effect

//事件绑定
useEffect(() => {const bindCallback = (e:MouseEvent) => {console.log('鼠标点击了')setPos({x:e.clientX,y:e.clientY})     }//绑定click事件document.addEventListener('click',bindCallback)
})

现在页面是这样的

image.png

我点一次

image.png

再点一次

image.png

再点一次

image.png

每次点击,都会增加N的effect次数。为什么呢?因为useEffect会在每次dom更新后触发,而我们添加了点击事件后没有及时地去清除,所以之前的点击事件还存在,导致每次更新后,所有的点击事件都触发了effect。 怎么解决呢? 我们看下useEffect的源码

    /*** Accepts a function that contains imperative, possibly effectful code.** @param effect Imperative function that can return a cleanup function* @param deps If present, effect will only activate if the values in the list change.** @version 16.8.0* @see {@link https://react.dev/reference/react/useEffect}*/function useEffect(effect: EffectCallback, deps?: DependencyList): void;

可以看到,这个函数的第一个参数,callback,并不是一定为void,也允许我们返回一个函数。  @param effect Imperative function that can return a cleanup function 意思很明确了,它允许我们返回一个cleanup function,也就是清除函数。 我们修改一下代码

//事件绑定
useEffect(() => {const bindCallback = (e:MouseEvent) => {console.log('鼠标事件绑定')setPos({x:e.clientX,y:e.clientY})     }//绑定click事件document.addEventListener('click',bindCallback)return () => {document.removeEventListener('click',bindCallback)}
})

我连续点击了四次,符合预期。

image.png

怎么限制Effect

上面的例子告诉我们,只有一重新渲染,effect必执行,这可能会带来一些性能问题,有什么办法对effect做限制嘛? 再来看源码

    /*** Accepts a function that contains imperative, possibly effectful code.** @param effect Imperative function that can return a cleanup function* @param deps If present, effect will only activate if the values in the list change.** @version 16.8.0* @see {@link https://react.dev/reference/react/useEffect}*/function useEffect(effect: EffectCallback, deps?: DependencyList): void;

这次我们重点看第二个参数 @param deps If present, effect will only activate if the values in the list change. 第二个参数是一个数组,如果数组里面的任何一项发生变化,就会触发effect的执行。 刚才的例子中,我们每次点击鼠标,就会触发一次事件的绑定和卸载,实在是有点太浪费了。 我们希望在组件加载的时候绑定一次就行了,然后在组件卸载的时候再解绑。

useEffect(() => {console.log('点击事件绑定')const bindCallback = (e:MouseEvent) => {console.log('鼠标点击了')setPos({x:e.clientX,y:e.clientY})     }//绑定click事件document.addEventListener('click',bindCallback)return () => {console.log('点击事件解绑')document.removeEventListener('click',bindCallback)}
},[])

我添加一个空数组,代表这个effect不依赖于任何state和props,只跟组件的安装和卸载有关。 然后修改App.tsx(省略页面其他代码)

import React,{useState} from "react";
import MouseTracker from "./components/MouseTracker";function App() {let [showMouseTracker,setShowMouseTracker] = useState(true)return (<div className="App"><div><button onClick={() => {setShowMouseTracker(!showMouseTracker)}}>{showMouseTracker ? '关闭鼠标追踪': '打开鼠标追踪'}</button></div>{showMouseTracker && <MouseTracker />}</div>);
}export default App;

刷新页面

image.png

多点几次

image.png

点击关闭按钮

image.png

页面卸载,事件解绑了。 如果你希望这个effect只依赖某一个State,将对应的变量填入这个数组即可,有多个就填多个。

本节,我们学会了useEffect的用法。

相关文章:

  • Qt中使用MySQL数据库详解,好用的模块类封装
  • 比较Java爬虫框架:哪个是最佳选择?
  • PostgreSQL 性能优化与调优(六)
  • Java核心知识(一):JVM
  • PDF一键转PPT文件!这2个AI工具值得推荐,办公必备!
  • Vue 全局状态管理新宠:Pinia实战指南
  • 网络编程:使用UDP实现数据帧的接收
  • AI安全研究滞后?清华专家团来支招
  • VMamba: Visual State Space Model论文笔记
  • [PyTorch]:加速Pytorch 模型训练的几种方法(几行代码),最快提升八倍(附实验记录)
  • 入门PHP就来我这(纯干货)05
  • 【CSS】什么是CSS的columns属性
  • 全面了解机器学习
  • 数据结构 - C/C++ - 队列
  • 《刺客信条:英灵殿》找不到emp.dll文件的多种解决方法,亲测有效
  • [分享]iOS开发-关于在xcode中引用文件夹右边出现问号的解决办法
  • es6
  • HTTP 简介
  • JAVA_NIO系列——Channel和Buffer详解
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • Java读取Properties文件的六种方法
  • Laravel5.4 Queues队列学习
  • MaxCompute访问TableStore(OTS) 数据
  • nodejs:开发并发布一个nodejs包
  • Octave 入门
  • Phpstorm怎样批量删除空行?
  • Python利用正则抓取网页内容保存到本地
  • Vim 折腾记
  • Vue.js-Day01
  • 前端每日实战 2018 年 7 月份项目汇总(共 29 个项目)
  • 使用 Xcode 的 Target 区分开发和生产环境
  • 数据仓库的几种建模方法
  • 王永庆:技术创新改变教育未来
  • 详解移动APP与web APP的区别
  • 学习笔记TF060:图像语音结合,看图说话
  • 用Canvas画一棵二叉树
  • 【云吞铺子】性能抖动剖析(二)
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • NLPIR智能语义技术让大数据挖掘更简单
  • #LLM入门|Prompt#3.3_存储_Memory
  • #vue3 实现前端下载excel文件模板功能
  • (C语言)二分查找 超详细
  • (Redis使用系列) Springboot 整合Redisson 实现分布式锁 七
  • (附源码)spring boot球鞋文化交流论坛 毕业设计 141436
  • (附源码)springboot电竞专题网站 毕业设计 641314
  • (函数)颠倒字符串顺序(C语言)
  • (四)linux文件内容查看
  • (一)utf8mb4_general_ci 和 utf8mb4_unicode_ci 适用排序和比较规则场景
  • *Algs4-1.5.25随机网格的倍率测试-(未读懂题)
  • .axf 转化 .bin文件 的方法
  • .NET 8.0 发布到 IIS
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .Net Core webapi RestFul 统一接口数据返回格式
  • .NET/C# 使用 #if 和 Conditional 特性来按条件编译代码的不同原理和适用场景
  • .NET/C# 使用反射调用含 ref 或 out 参数的方法