React第十五章(useEffect)

devtools/2024/11/17 4:54:26/

useEffect

useEffect 是 React 中用于处理副作用的钩子。并且useEffect 还在这里充当生命周期函数,在之前你可能会在类组件中使用 componentDidMountcomponentDidUpdatecomponentWillUnmount 来处理这些生命周期事件。

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

这个问题在面试中也会被经常被问到。

纯函数

  1. 输入决定输出:相同的输入永远会得到相同的输出。这意味着函数的行为是可预测的。
  2. 无副作用:纯函数不会修改外部状态,也不会依赖外部可变状态。因此,纯函数内部的操作不会影响外部的变量、文件、数据库等。
例子(纯函数)
const add = (x: number, y: number) => x + y
add(1,2) //3

副作用函数

  1. 副作用函数 指的是那些在执行时会改变外部状态或依赖外部可变状态的函数。
  2. 可预测性降低但是副作用不一定是坏事有时候副作用带来的效果才是我们所期待的
  3. 高耦合度函数非常依赖外部的变量状态紧密
  • 操作引用类型
  • 操作本地存储例如localStorage
  • 调用外部API,例如fetch ajax
  • 操作DOM
  • 计时器
let globalVariable = 0;function calculateDouble(number){  globalVariable += 1; //修改函数外部环境变量localStorage.setItem('globalVariable', globalVariable); //修改 localStoragefetch(/*…*/).then((res)=>{ //网络请求//…  }); document.querySelector('.app').style.color = 'red'; //修改 DOM elementreturn number *2
}
例子(副作用函数)
//------------副作用函数--------------
let obj = {name:'小满'}
const changeObj = (obj) => {obj.name = '大满'return obj
}
//小满
changeObj(obj) //修改了外部变量属于副作用函数
//大满
//------------修改成纯函数--------------
//也就是不会改变外部传入的变量
let obj = {name:'小满'}
const changeObj = (obj) => {const newObj = window.structuredClone(obj) //深拷贝newObj.name = '大满'return newObj
}
console.log(obj,'before') //obj 小满
let newobj = fn(obj)
console.log(obj,'after',newobj) //obj 小满 newobj 大满

了解了副作用函数之后我们可以正式开始了解useEffect

useEffect用法

useEffect(setup, dependencies?)

参数

  • setup:Effect处理函数,可以返回一个清理函数。组件挂载时执行setup,依赖项更新时先执行cleanup再执行setup,组件卸载时执行cleanup。

  • dependencies(可选):setup中使用到的响应式值列表(props、state等)。必须以数组形式编写如[dep1, dep2]。不传则每次重渲染都执行Effect。

返回值

useEffect 返回 undefined

let a = useEffect(() => {})
console.log('a', a) //undefined

基本使用

副作用函数能做的事情useEffect都能做,例如操作DOM、网络请求、计时器等等。

操作DOM
import { useEffect } from 'react'function App() {const dom = document.getElementById('data')console.log(dom) //nulluseEffect(() => {const data = document.getElementById('data')console.log(data) //<div id='data'>小满zs</div>}, [])return <div id='data'>小满zs</div>
}
网络请求
useEffect(() => {fetch('http://localhost:5174/?name=小满')
}, [])

执行时机

组件挂载时执行

根据我们下面的例子可以观察到,组件在挂载的时候就执行了useEffect的副作用函数。

类似于componentDidMount

useEffect(() => {console.log('组件挂载时执行')
})
组件更新时执行
  • 无依赖项更新

根据我们下面的例子可以观察到,当有响应式值发生改变时,useEffect的副作用函数就会执行。

类似于componentDidUpdate + componentDidMount

import { useEffect, useState } from "react"const App = () => {const [count, setCount] = useState(0)const [name, setName] = useState('')useEffect(() => {console.log('执行了', count, name)})return (<div id='data'><div><h3>count:{count}</h3><button onClick={() => setCount(count + 1)}>+</button></div><div><h3>name:{name}</h3><input value={name} onChange={e => setName(e.target.value)} /></div></div>)
}
export default App
  • 有依赖项更新

根据我们下面的例子可以观察到,当依赖项数组中的count值发生改变时,useEffect的副作用函数就会执行。而当name值改变时,由于它不在依赖项数组中,所以不会触发副作用函数的执行。

import { useEffect, useState } from "react"const App = () => {const [count, setCount] = useState(0)const [name, setName] = useState('')useEffect(() => {console.log('执行了', count, name)}, [count]) //当count发生改变时执行return (<div id='data'><div><h3>count:{count}</h3><button onClick={() => setCount(count + 1)}>+</button></div><div><h3>name:{name}</h3><input value={name} onChange={e => setName(e.target.value)} /></div></div>)
}
export default App
  • 依赖项空值

根据我们下面的例子可以观察到,当依赖项为空数组时,useEffect的副作用函数只会执行一次,也就是组件挂载时执行。

适合做一些初始化的操作例如获取详情什么的。

import { useEffect, useState } from "react"const App = () => {const [count, setCount] = useState(0)const [name, setName] = useState('')useEffect(() => {console.log('执行了', count, name)}, []) //只会执行一次return (<div id='data'><div><h3>count:{count}</h3><button onClick={() => setCount(count + 1)}>+</button></div><div><h3>name:{name}</h3><input value={name} onChange={e => setName(e.target.value)} /></div></div>)
}
export default App
组件卸载时执行

useEffect的副作用函数可以返回一个清理函数,当组件卸载时,useEffect的副作用函数就会执行清理函数。

确切说清理函数就是副作用函数运行之前,会清楚上一次的副作用函数。

根据我们下面的例子可以观察到,当组件卸载时,useEffect的副作用函数就会执行。

类似于componentWillUnmount

import { useEffect, useState } from "react"
// 子组件
const Child = (props: { name: string }) => {useEffect(() => {console.log('render', props.name)// 返回一个清理函数return () => {console.log('unmount', props.name)}}, [props.name])return <div>Child:{props.name}</div>
}
const App = () => {const [show, setShow] = useState(true)const [name, setName] = useState('')return (<div id='data'><div><h3>父组件</h3><input value={name} onChange={e => setName(e.target.value)} /><button onClick={() => setShow(!show)}>显示/隐藏</button></div><hr /><h3>子组件</h3>{show && <Child name={name} />}</div>)
}export default App
清理函数应用场景

例如我们下面这个例子,当name值发生改变时,useEffect的副作用函数就会执行,并且会开启一个定时器,当name值再次发生改变时,useEffect的副作用函数就会执行清理函数,清除上一次的定时器。这样就避免了接口请求的重复执行。

import { useEffect, useState } from "react"
// 子组件
const Child = (props: { name: string }) => {useEffect(() => {let timer = setTimeout(() => {fetch(`http://localhost:5174/?name=${props.name}`)}, 1000)return () => {clearTimeout(timer)}}, [props.name])return <div>Child</div>
}
const App = () => {const [show, setShow] = useState(true)const [name, setName] = useState('')return (<div id='data'><div><h3>父组件</h3><input value={name} onChange={e => setName(e.target.value)} /><button onClick={() => setShow(!show)}>显示/隐藏</button></div><hr /><h3>子组件</h3>{show && <Child name={name} />}</div>)
}export default App

真实案例

下面是一个真实的用户信息获取案例,通过id获取用户信息,并且当id发生改变时,会获取新的用户信息。

import React, { useState, useEffect } from 'react';
interface UserData {name: string;email: string;username: string;phone: string;website: string;
}
function App() {const [userId, setUserId] = useState(1); // 假设初始用户ID为1const [userData, setUserData] = useState<UserData | null>(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {const fetchUserData = async () => {setLoading(true);try {const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`); //免费api接口 可以直接使用if (!response.ok) {throw new Error('网络响应不正常');}const data = await response.json();setUserData(data);} catch (err: any) {setError(err.message);} finally {setLoading(false);}};fetchUserData();}, [userId]);const handleUserChange = (event: React.ChangeEvent<HTMLInputElement>) => {setUserId(parseInt(event.target.value));};return (<div><h1>用户信息应用</h1><label>输入用户ID:<input type="number" value={userId} onChange={handleUserChange} min="1" max="10" /></label>{loading && <p>加载中...</p>}{error && <p>错误: {error}</p>}{userData && (<div><h2>用户信息</h2><p>姓名: {userData.name}</p><p>邮箱: {userData.email}</p><p>用户名: {userData.username}</p><p>电话: {userData.phone}</p><p>网站: {userData.website}</p></div>)}</div>);
}export default App;

在这里插入图片描述


http://www.ppmy.cn/devtools/134620.html

相关文章

昆明华厦眼科龙俊飞副院长:眼视光的坚守者与传承者

在云南眼视光行业从萌芽到繁荣的征途中&#xff0c;有一位医者始终站在时代的前沿&#xff0c;用他的坚守与智慧书写着行业的传奇。他的名字已经成为眼视光行业中的一面旗帜&#xff0c;引领着更多医者在这条光明的道路上不断前行。他就是昆明华厦眼科医院业务副院长——龙俊飞…

shell脚本(1)

声明&#xff1a;学习视频来自b站up主 泷羽sec&#xff0c;如涉及侵权马上删除文章 感谢泷羽sec 团队的教学 视频地址&#xff1a;shell脚本&#xff08;1&#xff09;脚本创建执行与变量使用_哔哩哔哩_bilibili 本文主要讲解shell脚本的创建、执行和变量的使用。 一、脚本执行…

unittest和pytest

unittest终端运行方法 ecshop_login.py import unittestclass EcshopLoginTest(unittest.TestCase):def test01_baidu(self):print("百度")def test01_bytedance(self):print("字节跳动")终端运行 python -m unittest ecshop_login.EcshopLoginTest -v p…

2:Vue.js 父子组件通信:让你的组件“说话”

上一篇我们聊了如何用 Vue.js 创建一个简单的组件&#xff0c;这次咱们再往前走一步&#xff0c;讲讲 Vue.js 的父子组件通信。组件开发里&#xff0c;最重要的就是让组件之间能够“说话”&#xff0c;数据能流通起来。废话不多说&#xff0c;直接开干&#xff01; 父组件传数据…

蓝桥杯——杨辉三角

代码 package day3;public class Demo2 {public static void main(String[] args) {// TODO Auto-generated method stub// for (int i 0; i < 10; i) {// for (int j 0; j < 10; j) {// System.out.print("外&#xff1a;"i"内&#xff1a;&qu…

【计算机网络】设备网卡NIC的工作内容有哪些呢?

我们平时上网&#xff0c;都需要经过设备网卡的处理&#xff0c;网络接口卡&#xff08;Network Interface Card&#xff0c;简称 NIC&#xff09;是计算机与网络之间的物理连接设备&#xff0c;负责处理网络数据的发送和接收。NIC 的功能涵盖了从物理层到数据链路层的多个方面…

leetcode21. Merge Two Sorted Lists

You are given the heads of two sorted linked lists list1 and list2. Merge the two lists into one sorted list. The list should be made by splicing together the nodes of the first two lists. Return the head of the merged linked list. 将两个升序链表合并为一…

ffmpeg视频编码

一、视频编码流程 使用ffmpeg解码视频帧主要可分为两大步骤&#xff1a;初始化编码器和编码视频帧&#xff0c;以下代码以h264为例 1. 初始化编码器 初始化编码器包含以下步骤&#xff1a; &#xff08;1&#xff09;查找编码器 videoCodec avcodec_find_encoder_by_name…