# e-redux **Repository Path**: ymcdhr/e-redux ## Basic Information - **Project Name**: e-redux - **Description**: redux基本示例、redux-saga异步请求示例、自定义中间件、代码组织结构示例等 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-09-14 - **Last Updated**: 2021-11-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Redux ### redux 工作流程 view => dispatch => action => reducer => store ![输入图片说明](https://images.gitee.com/uploads/images/2021/0829/131825_7a630694_9130428.png "屏幕截图.png") ### redux 模块 => [redux store 的核心 api](https://www.redux.org.cn/docs/basics/Store.html) 1. createStore => 创建store 2. getState => 获取store 3. subscribe => 监听store 4. dispatch => 触发reducer ![输入图片说明](https://images.gitee.com/uploads/images/2021/0729/140342_3042ba1d_9130428.png "屏幕截图.png") 5. 核心 API 实现逻辑:createStore() ```js // redux 原理,简版redux // 参数1:reducer // 参数2:initalState // 参数3:注册中间件的函数 function createStore (reducer, preloadedState, enhancer) { // reducer 类型判断 if (typeof reducer !== 'function') throw new Error('redcuer必须是函数'); if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('enhancer必须是函数') } return enhancer(createStore)(reducer, preloadedState); } // 状态:通过 getState() 方法形成闭包,将局部变量留在内存中使用 var currentState = preloadedState; // 订阅者 var currentListeners = []; // 获取状态 function getState () { return currentState; } // 用于触发action的方法 function dispatch (action) { // 判断action是否是一个对象 if (!isPlainObject(action)) throw new Error('action必须是一个对象'); // 判断action中的type属性是否存在 if (typeof action.type === 'undefined') throw new Error('action对象中必须有type属性'); // 调用reducer函数 处理状态 currentState = reducer(currentState, action); // 调用订阅者 通知订阅者状态发生了改变 for (var i = 0; i < currentListeners.length; i++) { var listener = currentListeners[i]; listener(); } } // 订阅状态的改变 function subscribe (listener) { currentListeners.push(listener); } // 默认调用一次dispatch方法 存储初始状态(通过reducer函数传递的默认状态) dispatch({type: 'initAction'}) return { getState, dispatch, subscribe } } // 判断参数是否是对象类型 // 判断对象的当前原型对象是否和顶层原型对象相同 function isPlainObject (obj) { if (typeof obj !== 'object' || obj === null) return false; var proto = obj; while (Object.getPrototypeOf(proto) != null) { proto = Object.getPrototypeOf(proto) } return Object.getPrototypeOf(obj) === proto; } function applyMiddleware (...middlewares) { return function (createStore) { return function (reducer, preloadedState) { // 创建 store var store = createStore(reducer, preloadedState); // 阉割版的 store var middlewareAPI = { getState: store.getState, dispatch: store.dispatch } // 调用中间件的第一层函数 传递阉割版的store对象 var chain = middlewares.map(middleware => middleware(middlewareAPI)); var dispatch = compose(...chain)(store.dispatch); return { ...store, dispatch } } } } ``` ### react-redux 模块 => 结合 react 和 redux **react-redux 模块** 让组件中能够拿到 redux 的 store,提供 provider、connect 等方法 1. provider 将 react 入口组件包裹起来并注入 store; 2. connect 将 react 组件和 store 进行关联; - // 1. connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件 - // 2. connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件 - // 3. connect 方法可以让我们获取 dispatch 方法 **redux 的基本使用** 包括以下: - **创建 store:** 在入口文件,定义默认初始化 initialStore 数据;并且将 store 绑定到 provider 上; - **定义 action:** 对应的业务 action,定义使用哪个 reducer 更新数据; - **定义 reducer:** 对应业务的 reducer,根据action,更新数据到 store; - **绑定 store 到组件,** 在组件中触发 dispatch:组件中需要使用 connect 关联 store、组件和 actions,这里面做一些 map 映射方便调用 dispatch 使用 action。 ### Redux 基本使用示例,[参考源码](https://gitee.com/ymcdhr/e-code/tree/master/04-02/4-2-1-redux/code/react-redux-guide) #### 1. 定义stroe ```js import { createStore, applyMiddleware } from "redux"; import RootReducer from "./reducers/root.reducer"; // import thunk from 'redux-thunk'; // import logger from "./middleware/logger"; // import test from "./middleware/test"; // import thunk from './middleware/thunk'; import createSagaMidddleware from 'redux-saga'; import rootSaga from './sagas/root.saga'; // 创建 sagaMiddleware const sagaMiddleware = createSagaMidddleware(); // export const store = createStore(RootReducer, applyMiddleware(thunk)); export const store = createStore(RootReducer, applyMiddleware(sagaMiddleware)); // 启动 counterSaga sagaMiddleware.run(rootSaga) ``` #### 2. 定义actions ```js import { INCREMENT, DECREMENT, INCREMENT_ASYNC } from "../const/counter.const"; export const increment = payload => ({type: INCREMENT, payload}); export const decrement = payload => ({type: DECREMENT, payload}); export const increment_async = payload => ({type: INCREMENT_ASYNC, payload}); ``` #### 3. 定义reducer、拆分reducer ```js import { combineReducers } from 'redux'; import CounterReducer from './counter.reducer'; import ModalReducer from './modal.reducer'; // { counter: { count: 0 }, model: { show: false } } export default combineReducers({ counter: CounterReducer, modal: ModalReducer }) ``` ```js import { INCREMENT, DECREMENT } from "../const/counter.const"; const initialState = { count: 0 } export default (state = initialState, action) => { switch(action.type) { case INCREMENT: return { ...state, count: state.count + action.payload } case DECREMENT: return { ...state, count: state.count - action.payload } default: return state; } } ``` #### 4. 主入口使用provider ```js import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import { Provider } from 'react-redux'; import { store } from './store'; ReactDOM.render( // 通过provider组件 将 store 放在了全局的组件可以够的到的地方 , document.getElementById('root') ); ``` #### 5. 在组件中使用数据 ```js import React from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import * as couterActions from '../store/actions/counter.actions'; function Counter ({count, increment, decrement, increment_async}) { return
{count}
} // 1. connect 方法会帮助我们订阅store 当store中的状态发生更改的时候 会帮助我们重新渲染组件 // 2. connect 方法可以让我们获取store中的状态 将状态通过组件的props属性映射给组件 // 3. connect 方法可以让我们获取 dispatch 方法 const mapStateToProps = state => ({ count: state.counter.count }); const mapDispatchToProps = dispatch => bindActionCreators(couterActions, dispatch) // ==> 等同于 // const mapDispatchToProps = dispatch => ({ // increment_async () { // dispatch(couterActions.increment_async) // }, // decrement () { // dispatch(couterActions.decrement) // } // }) export default connect(mapStateToProps, mapDispatchToProps)(Counter); ``` #### 6. subscribe 订阅事件的使用 ![输入图片说明](https://images.gitee.com/uploads/images/2021/0924/104334_83b15439_9130428.png "屏幕截图.png") ```js function select(state) { return state.some.deep.property } let currentValue function handleChange() { let previousValue = currentValue currentValue = select(store.getState()) if (previousValue !== currentValue) { console.log('Some deep nested property changed from', previousValue, 'to', currentValue) } } let unsubscribe = store.subscribe(handleChange) unsubscribe() ``` ### Redux 中间件 #### 1. 什么是中间件? ![输入图片说明](https://images.gitee.com/uploads/images/2021/0729/201639_c6462ddf_9130428.png "屏幕截图.png") #### 2. 如何开发中间件? - 在store中引入自定义中间件:index.js ```js import { createStore, applyMiddleware } from "redux"; import thunk from './middleware/thunk'; export const store = createStore(RootReducer, applyMiddleware(thunk)); ``` - 在中间件中对逻辑进行处理,然后执行action:middleware/thunk.js ```js export default (store) => next => action => { const { dispatch } = store // 1. 当前这个中间件函数不关心你想执行什么样的异步操作 只关心你执行的是不是异步操作 // 2. 如果你执行的是异步操作 你在触发action的时候 给我传递一个函数 如果执行的是同步操作 就传递action对象 // 3. 异步操作代码要写在你传递进来的函数中 // 4. 当前这个中间件函数在调用你传递进来的函数时 要将dispatch方法传递过去 if (typeof action === 'function') { return action(dispatch) } // next 是一个函数,调用执行 action next(action) } // action 中定义异步操作 increment_async:返回一个 function,而不是对象 // export const increment_async = payload => dispath => { // setTimeout(()=>{ // dispath({type: INCREMENT_ASYNC, payload}) // }, 2000) // }; ``` #### 3. 常用的中间件有哪些? - redux-thunk => 在redux工作流中加入异步代码,但是异步代码需要卸载actions里面; ```js // 1. 在store里面引入thunk:index.js import { createStore, applyMiddleware } from "redux"; import thunk from 'redux-thunk'; export const store = createStore(RootReducer, applyMiddleware(thunk)); // 2. 在actions里面返回一个function:counter.actions.js export const increment_async = payload => dispath => { setTimeout(()=>{ dispath(increment(payload)) }, 2000) }; ``` - redux-sega => 在redux工作流中加入异步代码,允许将异步代码抽离到单独的文件中; ```js // 1. 在store里面注册sega:index.js import { createStore, applyMiddleware } from "redux"; import RootReducer from "./reducers/root.reducer"; import createSagaMidddleware from 'redux-saga'; import rootSaga from './sagas/root.saga'; // 创建 sagaMiddleware const sagaMiddleware = createSagaMidddleware(); export const store = createStore(RootReducer, applyMiddleware(sagaMiddleware)); // 启动 counterSaga sagaMiddleware.run(rootSaga) // 2. 创建一个单独的文件来写异步代码:counter.saga.js import { takeEvery, put, delay } from 'redux-saga/effects'; import { increment } from '../actions/counter.actions'; import { INCREMENT_ASYNC } from '../const/counter.const'; // takeEvery 接收 action // put 触发 action function* increment_async_fn (action) { yield delay(2000); yield put(increment(action.payload)) } export default function* counterSaga () { // 接收action yield takeEvery(INCREMENT_ASYNC, increment_async_fn) } // 3. actions中定义:counter.actions.js export const increment_async = payload => ({type: INCREMENT_ASYNC, payload}); // 4. saga的代码拆分:root.saga.js import { all } from 'redux-saga/effects'; import counterSaga from './counter.saga'; import modalSaga from './modal.saga'; export default function* rootSaga () { yield all([ counterSaga(), modalSaga() ]) } ``` - redux-actions 简化 action 和 reducer 中的代码 #### 4. Redux 中间件的实现原理 1. 中间件的作用: - 正常的流程:触发 dispatch(action) 之后,reducer 接收到 action 更新 store; - 中间件流程:触发 dispatch(action) 之后,中间件接收到 action 然后执行所有中间件(在 reducer 接受到 action 之前,本质上市对 dispatch 进行增强),然后中间件中执行最后的 next() 传递给 reducer(或者中间件中触发 dispatch(action) 调用正常的 action,传递给 reducer) ``` dispatch(action) => store(执行中间件函数) => next(action) => action => reducer dispatch(action) => store(执行中间件函数) => dispatch(action) => action => reducer ``` 2. 中间件的创建:三层函数; - (1)logger 的 next 实际上就是 thunk 最里层的那个函数 - (2)thunk 的 next 实际上是最后的中间件函数 ```js // 定义中间件1:logger function logger (store) { return function (next) { return function (action) { console.log("logger") next(action) } } } // 定义中间件1:thunk function thunk(store) { return function (next) { // 最里层的才是中间件函数,对应 logger 的 next return function (action) { console.log("thunk") next(action) } } } // 注册中间件,执行顺序:logger => thunk => reducer export const store = createStore(RootReducer, applyMiddleware(logger, thunk)); ``` 2. 类似逻辑源码 ![输入图片说明](https://images.gitee.com/uploads/images/2021/0924/101557_28821d4a_9130428.png "屏幕截图.png") --- ## redux-saga #### redux-saga 的流程 ``` 1. 页面组件调用异步Action => 2. Saga接受异步Action(Store中注册了Saga) => 3. Saga调用异步方法:async_getUsers_fn => 4. Saga异步方法中调用更新数据的同步Action:updateUsers => 5. Reducer根据指定Action更新State => 6. 页面组件刷新数据 ``` 1. Page 页面组件中调用异步action:async_getUsers ```js ``` 2. Action 中定义了异步action:async_getUsers ``` // 异步 action,使用 redux-saga 示例 export const async_getUsers = (url) => { return { type: ASYNC_GETUSERS, url } } ``` 3. Store 页面注册了saga中间件,所以调用异步action时会执行对应saga: ``` // Store: sagaMiddleware.run(pageSaga) ``` 4. Saga 中定义了接受对应的action:async_getUsers/ASYNC_GETUSERS;接受action时执行执行异步函数:async_getUsers_fn ```js // 执行异步操作: // 在 saga 中执行异步操作不用 setTimeout,为什么?而是用 delay function* async_getUsers_fn (action) { yield delay(2000) // 通过 action.url 接受组件传过来的参数 const { data } = yield axios.get(action.url) console.log("users:", data) yield put(updateUsers(data)) } // pageSaga: // 导出 Generator 函数 export default function* pageSaga (url) { // 接受 action 的 type yield takeEvery(ASYNC_GETUSERS, async_getUsers_fn) } ``` 5. Saga 中执行异步函数时,执行同步的action更新store;其实调用的更新数据的action:updateUsers ``` yield put(updateUsers(data)) ``` 6. Reducer根据指定Action更新State,页面组件重新渲染; #### redux-saga 的传参(页面组件向saga异步方法传参) 1. 页面组件传参给异步action ```js ``` 2. 异步action接收参数并传递: ``` // 异步 action,使用 redux-saga 示例 export const async_getUsers = (url) => { return { type: ASYNC_GETUSERS, url } } ``` 3. Saga 回调函数中接受参数: ``` // 执行异步操作: // 在 saga 中执行异步操作不用 setTimeout,为什么?而是用 delay function* async_getUsers_fn (action) { yield delay(2000) // 通过 action.url 接受组件传过来的参数 const { data } = yield axios.get(action.url) console.log("users:", data) yield put(updateUsers(data)) } ``` #### redux-saga 的合并文件【待整理】 --- ## redux 的调试方法 #### redux 的调试思想 1. 总体来说,还是按照数据流的流动来进行调试 2. // ... 【遗留问题,待整理】 --- ## Redux 的源码解读 --- ## Redux 可能的面试题 ### Redux 的使用 1. 入口使用 provider 包裹 store 2. 创建store,传入reducer作为参数,注册中间件 3. 定义reducer 4. 定义action 5. 页面组件中,使用connect绑定store、dispatch等 ### Redux 的使用/数据流流程 页面组件 => dispatch(action) => action => reducer(state, action) => state(新的state) ### Redux 的中间件流程 页面组件 => dispatch(action) => action => store(之前注册了中间件函数,此时执行中间件函数) => next(action) => action => reducer ### Redux-saga 的执行流程 ### Redux 的中间件如何开发 三层函数
![输入图片说明](https://images.gitee.com/uploads/images/2021/0924/115205_ce82a6c2_9130428.png "屏幕截图.png") ### Redux 的中间件流的实现原理 利用 compose 函数组合 + 循环调用(传参:next为中间件函数)