redux-saga
redux-saga
官网:About | Redux-Saga
中文网:自述 · Redux-Sagaredux-saga 是一个用于管理
异步获取数据(副作用)的redux中间件;它的目标是让副作用管理更容易,执行更高效,测试更简单,处理故障时更容易…学习 redux-saga 之前,需要先掌握 ES6 中的 Iterator迭代器 和 Generator生成器 !!
1. redux-thunk与redux-saga的比较
redux中的数据流
action ———> reducer ———> state
- action是一个纯粹对象(plain object)
- reducer是一个纯函数(和外界没有任何关系)
- 都只能处理同步的操作
redux-thunk中间件的处理流程
action1 ———> middleware ———> action2 ———> reducer ———> state
/* redux-thunk中间件的部分源码 */
'use strict';
function createThunkMiddleware(extraArgument) {var middleware = function middleware(_ref) {var dispatch = _ref.dispatch,getState = _ref.getState;return function (next) {return function (action) {if (typeof action === 'function') {// 如果返回的action是个函数,则把函数执行「在函数中完成异步操作,用传递的dispatch单独实现派发!!」return action(dispatch, getState, extraArgument);}return next(action);};};};return middleware;
}
弊端:异步操作分散到每一个action中;而且返回函数中的代码具备多样性!!
redux-saga中间件的工作流程
redux-saga中提供了一系列的api,可以去监听纯粹对象格式的action,方便单元测试!!
action1 ———> redux-saga监听 ———> 执行提供的API方法 ———> 返回描述对象 ———> 执行异步操作 ———> action2 ———> reducer ———> state
这样说完,大家可能是不太理解的,那么接下来,我们去做一个案例,详细解读一下redux-saga的语法和优势!!
常用的中间件
- redux-logger 输出派发日志(开发环境下)
- redux-thunk / redux-promise 实现异步派发
- redux-saga 实现异步派发的 管理的
- redux-persist 实现公共状态持久化存储 [ 实施同步数据到 localStorage中;当页面刷新或者重新打开的时候,会把存储的信息放到 redux 容器中一份 ]
2. redux-saga的基础知识
备注:需要先准备一套基于 “redux、redux-thunk” 实现的投票案例,我们在此基础上去修改 !!
安装中间件
$ npm install redux-saga
$ yarn add redux-saga
使用中间件
store/index.js
- 创建 sagaMiddleware
- 插件中引入 sagaMiddleware
- 启动saga ( 需要创建一个saga文件 )
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducer';
import saga from './saga';
// saga
const sagaMiddleware = createSagaMiddleware();
// 创建store容器
const store = createStore(reducer,applyMiddleware(sagaMiddleware)
);
// 启动saga
sagaMiddleware.run(saga);
export default store;
saga中间件原理:

saga.js:
import {take, takeLatest, throttle, debounce,call, apply, fork, delay, put, select, all, cancel
} from 'redux-saga/effects';
import * as TYPES from './action-types';/* 工作区域 */
const workingCount = function* workingCount(action) {yield delay(2000);yield put({type: TYPES.DEMO_COUNT,payload: action.payload});
};
const workingSupport = function* workingSupport() {yield delay(1000);yield put({type: TYPES.VOTE_SUP});
};
const workingOppose = function* workingOppose() {yield delay(1000);yield put({type: TYPES.VOTE_OPP});
};/* 创建监听器,监听派发的异步任务 */
const saga = function* saga() {/* while (true) {let action = yield take(`${TYPES.DEMO_COUNT}@SAGA@`);yield fork(workingCount, action);yield fork(workingSupport, action);yield fork(workingOppose, action);} */yield takeLatest(`${TYPES.DEMO_COUNT}@SAGA@`, workingCount);yield takeLatest(`${TYPES.VOTE_SUP}@SAGA@`, workingSupport);yield takeLatest(`${TYPES.VOTE_OPP}@SAGA@`, workingOppose);
};
export default saga;
store/action-types.js
export const VOTE_SUP = "VOTE_SUP";
export const VOTE_OPP = "VOTE_OPP";export const DEMO = "DEMO";
store/reducer
// demoReducer
import * as TYPES from '../action-types';
import _ from '../../assets/utils';
let initial = {num: 0
};
export default function demoReducer(state = initial, action) {state = _.clone(state);let { payload = 1 } = action;switch (action.type) {case TYPES.DEMO:state.num += payload;break;default:}return state;
};// index.js
import { combineReducers } from 'redux';
import voteReducer from './voteReducer';
import demoReducer from './demoReducer';
const reducer = combineReducers({vote: voteReducer,demo: demoReducer
});
export default reducer;
Demo.jsx组件中
import React from "react";
import { Button } from 'antd';
import { useSelector, useDispatch } from 'react-redux';
const Demo = function Demo() {const { num } = useSelector(state => state.demo),dispatch = useDispatch();return <div><span style={{ fontSize: 20, paddingLeft: 10 }}>{num}</span><br /><Button type="primary"onClick={() => {//基于dispatch进行派发....}}>按钮</Button></div>;
};
export default Demo;
saga.js 工作流程

第一部分:创建监听器「基于saga的辅助函数」
take(pattern)
takeEvery(pattern, saga, …args)
takeLatest(pattern, saga, ..args)
throttle(ms, pattern, saga, ..args)
…
第二部分:创建执行函数「基于Effect创建器(API)」
put(action)
call(fn, …args)
fork(fn, …args)
select(selector, …args)
…
每一次组件派发后发生的事情
每一次在组件中,基于dispatch(action)的时候:
首先会通知 reducer 执行
然后再去通知 saga 中的监听器执行
关于监听器创建的细节
组件中
<Button type="primary"onClick={() => {dispatch({type: "DEMO-SAGA",payload: 10});}}>按钮
</Button>
saga.js -> take 函数的运用
import * as TYPES from './action-types';
const working = function* working(action) {// 等价于 dispatch派发:通知reducer执行yield put({type: TYPES.DEMO,payload: action.payload});
};
export default function* saga() {/* // 创建监听器,当监听到派发后,才会继续向下执行// 特征:只能监听一次// action:可以获取派发时传递的actionlet action = yield take("DEMO-SAGA");yield working(action); */// 可基于循环,创建无限监听机制while (true) {let action = yield take("DEMO-SAGA");yield working(action);}
};
saga.js -> 其它监听器辅助函数的运用
const working = function* working(action) {console.log('AAA');// 设置延迟函数:等待2000ms后,才会继续向下执行!!yield delay(2000);yield put({type: TYPES.DEMO,payload: action.payload});
};
export default function* saga() {/* // 派发后,立即通知异步的working执行;// 但是在working没有处理完毕之前,所有其他的派发任务都不在处理!!while (true) {let action = yield take("DEMO-SAGA");yield working(action);} *//* // 每一次派发任务都会被执行yield takeEvery("DEMO-SAGA", working); *//* // 每一次派发任务都会被执行,但是会把之前没有处理完毕的干掉yield takeLatest("DEMO-SAGA", working); *//* // 每一次派发的任务会做节流处理;在频繁触发的操作中,1000ms内,只会处理一次派发任务yield throttle(1000, "DEMO-SAGA", working); *//* // 每一次派发的任务会做防抖处理;在频繁触发的操作中,只识别最后一次派发任务进行处理yield debounce(1000, "DEMO-SAGA", working); */
};
saga.js -> yield call/select…
import * as TYPES from './action-types';
import http from '../api/http';const working = function* working() {// 获取目前的公共状态信息let { num } = yield select(state => state.demo);// 从服务器获取数据// let result = yield apply(null, http.get, ['/api/news/latest']);let result = yield call(http.get, '/api/news/latest');console.log(result); //从服务器获取的数据yield put({type: TYPES.DEMO});
};
export default function* saga() {yield takeLatest("DEMO-SAGA", working);
};
saga.js -> yield fork
const query1 = function* query1() {console.log(1);yield delay(2000);
};
const query2 = function* query2() {console.log(2);yield delay(2000);
};
const working = function* working() {/* // 串行yield call(query1);yield call(query2); *//* // 并行:无阻塞调用yield fork(query1);yield fork(query2); */console.log(3);
};
export default function* saga() {yield takeLatest("DEMO-SAGA", working);
};
3. 基于redux-saga重写Vote案例
组件
import { useSelector, useDispatch } from 'react-redux';
import * as TYPES from '../store/action-types';
...
const Vote = function Vote() {const { supNum, oppNum } = useSelector(state => state.vote),dispatch = useDispatch();return <VoteBox>...<div className="footer"><Button type="primary"onClick={() => {dispatch({type: TYPES.VOTE_SUP});}}>支持</Button><Button type="primary"onClick={() => {dispatch({type: "VOTE-SUP-SAGA"});}}>异步支持</Button><Button type="primary" dangeronClick={() => {dispatch({type: TYPES.VOTE_OPP});}}>反对</Button><Button type="primary" dangeronClick={() => {dispatch({type: "VOTE-OPP-SAGA"});}}>反对异步</Button></div></VoteBox>;
};
export default Vote;
saga.js
import { takeLatest, put, delay } from 'redux-saga/effects';
import * as TYPES from './action-types';const voteSupWorking = function* voteSupWorking() {yield delay(2000);yield put({type: TYPES.VOTE_SUP});
};const voteOppWorking = function* voteOppWorking() {yield delay(2000);yield put({type: TYPES.VOTE_OPP});
};export default function* saga() {yield takeLatest("VOTE-SUP-SAGA", voteSupWorking);yield takeLatest("VOTE-OPP-SAGA", voteOppWorking);
};
redux-saga 常用API总结
1. 在组件中基于dispatch派发的时候,派发的action对象中的type属性「派发的行为标识」,它的命名上需要注意一个细节!!因为:每一次派发,一定会把reducer执行一遍,再去saga中间中,判断此任务是否被监听...如果打算进行“同步派发”:则我们派发的行为标识需要和reducer中做判断的行为标识保持一致!!并且在saga中,不要再对这个标识进行监听了!!这样的标识,我们可以在 store/action-types 中进行统一管理!!如果打算进行“异步派发”:我们派发的标识,“一定不能”和reducer中做判断的标识一样!!需要saga中对这个标识进行监听!监听到派发后,进行异步的操作处理!!我们可以在正常标识的后面加“@SAGA@”「规范:我自己定义的」当异步操作结束,我们基于 yield put 进行派发的时候,设置的派发标识,要和reducer中做判断的标识一样!!2. yield take(异步标识):创建监听器,监听派发指定标识的异步任务+ 单纯这样处理,只会被监听一次,我们特殊处理一下while (true) {let action = yield take(异步标识);yield workingCount(action);}3. yield takeEvery(异步标识,要执行的方法)+ 等价于上述基于while(true)的操作!!+ 本身就可以实现一直监听的操作!!被监到后,把传递进来的函数执行!!yield takeEvery(异步标识, workingCount);yield takeLatest(异步标识,working)+ 和takeEvery一样,每一次异步派发都会被监测到,都会把working执行+ 只不过,在执行working之前,会把正在运行的操作都结束掉,只保留当前最新的「也就是最后一次」+ 对异步派发任务的防抖处理「结束边界」yield throttle(ms, 异步标识, working);+ 对异步派发进行节流处理:组件中频繁进行派发操作,我们控制一定的触发频率「依然会触发多次,只不过做了降频」+ 它不是对执行的方法做节流,而是对异步任务的监测做节流:第一次异步任务被监测到派发后,下一次监测到,需要过“ms”这么长时间!!yield debounce(ms, 异步标识, working);+ 和takeLatest一样,也是做防抖处理「只识别一次」+ 但是原理和takeLatest是不一样的,和throttle类似:它是对异步任务的监测做防抖处理,在指定的“ms”时间内,我们触发多次,任务也只能被监测到一次「监测最后一次」,把working执行一次!!4. working工作区中使用的EffectsAPIyield delay(ms) 设置延迟操作「和我们之前自己写的delay延迟函数类型」,只有延迟时间到达后,其下面的代码才会继续执行!!yield put(action) 派发任务到reducer,等价于dispatchlet { ... } = yield select(mapState)+ 基于mapState函数,返回需要使用的公共状态+ yield处理后的结果,就是返回的公共状态,我们可以解构赋值let { num } = yield select(state => state.demo);let result = yield call(方法, 实参1, 实参2, ...)+ 基于call方法,可以把指定的函数执行,把实参一项项的传递给方法+ 真实项目中,我们一般基于call方法,实现从服务器获取数据+ result就是异步调取接口成功,从服务器获取的信息+ ...let result = yield apply(this, 方法, [实参1, 实参2, ...]);yield fork(方法, 实参1, 实参2, ...)+ 以 非阻塞调用 的形式执行方法
模拟接口
import {take, takeEvery, takeLatest, throttle, debounce,call, apply, fork, delay, put, select, all
} from 'redux-saga/effects';
import * as TYPES from './action-types';/* 模拟了两个接口 */
const api = {queryData(id, name) {return new Promise(resolve => {setTimeout(() => {let result = {code: 0,data: [10, 20, 30, 40]};resolve(result);}, 2000);});},queryBanner() {return new Promise(resolve => {setTimeout(() => {let result = {code: 0,data: '轮播图数据'};resolve(result);}, 1000);});}
};/* 创建执行函数,在任务被监听后,去做异步操作「Generator函数」 */
const workingCount = function* workingCount(action) {/* // let { num } = yield select(state => state.demo);yield delay(2000);// let { code, data } = yield call(api.queryData, 108, '珠峰');// let { code, data } = yield apply(null, api.queryData, [108, '珠峰']);yield put({type: TYPES.DEMO_COUNT,payload: action.payload}); *//* // 基于yield call处理,实现的是标准的串行效果:上一个请求成功,才会发送下一个请求let { data } = yield call(api.queryData, 100, '珠峰培训');console.log('第一个请求成功:', data);let { data: data2 } = yield call(api.queryBanner);console.log('第二个请求成功:', data2); *//* // 如果想实现并行效果,则基于yield all处理:等待所有请求都成功,再向下继续执行let { home, banner } = yield all({home: call(api.queryData, 100, '珠峰培训'),banner: call(api.queryBanner)});console.log(home, banner); //分别获取了两个请求成功的结果 */
};/* 创建监听器,监听派发的任务「Generator函数」 */
const saga = function* saga() {yield takeLatest(`${TYPES.DEMO_COUNT}@SAGA@`, workingCount);
};
export default saga;
saga
import {take, takeLatest, throttle, debounce,call, apply, fork, delay, put, select, all, cancel
} from 'redux-saga/effects';
import * as TYPES from './action-types';/* 工作区域 */
const workingCount = function* workingCount(action) {yield delay(2000);yield put({type: TYPES.DEMO_COUNT,payload: action.payload});
};
const workingSupport = function* workingSupport() {yield delay(1000);yield put({type: TYPES.VOTE_SUP});
};
const workingOppose = function* workingOppose() {yield delay(1000);yield put({type: TYPES.VOTE_OPP});
};/* 创建监听器,监听派发的异步任务 */
const saga = function* saga() {/* while (true) {let action = yield take(`${TYPES.DEMO_COUNT}@SAGA@`);yield fork(workingCount, action);yield fork(workingSupport, action);yield fork(workingOppose, action);} */yield takeLatest(`${TYPES.DEMO_COUNT}@SAGA@`, workingCount);yield takeLatest(`${TYPES.VOTE_SUP}@SAGA@`, workingSupport);yield takeLatest(`${TYPES.VOTE_OPP}@SAGA@`, workingOppose);
};
export default saga;
相关文章:
redux-saga
redux-saga 官网:About | Redux-Saga 中文网:自述 Redux-Saga redux-saga 是一个用于管理 异步获取数据(副作用) 的redux中间件;它的目标是让副作用管理更容易,执行更高效,测试更简单,处理故障时更容易… …...
【C++】-- 智能指针
目录 智能指针意义 智能指针的使用及原理 RAII 智能指针的原理 std::auto_ptr std::auto_ptr的模拟实现 std::unique_ptr std::unique_ptr模拟实现 std::shared_ptr std::shared_ptr的模拟实现 循环引用问题 智能指针意义 #问:为什么需要智能指针&#…...
数据结构与算法——4时间复杂度分析2(常见的大O阶)
这篇文章是时间复杂度分析的第二篇。在前一篇文章中,我们从0推导出了为什么要用时间复杂度,时间复杂度如何分析以及时间复杂度的表示三部分内容。这篇文章,是对一些常用的时间复杂度进行一个总结,相当于是一个小结论 1.常见的大O…...
IIS解析漏洞
IIS 6.0在解析文件时存在以下两个解析漏洞。 ①当建立*.asa、*.asp格式的文件夹时,其目录下的任意文件都将被IIS当作asp文件来解析。 例如:建立文件夹 parsing.asp,在 parsing.asp 文件夹内新建一个文本文档 test.txt,其内容为&…...
2023 年腾讯云轻量和CVM服务器租用价格表出炉(CPU/内存/带宽/系统盘)
腾讯云服务器的价格表是用户比较关心的问题,服务器的价格组成包括云服务器的机型价格、磁盘价格和宽带价格,主机教程网来详细说下腾讯云最新的云服务器价格表。我们以北京一区、Linux系统的云服务器为例,其他地域的价格会有所差异,…...
Java学习之路002——面向对象编程
【说明】部分内容来源于网络,如有冲突,请联系作者删除。 一、面向对象编程(OOP) 2.1 对象和类的关系 2.2 面向对象的特征 2.2.1 封装 2.2.2 继承 2.2.3 多态 3、抽象 使用abstract关键字修饰的类或者方法 定义抽象类(使用abstract) // 1、定义抽象方法…...
VR直播丨颠覆性技术革命,新型直播已经到来
细数当下最火热的营销手段,首先浮现脑海的无疑是“直播”。前有罗永浩、李佳琦,后有刘畊宏和东方甄选,直播如日中天,俨然成了大众足不出户就能休闲娱乐的重要途径。 而随着虚拟现实在“十四五规划”中被列入“建设数字中国”数字…...
【微信小程序】-- WXSS 模板样式- rpx import (十三)
💌 所属专栏:【微信小程序开发教程】 😀 作 者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! &…...
Biotin-PEG-SVA,生物素聚乙二醇琥珀酰亚胺戊酸酯,可用于检测或分子标记
Biotin-PEG-SVA 结构式:PEG分子量: 1000,2000,3400,5000,10000中文名称:生物素聚乙二醇琥珀酰亚胺戊酸酯,生物素-PEG-琥珀酰亚胺戊酸酯英文名称:Biotin-PEG-SVA …...
云原生是什么?核心概念和应用方法解析
什么是云原生? 云原生是一种基于容器、微服务和自动化运维的软件开发和部署方法。它可以使应用程序更加高效、可靠和可扩展,适用于各种不同的云平台。 如果要更直接通俗的来解释下上面的概念。云原生更准确来说就是一种文化,是一种潮流&…...
Editor工具开发实用篇:EditorGUI/EditorGUILayout的区别和EditorGUILayout的方法介绍
目录 一:EditorGUI和EditorGUILayout区别 二:EditorGUILayout 1.EditorGUILayout.BeginFadeGroup(float value); 2.EditorGUILayout.BeginHorizontal EditorGUILayout.BeginVertical 3.EditorGUILayout.BeginScrollView 4.EditorGUILayout.BeginT…...
(五十二)大白话不断在表中插入数据时,物理存储是如何进行页分裂的?.md
上回我们讲到了数据页的物理存储结构,数据页之间是组成双向链表的,数据页内部的数据行是组成单向链表的,每个数据页内根据主键做了一个页目录 然后一般来说,你没有索引的情况下,所有的数据查询,其实在物理…...
Unity 渲染顺序
Unity中的渲染顺序自上而下大致分为三层渲染优先级 Camera depth > Sorting Layer > Order in Layer > RenderQueueCamera depth:越小越优先(大的显示在小的前面)如图:尽管Sphere距离摄像机较远,但由于Camera_Sphere dep…...
短视频美颜sdk人脸编辑技术详解、美颜sdk代码分析
短视频美颜sdk中人脸编辑技术可以将人像风格进行转变,小编认为这也是未来的美颜sdk的一个重要发展方向,下文小编将为大家讲解一下短视频美颜sdk中人脸编辑的关键点。 一、人脸编辑的细分关键点 1、年龄 通过更改人脸的年龄属性,可用于模仿人…...
error: expected declaration specifiers or ‘...’ before ‘(’ token
一、问题 最近写函数时,遇到了一个比较奇怪的问题,相信也好多人遇到一下的问题: error: expected declaration specifiers or ‘...’ before ‘(’ token代码如下: #include<stdio.h> struct stu{char *name;int score;…...
系列七、索引
一、索引概述 1.1、概述 索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以…...
Java开发 - Elasticsearch初体验
目录 前言 什么是es? 为什么要使用es? es查询的原理? es需要准备什么? es基本用法 创建工程 添加依赖 创建操作es的文件 使用ik分词插件 Spring Data 项目中引入Spring Data 添加依赖 添加配置 创建操作es的业务逻…...
mysql进阶
mysql进阶视图视图是一个基于查询的虚拟表,封装了一条sql语句,通俗的解释,视图就是一条select查询之后的结果集,视图并不存储数据,数据仍旧存储在表中。创建视图语句:create view view_admin as select * from admin使…...
SD卡损坏了?储存卡恢复数据就靠这3个方法
作为一种方便的储存设备,SD卡在我们的日常生活中使用非常广泛。但是,有时候我们可能会遇到SD卡损坏的情况,这时候里面存储的数据就会受到影响。SD卡里面保存着我们很多重要的数据,有些还是工作必须要使用的。 如果您遇到了这种情…...
springboot+实践(总结到位)
一。【SpringBoot注解-1】 牛逼:云深i不知处 【SpringBoot注解-1】:常见注解总览_云深i不知处的博客-CSDN博客 二。【SpringBoot-3】Lombok使用详解 【SpringBoot-3】Lombok使用详解_云深i不知处的博客-CSDN博客_springboot lombok 三࿰…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 的无缝对接&…...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...
C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...
【Linux系统】Linux环境变量:系统配置的隐形指挥官
。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量:setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...
