React的学习系列之Redux
什么是 Redux ?
Redux 是 JavaScript 应用的状态容器,提供可预测的状态管理!Redux 除了和 React 一起用外,还支持其它框架;它体小精悍(只有2kB,包括依赖),却有很强大的插件扩展生态!Redux 提供的模式和工具使您更容易理解应用程序中的状态何时、何地、为什么以及如何更新
,以及当这些更改发生时,您的应用程序逻辑将如何表现!
什么时候应该用 Redux ?
- 在应用的大量地方,都存在大量的状态
- 应用状态会随着时间的推移而频繁更新
- 更新该状态的逻辑可能很复杂
- 中型和大型代码量的应用,很多人协同开发
Redux 库和工具
Redux 是一个小型的独立 JS 库, 但是它通常与其他几个包一起使用:React-Redux是我们的官方库,它让 React 组件与 Redux 有了交互,可以从 store 读取一些 state,可以通过 dispatch actions 来更新 store!Redux ToolkitRedux Toolkit 是我们推荐的编写 Redux 逻辑的方法。 它包含我们认为对于构建 Redux 应用程序必不可少的包和函数。 Redux Toolkit 构建在我们建议的最佳实践中,简化了大多数 Redux 任务,防止了常见错误,并使编写 Redux 应用程序变得更加容易。Redux DevTools 拓展Redux DevTools Extension 可以显示 Redux 存储中状态随时间变化的历史记录,这允许您有效地调试应用程序。
Redux基础工作流
Redux 的原理
当我们知道了Redux
的底层原理后,接下来我们来实现一个mini
版本的Redux
mini-Redux
的实现和应用
-
创建一个
React
的项目==(基于vite构构建)==yarn vite 目录 src ├─ assets │ └─ react.svg ├─ store │ ├─ index.js ├─ App.css ├─ App.jsx ├─ index.css └─ main.jsx
-
reduct
中的createStore
方法的解析方法中有一个state存储公共的状态,还有一个
listeners
事件池「集合」,已经三个方法和一个形参store/index.
基础的结构
export const createStore = reducer => { /* 存储公共状态 */ let state; /* 存放事件池 */ let listeners = []; /* 获取公共状态 */ const getState = () => state; /* 像事件池添加组件更新的方法 */ const subscribe = listener => { }; /* 派发任务通知 reducer 执行 */ const dispatch = action => { }; return { getState, subscribe, dispatch }; };
首次执行
createStore
函数就会执行dispatch
方法export const createStore = reducer => { /* 规则校验 */ if (typeof reducer !== "function") throw new TypeError("reducer is not function"); ... /* 派发任务通知 reducer 执行 */ const dispatch = action => { }; /* 唯一值 */ const randomString = () => Math.random().toString(32).substring(7).split("").join("."); /* 默认执行一次 dispatch派发,给公共状态赋值(初始值) */ dispatch({ type: `@@redux/INIT${randomString}`, }); return { getState, subscribe, dispatch }; };
subscribe
方法的执行export const createStore = reducer => { ... /* 向事件池添加组件更新的方法 */ const subscribe = listener => { // 规则校验 if (typeof listener !== "function") throw new TypeError("listener is not Function"); // 添加组件更新的方法 if (!listeners.includes(listener)) { listeners.push(listener); } // 移除组件更新的方法 const unsubscribe = () => { let index = listeners.indexOf(listener); listeners.splice(index, 1); }; return unsubscribe; }; return { getState, subscribe, dispatch }; };
当事件行为执行时执行
dispatch
方法,更新公共状态并通知事件池中对应的方法执行完成后,移除方法const isPlainObject = obj => { let proto, Ctor; if (!obj || toString.call(obj) !== "[object Object]") return false; proto = Object.getPrototypeOf(obj); if (!proto) return true; Ctor = {}.hasOwnProperty.call(proto, "constructor") && proto.constructor; return typeof Ctor === "function" && Ctor === Object; }; export const createStore = reducer => { ... /* 派发任务通知 reducer 执行 */ const dispatch = action => { // 规则校验 if (!isPlainObject(action)) throw new TypeError("action is not object"); if (typeof action.type === undefined) throw new TypeError("Actions may not have an undefined 'type' property"); // 执行reducer,传递:公共状态、行为对象,接收执行的返回值,替换公共状态 state = reducer(state, action); // 当状态更更改,还需要把事件池中的方法执行 listeners.forEach(listener => listener()); return action; }; return { getState, subscribe, dispatch }; };
到此就完成了一个简单的
redux
,完整的代码如下:const isPlainObject = obj => { let proto, Ctor; if (!obj || toString.call(obj) !== "[object Object]") return false; proto = Object.getPrototypeOf(obj); if (!proto) return true; Ctor = {}.hasOwnProperty.call(proto, "constructor") && proto.constructor; return typeof Ctor === "function" && Ctor === Object; }; export const createStore = reducer => { /* 规则校验 */ if (typeof reducer !== "function") throw new TypeError("reducer is not function"); /* 存储公共状态 */ let state; /* 存放事件池 */ let listeners = []; /* 获取公共状态 */ const getState = () => state; /* 像事件池添加组件更新的方法 */ const subscribe = listener => { // 规则校验 if (typeof listener !== "function") throw new TypeError("listener is not Function"); // 添加组件更新的方法 if (!listeners.includes(listener)) { listeners.push(listener); } // 移除组件更新的方法 const unsubscribe = () => { let index = listeners.indexOf(listener); listeners.splice(index, 1); }; return unsubscribe; }; /* 派发任务通知 reducer 执行 */ const dispatch = action => { // 规则校验 if (!isPlainObject(action)) throw new TypeError("action is not object"); if (typeof action.type === undefined) throw new TypeError("Actions may not have an undefined 'type' property"); // 执行reducer,传递:公共状态、行为对象,接收执行的返回值,替换公共状态 state = reducer(state, action); // 当状态更更改,还需要把事件池中的方法执行 listeners.forEach(listener => listener()); return action; }; /* 唯一值 */ const randomString = () => Math.random().toString(32).substring(7).split("").join("."); /* 默认执行一次 dispatch派发,给公共状态赋值(初始值) */ dispatch({ type: `@@redux/INIT${randomString}`, }); return { getState, subscribe, dispatch }; };
-
mini
版本的Redux
完成后我们在项目中应用-
首先我们需要把公共状态放到全局上下文中,所有我们需要来创建一个上下文
目录
src ├─ assets │ └─ react.svg ├─ store │ ├─ index.js ├─ App.css ├─ App.jsx ├─ + ThemeContext.js ├─ index.css └─ main.jsx
ThemeContext.js
import { createContext } from 'react'; const ThemeContext = createContext(); export default ThemeContext;
main.jsx
import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import "./index.css"; import ThemeContext from "./ThemeContext"; import store from "./store"; ReactDOM.createRoot(document.getElementById("root")).render( <React.StrictMode> <ThemeContext.Provider value={{ store, }} > <App /> </ThemeContext.Provider> </React.StrictMode> )
-
创建三个组件来实现业务需求
目录结构
src ├─ assets │ └─ react.svg ├─ store │ ├─ index.js ├─ + views │ + ├─ Vote.jsx │ + ├─ VoteFooter.jsx │ + └─ VoteMain.jsx ├─ App.css ├─ App.jsx ├─ ThemeContext.js ├─ index.css └─ main.jsx
App.jsx
import './App.css' import Vote from './views/Vote' function App() { return ( <div className="App"> <Vote /> </div> ) } export default App
Vote.jsx
import VoteMain from "./VoteMain"; import VoteFooter from "./VoteFooter"; import ThemeContext from "../ThemeContext"; import { useContext, useState, useEffect } from "react"; const Vote = () => { const { store } = useContext(ThemeContext); const { supNum, oppNum } = store.getState(); let [_, setRandom] = useState(0); useEffect(() => { const unsubscribe = store.subscribe(() => { setRandom(Math.random()); }); }, []); return ( <> <header className="header"> <h2 className="title">这个是我手写的一个 Mini Redux 状态管理</h2> <span className="num">{supNum + oppNum}人</span> </header> <VoteMain /> <VoteFooter /> </> ); }; export default Vote;
VoteMain.jsx
import { useContext } from "react"; import ThemeContext from "../ThemeContext"; const VoteFooter = () => { const { store } = useContext(ThemeContext); return ( <div className="footer"> <button onClick={() => { store.dispatch({ type: "VOTE_SUP", }); }} > 支持 </button> <button onClick={() => { store.dispatch({ type: "VOTE_OPP", }); }} > 反对 </button> </div> ); }; export default VoteFooter;
VoteFooter.jsx
import { useContext, useEffect, useState } from "react"; import ThemeContext from "../ThemeContext"; const VoteMain = () => { const { store } = useContext(ThemeContext); const { supNum, oppNum } = store.getState(); const [_, setRandom] = useState(0) useEffect(() => { const unsubscribe = store.subscribe(() => { setRandom(Math.random()); }) }, []) return ( <div className="main"> <p>支持人数:{supNum}人</p> <p>反对人数:{oppNum}人</p> </div> ); }; export default VoteMain;
-
最后执行命令启动项
yarn dev
到此我们就已经完成了一个
mini-redux
的实现和应用
-