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

『React』组件副作用,useEffect讲解

在 React 开发中,有时候会听到“副作用”这个词。特别是用到 useEffect 这个 Hook 的时候,官方就明确说它是用来处理副作用的。那什么是副作用?为什么我们要专门管控它?今天就聊聊 React 中的组件副作用。

📌 什么是“副作用”?

其实“副作用”并不是 React 特有的东西,在原生 JS 里也很常见。

副作用的”反义词“是纯函数。纯函数的意思是:相同的输入,永远得到相同的输出,且不影响函数外部的任何状态。

举个例子

function add(a, b) {return a + b
}

上面这个就是纯函数,无论执行多少次 add(1, 2),输出的结果永远是 3

那么什么是副作用呢?

let count = 0;function add() {count += 1; // 修改了外部变量 count,产生了副作用return count;
}add() // 1
add() // 2

这里 add() 除了返回值之外,还改变了 count 这个函数外部的变量,就算是副作用。每次执行它都会得到不一样的结果,这就是副作用。

🔥 常见的副作用行为包括:

  • 修改全局变量 / 外部变量
  • 修改对象属性(引用类型)
  • 发起网络请求(HTTP 请求)
  • 定时器(setInterval)
  • 操作本地存储(localStorage.setItem)
  • 操作 DOM
  • 读写文件(在 Node.js 里)
  • 使用 Date.now()Math.random() 这种非确定性函数
  • 等…

🤔 为什么要管理副作用?

如果不合理管理副作用,React 应用可能会遇到:

  • 内存泄漏
  • 重复订阅
  • 异步任务未清理
  • 数据竞争问题
  • UI 不一致

副作用是和组件生命周期息息相关的,所以 React 提供了 useEffect 来专门管理副作用行为。

🙅‍♂️ 错误示范

这个例子展示了一个定时器功能,每隔1秒就会更新一下当前时间,并在页面中展示。

在这里插入图片描述

import { useState } from 'react';function App() {const [dateTime, setDateTime] = useState(new Date());const id = setInterval(() => {setDateTime(new Date());}, 1000);console.log(id);return (<div>{dateTime.toLocaleString('zh-CN')}</div>)
}export default App;

如果只看页面展示的情况,看上去是没问题的。但打开控制台一看的话会发现随着时间的推移,每秒输出的id数量会增加。

这个例子的问题在于,当组件的 state 发生变化时,整个组件的代码都会重新执行一次,这就相当于反复执行了 setInterval 好多次。当这个页面运行时间长了就会导致内存溢出。

👍 useEffect:管理副作用的标准方案

使用 useEffect 可以解决上面「错误示范」的副作用问题。

在这里插入图片描述

import { useEffect, useState } from 'react';function App() {const [dateTime, setDateTime] = useState(new Date());useEffect(() => {const id = setInterval(() => {setDateTime(new Date());}, 1000);console.log(id);}, [])return (<div>{dateTime.toLocaleString('zh-CN')}</div>)
}export default App;

从上面的例子可以看出,页面的值是会变化的,但控制台并不会一直打印 id

useEffect 可以传入2个参数,第一个参数是要执行的代码,第二个参数可以传入一个空数组。这样 useEffect 里的代码就会在组件第一次加载的时候执行一次,因为里面执行的是一个定时器函数,所以定时器函数会自己继续执行,更新完页面后也不会再次执行新的定时器。

如果没传入第二个参数的话就和上面的「错误示范」的效果是一样的。

⏰ useEffect 的执行时机

在使用函数式组件时,结合 useEffect 函数,组件的生命周期只需关注以下几个:

  • 组件第一次加载
  • 组件重新渲染(更新)
  • 组件卸载

useEffect 就是在以上时机里做一些副作用。

  • useEffect 不传第二个参数时,意味着它会在组件每次 state 或者 props 发生变化时执行一次。

  • 而第二个参数时空数组时,意味着它不依赖与任何 state 或者 props 的变化,只在组件第一次加载时执行它的副作用(里面的函数)。

  • 如果第二个参数是数组,且依赖其他状态的话,那么其依赖的其中一个状态发生变化时,useEffect 里的代码都会重新执行一次。

关于最后一点举个例子 。

在这里插入图片描述

import { use } from 'react';
import { useEffect, useState } from 'react';function App() {const [count, setCount] = useState(0);const [doubleCount, setDoubleCount] = useState(0);useEffect(() => {setDoubleCount(() => {return count * 2;}); }, [count]);return (<div><div>{doubleCount}</div><button onClick={() => setCount(count + 1)}>Increment</button></div>)
}export default App;

在这个例子中,useEffect 的第二个参数是 [count],表示这个 useEffect 依赖了 count ,当 count 发生变化时就会执行 useEffect 第一个参数的函数。

前面也提到,useEffect 的第二个参数可以依赖多个状态,当其中一个状态发生变化时也会执行第一个参数的代码。可以自己手动试试~

🧹 清理副作用

在副作用函数中返回一个函数,用于在下一次执行副作用之前或组件卸载时进行清理。

示例1:只在挂载/卸载时清理

  • 因为依赖数组是空的,effect 执行一次后就不再重新运行。
  • 返回的清理函数只会在组件卸载时被调用一次。
import React, { useEffect } from 'react';function Countdown({ start }) {useEffect(() => {const timerId = setInterval(() => {console.log('倒计时:', new Date().toLocaleTimeString());}, 1000);// 清理函数:只在组件卸载时调用return () => {clearInterval(timerId);console.log('倒计时已清理');};}, []); // 空依赖,effect 只在 mount 时执行,cleanup 只在 unmount 时执行return <div>查看控制台的倒计时日志。</div>;
}

示例2:依赖变化时清理

  • 当 userId 发生变化时,React 会先调用上一个 effect 返回的 controller.abort(),取消旧请求,然后执行新的 fetch 请求。
  • 当组件卸载前,也会执行 controller.abort(),避免请求继续运行、然后试图更新已卸载的组件。
import React, { useState, useEffect } from 'react';function FetchData({ userId }) {const [data, setData] = useState(null);useEffect(() => {const controller = new AbortController();// 发起数据请求fetch(`/api/user/${userId}`, { signal: controller.signal }).then(res => res.json()).then(setData).catch(err => {if (err.name === 'AbortError') {console.log('请求已被取消');} else {console.error(err);}});// 依赖变化时(userId 改变),或组件卸载时,调用清理函数return () => {controller.abort(); // 取消未完成的请求};}, [userId]); // 只有 userId 变动时才重新发起请求return <div>用户名:{data ? data.name : '加载中...'}</div>;
}

⏳ useEffect 中使用异步函数

在 React 的函数式组件中,我们经常需要在组件挂载或更新时发起异步操作(如网络请求、读取本地储存、调用异步 API 等)。通常这些副作用逻辑都放在 useEffect 中。然而,useEffect 的回调函数本身不能被标记为 async(因为会返回一个 Promise,而 useEffect 期待的是可选的“清理函数”而非 Promise)。下面从原理、常见写法以及注意事项三个角度,详细讲解如何在 useEffect 中使用异步函数。

为什么不能直接把 useEffect 回调写成 async

function MyComponent() {useEffect(async () => {// ❌ 这样写是错误的const res = await fetch('/api/data');const data = await res.json();// ...}, []);// ...
}
  • 返回值的冲突:当你给 useEffect 传入一个 async 函数时,该函数会自动返回一个 Promise(因为 async 函数的返回值就是 Promise)。但 React 要求 useEffect 回调“要么直接返回 undefined,要么返回一个同步的清理函数(() => { ... })”,用来在组件卸载或依赖变化时执行清理。若回调返回了 Promise,React 无法识别这段 Promise 是清理逻辑还是误写,因而会抛出警告并导致逻辑混乱。
  • 语义不清:清理函数(cleanup)本身必须是同步的,负责“撤销”前一次 effect 创建的资源(如取消订阅、清除定时器)。如果让 useEffect 回调变成 async,React 无法得知你是要在 await 之前进行清理,还是在 await 之后返回另一个函数,这会打乱生命周期的可预测性。

因此,千万不要将 useEffect 回调直接写成 async。接下来介绍几种常见的正确做法。

在 useEffect 里调用异步函数的常见写法

在 effect 内部定义并立即执行一个 async 函数

这是最常见也最推荐的方式:在 useEffect 回调体内,先定义一个 async 函数(可以用命名函数或箭头函数),然后马上调用它。

import React, { useEffect, useState } from 'react';function UserProfile({ userId }) {const [userData, setUserData] = useState(null);const [error, setError] = useState(null);useEffect(() => {// 定义一个 async 函数async function fetchUser() {try {const response = await fetch(`/api/user/${userId}`);if (!response.ok) {throw new Error(`网络错误:${response.status}`);}const json = await response.json();setUserData(json);} catch (err) {setError(err);}}// 立即执行fetchUser();// (可选)返回一个清理函数return () => {// 如果你想在 userId 变化时取消之前的请求,可以在这里处理// 例如,使用 AbortController 来中止 fetch};}, [userId]); // 依赖列表中包含 userId,当 userId 改变时重新执行上述逻辑if (error) {return <div>加载出错:{error.message}</div>;}if (!userData) {return <div>加载中...</div>;}return <div>用户名:{userData.name}</div>;
}
  • useEffect 中先定义 async function fetchUser(),然后同步地调用它。这样,useEffect 回调本身依旧是一个同步函数,返回值可以是 undefined 或者一个同步的清理函数。
  • 如果需要在组件卸载或依赖变化时取消网络请求,可以配合 AbortController

使用立即执行的箭头 async 函数(IIFE)

有些人喜欢用 “Immediately Invoked Function Expression”(IIFE) 的写法,更紧凑一些,但可读性与上一种等价:

useEffect(() => {(async () => {try {const res = await fetch(`/api/user/${userId}`);const data = await res.json();setUserData(data);} catch (err) {setError(err);}})();// 不返回任何清理函数,或者在此处也可返回同步清理
}, [userId]);

虽然这种写法更“短”,但对一些开发者而言可读性稍低。

将异步逻辑抽成自定义 Hook

为了让组件逻辑更清晰、可复用,我们可以把“异步请求 + 加载/错误状态管理”封装到自定义 Hook 里,然后在组件中直接使用。这样组件本身的 useEffect 只负责“调用 Hook”就好。

// useFetch.js
import { useState, useEffect } from 'react';export function useFetch(url) {const [data, setData] = useState(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {const controller = new AbortController();async function fetchData() {setLoading(true);try {const res = await fetch(url, { signal: controller.signal });if (!res.ok) throw new Error(`请求失败:${res.status}`);const json = await res.json();setData(json);} catch (err) {if (err.name !== 'AbortError') {setError(err);}} finally {setLoading(false);}}fetchData();// 清理:在组件卸载或 url 变更时取消请求return () => {controller.abort();};}, [url]);return { data, loading, error };
}

组件使用时就非常简洁:

import React from 'react';
import { useFetch } from './useFetch';function Dashboard({ userId }) {const { data, loading, error } = useFetch(`/api/dashboard/${userId}`);if (loading) return <div>加载中...</div>;if (error) return <div>加载出错:{error.message}</div>;return <div>欢迎,{data.userName}</div>;
}
  • 关注点分离:组件只关心要什么数据,而不关心“如何发请求并管理状态”。
  • 逻辑易复用:其他组件也可以复用 useFetch。

如果需要在 effect 中做取消逻辑(AbortController)

在一个组件可能在“请求还未返回”时就卸载,或者依赖改变需要取消前一次请求时,常见做法是借助原生的 AbortController,配合 fetchsignal 参数,让旧请求可被中止。

useEffect(() => {const controller = new AbortController();const signal = controller.signal;async function loadData() {try {const response = await fetch(`/api/data/${id}`, { signal });if (!response.ok) throw new Error(`错误:${response.status}`);const json = await response.json();setData(json);} catch (err) {if (err.name === 'AbortError') {// 请求被取消时,fetch 会抛出 AbortErrorconsole.log('Fetch 已取消');} else {setError(err);}}}loadData();return () => {// 在依赖改变或组件卸载时,调用 abort() 取消本次 fetchcontroller.abort();};
}, [id]);
  • effect 里先创建 AbortController,拿到 signal
  • signal 传给 fetch,一旦执行 controller.abort(),该 fetch 就会立刻以 AbortError 终止。
  • catch 中判断 err.name === 'AbortError',即可区分“取消”与“真实网络错误”。
  • 最后在 effect 的返回函数里调用 controller.abort(),实现“组件卸载或 id 变化时,取消当前请求”。

多个异步操作与清理

如果在同一个 useEffect 中做多次异步调用(例如先拿到 token,再根据 token 请求数据),可以串联 await,也要在最外层的 effect 返回一个总的清理逻辑:

useEffect(() => {const controller = new AbortController();const signal = controller.signal;async function loadAll() {try {// 第一步:获取 tokenconst tokenRes = await fetch('/api/get-token', { signal });const { token } = await tokenRes.json();// 如果组件在这一步就卸载了,tokenRes 会被中止,下面 fetch 不会再执行// 第二步:根据 token 请求数据const dataRes = await fetch(`/api/data?token=${token}`, { signal });const json = await dataRes.json();setData(json);} catch (err) {if (err.name !== 'AbortError') {setError(err);}}}loadAll();return () => {controller.abort();};
}, [dependencyA, dependencyB]);

使用 Promise.then 也可以

如果你不习惯 async/await,也可以使用链式 Promise 写法。但同样不能把 useEffect 回调改为异步:

useEffect(() => {const controller = new AbortController();const signal = controller.signal;fetch(`/api/data/${id}`, { signal }).then(res => {if (!res.ok) throw new Error(`HTTP 错误:${res.status}`);return res.json();}).then(json => {setData(json);}).catch(err => {if (err.name === 'AbortError') {console.log('请求被取消');} else {setError(err);}});return () => {controller.abort();};
}, [id]);

功能与 async/await 等价,只是可读性略差。然而在一些需要兼容较旧环境或团队风格偏好 Promise 链时,也是常见写法。


以上就是本文的全部内容啦,如果本文对你有帮助的话,也可以转发给你的朋友~

相关文章:

『React』组件副作用,useEffect讲解

在 React 开发中&#xff0c;有时候会听到“副作用”这个词。特别是用到 useEffect 这个 Hook 的时候&#xff0c;官方就明确说它是用来处理副作用的。那什么是副作用&#xff1f;为什么我们要专门管控它&#xff1f;今天就聊聊 React 中的组件副作用。 &#x1f4cc; 什么是“…...

使用VSCode在WSL和Docker中开发

通过WSL&#xff0c;开发人员可以安装 Linux 发行版&#xff08;例如 Ubuntu、OpenSUSE、Kali、Debian、Arch Linux 等&#xff09;&#xff0c;并直接在 Windows 上使用 Linux 应用程序、实用程序和 Bash 命令行工具&#xff0c;不用进行任何修改&#xff0c;也无需使用传统虚…...

ZooKeeper 命令操作

文章目录 Zookeeper 数据模型Zookeeper 服务端常用命令Zookeeper 客户端常用命令 Zookeeper 数据模型 ZooKeeper 是一个树形目录服务,其数据模型和Unix的文件系统目录树很类似&#xff0c;拥有一个层次化结构。这里面的每一个节点都被称为&#xff1a; ZNode&#xff0c;每个节…...

解决 Ubuntu 20.04 虚拟机中 catkin_make 编译卡死问题

完整解决步骤 1. 禁用当前交换文件 sudo swapoff /swapfile 2. 删除旧的交换文件 sudo rm /swapfile 3. 使用更可靠的创建方法 # 使用 dd 命令创建交换文件&#xff08;更兼容但较慢&#xff09; sudo dd if/dev/zero of/swapfile bs1M count4096# 或者使用 truncate 命令…...

【HTML-15】HTML表单:构建交互式网页的基石

表单是HTML中最强大的功能之一&#xff0c;它允许网页收集用户输入并与服务器进行交互。无论是简单的搜索框、登录页面&#xff0c;还是复杂的多步骤调查问卷&#xff0c;表单都是实现这些功能的核心元素。本文将深入探讨HTML表单的各个方面&#xff0c;帮助您构建高效、用户友…...

一些较好的学习方法

1、网上有一些非常经典的电路&#xff0c;而且有很多视频博主做了详细的讲解。 2、有一部分拆解的UP主&#xff0c;拆解后会还原该器件的原理图&#xff0c;并一步步做讲解。 3、有两本书&#xff0c;数电、模电&#xff0c;这两本书中的内容很多都值得学习。 5、某宝上卖的…...

Redis底层数据结构之深入理解跳表(1)

在上一篇文章中我们详细的介绍了一下Redis中跳表的结构以及为什么Redis要引入跳表而不是平衡树或红黑树。这篇文章我们就来详细梳理一下跳表的增加、搜索和删除步骤。 SkipList的初始化 跳表初始化时&#xff0c;将每一层链表的头尾节点创建出来并使用集合将头尾节点进行存储&…...

鸿蒙【HarmonyOS 5】 (React Native)的实战教程

一、环境配置 ‌安装鸿蒙专属模板‌ bashCopy Code npx react-native0.72.5 init HarmonyApp --template react-native-template-harmony:ml-citation{ref"4,6" data"citationList"} ‌配置 ArkTS 模块路径‌ 在 entry/src/main/ets 目录下创建原生模块&…...

PCB设计教程【入门篇】——电路分析基础-元件数据手册

前言 本教程基于B站Expert电子实验室的PCB设计教学的整理&#xff0c;为个人学习记录&#xff0c;旨在帮助PCB设计新手入门。所有内容仅作学习交流使用&#xff0c;无任何商业目的。若涉及侵权&#xff0c;请随时联系&#xff0c;将会立即处理 目录 前言 一、数据手册的重要…...

20250529-C#知识:继承、密封类、密封方法、重写

C#知识&#xff1a;继承、密封类、密封方法、重写 继承是面向对象的三大特性之一&#xff0c;通过继承能够减少重复代码的编写&#xff0c;有助于提升开发效率。 1、继承 C#不同于C&#xff0c;只支持单继承当子类出现与父类同名的成员时&#xff0c;父类成员被隐藏&#xff0…...

从0到1,带你走进Flink的世界

目录 一、Flink 是什么&#xff1f; 二、Flink 能做什么&#xff1f; 三、Flink 架构全景概览 3.1 分层架构剖析 3.2 核心组件解析 四、Flink 的核心概念 4.1 数据流与数据集 4.2 转换操作 4.3 窗口 4.4 时间语义 4.5 状态与检查点 五、Flink 安装与快速上手 5.1 …...

springboot @value

#springboot value value 可以读取 yaml 中 的数据...

Dify-5:Web 前端架构

本文档提供了 Dify Web 前端架构的技术概述&#xff0c;包括核心组件、结构和关键技术。它解释了前端如何组织、组件如何通信以及国际化功能如何实现。 技术栈 Dify 的 Web 前端基于现代 JavaScript 技术栈构建&#xff1a; 框架&#xff1a;Next.js&#xff08;基于 React …...

深度学习赋能图像识别:技术、应用与展望

论文&#xff1a; 一、引言​ 1.1 研究背景与意义​ 在当今数字化时代&#xff0c;图像作为信息的重要载体&#xff0c;广泛存在于各个领域。图像识别技术旨在让计算机理解和识别图像内容&#xff0c;将图像中的对象、场景、行为等信息转化为计算机能够处理的符号或数据 &am…...

八N皇后问题

1 问题的提出 在8X8格的国际象棋上摆放八个皇后&#xff0c;使其不能互相攻击&#xff0c;即任意两个皇后都不能处于同一行、同一列或同一斜线上&#xff0c;问有多少种摆法 我们的任务就是用MATLAB进行求解 2 数学模型的构建 首先我们分析题目就是 任意两个皇后都不能处于…...

TMS320F28388D使用sysconfig配置IPC

第1章 配置IPC底层代码 使用IPC的动机&#xff1a; 我计划我的项目中要使用RS485&#xff0c;CANFD通信和EtherCAT通信&#xff0c;由于通信种类较多&#xff0c;而对于电机控制来说大部分数据都是重复的&#xff0c;并且有些数据可以很久才改变一次&#xff0c;所以我计划使…...

代码训练LeetCode(19)轮转数组

代码训练(19)LeetCode之轮转数组 Author: Once Day Date: 2025年6月3日 漫漫长路&#xff0c;才刚刚开始… 全系列文章可参考专栏: 十年代码训练_Once-Day的博客-CSDN博客 参考文章: 189. 轮转数组 - 力扣&#xff08;LeetCode&#xff09;力扣 (LeetCode) 全球极客挚爱的…...

每日算法 -【Swift 算法】将整数转换为罗马数字

&#x1f4a1; Swift&#xff1a;将整数转换为罗马数字&#xff08;含思路讲解与详细注释&#xff09; 罗马数字是一种古老的数字表示方式&#xff0c;虽然在现代我们不再使用它进行计算&#xff0c;但在表盘、章节、纪念碑等地方依然很常见。今天我们就来实现一个经典算法题&…...

Qwen与Llama分词器核心差异解析

Qwen和 Llama 词映射(分词器)的区别及通用词映射逻辑 一、Qwen 与 Llama 词映射(分词器)区别 维度Qwen 分词器Llama 分词器技术基础基于字节级别字节对编码(BBPE),以 cl100k 为基础词库,扩充中文字词、多语言词汇基于 BPE,但依赖 SentencePiece 单字模型,核心为英文优…...

华为云Flexus+DeepSeek征文 | 基于ModelArts Studio 与 Cline 快速构建AI编程助手

目录 一、前言 二、ModelArts Studio&#xff08;MaaS&#xff09;介绍与应用场景 2.1ModelArts Studio&#xff08;MaaS&#xff09;介绍 2.2 ModelArts Studio&#xff08;MaaS&#xff09;使用场景 2.3 开通MaaS服务 2.4 开通DeepSeek-V3商用服务 三、Cline简介和安装 3.1 C…...

pikachu靶场通关笔记11 XSS关卡07-XSS之关键字过滤绕过(三种方法渗透)

目录 一、源码分析 1、进入靶场 2、代码审计 3、攻击思路 二、渗透实战 1、探测过滤信息 2、注入Payload1 3、注入Payload2 4、注入Payload3 本系列为通过《pikachu靶场通关笔记》的XSS关卡(共10关&#xff09;渗透集合&#xff0c;通过对XSS关卡源码的代码审计找到安…...

Android App引用vendor编写的jni动态库

简单描述一下&#xff0c;就是我自己基于FastDDS写了一个Jni的so&#xff0c;然后编写了jar包引用该so&#xff0c;最后写了一个Android的测试apk使用jar包&#xff0c;调用jni中的接口去创建Participant&#xff0c;Subscriber等。 实际将jni的so放到 /system_ext/lib64&#…...

React从基础入门到高级实战:React 核心技术 - 错误处理与错误边界:构建稳定的应用

React 错误处理与错误边界&#xff1a;构建稳定的应用 在开发 React 应用时&#xff0c;错误处理是确保应用稳定性和用户体验的重要环节。无论是运行时错误、API 请求失败还是用户操作失误&#xff0c;合理的错误处理机制都能防止应用崩溃&#xff0c;并为用户提供清晰友好的反…...

页面输入数据的表格字段(如 Web 表单或表格控件)与后台数据库进行交互时常用的两种方式

“从页面输入数据的表格字段(如 Web 表单或表格控件)在与后台数据库进行交互时,常用的有两种方式:” 🎯 两种方式(操作调用数据库、绑定数据) 🚀 方式1:前端代码提交数据到后端,再由后端调用数据库 💡 原理和逻辑: 用户在页面上(比如输入表单、表格)输入数据…...

碰一碰发视频-源码系统开发技术分享

#碰一碰营销系统# #碰一碰系统# #碰一碰发视频# 架构设计哲学&#xff1a;近场通信的优雅平衡 一、核心通信技术选型 1. 双模协同传输引擎 技术协议栈延迟控制适用场景NFCISO 14443-A<100ms精准触发场景BLE 5.0GATT Profile300-500ms中距传输场景 工程决策依据&…...

C++学习过程分享

空指针&#xff1a;int *p NULL; 空指针&#xff1a;指针变量指向内存中编号为0的空间&#xff1b;用途&#xff1a;初始化指针变量注意&#xff1a;空指针指向的内存不允许访问注意&#xff1a;内存编号为0-255为系统占用空间&#xff0c;不允许用户访问 野指针&#xff1a;…...

C语言 — 动态内存管理

目录 1.malloc和free函数1.1 malloc函数1.2 free函数1.3 malloc函数的使用 2.calloc函数2.1 calloc函数2.2 calloc函数的使用 3.realloc函数3.1 realloc函数3.2 realloc函数的使用 4.动态内存管理笔试题4.1 笔试题&#xff08;1&#xff09;4.2 笔试题&#xff08;2&#xff09…...

《TCP/IP 详解 卷1:协议》第5章:Internet协议

IPv4和IPv6头部 IP是TCP/IP协议族中的核心协议。所有TCP、UDP、ICMP和IGMP 数据都通过IP数据报传输。IP提供了一种尽力而为、无连接的数据报交付服务。 IP头部字段 IPv4 头部通常为 20 字节&#xff08;无选项时&#xff09;&#xff0c;而 IPv6 头部固定为 40 字节。IPv6 不…...

C#面向对象实践项目--贪吃蛇

目录 一、项目整体架构与核心逻辑 二、关键类的功能与关系 1. 游戏核心管理类&#xff1a;Game 2. 场景接口与基类 3. 具体场景类 4. 游戏元素类 5. 基础结构体与接口 三.类图 四、核心流程解析 五、项目可优化部分 一、项目整体架构与核心逻辑 该项目运用场景管理模…...

学习STC51单片机26(芯片为STC89C52RCRC)

每日一言 真正的强者&#xff0c;不是没有眼泪&#xff0c;而是含着泪依然奔跑。 硬件&#xff1a;4G模块 这个是接线原理&#xff0c;我们也只要知道这个4根线的连接就好了&#xff0c;我们也是连接到USB转TTL的模块上 要插卡哈......... 随后我们下载一个叫做亿佰特的调试助…...