想了解关于|想了解关于 Redux 的这里都有

一、Redux 核心 官方是这样解释Redux的:JavaScript 状态容器,提供可预测化的状态管理。

const state = { modleOpen: "yes", btnClicked: "no", btnActiveClass: "active", page: 5, size: 10 }

  1. Redux 核心概念及工作流程
    想了解关于|想了解关于 Redux 的这里都有
    文章图片
  2. store: 存储状态的容器,JavaScript对象
  3. View: 视图,HTML页面
  4. Actions: 对象,描述对状态进行怎样的操作
  5. Reducers: 函数,操作状态并返回新的状态
  6. Redux 计数器案例 ../Redux/src/counter
    0

  7. Redux核心API
  8. const store = Redux.crateStore(reducer): 创建 Store 容器
  9. function reducer (state = initialState, action) {}: 创建用于处理状态的 reducer 函数
  10. store.getState(): 获取状态
  11. store.subscribe(function(){}): 订阅状态
  12. store.dispatch({type: 'discription...'}): 触发action
二、React + Redux 1.在 React 中不使用 Redux 时遇到的问题
在 React 中组件通信的数据流是单向的,顶层组件可以通过props属性向下层组件传递数据,而下层组件不能向上层组件传递数据,要实现下层组件修改数据,需要
上层组件传递修改数据方法到下层组件,当项目越来越大的时候,组件之间传递数据也就变得越来越困难。
想了解关于|想了解关于 Redux 的这里都有
文章图片

2.在 React 项目中引入 Redux 的好处
使用Redux管理数据,由于Store独立于组件,使得数据管理独立于组件,解决了组件与组件之间传递数据困难的问题。
想了解关于|想了解关于 Redux 的这里都有
文章图片

3.下载Redux
npm install redux react-redux
4.Redux 工作流程
  1. 组件通过 dispatch 方法触发 action
  2. Store 接收 Action 并将 Action 分发给 Reducer
  3. Reducer 根据 Action 类型对状态进行更改并将更改后的状态返回给Store
  4. 组件订阅了Store中的状态,Store中的状态更新会同步到组件
    想了解关于|想了解关于 Redux 的这里都有
    文章图片
5.代码案例
计数器案例 ../Redux/react-redux-guide/src/components/Counter.js
弹出框案例 ../Redux/react-redux-guide/src/components/Modal.js
6.拆分Reducer
../Redux/react-redux-guide/src/react-redux-guide/src/store/reducers/root.reducer.js
三、Redux 中间件 中间件本质上就是一个函数,Redux允许我们通过中间件的方式,扩展和增强Redux应用程序,增强体现在对action处理能力上,之前的计数器与弹出框案例中。actions都是直接被reducer函数处理的,再加入了中间件以后,在出发了一个action之后这个action会优先被中间件处理,当中间处理完这个action以后,中间件会把这个action传递给reducer,让reducer继续处理
想了解关于|想了解关于 Redux 的这里都有
文章图片

1.开发Redux中间件
开发中间件模版代码,本质上是一个函数,并且是一个柯里化的一个函数
export default store => next => action => {}

这个函数中要求我们返回一个函数,在这个返回的函数中要求我们再返回一个函数,在最里层的函数中我们可以执行我们自己的业务逻辑,在最外层的函数中给我们提供了一个参数叫store,可以用store.getState获取当前state状态,也可以使用store.dispatch来触发另外一个action,至于干什么具体根据使用的业务逻辑来决定。在最里层的函数也有一个形参,这个形参就是组件触发的action对象,可以根据action.type来决定是否对当前action进行处理。中间的函数也有一个参数,这个参数是一个函数,我们称之为next,在我们执行完了逻辑代码之后,我们要去调用next方法,把当前action 传递给reducer,或者说传递给下一个中间件,因为中间件可以有多个。中间件开发好之后需要引入我们写的中间件并且注册给redux。
2.注册中间件
中间件在开发完成以后只有被注册才能在Redux的工作流程中生效。
import { createStore, applyMiddleware } from 'redux' import logger from './middlewares/logger'createStore(reducer, applyMiddleware( logger ))

代码案例:src/react-redux-guide/src/store/middleware
/** * src/react-redux-guide/src/store/middleware/logger.js * 开发中间件 * 中间件模板代码 export default store => next => action => {} */ export default store => next => action => { console.log(action); console.log(store); next(action); // 只有调用了next方法才可以将action传递给下一个中间件或者reducer }

/** * src/react-redux-guide/src/index.js */ import { createStore, applyMiddleware } from 'redux'; import RootReducer from "./reducers/root.reducer"; import logger from "./middleware/logger"; import test from "./middleware/test"; // 注册中间件 applyMiddleware(logger); 中间件的执行顺序取决去注册的顺序export default createStore(RootReducer, applyMiddleware(logger, test));

  • 中间件通用性改造
    /** * src/react-redux-guide/src/store/middleware/thunk.js */ import actionTypes from "../actionTypes"; export default store => next => action => { // if(action.type === actionTypes.countIncrementType || action.type === actionTypes.countDecrementType) { //// 开启延迟操作 //setTimeout(() => { //next(action); //},2000) // }// 1.当这个中间件函数不关心你想执行什么异步操作,只关心你执行的是不是异步操作 // 2.如果你执行的是异步操作,再你触发action的时候,给中间件传递一个函数,如果执行的是同步操作就传递一个正常action对象 // 3.异步操作代码要写在传递进来的函数中 // 4.当前这个中间件的函数在点用你传递进来的函数时,要将dispatch方法传递进去if(typeof action === 'function') { return action(store.dispatch); } next(action); }

/** * src/react-redux-guide/src/store/middleware/thunk.js */ import actionTypes from './actionTypes'; const actions ={ // Counter countIncrementAction: () => ({type: actionTypes.countIncrementType}), countDecrementAction: () => ({type: actionTypes.countDecrementType}), // 异步的action countIncrementActionAsync: () => dispatch => { setTimeout(() => { dispatch(actions.countIncrementAction()); }, 2000); },// Modal boxShowAction: () => ({type: actionTypes.boxShowType}), boxHiddenAction: () => ({type: actionTypes.boxHiddenType}), boxShowActionAsync: () => dispatch => { setTimeout(() => { dispatch(actions.boxShowAction()); }, 2000) } } export default actions;

3.Redux 常用中间件
1.redux-thunk redux-thunk 允许我们在redux的工作流程中使用异步操作,与自定义thunk中间件用法相同(src/react-redux-guide/src/store/middleware/thunk.js)
安装:npm install redux-thunk
允许action返回一个函数,这个函数接受一个dispatch参数,在这个函数当中做异步操作。
2.redux-thunk
redux-sage 可以将异步操作从ActionCreator文件中抽离出来,放在一个单独的文件中
1.创建 redux-sage 中间件
import createSagaMiddleware from 'redux-saga'; const sagaMiddleware = createSagaMiddleware();

2.注册sagaMiddleware
createStore(reducer, appleMiddleware(sagaMiddleware));

3.使用 saga 接收 action 执行一步操作
/** * src/react-redux-guide/src/store/sagas/counter.saga.js */ import { takeEvery, put, delay} from 'redux-saga/effects'; import actionTypes from "../actionTypes"; import actions from "../actionCreators"; /** * takeEvery: 接收 action * put: 触发 action * delay: 延迟(这里不可以用setTimeout) */function* countIncrement() { yield delay(2000); yield put(actions.countIncrementAction()); }export default function* counterSaga () { yield takeEvery(actionTypes.COUNTINCREMENTSAGATYPE, countIncrement) }

4.启用saga
/** * src/react-redux-guide/src/store/index.js */ import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; // 官方中间件 import createSagaMiddleware from 'redux-saga'; import counterSaga from './sagas/counter.saga' import RootReducer from "./reducers/root.reducer"; // 自定义中间件 // import logger from "./middleware/logger"; // import test from "./middleware/test"; // import thunk from "./middleware/thunk"; // 注册中间件 applyMiddleware(logger); 中间件的执行顺序取决去注册的顺序// 创建 saga const sagaMiddleware = createSagaMiddleware(); // export default createStore(RootReducer, applyMiddleware(thunk)); export default createStore(RootReducer, applyMiddleware(sagaMiddleware)); // 启动 saga sagaMiddleware.run(counterSaga)

5.saga 中 action 传值
// 可以接收到 action 返回的对象 function* countIncrement(action) { yield delay(2000); // 获取 action 中的值并传递给下一个action yield put(actions.countIncrementAction(action.value)); }export default function* counterSaga () { yield takeEvery(actionTypes.COUNTINCREMENTSAGATYPE, countIncrement) }

6.saga拆分合并
/** * src/react-redux-guide/src/store/sagas/root.saga.js */ import { all } from 'redux-saga/effects'; import counterSaga from "./counter.saga"; import modalSaga from "./modal.saga"; // 合并Saga export default function* rootSaga() { yield all([ counterSaga(), modalSaga() ]) }

/** * src/react-redux-guide/src/store/index.js */ // 修改启动 saga sagaMiddleware.run(rootSaga);

4.redux-actions
redux流程中大量的样板代码编写是非常痛苦的,使用 redux-actions可以简化action和reducer的处理
  1. 下载
    npm install redux-actions
  2. 创建 Action
    /** * src/react-redux-guide/src/store/actions/counter.actions.js */ import { createAction } from 'redux-actions'; // 接收和触发action只需要使用 createAction() 的返回值就可以 export const incrementAction = createAction('increment'); export const decrementAction = createAction('decrement');

  3. 创建Reducer
    /** * src/react-redux-guide/src/store/reducers/counter.reducer.js */ import { handleActions as createReducer } from 'redux-actions'; import * as counterActions from '../actions/counter.actions'; /** * handleActions 返回值就是reducer函数 */const initialState = { count: 0, }const handleIncrement = (state, action) => ({count: state.count + 1}) const handleDecrement = (state, action) => ({count: state.count - 1})export default createReducer({ [counterActions.incrementAction]: handleIncrement, [counterActions.decrementAction]: handleDecrement, }, initialState);

    3.redux-actions传值
redux-action 传值并不需要再actions中去接收,默认会挂载到 action.payload 的属性中
/** * src/react-redux-guide/src/components/Counter.js */ import React from 'react'; import { connect } from 'react-redux'; import * as counterActions from '../store/actions/counter.actions'; const Counter = (props) => { const {count, handleCountPlus, handleCountMinus} = props; return ( {count} ) }const mapStateToProps = state => ({ count: state.counter.count })const mapDispatchToProps = dispatch => ({ handleCountPlus(value) { dispatch(counterActions.incrementAction(value)) // redux-actions}, handleCountMinus (value){ dispatch(counterActions.decrementAction(value)) // redux-actions } })export default connect(mapStateToProps, mapDispatchToProps)(Counter);

/** * src/react-redux-guide/src/store/actions/counter.actions.js */ import { createAction } from 'redux-actions'; // 这里不需要去接收传递的值 // export const incrementAction = createAction('increment', value); 错误 export const decrementAction = createAction('decrement');

/** * src/react-redux-guide/src/store/reducers/counter.reducer.js */ // 使用 redux-action 简化reducer const handleIncrement = (state, action) => ({count: state.count + action.payload}) const handleDecrement = (state, action) => ({count: state.count - action.payload})export default createReducer({ [counterActions.incrementAction]: handleIncrement, [counterActions.decrementAction]: handleDecrement, }, initialState);

5. 实战案例
  • src/shopping-demo:前端文件
    • 启动 npm start
    • 端口 localhost:3000
  • src/shoppingCartService: 服务端文件
    • 启动 npm start
    • 端口 localhost:3005
四、 Redux 源码实现 五、Redux-toolkit 1.概述
redux toolkit 对 redux 进行的二次封装,用于高效的 Redux 开发,使 Redux 的使用变得简单。
2.快速入门
  1. 创建状态切片
对于状态切片,我们可以认为他就是原本的Redux中的那一个个小的reducer函数。
在Redux中,原本的 Reducer 函数和 Action 对象需要分别创建,现在通过状态切片来替代,它会返回 reducer 函数和 Action 对象。
import {createSlice} from "@reduxjs/toolkit"; const TODOS_FEATURE_KEY = 'todos'; const {reducer: todoReducer, actions } = createSlice({ name: TODOS_FEATURE_KEY, // 切片名称 initialState: [], // 切片状态 reducers: { // 创建 actionCreator 和状态处理函数 // addTodo 是 action 的名称 addTodo: (state, action) => { // 在 redux-toolkit 中使用createSlice 可以直接修改 state 的值,而并不需要返回一个新的状态 state.push(action.payload); } } }); export const {addTodo} = actions; export default todoReducer;

2.创建 store
import {configureStore} from "@reduxjs/toolkit"; import TodosReducer, {TODOS_FEATURE_KEY} from "./todos.slice"; export default configureStore({ reducer: { [TODOS_FEATURE_KEY]: TodosReducer }, devTools: process.env.NODE_ENV !== 'production' });

3.配置Provider
import React from 'react'; import ReactDOM from 'react-dom/client'; import {Provider} from "react-redux"; import App from './App'; import store from "./Store"; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( );

4.组件中触发Action
import {useDispatch, useSelector} from "react-redux"; import {TODOS_FEATURE_KEY, addTodo} from "../../Store/todos.slice"; const Todo = () => { // 获取dispatch方法 const dispatch = useDispatch(); // 获取最新的状态 const todos = useSelector(state => state[TODOS_FEATURE_KEY]); return (
    { todos && todos.map(todo => (
  • {todo.title}
  • )) }
) }export default Todo;

  1. action 预处理
当 Action 被触发后,可以通过prepare方法进行预处理,处理完成后交给Reduce.prepare方法必须返回对象
import {createSlice} from "@reduxjs/toolkit"; export const TODOS_FEATURE_KEY = 'todos'; // 使用 reducer.prepare 进行 action 预处理 const {reducer: TodosReducer, actions } = createSlice({ name: TODOS_FEATURE_KEY, // 切片名称 initialState: [], // 切片状态 reducers: { // 创建 actionCreator addTodo: { // 状态处理函数 reducer: (state, action) => { console.log('reducer', action); state.push(action.payload) }, // action预处理,必须返回一个对象 {payload: {...todo, title: 'haha'}}, 覆盖原有的payload prepare: todo => { console.log('prepare', todo) // 修改 action 传过来的数据 return { payload: {id: Math.random(), ...todo} } } } } }); export const {addTodo} = actions; export default TodosReducer;

  1. 执行异步的操作方式
【想了解关于|想了解关于 Redux 的这里都有】第一种方式
import {createSlice, createAsyncThunk} from "@reduxjs/toolkit"; import axios from 'axios'; export const TODOS_FEATURE_KEY = 'todos'; /** * 异步操作:第一种 * createAsyncThunk('action-type', (payload, thunkAPI) => {异步操作}) */// 定义请求方法 const loadTodos = createAsyncThunk('async-load-todos',(payload, thunkAPI) => { axios.get(payload) .then(res => { // 触发保存数据action setTodos thunkAPI.dispatch(setTodos(res.data)); }) .catch(err => new Error(err)) }); // 使用 reducer.prepare 进行 action 预处理 const {reducer: TodosReducer, actions } = createSlice({ name: TODOS_FEATURE_KEY, // 切片名称 initialState: [], // 切片状态 reducers: { // 创建 actionCreator addTodo: { // 状态处理函数 reducer: (state, action) => { console.log('reducer', action); state.push(action.payload) }, // action预处理,必须返回一个对象 {payload: {...todo, title: 'haha'}}, 覆盖原有的payload prepare: todo => { console.log('prepare', todo) // 修改 action 传过来的数据 return { payload: {id: Math.random(), ...todo} } } }, setTodos: (state, action) => action.payload } }); // jsx export const {addTodo, setTodos} = actions; export {loadTodos}; export default TodosReducer; import {TODOS_FEATURE_KEY, addTodo, loadTodos} from "../../Store/todos.slice"; import {useDispatch, useSelector} from "react-redux"; import {useEffect} from "react"; const Todo = () => { // // 获取dispatch方法 const dispatch = useDispatch(); // // 获取最新的状态 const todos = useSelector(state => state[TODOS_FEATURE_KEY]); // // 组件挂载完成之后请求 useEffect(() => { dispatch(loadTodos('http://localhost:3001/todos')); }, []) return (
    { todos && todos.map(todo => (
  • {todo.title}
  • )) }
) }export default Todo;

第二种方式
import {createSlice, createAsyncThunk} from "@reduxjs/toolkit"; import axios from 'axios'; export const TODOS_FEATURE_KEY = 'todos'; /** * 异步操作:第二种 * createAsyncThunk('action-type', payload => {异步操作}) * 不需要再去编写保存数据的action, 可以直接在extraReducers中处理异步的action */const loadTodos = createAsyncThunk('async-load-todos', payload => { return axios.get(payload) .then(res => res.data) .catch(err => new Error(err)); })// 使用 reducer.prepare 进行 action 预处理 const {reducer: TodosReducer, actions } = createSlice({ name: TODOS_FEATURE_KEY, // 切片名称 initialState: [], // 切片状态 reducers: { // 创建 actionCreator addTodo: { // 状态处理函数 reducer: (state, action) => { console.log('reducer', action); state.push(action.payload) }, // action预处理,必须返回一个对象 {payload: {...todo, title: 'haha'}}, 覆盖原有的payload prepare: todo => { console.log('prepare', todo) // 修改 action 传过来的数据 return { payload: {id: Math.random(), ...todo} } } }, setTodos: (state, action) => action.payload }, // 第二种异步请求编写方式:extraReducers 处理异步请求 extraReducers: { // 处理 loadTodos 返回的 Promise 对象的成功状态 [loadTodos.fulfilled]: (state, action) => { console.log('fulfilled'); return action.payload; }, [loadTodos.pending]: (state, action) => { console.log('pending'); return state; }, } }); export const {addTodo, setTodos} = actions; export {loadTodos}; export default TodosReducer; // jsx import {TODOS_FEATURE_KEY, addTodo, loadTodos} from "../../Store/todos.slice"; import {useDispatch, useSelector} from "react-redux"; import {useEffect} from "react"; const Todo = () => { return (
    { todos && todos.map(todo => (
  • {todo.title}
  • )) }
) }export default Todo;

  1. 配置中间件
    import {configureStore, getDefaultMiddleware} from "@reduxjs/toolkit"; import TodosReducer, {TODOS_FEATURE_KEY} from "./todos.slice"; import logger from 'redux-logger'; export default configureStore({ reducer: { [TODOS_FEATURE_KEY]: TodosReducer }, /** * middleware: [] 默认隐式的会挂载 @reduxjs/toolkit 内置的中间件 * 如果显示编写出来并添加自定义或者其他中间件时,需要通过getDefaultMiddleware()方法获取内置的中间件并挂载上去,不然内置的中间件就会被覆盖 */ middleware: [...getDefaultMiddleware(), logger], devTools: process.env.NODE_ENV !== 'production' });

  2. 实体适配器
    将状态放入实体适配器,实体适配器提供操作状态的各种方法,简化操作。
    • 创建实体适配器
    import {createEntityAdapter} from "@reduxjs/toolkit"; import axios from 'axios'; export const TODOS_FEATURE_KEY = 'todos'; const loadTodos = createAsyncThunk('async-load-todos',(payload, thunkAPI) => { axios.get(payload) .then(res => { // 触发保存数据action setTodos thunkAPI.dispatch(setTodos(res.data)); }) .catch(err => new Error(err)) }); /** * 创建实体适配器 * todosAdapter.getInitialState(); // entities: {} 存储状态 ids: [] 存储每一条状态的 id */ const todosAdapter = createEntityAdapter(); const {reducer: TodosReducer, actions } = createSlice({ name: TODOS_FEATURE_KEY, // 切片名称 // 将状态放入适配器当中 initialState: todosAdapter.getInitialState(), reducers: { // 创建 actionCreator addTodo: { // 状态处理函数 reducer: (state, action) => { // 使用状态适配器向状态中添加数据 todosAdapter.setOne(state, action.payload); // 添加一条 }, // action预处理,必须返回一个对象 {payload: {...todo, title: 'haha'}}, 覆盖原有的payload prepare: todo => ({payload: {id: Math.random(), ...todo}}) }, setTodos: (state, action) => todosAdapter.setMany(state, action.payload), }, }); export const {addTodo, setTodos} = actions; export {loadTodos}; export default TodosReducer;

  • 简化实体适配器
    const {reducer: TodosReducer, actions } = createSlice({ name: TODOS_FEATURE_KEY, // 切片名称 // 将状态放入适配器当中 initialState: todosAdapter.getInitialState(), reducers: { // 创建 actionCreator addTodo: { /** * 简化实体适配器函数,自动检测第二个是否是action参数,如果是action的话就会去找action.payload并将其放到state中 */ reducer: todosAdapter.setOne,// action预处理,必须返回一个对象 {payload: {...todo, title: 'haha'}}, 覆盖原有的payload prepare: todo => { console.log('prepare', todo) // 修改 action 传过来的数据 return { payload: {id: Math.random(), ...todo} } } }, setTodos: todosAdapter.setMany } });

  • 更改实体适配器的唯一表示
实体适配器要求每一个实体必须拥有 id 属性作为唯一标识,如果实体中的唯一标识字段不叫作 id, 需要使用 selectId 进行声明。
const todosAdapter = createEntityAdapter({ selectId: todo => todo.cid})

  • 状态选择器
    提供了从实体状态器中获取状态的快捷途径。
    import {createSelector} from '@reduxjs/toolkit'; /** * 创建状态选择器 * createSelector(相应的state, 具体的状态选择器(由实体适配器返回) */ const {selectAll} = todosAdapter.getSelectors(); export const selectorTodos = createSelector(state => state[TODOS_FEATURE_KEY], selectAll); // jsx const dispatch = useDispatch(); // 使用状态选择器获取状态 const todos = useSelector(selectorTodos); useEffect(() => { dispatch(loadTodos('http://localhost:3001/todos')); }, [])

    推荐阅读