Redux 核心概念
action reducer store
MVC
它是一个 UI 的解决方案,用于降低 UI,以及 UI 关联的数据的复杂度。
传统的服务器端的 MVC
环境:
- 服务端需要响应一个完整的 HTML
- 该 HTML 中包含页面需要的数据
- 浏览器仅承担渲染页面的作用
以上的这种方式叫做服务端渲染,即服务器端将完整的页面组装好之后,一起发送给客户端。
服务器端需要处理 UI 中要用到的数据,并且要将数据嵌入到页面中,最终生成一个完整的 HTML 页面响应。
为了降低处理这个过程的复杂度,出现了 MVC 模式。
Controller: 处理请求,组装这次请求需要的数据 Model:需要用于 UI 渲染的数据模型 View:视图,用于将模型组装到界面中
前端 MVC 模式的困难
React 解决了 数据 -> 视图 的问题
- 前端的 controller 要比服务器复杂很多,因为前端中的 controller 处理的是用户的操作,而用户的操作场景是复杂的。
- 对于那些组件化的框架(比如 vue、react),它们使用的是单向数据流。若需要共享数据,则必须将数据提升到顶层组件,然后数据再一层一层传递,极其繁琐。 虽然可以使用上下文来提供共享数据,但对数据的操作难以监控,容易导致调试错误的困难,以及数据还原的困难。并且,若开发一个大中型项目,共享的数据很多,会导致上下文中的数据变得非常复杂。
比如,上下文中有如下格式的数据:
value = {
users: [{}, {}, {}],
addUser: function (u) {},
deleteUser: function (u) {},
updateUser: function (u) {},
}
前端需要一个独立的数据解决方案
Flux
Facebook 提出的数据解决方案,它的最大历史意义,在于它引入了 action 的概念
action 是一个普通的对象,用于描述要干什么。action 是触发数据变化的唯一原因
store 表示数据仓库,用于存储共享数据。还可以根据不同的 action 更改仓库中的数据
示例:
var loginAction = {
type: "login",
payload: {
loginId: "admin",
loginPwd: "123123",
},
}
var deleteAction = {
type: "delete",
payload: 1, // 用户id为1
}
Redux
在 Flux 基础上,引入了 reducer 的概念
reducer:处理器,用于根据 action 来处理数据,处理后的数据会被仓库重新保存。

使用 Redux 管理数据
yarn add redux
import { createStore } from "redux"
//假设仓库中仅存放了一个数字,该数字的变化可能是+1或-1
//约定action的格式:{type:"操作类型", payload:附加数据}
/**
* reducer本质上就是一个普通函数
* @param state 之前仓库中的状态(数据)
* @param action 描述要作什么的对象
*/
function reducer(state, action) {
//返回一个新的状态
if (action.type === "increase") {
return state + 1
} else if (action.type === "decrease") {
return state - 1
}
return state //如果是一个无效的操作类型,数据不变
}
window.store = createStore(reducer, 10)
const action = {
type: "increase",
}
console.log(window.store.getState()) //得到仓库中当前的数据
window.store.dispatch(action) //向仓库分发action
console.log(window.store.getState()) //得到仓库中当前的数据
Action
- action 是一个 plain-object(平面对象)
- 它的proto指向 Object.prototype
- 通常,使用 payload 属性表示附加数据(没有强制要求)
- action 中必须有 type 属性,该属性用于描述操作的类型
- 但是,没有对 type 的类型做出要求
- 在大型项目,由于操作类型非常多,为了避免硬编码(hard code),会将 action 的类型存放到一个或一些单独的文件中(样板代码)。
- 为了方面传递 action,通常会使用 action 创建函数(action creator)来创建 action
- action 创建函数应为无副作用的纯函数
- 不能以任何形式改动参数
- 不可以有异步
- 不可以对外部环境中的数据造成影响
- action 创建函数应为无副作用的纯函数
- 为了方便利用 action 创建函数来分发(触发)action,redux 提供了一个函数
bindActionCreators,该函数用于增强 action 创建函数的功能,使它不仅可以创建 action,并且创建后会自动完成分发。
import { createStore } from "redux"
import * as actionTypes from "./action/action-type"
import * as numberActions from "./action/number-action"
function reducer(state = 10, action) {
if (action.type === actionTypes.INCREASE) {
return state + 1
} else if (action.type === actionTypes.DECREASE) {
return state - 1
} else if (action.type === actionTypes.SET) {
return action.payload
}
return state
}
window.store = createStore(reducer, 10)
// const action = {
// type: 'increase',
// };
console.log(window.store.getState())
window.store.dispatch(numberActions.getSetAction(3))
console.log(window.store.getState())
//自己写的,简单实现以下createStore
function createS(reducer, state) {
state = reducer(state, {})
let obj = {
state: null,
dispatch(action) {
this.state = reducer(state, action)
},
getState() {
if (this.state) {
return this.state
} else {
return state
}
},
}
return obj
}
window.store = createS(reducer, 10)
console.log(window.store.getState())
window.store.dispatch(numberActions.getSetAction(3))
console.log(window.store.getState())
bindActionCreators
//number-action.js
import * as actionTypes from "./action-type"
export function getIncreaseAction() {
return {
type: actionTypes.INCREASE,
}
}
export function getDecreaseAction() {
return {
type: actionTypes.DECREASE,
}
}
export function getSetAction(newNumber) {
return {
type: actionTypes.SET,
payload: newNumber,
}
}
//action-type.js
export const INCREASE = Symbol("increase")
export const DECREASE = Symbol("decrease")
export const SET = Symbol("set")
//index.js
import { createStore, bindActionCreators } from "redux"
import * as actionTypes from "./action/action-type"
import * as numberActions from "./action/number-action"
//假设仓库中仅存放了一个数字,该数字的变化可能是+1或-1
//约定action的格式:{type:"操作类型", payload:附加数据}
/**
* reducer本质上就是一个普通函数
* @param state 之前仓库中的状态(数据)
* @param action 描述要作什么的对象
*/
function reducer(state, action) {
//返回一个新的状态
if (action.type === actionTypes.INCREASE) {
return state + 1
} else if (action.type === actionTypes.DECREASE) {
return state - 1
} else if (action.type === actionTypes.SET) {
return action.payload
}
return state //如果是一个无效的操作类型,数据不变
}
const store = createStore(reducer, 10)
//第一个参数,是action创建函数合并的对象,第二个参数是仓库的dispatch函数
//得到一个新的对象,新对象中的属性名与第一个参数的属性名一致
const boundActions = bindActionCreators(numberActions, store.dispatch)
console.log(store.getState()) //得到仓库中当前的数据
//得到一个increase action,并直接分发
boundActions.getIncreaseAction() //向仓库分发action
console.log(store.getState()) //得到仓库中当前的数据
boundActions.getSetAction(3)
console.log(store.getState()) //得到仓库中当前的数据
Reducer
Reducer 是用于改变数据的函数
- 一个数据仓库,有且仅有一个 reducer,并且通常情况下,一个工程只有一个仓库,因此,一个系统,只有一个 reducer
- 为了方便管理,通常会将 reducer 放到单独的文件中。
- reducer 被调用的时机
- 通过 store.dispatch,分发了一个 action,此时,会调用 reducer
- 当创建一个 store 的时候,会调用一次 reducer
- 可以利用这一点,用 reducer 初始化状态
- 创建仓库时,不传递任何默认状态
- 将 reducer 的参数 state 设置一个默认值
- reducer 内部通常使用 switch 来判断 type 值
- reducer 必须是一个没有副作用的纯函数
- 为什么需要纯函数
- 纯函数有利于测试和调式
- 有利于还原数据
- 有利于将来和 react 结合时的优化
- 具体要求
- 不能改变参数,因此若要让状态变化,必须得到一个新的状态
- 不能有异步
- 不能对外部环境造成影响
- 为什么需要纯函数
- 由于在大中型项目中,操作比较复杂,数据结构也比较复杂,因此,需要对 reducer 进行细分。
- redux 提供了方法,可以帮助我们更加方便的合并 reducer
- combineReducers: 合并 reducer,得到一个新的 reducer,该新的 reducer 管理一个对象,该对象中的每一个属性交给对应的 reducer 管理。
Store
Store:用于保存数据
通过 createStore 方法创建的对象。
该对象的成员:
- dispatch:分发一个 action
- getState:得到仓库中当前的状态
- replaceReducer:替换掉当前的 reducer
- subscribe:注册一个监听器,监听器是一个无参函数,该分发一个 action 之后,会运行注册的监听器。该函数会返回一个函数,用于取消监听
import { createStore } from "redux"
import reducer from "./reducer"
import {
createAddUserAction,
createDeleteUserAction,
} from "./action/usersAction"
const store = createStore(reducer)
console.log(store)
const unListen = store.subscribe(() => {
console.log(store.getState())
})
store.dispatch(
createAddUserAction({
id: 3,
name: "abc",
age: 10,
})
)
unListen() //取消监听
store.dispatch(createDeleteUserAction(3))
手写 createStore
/**
* 判断某个对象是否是一个plain-object
* @param {*} obj
*/
function isPlainObject(obj) {
if (typeof obj !== "object") {
return false
}
return Object.getPrototypeOf(obj) === Object.prototype
}
/**
* 得到一个指定长度的随机字符串
* @param {*} length
*/
function getRandomString(length) {
return Math.random().toString(36).substr(2, length).split("").join(".")
}
/**
* 实现createStore的功能
* @param {function} reducer reducer
* @param {any} defaultState 默认的状态值
*/
export default function (reducer, defaultState) {
let currentReducer = reducer, //当前使用的reducer
currentState = defaultState //当前仓库中的状态
const listeners = [] //记录所有的监听器(订阅者)
function dispatch(action) {
//验证action
if (!isPlainObject(action)) {
throw new TypeError("action must be a plain object")
}
//验证action的type属性是否存在
if (action.type === undefined) {
throw new TypeError("action must has a property of type")
}
currentState = currentReducer(currentState, action)
//运行所有的订阅者(监听器)
for (const listener of listeners) {
listener()
}
}
function getState() {
return currentState
}
/**
* 添加一个监听器(订阅器)
*/
function subscribe(listener) {
listeners.push(listener) //将监听器加入到数组中
let isRemove = false //是否已经移除掉了
return function () {
if (isRemove) {
return
}
//将listener从数组中移除
const index = listeners.indexOf(listener)
listeners.splice(index, 1)
isRemove = true
}
}
//创建仓库时,需要分发一次初始的action
dispatch({
type: `@@redux/INIT${getRandomString(7)}`,
})
return {
dispatch,
getState,
subscribe,
}
}
手写 bindActionCreators
使用规则
- 如果 bindActionCreators 的第一个参数为对象的情况
import { bindActionCreators, createStore } from "redux"
import reducer from "./reducer"
import {
createAddUserAction,
createDeleteUserAction,
} from "./action/usersAction"
const store = createStore(reducer)
const actionCreators = {
addUser: createAddUserAction,
deleteUser: createDeleteUserAction,
}
const actions = bindActionCreators(actionCreators, store.dispatch)
//监听器要放在actions.addUser前面才能执行
const unListen = store.subscribe(() => {
console.log("监听器1", store.getState())
})
actions.addUser({ id: 3, name: "abc", age: 111 })
actions.deleteUser(3)
// function bindActionCreators(actionCreators, dispatch) {
// // 这个函数是做增强的,所以需要抽离出去,以后遇到函数做增强就把它抽离出去
// // var temp = function (...args) {
// // dispatch(createAddUserAction(args));
// // };
// let obj = {};
// for (let prop in actionCreators) {
// obj[prop] = getAutoDispatchActionCreator(actionCreators[prop], dispatch);
// }
// return obj;
// }
// function getAutoDispatchActionCreator(actionCreator, dispatch) {
// return function (...args) {
// dispatch(actionCreator(args));
// };
// }
- 如果 bindActionCreators 的第一个参数为函数的情况
// export const createAddUserAction = (user) => ({
// type: ADDUSER,
// payload: user,
// });
import { createStore, bindActionCreators } from "../redux"
import reducer from "./reducer"
import { createAddUserAction } from "./action/usersAction"
const store = createStore(reducer)
//直接调用这个函数
const addUser = bindActionCreators(createAddUserAction, store.dispatch)
store.subscribe(() => {
console.log("监听器1", store.getState())
})
addUser({ id: 3, name: "abc", age: 111 })
addUser({ id: 4, name: "abc", age: 111 })
addUser({ id: 5, name: "abc", age: 111 })
addUser({ id: 6, name: "abc", age: 111 })
// function bindActionCreators(createAddUserAction, dispatch) {
// return function (...args) {
// dispatch(createAddUserAction(args));
// };
// }
- 手写源码
export default function (actionCreators, dispatch) {
if (typeof actionCreators === "function") {
return getAutoDispatchActionCreator(actionCreators, dispatch)
} else if (typeof actionCreators === "object") {
const result = {} //返回结果
for (const key in actionCreators) {
if (actionCreators.hasOwnProperty(key)) {
const actionCreator = actionCreators[key] //取出对应的属性值
if (typeof actionCreator === "function") {
result[key] = getAutoDispatchActionCreator(actionCreator, dispatch)
}
}
}
return result
} else {
throw new TypeError(
"actionCreators must be an object or function which means action creator"
)
}
}
/**
* 得到一个自动分发的action创建函数
*/
function getAutoDispatchActionCreator(actionCreator, dispatch) {
return function (...args) {
const action = actionCreator(...args)
dispatch(action)
}
}
手写 combineReducers
// 使用
export const staticReducers = {
config: configReducer,
global: globalReducer,
}
// 原理
export default combineReducers(staticReducers)
function combineReducers(staticReducers) {
return function (initState, action) {
const newState = {}
for (let key in staticReducers) {
newState[key] = staticReducers[key]
}
return newState
}
}
import loginUser from "./loginUser"
import users from "./users"
// import { combineReducers } from 'redux';
export default (state = {}, action) => {
const newState = {
loginUser: loginUser(state.loginUser, action),
users: users(state.users, action),
}
return newState
}
// export default combineReducers({
// loginUser,
// users,
// });
import isPlainObject from "./utils/isPlainObject"
import ActionTypes from "./utils/ActionTypes"
function validateReducers(reducers) {
if (typeof reducers !== "object") {
throw new TypeError("reducers must be an object")
}
if (!isPlainObject(reducers)) {
throw new TypeError("reducers must be a plain object")
}
//验证reducer的返回结果是不是undefined
for (const key in reducers) {
if (reducers.hasOwnProperty(key)) {
const reducer = reducers[key] //拿到reducer
//传递一个特殊的type值
let state = reducer(undefined, {
type: ActionTypes.INIT(),
})
if (state === undefined) {
throw new TypeError("reducers must not return undefined")
}
state = reducer(undefined, {
type: ActionTypes.UNKNOWN(),
})
if (state === undefined) {
throw new TypeError("reducers must not return undefined")
}
}
}
}
export default function (reducers) {
//1. 验证
validateReducers(reducers)
/**
* 返回的是一个reducer函数
*/
return function (state = {}, action) {
const newState = {} //要返回的新的状态
for (const key in reducers) {
if (reducers.hasOwnProperty(key)) {
const reducer = reducers[key]
newState[key] = reducer(state[key], action)
}
}
return newState //返回状态
}
}
Redux 中间件(Middleware)
中间件:类似于插件,可以在不影响原本功能、并且不改动原本代码的基础上,对其功能进行增强。在 Redux 中,中间件主要用于增强 dispatch 函数。
实现 Redux 中间件的基本原理,是更改仓库中的 dispatch 函数。
Redux 中间件书写:
-
中间件本身是一个函数,该函数接收一个 store 参数,表示创建的仓库,该仓库并非一个完整的仓库对象,仅包含 getState,dispatch。该函数运行的时间,是在仓库创建之后运行。
- 由于创建仓库后需要自动运行设置的中间件函数,因此,需要在创建仓库时,告诉仓库有哪些中间件
- 需要调用 applyMiddleware 函数,将函数的返回结果作为 createStore 的第二或第三个参数。
-
中间件函数必须返回一个 dispatch 创建函数
-
applyMiddleware 函数,用于记录有哪些中间件,它会返回一个函数
- 该函数用于记录创建仓库的方法,然后又返回一个函数
import { createStore, bindActionCreators } from "../redux"
import reducer from "./reducer"
import {
createAddUserAction,
createDeleteUserAction,
} from "./action/usersAction"
const store = createStore(reducer)
const oldDispatch = store.dispatch //保留原本的dispatch函数
store.dispatch = function (action) {
//更改store中的dispatch
console.log("中间件1")
console.log("旧数据", store.getState())
console.log("action", action)
oldDispatch(action)
console.log("新数据", store.getState())
console.log("")
}
const oldDispatch = store.dispatch //保留原本的dispatch函数
store.dispatch = function (action) {
//更改store中的dispatch
console.log("中间件2")
console.log("旧数据", store.getState())
console.log("action", action)
oldDispatch(action)
console.log("新数据", store.getState())
console.log("")
}
const actionCreators = {
addUser: createAddUserAction,
deleteUser: createDeleteUserAction,
}
const actions = bindActionCreators(actionCreators, store.dispatch)
actions.addUser({ id: 3, name: "abc", age: 111 })
actions.deleteUser(3)
import { createStore, bindActionCreators, applyMiddleware } from "redux"
import reducer from "./reducer"
import {
createAddUserAction,
createDeleteUserAction,
} from "./action/usersAction"
const logger1 = (store) => (next) => (action) => {
console.log("中间件1")
console.log("旧数据", store.getState())
console.log("action", action)
next(action)
console.log("新数据", store.getState())
console.log("")
}
const logger2 = (store) => (next) => (action) => {
console.log("中间件2")
console.log("旧数据", store.getState())
console.log("action", action)
next(action)
console.log("新数据", store.getState())
console.log("")
}
// /**
// * 一个中间件函数
// * @param {*} store
// */
// function logger1(store) {
// return function (next) {
// //下面返回的函数,是最终要应用的dispatch
// return function (action) {
// console.log("中间件1")
// console.log("旧数据", store.getState());
// console.log("action", action);
// next(action);
// console.log("新数据", store.getState());
// console.log("")
// }
// }
// }
// function logger2(store) {
// return function (next) { //数组最后一项拿到的参数是dispatch 所以这里的next为dispatch
// //下面返回的函数,是最终要应用的dispatch
// return function (action) {
// console.log("中间件2")
// console.log("旧数据", store.getState());
// console.log("action", action);
// next(action);
// console.log("新数据", store.getState());
// console.log("")
// }
// }
// }
//应用中间件,方式1:
const store = createStore(reducer, applyMiddleware(logger1, logger2))
//方式2:
// const store = applyMiddleware(logger1, logger2)(createStore)(reducer);
const actionCreators = {
addUser: createAddUserAction,
deleteUser: createDeleteUserAction,
}
const actions = bindActionCreators(actionCreators, store.dispatch)
actions.addUser({ id: 3, name: "abc", age: 111 })
actions.deleteUser(3)
logger 中间件
// logger在封装的时候先进行next 然后再执行打印(这样做是为了看起来好看一点)
const logger1 = (store) => (next) => (action) => {
var prev = store.getState()
next(action)
console.log("旧数据--", prev)
console.log("action", action)
console.log("新数据--", store.getState())
console.log("")
}
// 在做react-redux的时候,如果是下面这种形式,那么调用next的时候会改变仓库的数据,导致依赖仓库的组件重新渲染,由于代码同步执行,所以
// 旧数据 和 新数据之间的会打印出我们在组件中console.log调试的内容,这样显然不太好看
const logger1 = (store) => (next) => (action) => {
console.log("旧数据", store.getState())
console.log("action", action)
next(action)
console.log("新数据", store.getState())
}
手写 applyMiddleware
middleware 的本质,是一个调用后可以得到 dispatch 创建函数的函数
compose:函数组合,将一个数组中的函数进行组合,形成一个新的函数,该函数调用时,实际上是反向调用之前组合的函数
// 组合函数
function compose(...funcs) {
if (funcs.length === 0) {
return (args) => args //如果没有要组合的函数,则返回的函数原封不动的返回参数
} else if (funcs.length === 1) {
//要组合的函数只有一个
return funcs[0]
}
return funcs.reduce(
(a, b) =>
(...args) =>
a(b(...args))
)
// return function (...args) {
// let lastReturn = null; //记录上一个函数返回的值,它将作为下一个函数的参数
// for (let i = funcs.length - 1; i >= 0; i--) {
// const func = funcs[i];
// if (i === funcs.length - 1) {//数组最后一项
// lastReturn = func(...args)
// }
// else {
// lastReturn = func(lastReturn)
// }
// }
// return lastReturn;
// }
}
/**
* 注册中间件
* @param {...any} middlewares 所有的中间件
*/
export default function (...middlewares) {
return function (createStore) {
//给我创建仓库的函数
//下面的函数用于创建仓库
return function (reducer, defaultState) {
//创建仓库
const store = createStore(reducer, defaultState)
let dispatch = () => {
throw new Error("目前还不能使用dispatch")
}
const simpleStore = {
getState: store.getState,
dispatch: store.dispatch,
}
//给dispatch赋值
//根据中间件数组,得到一个dispatch创建函数的数组
const dispatchProducers = middlewares.map((mid) => mid(simpleStore))
dispatch = compose(...dispatchProducers)(store.dispatch)
return {
...store,
dispatch,
}
}
}
}
核心思想
function a(next) {
return function (action) {
// console.log(next); //这里的next为b函数的返回值
action = action + 1
next(action)
}
}
function b(next) {
return function (action) {
// console.log(next);//这里next为初始的dispatch
action = action + 1
next(action)
}
}
let arr = []
arr.push(a)
arr.push(b)
let compose = arr.reduce(function (prev, curr) {
return function (next) {
return prev(curr(next))
}
})
var globalV = 0 //定义一个全局变量,用来计数
//初始的dispatch
let dispatch = function (action) {
//初始功能
globalV += action
}
//加强后的dispatch
let strendispatch = compose(dispatch)
strendispatch(1)
console.log(globalV) //结果是 3
redux-logger
import logger from "redux-logger"
const store = createStore(reducer, applyMiddleware(logger))
利用中间件进行副作用处理(redux-thunk,redux-promise,redux-saga)
redux-thunk
thunk 允许 action 是一个带有副作用的函数,当 action 是一个函数被分发时,thunk 会阻止 action 继续向后移交。
thunk 会向函数中传递三个参数:
dispatch:来自于 store.dispatch
getState:来自于 store.getState
extra:来自于用户设置的额外参数
//使用thunk
import { createStore, applyMiddleware } from "redux"
import reducer from "./reducer"
import logger from "redux-logger"
import thunk from "redux-thunk"
// 为了避免不必要的问题,logger中间件必须放到所有的中间件后面createStore(reducer, applyMiddleware(logger,thunk));
// 因为thunk中间件在开始的时候dipatch处理的是函数,logger放第一个处理不了函数,然后调next触发thunk,最后还是得由thunk处理
export default createStore(reducer, applyMiddleware(thunk, logger))
function createThunkMiddleware(extra) {
//该函数返回一个thunk中间件
return (store) => (next) => (action) => {
if (typeof action === "function") {
return action(store.dispatch, store.getState, extra) //如果是函数到这里就结束了,不会执行next
} //这里的返回值是action函数的返回值,最终是store.dispatch(fetchStudents())的返回值
return next(action) //这里的next是下一个中间件执行的返回值
}
}
const thunk = createThunkMiddleware()
thunk.withExtraArgument = createThunkMiddleware
export default thunk
迭代器和可迭代协议
解决副作用的 redux 中间件 redux-thunk:需要改动 action,可接收 action 是一个函数 redux-promise:需要改动 action,可接收 action 是一个 promise 对象,或 action 的 payload 是一个 promise 对象 以上两个中间件,会导致 action 或 action 创建函数不再纯净。 redux-saga 将解决这样的问题,它不仅可以保持 action、action 创建函数、reducer 的纯净,而且可以用模块化的方式解决副作用,并且功能非常强大。 redux-saga 是建立在 ES6 的生成器基础上的,要熟练的使用 saga,必须理解生成器。 要理解生成器,必须先理解迭代器和可迭代协议。
迭代
类似于遍历
遍历:有多个数据组成的集合数据结构(map、set、array 等其他类数组),需要从该结构中依次取出数据进行某种处理。
迭代:按照某种逻辑,依次取出下一个数据进行处理。
迭代器 iterator
JS 语言规定,如果一个对象具有 next 方法,并且 next 方法满足一定的约束,则该对象是一个迭代器(iterator)。
next 方法的约束:该方法必须返回一个对象,该对象至少具有两个属性:
- value:any 类型,下一个数据的值,如果 done 属性为 true,通常,会将 value 设置为 undefined
- done:bool 类型,是否已经迭代完成
通过迭代器的 next 方法,可以依次取出数据,并可以根据返回的 done 属性,判定是否迭代结束。
迭代器创建函数 iterator creator
它是指一个函数,调用该函数后,返回一个迭代器,则该函数称之为迭代器创建函数,可以简称位迭代器函数。
可迭代协议
ES6 中出现了 for-of 循环,该循环就是用于迭代某个对象的,因此,for-of 循环要求对象必须是可迭代的(对象必须满足可迭代协议)
可迭代协议是用于约束一个对象的,如果一个对象满足下面的规范,则该对象满足可迭代协议,也称之为该对象是可以被迭代的。
可迭代协议的约束如下:
- 对象必须有一个知名符号属性(Symbol.iterator)
- 该属性必须是一个无参的迭代器创建函数
for-of 循环的原理
调用对象的[Symbol.iterator]方法,得到一个迭代器。不断调用 next 方法,只有返回的 done 为 false,则将返回的 value 传递给变量,然后进入循环体执行一次。
//obj满足可迭代协议
//obj可被迭代
var obj = {
[Symbol.iterator]: function () {
var total = 3
i = 1
return {
next() {
var obj = {
//当前这一次迭代到的数据
value: i > total ? undefined : Math.random(),
done: i > total,
}
i++
return obj
},
}
},
}
//模拟for-of循环
var iterator = obj[Symbol.iterator]()
var result = iterator.next()
while (!result.done) {
//有数据
const item = result.value
console.log(item) //执行循环体
result = iterator.next()
}
for (const item of obj) {
console.log(item)
}
生成器 generator
generator
生成器:由构造函数 Generator 创建的对象,该对象既是一个迭代器,同时,又是一个可迭代对象(满足可迭代协议的对象)
//伪代码
var generator = new Generator()
generator.next() //它具有next方法
var iterator = generator[Symbol.iterator] //它也是一个可迭代对象
for (const item of generator) {
//由于它是一个可迭代对象,因此也可以使用for of循环
}
注意:Generator 构造函数,不提供给开发者使用,仅作为 JS 引擎内部使用
generator function
生成器函数(生成器创建函数):该函数用于创建一个生成器。
ES6 新增了一个特殊的函数,叫做生成器函数,只要在函数名与 function 关键字之间加上一个*号,则该函数会自动返回一个生成器
生成器函数的特点:
- 调用生成器函数,会返回一个生成器,而不是执行函数体(因为,生成器函数的函数体执行,收到生成器控制)
- 每当调用了生成器的 next 方法,生成器的函数体会从上一次 yield 的位置(或开始位置)运行到下一个 yield
- yield 关键字只能在生成器内部使用,不可以在普通函数内部使用
- 它表示暂停,并返回一个当前迭代的数据
- 如果没有下一个 yield,到了函数结束,则生成器的 next 方法得到的结果中的 done 为 true
- yield 关键字后面的表达式返回的数据,会作为当前迭代的数据
- 生成器函数的返回值,会作为迭代结束时的 value
- 但是,如果在结束过后,仍然反复调用 next,则 value 为 undefined
- 生成器调用 next 的时候,可以传递参数,该参数会作为生成器函数体上一次 yield 表达式的值。
- 生成器第一次调用 next 函数时,传递参数没有任何意义
- 生成器带有一个 throw 方法,该方法与 next 的效果相同,唯一的区别在于:
- next 方法传递的参数会被返回成一个正常值
- throw 方法传递的参数是一个错误对象,会导致生成器函数内部发生一个错误。
generator.throw()
- 生成器带有一个 return 方法,该方法会直接结束生成器函数
generator.return() - 若需要在生成器内部调用其他生成器,注意:如果直接调用,得到的是一个生成器,如果加入*号调用,则进入其生成器内部执行。如果是
yield* 函数()调用生成器函数,则该函数的返回结果,为该表达式的结果
function* g2() {
console.log("g2-开始")
let result = yield "g1"
console.log("g2-运行1")
result = yield "g2"
return 123
}
//下面的函数是一个生成器函数,用于创建生成器
function* createGenerator() {
console.log("生成器函数的函数体 - 开始")
let result = yield 1 //将1作为第一次的迭代的值
result = yield* g2() //result为g2函数的返回值
console.log("生成器函数的函数体 - 运行1", result)
result = yield 2 //将2作为第二次迭代的值
console.log("生成器函数的函数体 - 运行2", result)
result = yield 3
console.log("生成器函数的函数体 - 运行3", result)
return "结束"
}
var generator = createGenerator() //调用后,一定得到一个生成器
redux-promise
如果 action 是一个 promise,则会等待 promise 完成,将完成的结果作为 action 触发,如果 action 不是一个 promise,则判断其 payload 是否是一个 promise,如果是,等待 promise 完成,然后将得到的结果作为 payload 的值触发。
// /**
// * 由于使用了redux-promise中间件,因此,允许action是一个promise,在promise中,如果要触发action,则使用resolve
// */
// 第一种:fetchStudents返回的是promise对象
export function fetchStudents() {
return new Promise((resolve) => {
setTimeout(() => {
const action = setStudentsAndTotal(
[
{ id: 1, name: "aaa" },
{ id: 2, name: "bbb" },
],
2
)
resolve(action)
}, 3000)
})
}
// 第一种:使用async 直接返回promise对象
export async function fetchStudents(condition) {
const resp = await searchStudents(condition)
return setStudentsAndTotal(resp.datas, resp.cont)
}
// 第一种:这里的使用async关键字函数 返回的也是一个promise对象
// 这样会走一点弯路 首先进入源码
// 将这个fetchStudents的返回值是dispatch一次,由于此时dispatch的是一个带有payload为promise的对象
// 所以还要走一次dispatch(一共走了三次dispatch,初始一次,返回值为promise的时候一次,对象属性payload为promise时一次)
export async function fetchStudents(condition) {
return {
type: actionTypes.setStudentsAndTotal,
payload: searchStudents(condition).then((resp) => ({
datas: resp.datas,
total: resp.cont,
})),
}
}
// 第二种:返回的是一个平面对象,这个平面对象的payload是一个action
export function fetchStudents(condition) {
return {
type: actionTypes.setStudentsAndTotal,
payload: searchStudents(condition).then((resp) => ({
datas: resp.datas,
total: resp.cont,
})),
}
}
redux-saga
- 纯净
- 强大
- 灵活
在 saga 任务中,如果 yield 了一个普通数据,saga 不作任何处理,仅仅将数据传递给 yield 表达式(把得到的数据放到 next 的参数中),因此,在 saga 中,yield 一个普通数据没什么意义。
saga 需要你在 yield 后面放上一些合适的 saga 指令(saga effects),如果放的是指令,saga 中间件会根据不同的指令进行特殊处理,以控制整个任务的流程。
每个指令本质上就是一个函数,该函数调用后,会返回一个指令对象,saga 会接收到该指令对象,进行各种处理
一旦 saga 任务完成(生成器函数运行完成),则 saga 中间件一定结束
指令前面必须使用 yield,以确保该指令的返回结果被 saga 控制
-
take 指令:【阻塞】监听某个 action,如果 action 发生了,则会进行下一步处理,take 指令仅监听一次。yield 得到的是完整的 action 对象
-
all 指令:【阻塞】该函数传入一个数组,数组中放入生成器,saga 会等待所有的生成器全部完成后才会进一步处理
-
takeEvery 指令:不断的监听某个 action,当某个 action 到达之后,运行一个函数。takeEvery 永远不会结束当前的生成器
-
delay 指令:【阻塞】阻塞指定的毫秒数
-
put 指令:用于重新触发 action,相当于 dispatch 一个 action
-
call 指令:【可能阻塞】用于副作用(通常是异步)函数调用(如果是 promise 就阻塞,不是就不阻塞,不是 promise 也没必要使用 call)
-
apply 指令:【可能阻塞】用于副作用(通常是异步)函数调用
-
select 指令:用于得到当前仓库中的数据
-
cps 指令:【可能阻塞】用于调用那些传统的回调方式的异步函数
-
fork:用于开启一个新的任务,该任务不会阻塞,该函数需要传递一个生成器函数,fork 返回了一个对象,类型为 Task
-
cancel:用于取消一个或多个任务,实际上,取消的实现原理,是利用 generator.return。cancel 可以不传递参数,如果不传递参数,则取消当前任务线。
-
takeLastest:功能和 takeEvery 一致,只不过,会自动取消掉之前开启的任务
-
cancelled:判断当前任务线是否被取消掉了
-
race:【阻塞】竞赛,可以传递多个指令,当其中任何一个指令结束后,会直接结束,与 Promise.race 类似。返回的结果,是最先完成的指令结果。并且,该函数会自动取消其他的任务
take 指令&takeEvery 指令
//counterTake.js
import { takeEvery, take } from "redux-saga/effects"
import { actionTypes } from "../action/counter"
function* asyncIncrease() {
console.log("触发了asyncIncrease")
}
function* asyncDecrease() {
console.log("触发了asyncDecrease")
}
// takeEvery 指令
export default function* () {
yield takeEvery(actionTypes.asyncIncrease, asyncIncrease)
yield takeEvery(actionTypes.asyncDecrease, asyncDecrease)
console.log("正在监听asyncIncrease、asyncDecrease")
}
// take 指令
// export default function* () {
// while (true) {
// const action = yield take(actionTypes.asyncIncrease);
// console.log('监听到了async-increase', action);
// }
// }
all 指令
// sage/index.js
import { all } from "redux-saga/effects"
import counterTask from "./counterTask"
import studentTask from "./studentTask"
/**
* saga任务
*/
// all 指令
export default function* () {
yield all([counterTask(), studentTask()])
console.log("saga 完成")
}
delay 指令&put 指令
// saga/counterTask.js
import { takeEvery, take, delay } from "redux-saga/effects"
import { actionTypes } from "../action/counter"
function* asyncIncrease() {
var result = yield delay(2000, 123) //2s后有个返回结果,默认为true 如果传了第二个参数,那么返回值result就是第二个参数
console.log(result)
yield put(increase()) //两秒后重新触发action(increase)
console.log("触发了asyncIncrease")
}
call&apply&select&cps 指令
// saga/studentTask.js
import {
actionTypes,
setStudentsAndTotal,
} from "../action/student/searchResult"
import { takeEvery, put, call, apply, select, cps } from "redux-saga/effects"
import { setIsLoading } from "../action/student/searchResult"
import { searchStudents } from "../../services/index"
function mockStudent() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve("学生数据")
} else {
reject("错误")
}
})
})
}
function mockCps(condition, callback) {
console.log("mockCps", condition)
setTimeout(() => {
if (Math.random() > 0.8) {
callback(null, {
cont: 88,
datas: [
{ id: 1, name: "abc" },
{ id: 2, name: "bcd" },
],
})
} else {
callback(new Error("出错了"), null) //node格式的callback,第一个参数是error,第二个参数是data
}
}, 1000)
}
function* fetchStudents() {
//设置为正在加载中
yield put(setIsLoading(true))
//1. 这里的searchStudents为promise, saga会自动处理这个promise,将resolve返回值result传给next(result),这里就相当于await
//当saga发现得到的结果是一个promise对象,它会自动等待promise完成,完成之后会把完成的结果作为值传给下一个next
// let resp = yield searchStudents();
//2. 如果这里的promise触发的是reject,sage会使用generator.throw抛出一个错误
// let resp = yield mockStudent();
//3. 可以用try catch捕获
// try {
// const resp = yield mockStudent();
// console.log(resp);
// } catch (err) {
// console.log(err);
// }
//4. 抽离出的saga用来做副作用,保持了action的纯净
// const resp = yield searchStudents();
// yield put(setStudentsAndTotal(resp.datas, resp.cont));
// yield put(setIsLoading(false));
//5. yield 后面尽量用指令(好处,便于单元测试) 这里使用call指令和4的效果是一样的
// const resp = yield call(searchStudents);
// yield put(setStudentsAndTotal(resp.datas, resp.cont));
// yield put(setIsLoading(false));
//6. call的一个参数可以是数组,数组的的第一个元素是需要绑定的this,第二个元素的需要调用的返回promise的函数,其他参数是传递给 searchStudents的参数
// const resp = yield call(['abc', searchStudents], 'arg1', 'arg2');
// yield put(setStudentsAndTotal(resp.datas, resp.cont));
// yield put(setIsLoading(false));
//6.1 call 的参数还可以是一个对象的形式
// const resp = yield call({
// context: 'abc',
// fn: searchStudents,
// });
// yield put(setStudentsAndTotal(resp.datas, resp.cont));
// yield put(setIsLoading(false));
//7. apply作用和call一样,只是传参不同,和函数的apply参数一样(第一个参数是this,第二参数是函数,第三个参数的函数的参数)
// const resp = yield apply(null, searchStudents, []);
// yield put(setStudentsAndTotal(resp.datas, resp.cont));
// yield put(setIsLoading(false));
//8. select指令获取仓库的所有数据
// const state = yield select();
// console.log(state);
// 9. select指令接受一个函数参数,可以用来筛选需要的state
// const condition = yield select((state) => state.student.condition);
// console.log(condition); //打印 state.student.condition
// const resp = yield call(searchStudents, condition); //作为条件传给searchStudents
// yield put(setStudentsAndTotal(resp.datas, resp.cont));
// yield put(setIsLoading(false));
// 10. 使用cps指令会给mockCps的最后一个参数一个回调函数,在mockCps中调用该回调函数,callback的参数会作为返回值传给next
const condition = yield select((state) => state.student.condition)
const resp = yield cps(mockCps, condition) //作为条件传给searchStudents
// console.log(resp); //接受到的是mockCps中的calllback的参数
yield put(setStudentsAndTotal(resp.datas, resp.cont))
yield put(setIsLoading(false))
}
export default function* () {
// takeEvery("*"); //匹配所有的action
yield takeEvery(actionTypes.fetchStudents, fetchStudents)
console.log("正在监听fetchStudents")
}
fork 指令
// saga/counterTask.js
//使用fork指令得到与takeEvery相同的效果
// fork相当于一个分支,相当于在主线程上添加一个并发线程
import { takeEvery, take, delay, put, fork } from "redux-saga/effects"
import { actionTypes, increase } from "../action/counter"
function* asyncIncrease() {
while (true) {
//这里在2s内持续调用asyncIncrease只会触发一次put
//当take监听到actionTypes.asyncIncrease后会执行delay开始阻塞两秒,这个期间你再调用asyncIncrease就监听不到了,要等到2000s结束,put一次之后,进入第二次死循环有take监听
// yield take(actionTypes.asyncIncrease);
// yield delay(2000);
// yield put(increase());
//
//下面这种写法就相当于是takeEvery
// fork相当于会开一个异步线程,不影响主线程take的执行,就是take监听一次之后马上进行第二次take监听
yield take(actionTypes.asyncIncrease)
yield fork(function* () {
yield delay(2000)
yield put(increase())
})
}
}
function* asyncDecrease() {
yield delay(2000)
console.log("触发了asyncDecrease")
}
export default function* () {
yield fork(asyncIncrease)
yield takeEvery(actionTypes.asyncDecrease, asyncDecrease)
console.log("正在监听asyncIncrease、asyncDecrease")
}
cancel 指令
// saga/counterTask.js
import { takeEvery, take, delay, put, fork, cancel } from "redux-saga/effects"
import { actionTypes, increase } from "../action/counter"
function* asyncIncrease() {
let task
while (true) {
yield take(actionTypes.asyncIncrease)
//监听到了action,并且开启新任务之前,取消之前的任务(相当于防抖,保证在2s内只触发一个action)
if (task) {
//之前有任务
yield cancel(task)
console.log("之前的任务被取消")
}
task = yield fork(function* () {
yield delay(2000)
yield put(increase())
})
}
}
export default function* () {
yield fork(asyncIncrease)
yield takeEvery(actionTypes.asyncDecrease, asyncDecrease)
console.log("正在监听asyncIncrease、asyncDecrease")
}
takeEvery 指令与 fork 的区别&takeLastest
import { takeEvery, take, delay, put, fork, cancel } from "redux-saga/effects"
import { actionTypes, increase } from "../action/counter"
// takeEvery就是用fork实现的
// function takeEvery(actionType, saga) {
// return fork(function* () {
// while (true) {
// const action = yield take(actionType);
// fork(saga)
// }
// })
// }
let task
function* autoIncrease() {
console.log(task)
// task && cancel(task); //这里取消的是外部的fork,里面的fork还是会继续执行的
// 所以利用takeEvery是取消不了内部的fork的
if (task) {
yield cancel(task)
}
while (true) {
// yield delay(2000); //这里是内部fork执行 在控制台上执行autoIncrease() 每隔两秒会触发一次increase
yield delay(10000) //这里我把它设置成10s,然后到控制台上一直执行autoIncrease(),10s过后它还是会往下执行put(increase())
//说明我每次执行autoIncrease并没有取消task
yield put(increase())
}
}
export default function* () {
task = yield takeEvery(actionTypes.autoIncrease, autoIncrease)
console.log("正在监听autoIncrease")
}
import { takeEvery, take, delay, put, fork, cancel } from "redux-saga/effects"
import { actionTypes, increase } from "../action/counter"
// function takeEvery(actionType, saga) {
// return fork(function* () {
// while (true) {
// const action = yield take(actionType);
// fork(saga)
// }
// })
// }
function* autoIncrease() {
let task
while (true) {
yield take(actionTypes.autoIncrease)
if (task) {
yield cancel(task)
}
task = yield fork(function* () {
while (true) {
yield delay(2000)
yield put(increase())
}
})
}
}
export default function* () {
yield fork(autoIncrease)
console.log("正在监听autoIncrease")
}
// 利用takeLastest实现和上面fork相同的功能
// takeLastest其实就是在autoIncrease外层包了fork
// takeLastest就好像就每次监听到一个autoIncrease,就把之前还在执行等待的autoIncrease给return停止了
// function* autoIncrease() {
// while (true) {
// yield delay(2000);
// yield put(increase());
// }
// }
// export default function* () {
// yield takeLatest(actionTypes.autoIncrease, autoIncrease);
// console.log('正在监听autoIncrease');
// }
触发停止的方案 方案一:手动触发停止(通过变量方式停止)
import {
takeEvery,
take,
delay,
put,
fork,
cancel,
takeLatest,
} from "redux-saga/effects"
import { actionTypes, increase } from "../action/counter"
var isStop = false //是否停止
function* autoIncrease() {
isStop = false
while (true) {
yield delay(2000)
if (isStop) {
break
}
yield put(increase())
}
}
function stopAutoIncrease() {
isStop = true
}
export default function* () {
yield takeLatest(actionTypes.autoIncrease, autoIncrease)
yield takeLatest(actionTypes.stopAutoIncrease, stopAutoIncrease)
console.log("正在监听autoIncrease")
}
方案二:利用写的 fork 返回的 task,通过 cancel(task) 触发停止
import {
takeEvery,
take,
delay,
put,
fork,
cancel,
takeLatest,
} from "redux-saga/effects"
import { actionTypes, increase } from "../action/counter"
function* stopTask() {
if (task) {
yield cancel(task)
}
}
let task
function* autoIncrease() {
while (true) {
yield take(actionTypes.autoIncrease)
yield* stopTask()
task = yield fork(function* () {
while (true) {
yield delay(2000)
yield put(increase())
}
})
}
}
function* stopAutoIncrease() {
yield* stopTask()
}
export default function* () {
yield fork(autoIncrease)
yield takeEvery(actionTypes.stopAutoIncrease, stopAutoIncrease)
console.log("正在监听autoIncrease")
}
import {
takeEvery,
take,
delay,
put,
fork,
cancel,
takeLatest,
} from "redux-saga/effects"
import { actionTypes, increase } from "../action/counter"
/**
* 自动增加和停止的流程控制
* 流程:自动增加 -> 停止 -> 自动增加 -> 停止
*/
function* autoTask() {
while (true) {
yield take(actionTypes.autoIncrease) //只监听autoIncrease
const task = yield fork(function* () {
while (true) {
yield delay(2000)
yield put(increase())
}
})
yield take(actionTypes.stopAutoIncrease) //转而监听stopAutoIncrease
yield cancel(task)
}
}
export default function* () {
yield fork(autoTask)
console.log("正在监听autoIncrease")
}
方案三:使用 race
/**
* 自动增加和停止的流程控制
* 流程:自动增加 -> 停止 -> 自动增加 -> 停止
*/
function* autoTask() {
while (true) {
yield take(actionTypes.autoIncrease) //只监听autoIncrease
yield race({
autoIncrease: call(function* () {
while (true) {
yield delay(2000)
yield put(increase())
}
}),
cancel: take(actionTypes.stopAutoIncrease),
})
}
}
export default function* () {
yield fork(autoTask)
console.log("正在监听autoIncrease")
}
canceled 指令 (function(){try{return }finally{console.log(123)}})() finally 中的代码无论如何都会执行
/**
* 自动增加和停止的流程控制
* 流程:自动增加 -> 停止 -> 自动增加 -> 停止
*/
function* autoTask() {
while (true) {
yield take(actionTypes.autoIncrease) //只监听autoIncrease
const task = yield fork(function* () {
try {
while (true) {
yield delay(2000)
yield put(increase())
}
} finally {
if (yield cancelled()) {
//判断当前任务线是否被取消(generator.return)
console.log("自动增加任务被取消掉了!!!")
}
}
})
yield take(actionTypes.stopAutoIncrease) //转而监听stopAutoIncrease
yield cancel(task)
}
}
export default function* () {
yield fork(autoTask)
console.log("正在监听autoIncrease")
}
race 指令
/**
* 异步的得到一个action
*/
function asyncAction() {
//生成一个1000~5000毫秒的随机时间
var duration = Math.floor(Math.random() * 4000 + 1000)
return new Promise((resolve) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve(increase())
} else {
resolve(decrease())
}
}, duration)
})
}
export default function* () {
var result = yield race({
action1: call(asyncAction),
action2: call(asyncAction),
})
console.log(result) //{"action1": {type: Symbol(increase)} || action2: {type: Symbol(decrease)}
}
redux-saga 源码
//第三方库 is-generator 判断是否是一个生成器 is-promise 判断是否是一个 promise
yarn add is-generator is-promise
第一篇
// store1/index.js
// 用于创建仓库,并导出
import { createStore, applyMiddleware } from "redux"
import reducer from "./reducer"
import logger from "redux-logger"
import createSagaMiddleware from "../redux-saga"
import rootSaga from "./saga1"
const sagaMid = createSagaMiddleware() //创建一个saga的中间件
const store = createStore(reducer, applyMiddleware(sagaMid, logger))
sagaMid.run(rootSaga, 1, 2, 3) //启动saga任务
export default store
//store1/saga1/index.js
import { call } from "redux-saga/effects"
function test() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve("完成")
} else {
reject("失败")
}
}, 2000)
})
}
export default function* (a, b, c) {
console.log("sagea 开始", a, b, c)
var c = yield call(function () {})
console.log(c)
}
// export default function* (a, b, c) {
// console.log('sagea 开始', a, b, c);
// try {
// let result = yield test();
// console.log(result);
// result = yield 123;
// console.log(result);
// } catch (err) {
// console.log(err);
// const result = yield 456;
// console.log(result);
// }
// }
// redux-saga/index.js
import runSaga from "./runSaga"
/**
* 创建saga中间件函数
*/
export default function () {
function sagaMiddleware(store) {
const env = {
store,
}
sagaMiddleware.run = runSaga.bind(null, env)
return function (next) {
return function (action) {
return next(action) //直接交给下一个中间件处理
}
}
}
return sagaMiddleware
}
// redux-saga/runSaga.js
import { Task } from "./Task"
import isGenerator from "is-generator"
import { isEffect } from "./effectHelper"
import isPromise from "is-promise"
/**
* 开启一个新任务
* @param {*} env 全局环境的数据,被saga执行期共享的数据
* @param {*} generatorFunc 生成器函数
* @param {*} args 生成器函数的参数
*/
export default function (env, generatorFunc, ...args) {
const iterator = generatorFunc(...args)
if (isGenerator(iterator)) {
//不断调用next,直到迭代结束
next()
} else {
console.log("一个普通函数")
}
/**
*
* @param {*} nextValue 正常调用iterator.next时,传递的值
* @param {*} err 错误对象
* @param {boolean} isOver 是否结束
*/
function next(nextValue, err, isOver) {
// 情况1:调用iterator.next(value)
// 情况2:调用iterator.throw(err)
// 情况3:调用iterator.return()
let result //记录迭代的结果{value:xxx,done:false}
if (err) {
result = iterator.throw(err)
} else if (isOver) {
result = iterator.result() //结果整个迭代
} else {
console.log("result111", nextValue)
result = iterator.next(nextValue)
console.log("result222", result)
}
const { value, done } = result
if (done) {
//迭代结束了
return
}
//没有结束
if (isEffect(value)) {
//情况1:是一个effect对象
console.log("是一个effect对象")
} else if (isPromise(value)) {
//情况2:value是一个promise
value.then((r) => next(r)).catch((err) => next(null, err))
} else {
//情况3:其他
next(value) //直接走下一步
}
return new Task()
}
}
redux-actions
该库用于简化 action-types、action-creator 以及 reducer
createAction(s)
createAction
该函数用于帮助你创建一个 action 创建函数(action creator)
createActions
该函数用于帮助你创建多个 action 创建函数
handleAction(s)
handleAction
简化针对单个 action 类型的 reducer 处理,当它匹配到对应的 action 类型后,会执行对应的函数
handleActions
简化针对多个 action 类型的 reducre 处理
combineActions
配合 createActions 和 handleActions 两个函数,用于处理多个 action-type 对应同一个 reducer 处理函数。
react-redux
- React: 组件化的 UI 界面处理方案
- React-Router: 根据地址匹配路由,最终渲染不同的组件
- Redux:处理数据以及数据变化的方案(主要用于处理共享数据)
如果一个组件,仅用于渲染一个 UI 界面,而没有状态(通常是一个函数组件),该组件叫做展示组件 如果一个组件,仅用于提供数据,没有任何属于自己的 UI 界面,则该组件叫做容器组件,容器组件纯粹是为了给其他组件提供数据。
react-redux 库:链接 redux 和 react
-
Provider 组件:没有任何 UI 界面,该组件的作用,是将 redux 的仓库放到一个上下文中。
-
connect:高阶组件,用于链接仓库和组件的
- 细节一:如果对返回的容器组件加上额外的属性,则这些属性会直接传递到展示组件
- 第一个参数:mapStateToProps:
- 参数 1:整个仓库的状态
- 参数 2:使用者传递的属性对象(abc={123})
- 第二个参数:
- 情况 1:传递一个函数 mapDispatchToProps
- 参数 1:dispatch 函数
- 参数 2:使用者传递的属性对象
- 函数返回的对象会作为属性传递到展示组件中(作为事件处理函数存在)
- 情况 2:传递一个对象,对象的每个属性是一个 action 创建函数,当事件触发时,会自动的 dispatch 函数返回的 action
- 情况 1:传递一个函数 mapDispatchToProps
- 细节二:如果不传递第二个参数,通过 connect 连接的组件,会自动得到一个属性:dispatch,使得组件有能力自行触发 action,但是,不推荐这样做。
-
自己手动连接 react 和 redux
// Counter.js
import React from 'react';
import store from '../store1';
import {
asyncDecrease,
asyncIncrease,
increase,
decrease,
} from '../store1/action/counter';
//展示组件
function Counter(props) {
return (
<div>
<h1>{props.number}</h1>
<p>
<button onClick={props.onAsyncDecrease}> 异步减 </button>
<button onClick={props.onDecrease}> 减 </button>
<button onClick={props.onIncrease}> 加 </button>
<button onClick={props.onAsyncIncrease}> 异步加 </button>
</p>
</div>
);
}
/**
* 将整个仓库的状态,映射到当前需要的数据
* @param {*} state
*/
function mapStateToProps(state) {
return {
number: state.counter,
};
}
function mapDispatchToProps(dispatch) {
return {
onAsyncDecrease() {
dispatch(asyncDecrease());
},
onDecrease() {
dispatch(decrease());
},
onIncrease() {
dispatch(increase());
},
onAsyncIncrease() {
dispatch(asyncIncrease());
},
};
}
//容器组件
export default class CounterContainer extends React.Component {
constructor(props) {
super(props);
this.state = mapStateToProps(store.getState());
store.subscribe(() => {
this.setState(mapStateToProps(store.getState()));
});
}
render() {
const eventHandlers = mapDispatchToProps(store.dispatch);
return <Counter {...this.state} {...eventHandlers} />;
}
}
//App.js
import React from 'react';
import Counter from './components/Counter';
export default function App() {
return (
<div>
<Counter />
</div>
);
}
- 利用 connect 返回的高级包装组件
import React from 'react';
import {
asyncDecrease,
asyncIncrease,
increase,
decrease,
} from '../store/action/counter';
import { connect } from 'react-redux';
//展示组件
function Counter(props) {
return (
<div>
<h1>{props.number}</h1>
<p>
<button onClick={props.onAsyncDecrease}> 异步减 </button>
<button onClick={props.onDecrease}> 减 </button>
<button onClick={props.onIncrease}> 加 </button>
<button onClick={props.onAsyncIncrease}> 异步加 </button>
</p>
</div>
);
}
/**
* 将整个仓库的状态,映射到当前需要的数据
* @param {*} state
*/
function mapStateToProps(state, ownProps) {
console.log(ownProps);
return {
number: state.counter,
};
}
/**
* 映射事件处理函数
* @param {*} dispatch
*/
function mapDispatchToProps(dispatch) {
return {
onAsyncDecrease() {
dispatch(asyncDecrease());
},
onDecrease() {
dispatch(decrease());
},
onIncrease() {
dispatch(increase());
},
onAsyncIncrease() {
dispatch(asyncIncrease());
},
};
}
// 这里的mapDispatchToProps里面还可以使用bindActionCreators,
// 但是这种做法不能传参
/**
* 映射事件处理函数
* @param {*} dispatch
*/
// function mapDispatchToProps(dispatch) {
// return bindActionCreators({
// onAsyncDecrease: asyncDecrease,
// onDecrease: decrease,
// onIncrease: increase,
// onAsyncIncrease: asyncIncrease
// }, dispatch)
// }
// //connect返回一个高阶组件
// const hoc = connect(mapStateToProps, mapDispatchToProps)
// //传入展示组件,返回一个容器组件
// export default hoc(Counter)
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
//App.js
import React from 'react';
import { Provider } from 'react-redux';
import store from './store1';
import Counter from './components/Counter';
console.log(store.getState());
//高阶组件Counter中的abc属性会被传递给它包装的属性Counter(由于采用默认导出,同名没有影响)
// 如果加了ref,会进行ref转发
export default function App() {
return (
<Provider store={store}>
<Counter abc={123} />
</Provider>
);
}
- connect 的第二个参数传递的是对象
import React from "react"
import {
asyncDecrease,
asyncIncrease,
increase,
decrease,
} from "../store1/action/counter"
import { connect } from "react-redux"
//展示组件
function Counter(props) {
return (
<div>
<h1>{props.number}</h1>
<p>
<button onClick={props.onAsyncDecrease}> 异步减 </button>
<button onClick={props.onDecrease}> 减 </button>
<button onClick={props.onIncrease}> 加 </button>
<button onClick={props.onAsyncIncrease}> 异步加 </button>
</p>
</div>
)
}
/**
* 将整个仓库的状态,映射到当前需要的数据
* @param {*} state
*/
function mapStateToProps(state) {
return {
number: state.counter,
}
}
function mapDispatchToProps(dispatch) {
return {
onAsyncDecrease() {
dispatch(asyncDecrease())
},
onDecrease() {
dispatch(decrease())
},
onIncrease() {
dispatch(increase())
},
onAsyncIncrease() {
dispatch(asyncIncrease())
},
}
}
// 这里的mapDispatchToProps里面还可以使用bindActionCreators,
// 但是这种做法不能传参
/**
* 映射事件处理函数
* @param {*} dispatch
*/
// function mapDispatchToProps(dispatch) {
// return bindActionCreators({
// onAsyncDecrease: asyncDecrease,
// onDecrease: decrease,
// onIncrease: increase,
// onAsyncIncrease: asyncIncrease
// }, dispatch)
// }
const creators = {
onAsyncDecrease: asyncDecrease,
onDecrease: decrease,
onIncrease: increase,
onAsyncIncrease: asyncIncrease,
}
const hoc = connect(mapStateToProps, creators)
export default hoc(Counter)
手写 connect 类组件
import React, { PureComponent } from "react"
import { bindActionCreators } from "redux"
import ctx from "./ctx"
export default function (mapStateToProps, mapDispatchToProps) {
/**
* 返回一个高阶组件
*/
return function (Comp) {
//对于Temp组件,只有它需要的数据发生变化时才会重新渲染
class Temp extends PureComponent {
static contextType = ctx
/**
* 得到需要传递的事件处理属性
*/
getEventHandlers() {
if (typeof mapDispatchToProps === "function") {
return mapDispatchToProps(this.store.dispatch, this.props)
} else if (typeof mapDispatchToProps === "object") {
return bindActionCreators(mapDispatchToProps, this.store.dispatch)
}
}
constructor(props, context) {
super(props, context)
// console.log(this.context, 'context'); //{dispatch: ƒ, subscribe: ƒ, getState: ƒ, replaceReducer: ƒ, @@observable: ƒ}
this.store = this.context
if (mapStateToProps) {
// 状态中的数据,来自于仓库中映射的结果
this.state = mapStateToProps(this.store.getState(), this.props)
// 监听仓库中的数据变化,得到一个取消监听的函数
this.unlisten = this.store.subscribe(() => {
// debugger;
this.setState(mapStateToProps(this.store.getState(), this.props))
})
}
if (mapDispatchToProps) {
this.handlers = this.getEventHandlers()
}
}
componentWillUnmount() {
if (this.unlisten) {
//当组件卸载时,取消监听
this.unlisten()
}
}
render() {
// debugger;
console.log(`${Temp.displayName}重新渲染了`, this.state)
return <Comp {...this.state} {...this.handlers} {...this.props} />
}
}
Temp.displayName = Comp.displayName || Comp.name //类组件的名称和传入的组件一致
return Temp
}
}
react-redux 其他 api
详情参考:react-redux.js.org/
connect
- mergeProps: 一个函数
- 参数 1:stateProps,该参数的值来自于 mapStateToProps 返回的值
- 参数 2:dispatchProps,该参数的值来自于 mapDispatchToProps 返回的值
- 参数 3:ownProps,来自于组件使用者传递的属性
- 返回值:一个对象,该对象的属性最终会被传递到包装的组件中。
- options:配置对象
connect 源码-类组件版本
import React, { PureComponent } from "react"
import { bindActionCreators } from "redux"
import ctx from "./ctx"
export default function (mapStateToProps, mapDispatchToProps) {
/**
* 返回一个高阶组件
*/
return function (Comp) {
//对于Temp组件,只有它需要的数据发生变化时才会重新渲染
class Temp extends PureComponent {
static contextType = ctx
/**
* 得到需要传递的事件处理属性
*/
getEventHandlers() {
if (typeof mapDispatchToProps === "function") {
return mapDispatchToProps(this.store.dispatch, this.props)
} else if (typeof mapDispatchToProps === "object") {
return bindActionCreators(mapDispatchToProps, this.store.dispatch)
}
}
constructor(props, context) {
super(props, context)
// console.log(this.context, 'context'); //{dispatch: ƒ, subscribe: ƒ, getState: ƒ, replaceReducer: ƒ, @@observable: ƒ}
this.store = this.context
if (mapStateToProps) {
// 状态中的数据,来自于仓库中映射的结果
this.state = mapStateToProps(this.store.getState(), this.props)
// 监听仓库中的数据变化,得到一个取消监听的函数
this.unlisten = this.store.subscribe(() => {
// debugger;
this.setState(mapStateToProps(this.store.getState(), this.props))
})
}
if (mapDispatchToProps) {
this.handlers = this.getEventHandlers()
}
}
componentWillUnmount() {
if (this.unlisten) {
//当组件卸载时,取消监听
this.unlisten()
}
}
render() {
// debugger;
console.log(`${Temp.displayName}重新渲染了`, this.state)
return <Comp {...this.state} {...this.handlers} {...this.props} />
}
}
Temp.displayName = Comp.displayName || Comp.name //类组件的名称和传入的组件一致
return Temp
}
}
connect 源码-hook 版本
import React, { useContext, useState, useEffect } from "react"
import { bindActionCreators } from "redux"
import ctx from "./ctx"
function compare(obj1, obj2) {
for (const key in obj1) {
if (obj1[key] !== obj2[key]) {
return false
}
}
return true
}
// window.arr = [];
export default function (mapStateToProps, mapDispatchToProps) {
/**
* 返回一个高阶组件
*/
return function (Comp) {
//对于Temp组件,只有它需要的数据发生变化时才会重新渲染
function Temp(props) {
const store = useContext(ctx) //从上下文中拿到仓库
const [state, setState] = useState(
mapStateToProps && mapStateToProps(store.getState())
)
// window.arr.push(store); //window.arr[0] === window.arr[1] 说明store是currentState是不会变的
useEffect(() => {
return store.subscribe(() => {
var newState = mapStateToProps && mapStateToProps(store.getState())
// if(!compare(state,newState)){ //这里会产生闭包,每次state都是第一次state
// setState(newState);
// }
setState((prevState) => {
console.log(newState, prevState)
if (compare(prevState, newState)) {
//loading从false变为true 再变为false
return prevState
} else {
return newState
}
})
})
}, [store])
/**
* 得到需要传递的事件处理属性
*/
function getEventHandlers() {
if (typeof mapDispatchToProps === "function") {
return mapDispatchToProps(store.dispatch, props)
} else if (typeof mapDispatchToProps === "object") {
return bindActionCreators(mapDispatchToProps, store.dispatch)
}
}
var handlers = {}
if (mapDispatchToProps) {
handlers = getEventHandlers()
}
return <Comp {...state} {...handlers} {...props} />
}
Temp.displayName = Comp.displayName || Comp.name //类组件的名称和传入的组件一致
return Temp
}
}
connectAdvanced
该函数和 connect 一样,也是用于连接 React 组件和 Redux 仓库的,只不过它的配置比 connect 少一些
该函数需要传递两个参数:
- selectorFactory
- 参数 1:dispatch
- 参数 2:factoryOptions,配置
- 返回:函数
- 参数 1:state
- 参数 2:ownProps
- 返回的是一个对象:该对象的属性最终,会成为包装的组件的属性
- connectOptions
createProvider
createProvider(字符串 key):通过一个唯一的 key 值创建一个 Provider 组件。
var Provider1 = createProvider("p1")
var Provider2 = createProvider("p2")
预备知识
- chrome 插件:redux-devtools
- 使用 npm 安装第三方库:redux-devtools-extension
npm install --save redux-devtools-extension
redux 和 router 的结合(connected-react-router(以前用的是 reat-router-redux))
用于将 redux 和 react-router 进行结合
本质上,router 中的某些数据可能会跟数据仓库中的数据进行联动
该组件会将下面的路由数据和仓库保持同步
- action:它不是 redux 的 action,它表示当前路由跳转的方式(PUSH、POP、REPLACE)
- location:它记录了当前的地址信息
该库中的内容:
connectRouter
这是一个函数,调用它,会返回一个用于管理仓库中路由信息的 reducer,该函数需要传递一个参数,参数是一个 history 对象。该对象,可以使用第三方库 history 得到。
// history
// import { createBrowserHistory } from 'history';
// export default createBrowserHistory();
// store1/reducer
import student from "./student"
import counter from "./counter"
import { combineReducers } from "redux"
import history from "../history"
import { connectRouter } from "connected-react-router"
export default combineReducers({
student,
counter,
//添加路由状态
router: connectRouter(history),
})
routerMiddleware
该函数会返回一个 redux 中间件,用于拦截一些特殊的 action
import reducer from "./reducer"
import logger from "redux-logger"
import createSagaMiddleware from "redux-saga"
import rootSaga from "./saga"
import { composeWithDevTools } from "redux-devtools-extension"
import { routerMiddleware } from "connected-react-router"
import history from "./history"
const routerMid = routerMiddleware(history)
const sagaMid = createSagaMiddleware() //创建一个saga的中间件
const store = createStore(
reducer,
composeWithDevTools(applyMiddleware(routerMid, sagaMid, logger))
)
sagaMid.run(rootSaga) //启动saga任务
export default store
ConnectedRouter
这是一个组件,用于向上下文提供一个 history 对象和其他的路由信息(与 react-router 提供的信息一致)
之所以需要新制作一个组件,是因为该库必须保证整个过程使用的是同一个 history 对象
import React from "react"
import { Provider } from "react-redux"
import store from "./store1"
import { BrowserRouter, Route, Switch } from "react-router-dom"
import { ConnectedRouter } from "connected-react-router"
import Admin from "./pages/Admin"
import Login from "./pages/Login"
import history from "./store1/history"
export default function App() {
return (
<Provider store={store}>
<ConnectedRouter history={history}>
<Switch>
<Route path="/login" component={Login} />
<Route path="/" component={Admin} />
</Switch>
</ConnectedRouter>
</Provider>
)
}
// 用ConnectedRouter取代BrowserRouter,使用统一的history对象
{
/*
<BrowserRouter>
<Switch>
<Route path="/login" component={Login} />
<Route path="/" component={Admin} />
</Switch>
<BrowserRouter/>
*/
}
一些 action 创建函数
- push
- replace
// pages/StudentAdd.js
// 方法一:由于使用的是同一个history对象
import React from 'react';
import { push } from 'connected-react-router';
export default function StudentAdd({ history }) {
return (
<div>
<h1>添加学生页</h1>
<button
onClick={() => {
history.push('/courses');
}}
>
点击跳转到课程页面
</button>
</div>
);
}
// 方法二:通过改变仓库去映射
import React from 'react';
import { push } from 'connected-react-router';
import { connect } from 'react-redux';
function StudentAdd({ onClick }) {
return (
<div>
<h1>添加学生页</h1>
<button
onClick={() => {
onClick && onClick();
}}
>
点击跳转到课程页面
</button>
</div>
);
}
const mapDispatchToProps = (dispatch) => ({
onClick: () => {
dispatch(push('/courses'));
},
});
export default connect(null, mapDispatchToProps)(StudentAdd);
redux-toolkit
简单使用
npm install @reduxjs/toolkit react-redux npm i redux-devtools -D