一、Redux 外围

官网是这样解释Redux的:JavaScript 状态容器,提供可预测化的状态治理。

const state = {    modleOpen: "yes",    btnClicked: "no",    btnActiveClass: "active",    page: 5,    size: 10}
  1. Redux 外围概念及工作流程
  2. store: 存储状态的容器,JavaScript对象
  3. View: 视图,HTML页面
  4. Actions: 对象,形容对状态进行怎么的操作
  5. Reducers: 函数,操作状态并返回新的状态
  6. Redux 计数器案例 ../Redux/src/counter

    <body> <button id="plus">+</button> <span id="count">0</span> <button id="minus">-</button><script src="https://cdn.bootcdn.net/ajax/libs/redux/4.2.0/redux.min.js"></script><script> // 3 存储默认状态 let initialState = {     count: 0 } // 2 创立 reducer 函数 function reducer (state = initialState, action) {     // 7 接管 action 并判断 action 的类型     switch (action.type) {         case 'increment':             return {count : state.count + 1}         case 'decrement':             return {count : state.count - 1}         default:             // 初始化是会主动发送一个 init 的 action 用来存储默认的state             return state;     } } // 1 创立 store 对象, createStore 有第二个参数代表默认值,也就是 reducer 中的 state 参数 let store = Redux.createStore(reducer); // 4 定义 action let increment = { type: 'increment' } let decrement = { type: 'decrement' } // 5 获取按钮并增加事件 document.getElementById('plus').onclick = function() {     // 6 触发action     store.dispatch(increment); } document.getElementById('minus').onclick = function () {     store.dispatch(decrement); } // 8 订阅 store,当store发生变化的时候会执行回调 store.subscribe(() => {     // 获取 store 中存储的状态     console.log(store.getState())     document.getElementById('count').innerText = store.getState().count; });</script></body>
  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属性向上层组件传递数据,而上层组件不能向下层组件传递数据,要实现上层组件批改数据,须要
下层组件传递批改数据办法到上层组件,当我的项目越来越大的时候,组件之间传递数据也就变得越来越艰难。

2.在 React 我的项目中引入 Redux 的益处

应用Redux治理数据,因为Store独立于组件,使得数据管理独立于组件,解决了组件与组件之间传递数据艰难的问题。

3.下载Redux

npm install redux react-redux

4.Redux 工作流程

  1. 组件通过 dispatch 办法触发 action
  2. Store 接管 Action 并将 Action 分发给 Reducer
  3. Reducer 依据 Action 类型对状态进行更改并将更改后的状态返回给Store
  4. 组件订阅了Store中的状态,Store中的状态更新会同步到组件

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持续解决

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); 中间件的执行程序取决去注册的程序// 创立 sagaconst sagaMiddleware = createSagaMiddleware();// export default createStore(RootReducer, applyMiddleware(thunk));export default createStore(RootReducer, applyMiddleware(sagaMiddleware));// 启动 sagasagaMiddleware.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";// 合并Sagaexport default function* rootSaga() {  yield all([    counterSaga(),    modalSaga()  ])}
/** * src/react-redux-guide/src/store/index.js */// 批改启动 sagasagaMiddleware.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 (    <section>      <button onClick={() => handleCountPlus(10)}>Plus</button>      <span>{count}</span>      <button onClick={() => handleCountMinus(10)}>Minus</button>    </section>  )}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 简化reducerconst 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(  <React.StrictMode>    <Provider store={store}>      <App/>    </Provider>  </React.StrictMode>);

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 (          <div>            <button onClick={() => dispatch(addTodo({title: '测试工作'}))}>增加工作</button>            <ul>              {                      todos && todos.map(todo => (                              <li key={todo.title}>{todo.title}</li>                      ))              }            </ul>          </div>  )}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. 执行异步的操作形式

第一种形式

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  }});// jsxexport 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 (          <div>            <button onClick={() => dispatch(addTodo({title: '测试工作'}))}>增加工作</button>            <ul>              {                      todos && todos.map(todo => (                              <li key={todo.title}>{todo.title}</li>                      ))              }            </ul>          </div>  )}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;// jsximport {TODOS_FEATURE_KEY, addTodo, loadTodos} from "../../Store/todos.slice";import {useDispatch, useSelector} from "react-redux";import {useEffect} from "react";const Todo = () => {  return (    <div>      <button onClick={() => dispatch(addTodo({title: '测试工作'}))}>增加工作</button>      <ul>        {          todos && todos.map(todo => (            <li key={todo.title}>{todo.title}</li>          ))        }      </ul>    </div>  )}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);// jsxconst dispatch = useDispatch();// 应用状态选择器获取状态const todos = useSelector(selectorTodos);useEffect(() => {dispatch(loadTodos('http://localhost:3001/todos'));}, [])