1.Redux核心概念
纯函数
- 确定的输入,一定会产生确定的输出;
- 函数在执行过程中,不能产生副作用
store
存储数据
action
更改数据
reducer
连接store和action的纯函数
将传入的state和action结合,生成一个新的state
dispatch
派发action修改storesubscribe | unsubscribe
订阅store的数据发生变化
// store/index.js
import { createStore } from 'redux';const initState = {msg: "hello redux"
}
/*** 定义reducer,纯函数*****/
function reducer(state = initState, action){if (action.type === "change"){return {...state, msg: action.payload.msg};}return state;
}export default const store = createStore(reducer);// store/ actionCreator.js
/**** 动态生成action *****/
export const CHANGEMSGACTION = msg => ({type: 'change', payload: {msg}});// 使用的地方
import store from "~/store";
const unsubscribe = store.subscribe(() => {console.log("::::STORE", store.getState());
})
unsubscribe();// 修改store中的数据
const MSGAction = {type: "change", payload: {msg: "hello change",}
};
store.dispatch(MSGAction);
combineReducer
将多个reducer合并为一个reducer,达到拆分store的目的
2. Redux三大原则
单一数据源
- 整个应用程序的state被存储在一颗object tree中,并且这个object tree只存储在
一个 store
中: - R-edux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护;
- 单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改;
State是只读的
- 唯一修改State的方法一定是触发
action
,不要试图在其他地方通过任何的方式来修改State: - 这样就确保了View或网络请求都不能直接修改state,它们只能通过action来描述自己想要如何修改state;
- 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题;
使用纯函数来执行修改
- 通过
reducer
将 旧state和 actions联系在一起,并且返回一个新的State: - 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作不同state tree的一部分;
- 但是所有的reducer都应该是纯函数,不能产生任何的副作用;
3.react-redux的使用
- 通过
provider
给整个app提供store
// App.jsx
import { Provider } from 'react-redux';
import store from '~/store';const root = document.querySelector("#root");
root.render(<Provider store={store}><App/></Provider>
)
- 通过
connect
将组件和store
连接。connect会返回一个高阶组件,接受的参数将store中的部分数据映射到组件
// 组件中
import React, { PureComponent } from "react";
import { connect } from "react-redux";class MyComp extends PureComponent{render(){const { msg, changeMsg } = this.props;return (<div><h2>{msg}</h2><input onChange={val => changeMsg(val)} /></div>}
}
/**** 将state映射到props,组件中props中就会有msg ****/
function mapStateToProps(state){return {msg: state.msg}
}
/*** 将修改store的函数添加到组件的props中 ***/
function mspDispatchToProps(dispatch){return {changeMsg(msg){dispatch(CHANGEMSGACTION(msg));}}
}export default connect(mapStateToProps, mapDispatchToProps)(MyComp);
异步action
—中间件- Middleware可以帮助我们在请求和响应之间嵌入一些操作的代码,比如cookie解析、日志记录、文件压缩等操作
createStore
的第二个参数接受一个中间件,使用react-thunk
使得dispatch可以派发函数,在派发的函数中可以异步更新store。
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';const reducer = (state, action) => {...return state;
}const store = createStore(reducer, applyMiddleware(thunk));// actionCreator.js
...
/**** 被派发的函数,需要返回一个函数,该函数接受两个参数dispatch,getState,*****/
const fetchHomeDataAction = () => {return (dispatch, getState) => {fetch(url).then(res => {dispatch(HOMEDATAACTION(res.data));});}
}
// 组件中
function mapDispatchToProps(dispatch){return {fetchHomeData(){dispatch(fetchHomeDataAction()); // 执行action函数}}
}
- 使用redux-thunk
- 在创建store时传入应用了middleware的enhance函数
通过applyMiddleware来结合多个Middleware, 返回一个enhancer;将enhancer作为第二个参数传入到createStore中; - 定义返回一个函数的action:
这里不是返回一个对象了,而是一个函数;该函数在dispatch之后会被执行;
- 在创建store时传入应用了middleware的enhance函数
4.Redux/toolkit
npm install @reduxjs/toolkit react-redux
createSlice({name, initialState, reducers:{}})
接受reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions。- name:用户标记slice的名词, 在之后的redux-devtool中会显示对应的名词;
- initialState:初始化值. 第一次初始化时的值;
- reducers:相当于之前的reducer函数.对象类型,并且可以添加很多的函数;函数类似于redux原来reducer中的一个case语句;
- 参数一:state
- 参数二:调用这个action时,传递的action参数;
import { createSlice } from '@reduxjs/toolkit';
const CounterSlice = createSlice({name: "counter",initialState: {count: 0,},reducers: {addNumber(state, action){state.counter += action.payload;}}
});export const { addNumber } = CounterSlice.action;
export default CounterSlice.reducer;
configureStore
包装createStore以提供简化的配置选项和良好的默认值。它可以自动组合你的 slice reducer,添加你提供的任何 Redux 中间件,redux-thunk默认包含,并启用 Redux DevTools Extension。
const store = configureStore({reducer: {counter: counterReducer;}
})
createAsyncThunk
接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected基于该承诺分派动作类型的 thunk
const AXIOSDataSlice = createSlice({name: 'axiosdata',initialState: {data: []},reducers: {setData(state, action){state.data = action.payload;}},extraReducers: {/**[AxiosMultidataAction.pending](state, action){state.data = action.payload;}[AxiosMultidataAction.rejected](state, action){state.data = action.payload;}**/[AxiosMultidataAction.fulfilled](state, action){state.data = action.payload;}}/*** 链式写法 *****/extraReducers: (builder) => {builder.addCase(AxiosMultidataAction.pending, (state, action) => {console.log("pending"); }).addCase(AxiosMultidataAction.fulfilled, (state, action) => {})}
})
export default AXIOSDataSlice.reducer;const AxiosMultidataAction = createAsyncThunk("axiosdata", async (extraInfo, store) => {
// 第一个canconst res = await getData();return res;
})
immerjs
库保持数据不可变(持久化数据
)
5 手写connect
function connect(mapStateToProps, mapDispatchToProps){function hoc(Component){class HOCComponent extends PureComponent{constuctor(props){super(props);this.state = mapStateToProps(store.getState());}componentDidMount(){this.unsubscribe = store.subscribe(() => {//this.forceUpdate();this.setState(mapStateToProps(store.getState());})}componentWillUnmount() {this.unsubscribe();}render(){return <Component {...this.props} {...mapStateToProps(store.getState())} {...mapDispatchToProps(store.dispatch)} />}}return HOCComponent;}return hoc;
}
6. 自己实现一些中间件
- 打印日志中间件
function log(store){const next = store.dispatch;function logAndDispatch(action) {console.log("dispatch action", action);next(action);console.log("dispatch resulte", store.getState());}store.dispatch = logAndDispatch;
}log(store);
- 自动实现
thunk
function thunk(store){const next = store.dispatch;function thunkDispatch(action) {if (typeof action === 'function') {action(store.dispatch, store.getState());} else {next(action);}}store.dispatch = thunkDispatch;
}
applyMiddleware
的封装
function applyMiddleware(store, ...fns){fns.forEach(fn => {fn(store);})
}