经常写react的同学,一定对redux这个库不陌生,它是一个状态管理的库,在我们平时的项目开发中扮演着一个很重要的角色,但是有时候我们过于关注使用,只记住了各种使用方式,反而忽略了他们的核心原理,但是如果我们想真正的提高技术,最好还是一个一个搞清楚,所以今天我们就来看看redux的基本使用还有其原理实现。
redux的设计思想:
redux三大原则
接下来我们先看看基本的使用吧:
第一步,我们需要创建一个厂库:
import { createStore } from "redux";
import reducers from "./reducers";
//生成store对象
// 第一种创建厂库的方式
const store = createStore(
reducers,
);
//第二种创建厂库的方式
// const store = applyMiddleware(中间件)(createStore)(reducer)
export default store;
第二步,创建action types(动作类型):
export const SET_NAME = "SET_NAME";// 改变名字
export const SET_AGE = "SET_AGE";// 改变年龄
第三步,创建action(动作):
import * as types from "./../actions-type";
export default {
setName(data) {
return { type: types.SET_NAME, payload: data };
},
setAge(data) {
return { type: types.SET_AGE, payload: data };
},
};
第四步,创建reducer
import { SET_AGE } from "./../actions-type";
let initState = {
age: 18,
};
//改变年龄
function reducer(state = initState, action) {
switch (action.type) {
case SET_AGE:
return { age: action.payload };
default:
return { age: state.age };
}
}
export default reducer;
以上创建的就是redux使用中,需要用到的主要的角色代码,基本的一个开发目录如下图所示:
以上,我们就搭建了一个完整的redux开发目录,但是要想redux与视图绑定起来,即状态改变触发视图的更新,还无法实现,我们还需要借助另外一个库来完成,react-redux:
react-redux的基本使用:
react-redux提供了一个connect方法和一个Provider组件。
provider组件主要用于为容器组件提供state,connect的作用主要是将redux中的状态与组件关联起来,下面我们再一起来看看基本使用。
Provider组件:
我们需要在根index.js文件中引入该组件,并且将根页面组件包裹住,并且传入一个store属性,这样子在所有的组件中都能通过connect方法获取到store中的状态
import "antd/dist/antd.css";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import AppPage from "./app.page";
import Store from "./store/index";
ReactDOM.render(
// 包裹住根页面组件
<Provider store={Store}>
<AppPage />
</Provider>,
document.getElementById("root")
);
connect方法:
connect方法接受两个参数:mapStateToProps和mapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
mapStateToProps()
它是一个函数,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系。
mapStateToProps执行后应该返回一个对象,里面的每一个键值对就是一个映射。
const mapStateToProps = (state) => {
return {
name: state.name.name,
};
};
mapDispatchToProps()
mapDispatchToProps是connect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。
const mapDispatchToProps = (dispatch) => {
return {
setActionsAge:()=>{
return dispatch(actions.setAge(2000))
},
};
};
基于上面的代码,我们的react组件中就会多两个props属性,即:
componentDidMount() {
const {setActionsAge, name} = this.props
}
我们就可以通过调用mapDispatchToProps中定义出来的方法来派发动作,进而改变redux中的状态,最后达到更新视图的目的。
以上就是redux和react-redux的基本使用,下面我们来实现一下这其中的原理
首先我们知道redux中会导出三个方法,即createStore,combineReducers,applyMiddleware,bindActionCreators,compose。
我们就先依次把这些方法先实现一下。
createStore:
createStore接收三个参数,第一个是reducer,第二个是初始值,第三个参数官方称为enhancer,顾名思义他是一个增强器,用来增强store的能力的,通过调用createStore方法,我们会得到一个对像,对象中有三个方法,即 subscribe,getState,dispatch,此处先不考虑唇乳的enhance情况,实现如下:
import isPlainObject from "./utils/isPlainObject";
/**
*
* @param {*} reducer
* @returns
*/
function createStore(reducer, initState, enhancer) {
//考虑到createStore可以只传递两个参数的情况做一些判断
if (typeof initState === "function" && typeof enhancer === "undefined") {
enhancer = initState;
initState = undefined;
}
if (typeof enhancer !== "undefined") {
if (typeof enhancer !== "function") {
throw new Error(
`Expected the enhancer to be a function. Instead, received: '${typeof enhancer}'`
);
}
return enhancer(createStore)(reducer, initState);
}
//reducer必须是一个函数
if (typeof reducer !== "function") {
throw new Error("reduce must be a function");
}
let state; // 初始化state
let listener = []; // 保存所有的注册函数
if (typeof initState === "function") {
} else {
state = initState;
}
//订阅,每次调用都会返回一个取消订阅的方法
function subscribe(callback) {
listener.push(callback);
return () => {
listener = listener.filter((item) => item !== callback);
};
}
//获取状态,返回当前状态
function getState() {
return state;
}
//派发动作的时候,并且触发订阅函数的执行 => 发布订阅模式
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error("action必须是一个纯对象");
}
if (typeof action.type === "undefined") {
throw new Error("action的type属性不能是undefined");
}
state = reducer(state, action);
listener.forEach((item) => {
item();
});
}
// redux会在一开始的时候就去派发一个动作,为了初始化状态
dispatch({ type: "@@TYEP/REDUX_INIT" });
return {
subscribe,
getState,
dispatch,
};
}
export default createStore;
combineReducers
该方法的作用是把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,合并后的 reducers 可以调用各个子 reducer,并把他们的结果合并成一个 state 对象。state 对象的结构由传入的多个 reducer 的 key 决定。
最终,state 对象的结构会是这样的:
{
reducer1: ...
reducer2: ...
}
实现 如下:
/**
*
* @param {*} reducerMap reducer对象 => {reducer1: xxx , reducer2: xxx}
* @returns
*/
function combineReducers(reducerMap) {
const reducerKeys = Object.keys(reducerMap); // 先把参数里面所有的键值拿出来
// 返回值是一个普通结构的reducer函数
const reducer = (state = {}, action) => {
const newState = {};
for (let i = 0; i < reducerKeys.length; i++) {
// reducerMap里面每个键的值都是一个reducer,我们把它拿出来运行下就可以得到对应键新的state值
// 然后将所有reducer返回的state按照参数里面的key组装好
// 最后再返回组装好的newState就行
const key = reducerKeys[i];
// 获取到每一个reducer处理函数
const currentReducer = reducerMap[key];
// 获取到每一个reducer之前的state
const prevState = state[key];
// 获取到每一个reducer现在的值
newState[key] = currentReducer(prevState, action);
}
// 返回最新的值
return newState;
};
return reducer;
}
export default combineReducers;
bindActionCreators
bindActionCreators方法其实就是通过dispatch将action包裹起来,这样可以直接在action中返回一个纯对象,而不用dispatch去派发。
例如正常的action派发动作我们应该写成:
add(dispatch,value){
return dispatch({type:types.ADD, payload:value});
},
经过bindActionCreators处理之后,即:
add(value) {
return {type:types.ADD, payload:value};
},
let newAction = bindActionCreators(add,dispatch)
我们在组件中就可以直接执行 newAction() 来派发这个动作了,而不需传入dispatch方法,它内部的原理相当于就是用dispatch重新包裹了一层,并且返回了一个新的函数
原理
//传入的第一种情况: 一般传入的action是这样子的
const action = {
add: (value)=>{
return {type: 'add', payload: value}
}
}
//传入的第二种情况:或者直接传入一个方法 action.add
//将每一个函数重写,用dispatch方法进行绑定
function bindActionCreator(action,dispatch){
return function(){
return dispatch(action.call(this,...arguments))
}
}
export default function bindActionCreators(actions, dispatch){
if(typeof actions === 'function'){
return bindActionCreator(actions,dispatch)
}
//如果传入的不是一个方法,那么默认它传入的就是一个对象
let boundActionCreators = {};
for(let key in actions){
let actionItem = actions[key];
boundActionCreators[key] = bindActionCreator(actionItem,dispatch)
}
return boundActionCreators
}
applyMiddleware
使用这个方法可以增强redux的功能,可以传入一些中间件来处理不同的应用场景。
通常我们创建厂库的时候可以这么来使用:
let store = applyMiddleware(中间件)(createStore)(reducers)
从中我们看得出applyMiddleware中间返回了两个函数,即:
function applyMiddleware(middleware) {
return function(createStore){
return function(reduce){
//这个是原始的厂库
let store = createStore(reducer);
return store
}
}
}
export default applyMiddleware;
applyMiddleware的原理就是重写了redux的dispatch方法:
因为中间件的写法就是下面这个样子的:
export default function loggerMiddleware(store) {
return function (dispatch) {
return function (action) {
};
};
}
所以结合中间件的写法,我们可以写出当只接收一个中间件的源码:
function applyMiddleware(middleware) {
return function (createStore) {
return function (reduce) {
//这个是原始的厂库
let store = createStore(reducer);
//抛出一个错误,防止该变量被引用,主要是定义一个变量,保存新的重写之后的dispatch方法
let dispatch = () => {
throw new Error("null");
};
middleware = middleware(store);
//保存新的重写之后的dispatch方法
dispatch = middleware(store.dispatch);
return store;
};
};
}
当有多个中间件时,我们就需要将中间件进行组合,源码中有一个方法叫做compose,这个方法就是把中间件进行组合。
级联中间件:
import compose from "./compose";
function applyMiddleware(...middleware) {
return function (createStore) {
return function (reducer) {
//这个是原始的厂库
let store = createStore(reducer);
//抛出一个错误,防止该变量被引用,主要是定义一个变量,保存新的重写之后的dispatch方法
let dispatch = () => {
throw new Error("null");
};
//定义一个对象,主要是为了保存新的dispatch方法,并且作为参数传入到每一个中间件中
let middlewareApi = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
};
//去掉每一个中间件的第一层
const chain = middleware.map((item) => {
return item(middlewareApi);
});
//保存新的重写之后的dispatch方法
// 组合中间件
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch,
};
};
};
}
export default applyMiddleware;
compose
组合函数,达到类似于下面的这种效果:
function add1(str) {
return "1" + str;
}
function add2(str) {
return "2" + str;
}
function add3(str) {
return "3" + str;
}
function compose(...funcs) {
if (funcs.length === 0) {
return (args) => args;
}
if (funcs.length === 0) {
return funcs[0];
}
return funcs.reduce((a, b) => {
return (...args) => {
return a(b(...args));
};
});
}
let result = compose(add1, add2, add3)("hello");
console.log(result); // => 123hello
实现如下:
function compose(...funcs) {
if (funcs.length === 0) {
return (args) => args;
}
if (funcs.length === 0) {
return funcs[0];
}
return funcs.reduce((a, b) => {
return (...args) => {
return a(b(...args));
};
});
}
export default compose;