React进阶之React状态管理CRA

devtools/2025/2/13 21:00:14/

React状态管理&CRA

  • 状态管理
    • 理论讲解
      • 案例
    • context 上下文
    • 结合状态来维护todoList
      • index.js
      • App.js
      • TaskList.js
      • TasksContext.js
      • AddTask.js
    • Escape 脱围机制
      • ref
      • forwardRef(不建议使用)
  • CRA

状态管理

理论讲解

如何针对 effect -> 对action的触发 -> 影响到UI
怎么针对上述链路进行更好的抽象

还有,redux,mobx,vuex,pinia

重要的就是:state
不维护好就会造成:

  1. 冗余代码
  2. 预期之外的触发action
  3. 代码可维护性更差

有限状态自动机:各种各样的状态管理,通过事件触发一个行为,行为是固定的,相同的输入指向相同的输出,类似于纯函数

组件之间传达状态的方式:
状态提升,parent <=> childA,childB

reducer,类似触发器,是处理状态变更的,将不同组件中的不同状态合并起来
useReducer

javascript">const [type,dispatch]=useReducer(typeReducer,initType
)payload  //荷载  rest函数创建出来的对象 { ...rest }

案例

举例:

  • src
    • App.js
    • AddTask.js
    • TaskList.js

App.js

javascript">import React, { useReducer } from 'react';
import AddTask from './AddTask';
import TaskList from './TaskList';// 初始任务
const initialTasks = [{ id: 0, text: '参观卡夫卡博物馆', done: true },{ id: 1, text: '看木偶戏', done: false },{ id: 2, text: '列依墙图片', done: false }
];// 任务Reducer
function tasksReducer(tasks, action) {switch (action.type) {case 'added':return [...tasks,{ id: action.id, text: action.text, done: false }];case 'changed':return tasks.map(t => t.id === action.task.id ? action.task : t);case 'deleted':return tasks.filter(t => t.id !== action.id);default:throw new Error('未知操作: ' + action.type);}
}export default function App() {// 状态维护的核心const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);// 添加任务function handleAddTask(text) {const newTask = { id: Date.now(), text }; // 使用时间戳作为IDdispatch({ type: 'added', ...newTask });}// 修改任务function handleChangeTask(task) {dispatch({ type: 'changed', task });}// 删除任务function handleDeleteTask(taskId) {dispatch({ type: 'deleted', id: taskId });}return (<div><h1>布拉格行程</h1><AddTask onAddTask={handleAddTask} /><TaskList tasks={tasks} onChangeTask={handleChangeTask}onDeleteTask={handleDeleteTask}/></div>);
}

AddTask.js

javascript">import React, { useState } from 'react';export default function AddTask({ onAddTask }) {const [text, setText] = useState('');// 处理输入框变化function handleChange(event) {setText(event.target.value);}// 提交表单function handleSubmit(event) {event.preventDefault();if (text.trim() !== '') {onAddTask(text);setText(''); // 清空输入框}}return (<div><input type="text" value={text} onChange={handleChange} placeholder="请输入任务" /><button onClick={handleSubmit}>添加任务</button></div>);
}

TaskList.js

javascript">import React, { useState } from 'react';export default function TaskList({ tasks, onChangeTask, onDeleteTask }) {return (<ul>{tasks.map(task => (<li key={task.id}><Tasktask={task}onChange={onChangeTask}onDelete={onDeleteTask}/></li>))}</ul>);
}function Task({ task, onChange, onDelete }) {const [isEditing, setIsEditing] = useState(false);const [newText, setNewText] = useState(task.text);let taskContent;if (isEditing) {taskContent = (<><inputtype="text"value={newText}onChange={e => setNewText(e.target.value)}/><button onClick={() => { onChange({ ...task, text: newText });setIsEditing(false);}}>保存</button></>);} else {taskContent = (<><inputtype="checkbox"checked={task.done}onChange={() => onChange({ ...task, done: !task.done })}/><span>{task.text}</span><button onClick={() => onDelete(task.id)}>删除</button><button onClick={() => setIsEditing(true)}>编辑</button></>);}return <div>{taskContent}</div>;
}

请添加图片描述

initType初始值,像是tasks列表,需要去维护的状态,是状态集的概念,不会区分谁是哪个状态
需要去修改这个数组的时候,才会去通过动作或者手段选取其中某条执行,这时候需要借助于action,通过action去找到某一条处理这个task

上述是比较简单的例子,如果在复杂情况的话,需要区分文件,那么文件列表可能是这样:

  • src
    • reducer
      • dispatch.js
      • action.js
      • state.js

context 上下文

在vdom中树状结构就是通过上下文去维护的
本质:递归
通过一个参数,parentId 传递给下面的child,他们之间有一个关联的脐带:contextId,找到roots之间的关联方式

在实际开发中,props层级超过一层的话,就不能再通过props来传递了,除非是一些特殊的情况

举例:

  • src
    • index.js
    • App.js
    • Section.js
    • Heading.js
    • LevelContext.js
    • index.css

index.js

javascript">import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App";const root = createRoot(document.getElementById("root"));
root.render(<StrictMode><App /></StrictMode>
);

App.js

javascript">import Heading from './Heading.js';
import Section from './Section.js';export default function Page() {return (<Section>{/* 当前层级下的level,根据最近层级下的provider去获取的 */}{/* level+1 => LevelContext.Provider value=1 */}<Heading>大标题</Heading><Section>{/* level+1 => LevelContext.Provider value=2 */}<Heading>二级标题</Heading><Heading>二级标题</Heading><Heading>二级标题</Heading><Section>{/* level+1 => LevelContext.Provider value=3 */}<Heading>三级标题</Heading><Heading>三级标题</Heading><Heading>三级标题</Heading></Section></Section></Section>);
}

Heading.js

javascript">import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';export default function Heading({ children }) {const level = useContext(LevelContext);switch(level) {case 0:throw new Error('标题必须在 section 内!');case 1:return <h1>{children}</h1>;case 2:return <h2>{children}</h2>;case 3:return <h3>{children}</h3>;case 4:return <h4>{children}</h4>;case 5:return <h5>{children}</h5>;case 6:return <h6>{children}</h6>;default:throw new Error('未知级别:' + level);}
}

Section.js

javascript">import { useContext } from "react";
import { LevelContext } from './LevelContext.js';export default function Section({ children }) {const level = useContext(LevelContext);return (<section className="section"><LevelContext.Provider value={level + 1}>{children}</LevelContext.Provider></section>);
}

LevelContext.js

javascript">import { createContext, useState } from "react";export const LevelContext = createContext(0);

index.css

body {margin: 0;font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen','Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;
}code {font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',monospace;
}.section{padding: 10px;margin: 10px;border: 1px solid #ccc;border-radius: 5px;
}

在这里插入图片描述

  1. 日常开发中,这里的reducer是和context并行去维护的

结合状态来维护todoList

  • src
    • index.js
    • App.js
    • TaskList.js
    • TasksContext.js
    • AddTask.js

index.js

javascript">import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App";const root = createRoot(document.getElementById("root"));
root.render(<StrictMode><App /></StrictMode>
);

App.js

javascript">import AddTask from './AddTask.js';
import Tasklist from './TaskList.js';
import { TasksProvider } from './TasksContext.js';export default function App() {return (<TasksProvider><h1>在京都休息一天</h1><AddTask /><Tasklist /></TasksProvider>);
}

TaskList.js

javascript">import { useState, useContext } from 'react';
import { useTasks, useTasksDispatch } from "./TasksContext";export default function TaskList() {const tasks = useTasks();return (<ul>{tasks.map(task => (<li key={task.id}><Task task={task} /></li>))}</ul>);
}function Task({ task }) {const [isEditing, setIsEditing] = useState(false);const dispatch = useTasksDispatch();let taskContent;if (isEditing) {taskContent = (<><inputvalue={task.text}onChange={e => {dispatch({ type: 'changed', task: { ...task, text: e.target.value } });}}/><button onClick={() => setIsEditing(false)}>保存</button></>);} else {taskContent = (<>{task.text}<button onClick={() => setIsEditing(true)}>编辑</button></>)}return (<label><inputtype="checkbox"checked={task.done}onChange={e => {dispatch({ type: 'changed', task: { ...task, done: e.target.checked } });}}/>{taskContent}<button onClick={() => {dispatch({ type: 'deleted', id: task.id });}}>删除</button></label>);}

TasksContext.js

javascript">import { createContext, useContext, useReducer } from 'react';// 创建两个 context
const TasksContext = createContext(null);
const TasksDispatchContext = createContext(null);// 初始任务数据
const initialTasks = [{ id: 0, text: '哲学家之路', done: true },{ id: 1, text: '参观寺庙', done: false },{ id: 2, text: '喝抹茶', done: false }
];// 任务 reducer (用于更新任务状态)
function tasksReducer(tasks, action) {switch (action.type) {case 'added':return [...tasks,{id: action.id,text: action.text,done: false}];case 'changed':return tasks.map((t) => {if (t.id === action.task.id) {return action.task;} else {return t}});case 'deleted':return tasks.filter(t => t.id !== action.id);default:throw new Error('未知操作: ' + action.type);}
}// TasksProvider 组件,提供任务和 dispatch
export function TasksProvider({ children }) {const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);return (// 嵌套 Provider是非常常见的<TasksContext.Provider value={tasks}><TasksDispatchContext.Provider value={dispatch}>{children}</TasksDispatchContext.Provider></TasksContext.Provider>);
}// 自定义 Hook 用于获取任务数据
export function useTasks() {return useContext(TasksContext);
}// 自定义 Hook 用于获取 dispatch 函数
export function useTasksDispatch() {return useContext(TasksDispatchContext);
}

AddTask.js

javascript">import { useState } from 'react';
import { useTasksDispatch } from './TasksContext'; // 导入 dispatchexport default function AddTask({onAddTask}) {const [text, setText] = useState(''); // 管理输入框的文本状态const dispatch = useTasksDispatch(); // 获取 dispatch 函数const handleAddClick = () => {if (text.trim() === '') return; // 防止添加空任务setText(''); // 清空输入框dispatch({type: 'added', // 确保与 reducer 中的 action type 一致id: nextId++, // 使用 nextIdtext: text,});};return (<><inputplaceholder="添加任务"value={text}onChange={(e) => setText(e.target.value)} // 修正了 onChange 的语法错误/><button onClick={handleAddClick}>添加</button></>);
}let nextId = 3; // 初始 id,从 3 开始

请添加图片描述

javascript"> // 嵌套 Provider是非常常见的
<TasksContext.Provider value={tasks}><TasksDispatchContext.Provider value={dispatch}>{children}</TasksDispatchContext.Provider>
</TasksContext.Provider>

场景:
ToB:微应用

  1. 全局注入:从主应用下发的,mainAppContext,包含 主应用的currentSubApp,当前子应用的信息,主应用注入到全局上的数据
  2. 当前应用/用户信息:user,permissions,menu菜单,针对当前登录用户的鉴权,权限处理,这里也会创建 PermissionContext状态/RBACContext 权限管理的状态,不同用户的权限看到不同的内容,使用前面说的Context维护
  3. 当前运行时态:主题包,i18n语法,pc/mobile runtimeContext运行时态下的

createModel:就是provider,只不过是封装了一层
状态库 hox
飞冰
飞冰状态管理
Hox 根节点不需要创建多个provider,只需要一个就够了,可以看看源码
Redux store就是state,provider就是通过useReducer创建的provider

Escape 脱围机制

ref

ref:值更新了,组件没更新。不会更新重新渲染,一般是在与视图更新无关的时候使用
尽量避免使用与交互无关的
可以通过 useRef 去创建一些新的 ref

const ref = useRef(123)
使用:ref.current

ref 的使用:

javascript">import { useRef } from 'react';export default function App() {let ref = useRef(0);// 使用useState创建的话,需要使用setState来触发,会导致重新渲染re-renderfunction handleClick() {//这里是js原生的,这块更新不会触发组件的更新,值更新了,组件没更新// ref能够脱逃到当前的状态下的,也就是说,不会导致重新渲染re-renderref.current = ref.current + 1alert('你点击了 ' + ref.current + ' 次!');}return (<button onClick={handleClick}>点击我!</button>)
}

请添加图片描述
state的使用:

javascript">// state使用
import { useState } from "react";export default function App() {const [count, setCount] = useState(0);function handleClick() {setCount(count + 1);}return (<button onClick={handleClick}>你点击了 {count} 次!</button>)
}

请添加图片描述

ref 和 state的区别:

  1. ref 不会重新渲染,与视图无关的时候使用,如果与视图相关的使用ref,则不会达到想要的效果

    javascript">// ref作与视图相关的
    import { useRef } from "react";export default function App() {const count = useRef(0);//这里也可以使用useState,使用useState创建的话,需使用setState触发function handleClick() {count.current=count.current+1;}// 这里点击后触发,只是值更新了,组件没更新。setState会导致重新渲染re-render,但是ref不会return (<button onClick={handleClick}>你点击了 {count.current} 次!</button>)
    }  
    

    请添加图片描述

  2. ref一般用来存储大对象

  3. ref绑定到dom节点上

  4. ref使用在,timeoutId定时器绑定

拓展:
在react 19中,ref 已经作为props参数去传递了
例如,操作dom的时候

javascript">// ref 用作 props
import { useRef } from "react";
export default function App() {const inputRef = useRef(null)function handleClick() {inputRef.current.focus()}return (<><input ref={inputRef} /><button onClick={handleClick}>聚焦输入框</button></>)
}

请添加图片描述

scrollIntoView,打开某个页面后,翻到某个位置,重新刷新后,还能够定位到上次翻到的那个位置上,实现锚点效果
这里注意:img上绑定的ref必须在外层定义,这样才能使用到,如果在map等函数内部定义的ref,是不能使用的

javascript">
export default function App() {   const firstCatRef = useRef(null);  
const secondCatRef = useRef(null);   const thirdCatRef = useRef(null);function handleScrollToFirstCat() {firstCatRef.current.scrollIntoView({behavior: 'smooth',block: "nearest",inline: "center"});   }function handleScrollToSecondCat() {secondCatRef.current.scrollIntoView({behavior: 'smooth',block: "nearest",inline: 'center'});   }function handleScrollToThirdCat() {thirdCatRef.current.scrollIntoView({behavior: 'smooth',block: 'nearest',inline: "center"});   }return (<><nav><button onClick={handleScrollToFirstCat}>Neo</button><button onClick={handleScrollToSecondCat}>Millie</button><button onClick={handleScrollToThirdCat}>Bella</button></nav><div style={{ overflowX: 'auto', whiteSpace: 'nowrap' }}><ul style={{ display: 'inline-flex', listStyle: 'none', padding: 0, margin: 0 }}><li style={{ marginRight: '10px' }}><imgsrc="https://placecats.com/neo/300/200"alt="Neo"ref={firstCatRef}/></li><li style={{ marginRight: '10px' }}><imgsrc="https://placecats.com/millie/300/200"alt="Millie"ref={secondCatRef}/></li><li><imgsrc="https://placecats.com/bella/300/200"alt="Bella"ref={thirdCatRef}/></li></ul></div></>   ); } 

请添加图片描述

在react整个执行机制中会有两个阶段:

  1. render 内存中渲染判断我们需要展示哪些节点
  2. commit 缓存,缓存给dom,告诉dom这次需要更新哪些东西。
    因此,需要添加ref,必须保证是在commit阶段去添加的。ref必须关联在真实的节点上的,像这种,在物理空间中还没有存在真实的节点,ref是绑定不上的。
    错误示例:
javascript">export function App(){xxxxxconst list=[1,2,3]return list.map((item)=>{const ref=useRef(item);console.log(ref.current)return <div ref={ref}>{item}</div>})
}

避免react副作用
react 性能优化:
re-render 减少re-render 减少setState触发,escape useState
类似 useMemo,useCallback,保证不会导致depends变化,
在react中,没有必要的状态更新就不要加在状态更新中

React 19中,React Compiler 平常代码中 useMemo,memo,useCallback就不用写了,自己实现了
尽可能保证当前状态和其他状态解耦,并不是所有场景都需要使用effect

forwardRef(不建议使用)

React 19中已经被废弃掉了,
官网
类似于HOC的方式
能够通过ref进行传参
在这里插入图片描述
如果在不同目录下,ref真正关联组件的节点是不清楚的
使用forwardRef可以,但是目录层级不能超过两层

新的项目不建议直接使用 mobx 和 redux,使用 reducer 和 useContext 这两个足够了

CRA

国内一般使用的是 CRA 和 vite 这两种
没有人用,没有人维护

怎样去做一套自己的脚手架?

  1. commander
  2. inquirer

CLI 使用命令行的交互方式,获取到 config,拿到config交由给node脚本执行

pnpx/npx create-react-app my-app

pnpm create vite vite-react

在这里插入图片描述
awesome vite
里面有 react 的各种模板,这是社区的模板,就不是官方内的模板
类似使用react里的的这个:
在这里插入图片描述

后台管理模板
在这里插入图片描述

CRA现在用的少了,大部分使用的是 vite
vite源码
在这里插入图片描述


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

相关文章

跟着李沐老师学习深度学习(七)

权重衰退 丢弃法 权重衰退&#xff08;Weight Decay&#xff09; 回忆&#xff1a;如何控制一个模型的容量&#xff08;方法1&#xff1a;把模型变得比较小&#xff1b;方法2&#xff1a;使得模型参数的选择范围比较小&#xff09; 是一种正则化模型的技术&#xff1b; 使用…

【注意】sql语句where条件中的数据类型不一致,不仅存在性能问题,还会有数据准确性方面的bug......

隐式类型转换规则 MySQL 在进行比较操作时&#xff0c;如果比较双方的数据类型不一致&#xff0c;通常会尝试将其中一个数据类型转换为另一个数据类型&#xff0c;以便进行比较。 对于 select * from t_order where order_no 1538808276987285507 &#xff0c;当 order_no 为 …

自学人工智能大模型,满足7B模型的训练和微调以及推理,预算3万,如何选购电脑

如果你的预算是 3万元人民币&#xff0c;希望训练和微调 7B 参数规模的人工智能大模型&#xff08;如 LLaMA、Mistral 等&#xff09;&#xff0c;你需要一台高性能的深度学习工作站。在这个预算范围内&#xff0c;以下是推荐的配置&#xff1a; 1. 关键硬件配置 (1) GPU (显卡…

124 巨坑uni-app踩坑事件 uniCloud本地调试服务启动失败

1.事情是这样的 事情是这样的&#xff0c;我上午在运行项目的时候还是好好的&#xff0c;我什么都没干&#xff0c;没动代码&#xff0c;没更新&#xff0c;就啥也没干&#xff0c;代码我也还原成好好的之前的样子&#xff0c;就报这个错&#xff0c;但是我之前没用过这个服务呀…

C++STL容器之map的使用及复现

map 1. 关联式容器 vector、list、deque、forward_list(C11) 等STL容器&#xff0c;其底层为线性序列的数据结构&#xff0c;里面存储的是元素本身&#xff0c;这样的容器被统称为序列式容器。而 map、set 是一种关联式容器&#xff0c;关联式容器也是用来存储数据的&#xf…

ES6 Proxy 用法总结以及 Object.defineProperty用法区别

Proxy 是 ES6 引入的一种强大的拦截机制&#xff0c;用于定义对象的基本操作&#xff08;如读取、赋值、删除等&#xff09;的自定义行为。相较于 Object.defineProperty&#xff0c;Proxy 提供了更灵活、全面的拦截能力。 1. Proxy 语法 const proxy new Proxy(target, hand…

DeepSeek开源多模态大模型Janus-Pro部署

DeepSeek多模态大模型部署 请自行根据电脑配置选择合适环境配置安装conda以及gitJanus 项目以及依赖安装运行cpu运行gpu运行 进入ui界面 请自行根据电脑配置选择合适 本人家用电脑为1060&#xff0c;因此部署的7B模型。配置高的可以考虑更大参数的模型。 环境配置 安装conda…

【CubeMX-HAL库】STM32F407—无刷电机学习笔记

目录 简介&#xff1a; 学习资料&#xff1a; 跳转目录&#xff1a; 一、工程创建 二、板载LED 三、用户按键 四、蜂鸣器 1.完整IO控制代码 五、TFT彩屏驱动 六、ADC多通道 1.通道确认 2.CubeMX配置 ①开启对应的ADC通道 ②选择规则组通道 ③开启DMA ④开启ADC…