redux的基本使用与原理手写

经常写react的同学,一定对redux这个库不陌生,它是一个状态管理的库,在我们平时的项目开发中扮演着一个很重要的角色,但是有时候我们过于关注使用,只记住了各种使用方式,反而忽略了他们的核心原理,但是如果我们想真正的提高技术,最好还是一个一个搞清楚,所以今天我们就来看看redux的基本使用还有其原理实现。

redux的设计思想:

  • 1、Redux是将整个应用状态存储到一个地方,称为store
  • 2、里面保存的是一颗状态树 state tree
  • 3、组件可以派发dispatch行为给store,而不是直接通知其他组件
  • 4、其他组件可以通过订阅store中的状态(state)来刷新自己的视图

redux三大原则

  • 1、整个应用的state被存储在一个object tree中,并且这个object tree只存在于唯一一个store中
  • 2、state是只读的,唯一改变state的办法就是触发action,action是一个用于描述已发生事件的普通对象,使用纯函数来修改,为了描述action如何改变state tree,你需要编写reducers
  • 3、单一数据源的设计让React的组件之间的通信更加方便,同时也便于状态的统一管理
    redux工作流:
    redux的基本使用与原理手写_第1张图片
    从这张图我们可以看出:
  • 1、当我们派发一个动作时,redux会交给reducer进行处理并且返回一个新的状态。
  • 2、订阅了store的状态(state)的组件会在状态变化后重新渲染视图。

接下来我们先看看基本的使用吧:

第一步,我们需要创建一个厂库:

 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的基本使用与原理手写_第2张图片
以上,我们就搭建了一个完整的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,这个方法就是把中间件进行组合。
级联中间件:
redux的基本使用与原理手写_第3张图片

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;

你可能感兴趣的:(redux,JavaScript,react.js,javascript,reactjs,前端,webpack)