redux状态管理
第一节、redux 介绍
Section titled “第一节、redux 介绍”
Redux是JavaScript的状态容器,提供了可预测的状态管理,对与应用中越来越多,越来越复杂的数据进行管理。
- 优势:包括依赖在内,大小只有2kb
Redux除了和React一起使用之外,可以在任何地方使用(比如Vue,小程序)
redux 三大原则
Section titled “redux 三大原则”1、单一数据源
Section titled “1、单一数据源”-
整个应用程序的state被存储在一颗
object tree中,并且这个object tree只存储在一个store中: -
Redux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护; -
单一的数据源可以让整个应用程序的
state变得方便维护、追踪、修改;
2、State是只读的
Section titled “2、State是只读的”- **唯一修改
State的方法一定是触发action**,不要试图在其他地方通过任何的方式来修改State: - 这样就确保了
View或网络请求都不能直接修改 state ,它们只能通过action来描述自己想要如何修改state; - 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题;
3、使用纯函数来执行修改
Section titled “3、使用纯函数来执行修改”-
通过
reducer将 旧state和actions联系在一起,并且返回一个新的State: -
随着应用程序的复杂度增加,我们可以将
reducer拆分成多个小的reducers,分别操作不同state tree的一部分; -
但是所有的
Section titled “但是所有的reducer都应该是纯函数,不能产生任何的副作用;”reducer都应该是纯函数,不能产生任何的副作用;还是 SCU 优化浅层比较的原因。
-
要每次都返回新的state, 这个比较和组件中的state
第二节、redux 的核心
Section titled “第二节、redux 的核心”当在整个应用中进行管理全局状态,如果没有定义统一的规范来操作这段数据,那么整个数据的变化就是无法跟踪的,出现了
bug很难维护的
- 而
store就是应用中,统一管理状态的对象
action-dispatch
Section titled “action-dispatch”-
所有状态的变化,只能由
dispacth进行修改,整个是redux的原则。方便
react-devtool进行跟踪,所有的状态变化都是可跟追、可预测的
reducer
Section titled “reducer”
reducer必须是一个纯函数,用来将dispatch和state联系在一起
reducer做的事情就是将传入的state和action结合起来生成一个新的state- 参数一: 上次返回的
state - 参数二:
action对象
第三节、Redux的使用过程
Section titled “第三节、Redux的使用过程”需要先进行安装redux库
npm install redux
一、初始化 redux-store,配置reducer
Section titled “一、初始化 redux-store,配置reducer”-
reducer要保证是一个纯函数之所以是纯函数的原因,还是
SCU优化,浅层比较的原因,此处的浅层比较和组件中的前层比较有些不同,reducer只会比前后的state
//引入依赖const {createStore} = require('redux')//设置一个默认的stateconst defaultState = { count: 0, message: "zhangsan"}//通过reducer 创建 store 就可以直接使用了module.exports = createStore(reducer)//将defaultState 当作默认值是规范而已function reducer(state = defaultState, action) { switch(action.type) { case "editCount": return {...state, count: action.count} default: return state }}immutable 的使用
Section titled “immutable 的使用”
redux中的数据不可变性
-
在
redux中 每次修改state都需要浅拷贝一下,当state特别大的时候,进行浅拷贝也是一种性能浪费利用
immutable(不可变的) 减少开销原理:当一个对象树被修改之后会改变当前的节点的值,根据这个以变的值,重新生成一个对象树,但是会尽可能的复用已有的节点。
-
在
redux Toolkit底层使用的是immerjs这个库来保证数据的不可变性的
import { Map } from 'immutable';
import * as actionTypes from './constants';
const defaultState = Map({ hotAlbums: [], topAlbums: [], topTotal: 0})
export default function reducer(state = defaultState, action) { switch (action.type) { case actionTypes.CHANGE_HOT_ALBUMS: return state.set("hotAlbums", action.hotAlbums); case actionTypes.CHANGE_TOP_ALBUMS: return state.set("topAlbums", action.topAlbums); case actionTypes.CHANGE_TOP_TOTAL: return state.set("topTotal", action.total); default: return state; }}二、调用action
Section titled “二、调用action”const store = require("../store")//这样虽然也可以进行修改但是不能,进行跟踪// store.getState().count = 100
const countAction = { type: "editCount", count: 200}
store.dispatch(countAction)console.log(store.getState())三、subscribe订阅
Section titled “三、subscribe订阅”- 当通过
dispatch进行state修改的时候,会自动调用subscribe的回调
//subscribe 会返回一个取消订阅的函数 执行 subscribe() 函数之后会取消订阅const unsubscribe = store.subscribe(() => { console.log("zhangsna", store.getState())})第四节、Redux 结构划分规范
Section titled “第四节、Redux 结构划分规范”通常应用中会将
store、reducer、action、constants拆分成一个个文件
actionCreator.jsconstants.jsindex.jsreducer.js
-
创建
store/index.js文件:用于组装
reducer创建store,createStore()主文件 -
创建
store/reducer.js文件:由于过多的
action方法,会让整个的函数体变得非常大,和state都放在同一个js中,阅读性会很差。- 可以将单独的
action拆分出来
- 可以将单独的
-
创建
store/actionCreators.js文件:actionnCreators.js的拆分取决于dispatch方法的形参类型。dispatch规范要传入一个包含type字段的对象,就会存在很多的action对象。进行拆分的原因是方便统一管理
-
创建
store/constants.js文件:同
vuex,action的调用是通过type属性字符串类型进行定义的。action中type的字符串属性值,既要在 reducer 中使用也要在定义action对象的时候使用- 这样很容易拼写错误
- 可以定义一些常量,来避免发生一些不必要的错误
第五节、react-redux
Section titled “第五节、react-redux”开始之前需要强调一下,redux和react没有直接的关系,你完全可以在React, Angular, Ember, jQuery, or vanilla、JavaScript中使用Redux。
-
尽管这样说,
redux依然是和React库结合的更好,因为他们是通过state函数来描述界面的状态,Redux可以发射状态的更新,让他们作出相应 -
官方提供了
react-redux的库,用于链接 react 和 redux,可以更好的结合,也更加方便的在项目中使用redux,并且实现的逻辑会更加的严谨和高效。Terminal window npm install react-redux
一、react-redux实现原理
Section titled “一、react-redux实现原理”1、通过 Context 对 store 解耦
Section titled “1、通过 Context 对 store 解耦”1.1.创建上下文
Section titled “1.1.创建上下文”//1、创建上下文import {createContext} from 'react'export const Zhangsan = createContext()1.2.提供上下文-store
Section titled “1.2.提供上下文-store”render() { return ( //上下文中提供store <Zhangsan.Provider value={store}> <AppStyle> <div> <h3>App</h3> <div className="container"> <div className="counterA item"> <Counter1/> </div> <div className="counterB item"> <Counter2/> </div> <div className="counterC item"> <Counter3/> </div> </div> </div> </AppStyle> </Zhangsan.Provider> )}2、创建connect高阶函数实现自动刷新
Section titled “2、创建connect高阶函数实现自动刷新”//创建connect高阶函数实现自动刷新import { PureComponent } from "react";import { Zhangsan } from '../ctx/Provider'
export function connect(mapStateToProps, mapActionToProps) {
return function (Cpn){
class Temp extends PureComponent { constructor(props, context) { super(props) this.state = mapStateToProps(context.getState()) }
componentDidMount() { this.unsubscribe = this.context.subscribe(e => { this.setState(mapStateToProps(this.context.getState()) }) } componentWillUnmount() { this?.unsubscribe && this.unsubscribe() }
render() { return <Cpn {...mapStateToProps(this.context.getState())} {...mapActionToProps(this.context.dispatch)} {...this.props}></Cpn> } } Temp.contextType = Zhangsan
return Temp }}二、react-redux 的使用
Section titled “二、react-redux 的使用”1、导入内置上下文组件传入store
Section titled “1、导入内置上下文组件传入store”import React, { PureComponent } from 'react'import {Provider} from 'react-redux'import Counter1 from '../components/Counter1'import store from '../store'
export class App extends PureComponent { render() { return ( //传入store <Provider store={store}> <div className="container"> <Counter1/> </div> </Provider> ) }}
export default App2、导入connect使用
Section titled “2、导入connect使用”- 提示:
connect(mapStateToProps, mapActionToProps)(Counter1),参数函数返回的对象属性都会添加到Counter1组件的props属性当中
import React, { PureComponent } from 'react'//使用connect高阶函数import {connect} from 'react-redux'import {addCounter} from '../store/actionCreator'
export class Counter1 extends PureComponent {
render() { return ( <div> <h3>Counter1</h3> <div className="box"> {/*使用store传入属性*/} <div className="title">{this.props.message}</div> <div className="title">{this.props.counter}</div> </div> <div className="contorl"> {/*使用store传入函数*/} <button onClick={e => this.props.addCounter(7)}>trigger</button> </div> </div> ) }}//解耦,声明的state 都会传入到props中const mapStateToProps = (state) => ({ message: state.message, counter: state.counter})//声明的函数也同样会添加到 props 中const mapActionToProps = (dispatch) => ({ addCounter(num) { dispatch(addCounter(num)) }})//对组件进行增强export default connect(mapStateToProps, mapActionToProps)(Counter1)第六节、redux-thunk执行异步操作
Section titled “第六节、redux-thunk执行异步操作”-
redux中默认只能dispatch对象。这样会造成,“异步请求” 的 ”逻辑“ 无法在 action 中进行
-
可以使用
redux-thunk中间件,来实现让dispatch派发一个 “函数”,这样的话就可以将异步请求的逻辑放到acion里了
一、中间件 Middleware
Section titled “一、中间件 Middleware”Middleware可以在请求和响应之间 “嵌入” 一些操作的代码,类似express,帮助应用扩展一些其他功能
- 比如日志记录、调用异步接口、添加代码调试功能等等;
redux-thunk中间件是 “官方推荐” 的,用于嵌入react和redux 中间可以实现异步请求。
二、redux-thunk 的使用
Section titled “二、redux-thunk 的使用”-
redux默认使用dispatch的时候默认是不能分发函数的。 -
当然异步请求也可以写在
mapActionToProps方法中这样的话逻辑还是在当前组件中,并没有在 redux 中。当其他组件调用的时候,无法传入对应请求参数。
-
所以要让异步请求的逻辑放到 redux 中,这样不同的组件都可以传入不同的请求参数,来改变数据
虽然不太会有这种情况
-
-
redux-thunk应用之后允许,派发一函数,并将dispatch传入到分发函数的形参
1、配置中间件 applyMiddleware
Section titled “1、配置中间件 applyMiddleware”导入之后,可以利用
redux包中的applyMiddleware方法进行应用
import {createStore, applyMiddleware} from 'redux'import { reducer } from './reducer'//导入import thunk from 'redux-thunk'
//应用中间件方法 applyMiddlewareconst store = createStore(reducer, applyMiddleware(thunk))export default store3、配置Action
Section titled “3、配置Action”使用
redux-thunk之后允许dispatch一个高阶函数
-
配置
Section titled “配置 actionCreator”actionCreator-
会将
dispatch()方法赋值给返回函数的的形参以便于在
action中发送网络请求
export const findBanner = (banner) => ({type: actionTypes.findBanner,banner})export const findBannerInfo = () => {return function (dispatch) {fetch("http://123.207.32.32:8000/home/multidata").then(res => res.json()).then(res => {dispatch(findBanner(res.data.banner.list))})}} -
-
//组件中调用componentDidMount() {this.props.findBannerInfos()}//属性 和 函数 都会添加到 props 中export default connect(state => ({banner: state.banner}),dispatch => ({findBannerInfos() {dispatch(findBannerInfo())}}))(Counter3)
三、redux-thunk 的实现原理
Section titled “三、redux-thunk 的实现原理”-
个人理解:还是利用模块化
利用hack一点的技术:
Monkey Patching,在模块第一次加载执行的时候就修改原有的程序逻辑- 创建完 store的时候,直接替换掉
dispatch,手动封装一个 - 也是 store 中间件的实现方式,可以创建一个
applyMiddleWare.js, 将中间件进一步封装
- 创建完 store的时候,直接替换掉
const store = createStore(reducer)
function thunk(store) { //保存原有的 const next = store.dispatch function dispatch(action) {
if(typeof action === "object") { next(action) } else if(typeof action === "function") { action(store.dispatch) }
}
store.dispatch = dispatch
}//直接调用,修改逻辑thunk(store)第七节、Reducer代码拆分
Section titled “第七节、Reducer代码拆分”在
redux包中提供了,combineReducers方法,帮助进行多reducer合并
combineReducers 的使用
Section titled “combineReducers 的使用”-
直接在
redux包中导入即可 -
使用:需要
state. **“合并的属性名” **. “该模块中state属性”import {createStore, combineReducers} from 'redux'const reducer = combineReducers({counter1: counter1Reducerscounter2: counter2Reducers}) -
dispatch的时候,就需要手动定制规范,在action.type前面添加统一的模块名前缀const addCouner = "COUNTER1_ADD_COUNTER"const fetchBanner = "COUNTER1_FATCH_BANNER"
combineReducers 实现原理
Section titled “combineReducers 实现原理”- 就是利用了,每次返回的
state都会,传入当前reducer的第一个形参中 - 这样调用给的state的时候,正好用于区分那个模块的state
- 例:现在的
state的结构就是state = { counter1: counter1.state}
//这里给默认值是防止 state.couter1 报错,这样就算值等于undefined 的时候 每个reducer 都有默认值function reducer(state = {}, action) {
//返回的state,在次调用的时候都会当作第一个形参传入 return { //这样的话,当前对象就是下一次传入的state,state.counter1 就是上次自己返回的state counter1: counter1(state.counter1, action), counter2: counter2(state.counter2, action), }}第八节、redux-devtools
Section titled “第八节、redux-devtools”
react-devtools不用配置,直接下载就好这里就不多说了,redux-devtools需要配置下
-
redux官网为我们提供了
redux-devtools的工具,可以跟踪每次状态是如何被修改的,修改前后的状态变化 -
创建store的时候进行配置
-
查看配置可以到 github 查看。
//之后安装了 redux devtools 之后winddows才会有这个函数__REDUX_DEVTOOLS_EXTENSION_COMPOSE__,生产环境删除掉就可以了//const composeEnhancers = compose;//trace 是开启跟踪,可以查看调用栈,点击之后可以跳到代码执行的未知const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true}) || compose;const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)))第九节、Redux Toolkit
Section titled “第九节、Redux Toolkit”
Redux Toolkit是官方推荐的编写 Redux 逻辑的方法,也将之称为“RTK”;
- Redux Toolkit包旨在成为编写Redux逻辑的标准方式,从而解决redux的编写逻辑过于的繁琐和麻烦,和文件拆分,不利于管理的问题;
- 21年就有
npm install @reduxjs/toolkit react-redux一、Redux Toolkit特点及优势
Section titled “一、Redux Toolkit特点及优势”-
createAsyncThunk:
Section titled “createAsyncThunk:”- 接受一个动作类型字符串和一个返回承诺的函数,并生成一个
pending/fulfilled/rejected基于该承诺分派动作类型的thunk
- 接受一个动作类型字符串和一个返回承诺的函数,并生成一个
-
解决了复杂常量的抽取,和多action的声明,避免
action和reducer case不匹配的问题redux Toolkit可以直接将reducers option直接导出进行调用, -
多
reducer的合并问题,和异步请求的问题,自动合并了react-thunk还是需要
react-redux -
自动给
action添加前缀。直接导出模块中的action方法 -
自动配置
redux DevTools
二、configureStore
Section titled “二、configureStore”用于合并其他的
reducer片段创建store,
-
包装
createStore以提供简化的配置选项和良好的默认值。 -
它可以自动组合你的
Section titled “它可以自动组合你的 slice reducer,”slicereducer,可以自动合并其他的
reducer片段 -
添加你提供的任何
Redux中间件, -
redux-thunk默认包含,默认就可以发送异步请求
Section titled “redux-thunk默认包含,默认就可以发送异步请求” -
并启用
Section titled “并启用 Redux DevTools Extension。不用在及逆行配置了”ReduxDevToolsExtension。不用在及逆行配置了
-
参数 :
Object类型, -
传入
options选项-
Section titled “reducer:Object 类型”reducer:Object类型接收其他的 reducer slice-片段, 将slice中的reducer可以组成一个对象传入此处;
-
middleware:可以使用参数,传入其他的中间件(自行了解);
- 默认继承了
redux thunk和redux DevTools的配置
- 默认继承了
-
devTools:是否配置devTools工具,默认为true;
import {configureStore} from '@reduxjs/toolkit'import counterReducer from './counter1'const store = configureStore({reducer: {conter1: counterReducer},devTools: true})export default store -
三、createSlice
Section titled “三、createSlice”接受
reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions。
- 参数是
options对象
import {createSlice} from '@reduxjs/toolkit'
const reducer = createSlice({ name: "counter1", //初始化数据 initialState: { counter: 12 },
//类似与vue options 中的 methods reducers: { //这里的action 对象同样保存这type类型,但是是会将传入的实参,存放到该对象的payload属性中 addCounter(state, action) { //这里直接修改 state 属性即可,不需要return state state.counter = state.counter + action.payload } },
}) //注意:这里是导出的是actions 不是 reducersconst { addCounter } = reducer.actions//这里导出的也reducerSlice 中的reducer 属性export default reducerSlice.reducerexport { addCounter }-
name:字符串类型:用户标记
slice的名词 在之后的redux-devtool中会显示对应的名词;-
redux-devtool根据name来进行跟踪,区分state的类型使用
name,自动给dispatch的action的前缀 -
一般当前模块是什么名字就起什么名字
-
-
initialState:对应defaultState对象类型:初始化值,第一次初始化时的值;
-
reducers:对应 reducer 函数对象类型,并且可以添加很多的函数,
方法似于
redux原来**reducer中的一个case语句**,根据action中的type属性对应方法名,进行逻辑处理- 此步骤省去了,复杂的常量抽取,和多
action的声明,可以直接导出reducers的声明直接进行调用
- 参数一:
state - 参数二:调用这个
action时,传递的action参数;createSlice返回值是一个对象,包含所有的actions;
- 此步骤省去了,复杂的常量抽取,和多
四、redux Toolkit 异步请求
Section titled “四、redux Toolkit 异步请求”这里主要记两种写法
第一种 extraReducers options写法
Section titled “第一种 extraReducers options写法”在
createSlice的options选项种会有一个额外的reducer选项用来配置异步请求extraReducers
- 重点:发送异步请求,需要使用
@reduxjs/toolkit包中的createAsyncThunk函数来创建一个异步请求 - 个人决定,只能使用
async - 还有一种类似的
addCase函数链式调用方法。这里就不写了
import {createSlice, createAsyncThunk} from '@reduxjs/toolkit'//这里的 extraInfo 是调用的findBanner 传入的参数const findBanner = createAsyncThunk("zhnagsan", async (extraInfo, {dispatch}) => {
const res = await fetch("http://123.207.32.32:8000/home/multidata").then(res => res.json())
//这里返回成功的话,就会进入 fulfilled 状态,组装action和 “zhnagsan” 组装action传入到,extraReducers options 中该方法的 fulfilled 中 return res.data.banner.list})
const reducerSlice = createSlice({ name: "counter1", //初始化数据 initialState: { counter: 12, banner: [] },
reducers: { changeBanner(state, action) { state.banner = action.payload console.log(state.banner) } }, //在这里配置异步请求 extraReducers: { //这里处理 fulflled 还有 rejected 和 opening 状态 //[findBanner.rejected]:(state, action) =>{} //[findBanner.opening]:(state, action) =>{} [findBanner.fulfilled]: (state, action) => { state.banner = action.banner } }
})export const {changeBanner} = reducerSlice.actionsexport default reducerSlice.reducer第二种 createAsyncThunk 单独提交
Section titled “第二种 createAsyncThunk 单独提交”-
参数方法,会有第二个参数对象类型,包含了
dispatch、state… 一些对象可以直接在这里分发 -
区别就是,在这里提交,在
reducers选项种修改,而返回值的方法要在extraReducers种修改反正请求方法是要创建的,修改
state要么在reducers中写,要么在extraReducers中写
const findBanner = createAsyncThunk("张三", (extraInfo, {state, dispatch}) => {
fetch("http://123.207.32.32:8000/home/multidata").then(res => res.json()).then(res => { //changeBanner这个方法还是要在reducers 中创建的 dispatch(changeBanner(res.data.banner.list)) })
})export const {changeBanner} = reducerSlice.actionsexport default reducerSlice.reducer