让react用起来更得心应手——(react-redux)

让react用起来更得心应手系列文章:

  1. 让react用起来更得心应手——(react基础简析)
  2. 让react用起来更得心应手——(react-router原理简析)
  3. 让react用起来更得心应手——(react-redux原理简析)

Redux应用场景

在没有redux出来之前,父组件和子组件之间,平行组件之间传递和修改状态,需要将状态和修改状态的方法逐级往下传,组件嵌套过深则很容易出现管理混乱的问题。所以redux就是为了解决状态管理而诞生的。

redux应用场景

Redux实现

基础框架

//用来创建Store
function createStore(reducer){}

// 抽离出不同的type调用dispatch函数的复用部分
function bindActionCreators(actions,dispatch){}

// 合并reducer
function combineReducers (reducers){}

// 注册中间件
function applyMiddleware (...middlewares){}

//???????
function compose(...args){}

export  {
  createStore,
  bindActionCreators,
  combineReducers,
  compose,
  applyMiddleware
}
复制代码

Redux的设计思想

Redux的核心createStore

  1. 创建一个state变量用来存储平行组件间需要共享的变量
  2. 提供getState方法给外界获取state
  3. 提供subscribe方法给外界订阅回调在dispatch修改state时执行
  4. 提供dispatch方法给外界调用用户注册的reucer来修改state并执行订阅的回调
function createStore(reducer){
    let state;  //存放状态
    let listeners = []; //存放订阅的回调
    
    function dispath(action){
        state = reducer(state, action); // 调用reducer返回新的state
        listeners.forEach(fn=>fn());    // 发布所有订阅的回调
    }
    
    // 派发初始动作,type为reducer中没有的类型,目的是初始化状态为用户设置的状态
    dispatch({type:'@INIT'}); 
    
    function getState(){
        // 暴露的state属性不希望别人能改,改了也不会影响原有的状态
        return JSON.parse(JSON.stringify(state));
    }
    
    function subscribe(fn){
        //订阅回调,并返回一个从listeners删除回调的函数
        listeners.push(fn); 
        return ()=>{listeners = listeners.filter(l=>l!=fn)};
    }
    }
    return {
        getState,
        dispatch,
        subscribe
    }
}
复制代码

为了理解上面的东西,看下面的用例:

let initState = {
  title: { color: "red", text: "kbz" }
};

function reducer(state = initState, action) {
  switch (action.type) {
    case "CHANGE_TITLE_COLOR":
      return { ...state, title: { ...state.title, color: action.color } };
      break;
    case "CHANGE_TITLE_TEXT":
      return { ...state, content: { ...state.title, text: action.text } };
      break;
  }
  return state;
}
let store = createStore(reducer);

let unsubcribe = store.subscribe(function() {
  console.log(store.getState().title.color);
});

setTimeout(() => {
  store.dispatch({ type: "CHANGE_TITLE_COLOR", color: "yellow" });
}, 1000);
setTimeout(() => {
  unsubcribe();
  store.dispatch({ type: "CHANGE_TITLE_COLOR", color: "blue" });
}, 2000);

复制代码

bindActionCreators

在创建组件的时候,组件应该是存粹的,使用store.dispatch等其它变量,需要根据情况而区别的引入不同的变量,最好使用state或者props,所以需要将action和dispatch抽离封装然后赋予到组件的prop上

  1. 抽离type
//action-types.js
export const ADD = 'ADD';
export const MINUS = 'MINUS';
复制代码
  1. 抽离action
//actions.js
import * as types from '../action-types';
let actions = {
  add(count){
    return {type:types.ADD,count}
  },
  minus(count){
     return {type:types.MINUS,count}
  }
}
export default actions
复制代码
  1. 批量抽离dispatch
function bindActionCreators(actions,dispatch){
  let obj = {}
  for(let key in actions){ 
    obj[key] = (...args)=>dispatch(actions[key](...args))
  }
  return obj;
}
复制代码
  1. 属性赋值
import React,{Component} from 'react';
import {render} from 'react-dom';
import Counter from './components/Counter';
import {bindActionCreators} from 'redux';
import store from './store';
inmport action form './action'

let props= bindActionCreators(actions,store.dispatch)

render(<Counter {...props}></Counter>,window.root);
复制代码

combineReducers

为了更好的模块化管理,可以将每个组件的reducer分开来创建,然后再通过combineReducers将所有的reducer合并起来,其原理就是创建一个函数,dispatch的时候将所有reducer都执行一次

function combineReducers (reducers){
  //返回一个总的totalReducer,和所有的reducer一样接收state和action
  return (state={},action)=>{
    // totalState登记每一个组件的state
    // 遍历执行所有的reducer,将返回的state重新登记在totalState中
    let obj = {};   
    for(let key in reducers){
      obj[key] = reducers[key](state[key],action)
    }
    return obj;
  }
}
复制代码

applyMiddleware

常用的中间件

中间件原理:在原来的dispatch方法外包装一层函数,扩展其他功能,又能保证原来功能的使用。

// 打印日志中间件
let reduxLogger = (store)=>(dispatch)=>(action)=>{
  console.log('prev',store.getState());
  dispatch(action)
  console.log('next',store.getState());
}

//
let reduxThunk = (store)=>(dispatch)=>(action)=>{
  // 如果是函数将正真的dispatch传给用户,用户抉择是否要派发
  if(typeof action === 'function'){
    return action(dispatch,store.getState); 
  }
  dispatch(action); // 直接把对象派发即可
}

let reduxPromise = (store)=>(dispatch)=>(action)=>{
  // 判断当前action是不是一个promise,如果是promise就执行,执行的时候只会管成功的结果
  if( action.then &&typeof(action.then) == 'function'){
    return action.then(dispatch);
  }else if(action.payload && action.payload.then){  //action.payload是否为promise
    return action.payload.then(data=>{
      dispatch({...action,payload:data}); 
    },err=>{
      dispatch({...action,payload:err});
      return Promise.reject(err); // 对外抛出错误
    })
  }
  return dispatch(action); 
}
复制代码

applyMiddleware基础原理

let applyMiddleware = (middleware)=> (createStore)=> (reducer)=>{
  let store = createStore(reducer);
  
  // 返回新的dispatchL:(action)=>{xxxxxx}
  let fn = middleware(store);
  let newDispatch = fn(store.dispatch);
  
  //覆盖原有的dispatch,返回{getState,dispatch:newDispatch,subscribe}
  return {...store,dispatch:newDispatch};   
}
// 典型的柯里化,把多个middleware连起来,后面compose会介绍
export default applyMiddleware(reduxLogger)(createStore)(reducer);
复制代码

compose

compose的原理

项目中使用的插件不止一个,在使用多个插件的情况下,需要使用一个方法将多个插件合并成一个。

function add(a,b){
  return a+b;
}
function toUpperCase(str){
  return str.toUpperCase();
}
function len(str){
  return str.length
}

function compose(...args){
  return args.reduce((a,b)=>{(...args)=>a(b(...args))});
}
compose(len,toUpperCase,add)(a,b);  //(a,b) => len(toUpperCase(add(a,b)))
复制代码
a b 返回函数
len toUpperCase (...args)=>len(toUpperCase(...args))
(...args)=>{len(toUpperCase(...args)} add (...args)=>len(toUpperCase(add(...args)))

完善applyMiddleware

let reduxLogger = (store)=>(dispatch)=>(action)=>{
  console.log('prev',store.getState());
  dispatch(action)
  console.log('next',store.getState());
}

let applyMiddleware = (...middlewares)=> (createStore)=> (reducer)=>{
  let store = createStore(reducer);
  let fns = middlewares.map(middleware=>{
    return middleware(store)    //返回的函数接受disopatch用于在原来的基础上扩展
  });
  
  // compose(fn1,fn2)(store.dispatch)
  //fn执行返回一个新的包装dispatch函数传给fn1
  let newDispatch = compose(...fns)(store.dispatch);
  
  return {...store,dispatch:newDispatch};   //将合并后的dispatch覆盖原来的最初的dispatch
}
function compose(...args){
  return args.reduce((a,b)=>((...args)=>a(b(...args))));
}
复制代码

redux完整代码

function createStore(reducer,fn) {
  let state;
  let listeners = [];
  let dispatch = (action) => {
    state = reducer(state,action);
    listeners.forEach(fn=>fn());
  }
  dispatch({type:'@INIT'});
  
  // createStore(reducer,applyMiddleware(...middlewares))一步到位
  // 在内部使用applyMiddleware(...middlewares)(createStore)(reducer)
  if(typeof fn === 'function'){ 
    return fn(createStore)(reducer);
  }
  let getState = ()=> JSON.parse(JSON.stringify(state));
  let subscribe = (fn)=>{
    listeners.push(fn);
    return ()=>{
      listeners = listeners.filter(l=>l!=fn);
    }
  }
  return {getState,subscribe,dispatch}
}
function bindActionCreators(actions,dispatch){
  let obj = {}
  for(let key in actions){ 
    obj[key] = (...args)=>dispatch(actions[key](...args))
  }
  return obj;
}
let combineReducers = (reducers)=>{
  return (state={},action)=>{
    let obj = {}
    for(let key in reducers){
      obj[key] = reducers[key](state[key],action)
    }
    return obj;
  }
}
let applyMiddleware = (...middlewares)=> (createStore)=> (reducer)=>{
  let store = createStore(reducer);
  let fns = middlewares.map(middleware=>{
    return middleware(store)
  });
  let newDispatch = compose(...fns)(store.dispatch);
  return {...store,dispatch:newDispatch};
}
function compose(...args){
  return args.reduce((a,b)=>((...args)=>a(b(...args))));
}
export  {
  createStore,
  bindActionCreators,
  combineReducers,
  compose,
  applyMiddleware
}
复制代码

React-redux

Redux和React的关系

Redux是一款状态管理库,并且提供了react-redux库来与React亲密配合,这两者的关系如下图:

Redux和React
从上面可以看出,React-redux通过Provider和connet将Redux和React联系起来:

React-redux框架

import React,{Component} from 'react';
import {bindActionCreators} from './redux'
let Context = React.createContext();

//将store挂载在contex上,供嵌套组件使用
class Provider extends Component{}

// connect的作用就是获取store,子组件获取contex上的store
let connect = (mapStateToProps,mapDispatchToProp)=>{}

export  {
  Provider,
  connect
}
复制代码

Provider

React会提供一个createContext的API,调用它会生成一个Context,里面包含了Provider组件和Consume组件,Provider提供一个状态供跨组件使用,需要使用状态的组件只要嵌套在Consume中就获取Provider提供的状态。让react用起来更得心应手——(react基础解析)里面有介绍,这里不赘述。

let Context = React.createContext();
class Provider extends Component{
  // 将React-redux中的Provide包装了react提供的API生成的Context.Provider
  //<Provider store={xxxx}></Provider>,将store挂载在contex上
  render(){
    return  <Context.Provider value={{store:this.props.store}}>
        {this.props.children}   //子组件
      </Context.Provider>
  }
}
复制代码

connect

既然有挂载store,就必然有子组件获取store,connect的作用就是获取提供好的store

//调用方法:connect(mapStateToProps,mapDispatchToProp)(Com)
// connect是一个高阶组件,调用后的返回一个组件
let connect = (mapStateToProps,mapDispatchToProp)=>(Com) =>{
    return ()=>{ 
      // 高阶组件的特点就是把组件中公用的逻辑抽取来,返回一个经过处理的组件
      class Proxy extends Component{ 
        state = mapStateToProps(this.props.store.getState())
        componentWillMount(){
          this.unsub = this.props.store.subscribe(()=>{
            this.setState(mapStateToProps(this.props.store.getState()))
          })
        }
        componentWillUmount(){
          this.unsub()
        }
        
        //mapStateToProps就是将state中的部分或全部状态映射到需要的组件中作为其props
        //mapDispatchToProp就是将action中已经绑定成dispatch形式的action按需求映射到需要的组件作为其props
        
        render(){
          let b
          if(typeof mapDispatchToProp === 'function'){
              b = mapDispatchToProp(this.props.store.dispatch);
          }else{
              // bindActionCreators把直接将所有action的绑定成diapatch(action)形式组成一个对象
              b = bindActionCreators(mapDispatchToProp,this.props.store.dispatch)
          }
          //将所有的state和修改state的方法以props的方式传入
          return <Com {...this.state} {...b}></Com>
        }
      }
      
      //调用Consumer将获取到的store传给包装Com的Proxy
      return <Context.Consumer>
       {({store})=>{
          return <Proxy store={store}></Proxy>
       }}
      </Context.Consumer>
    }
}
复制代码

用例:

import React,{Component} from 'react';
import actions from '../store/actions/counter';
import {connect} from 'react-redux';
class Counter extends Component{
  render(){
    return (<div>
      <button onClick={()=>{
         this.props.add(2);
      }}>+</button>
        {this.props.number}
        <button  onClick={()=>{
          this.props.minus(2);
      }}>-</button>
    </div>)
 }
}
// mapStateToProps用户自己定义需要的状态
let mapStateToProps = (state)=>{ 
  return {number:state.counter.number}
}

// action也是用户自己定义的,可以是函数可以是对象
// 如果传递过来的不是方法是对象,会把这个对象自动用bindActionCreators包装好
export default connect(mapStateToProps,actions)(Counter);
复制代码

结语:

个人使用一种框架时总有一种想知道为啥这样用的强迫症,不然用框架用的不舒服,不要求从源码上知道其原理,但是必须得从心理上说服自己。

猜你喜欢

转载自juejin.im/post/5bcfce9ff265da0aa5294a25