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

栏目: 服务器 · 发布时间: 6年前

内容简介:在没有redux出来之前,父组件和子组件之间,平行组件之间传递和修改状态,需要将状态和修改状态的方法逐级往下传,组件嵌套过深则很容易出现管理混乱的问题。所以redux就是为了解决状态管理而诞生的。为了理解上面的东西,看下面的用例:在创建组件的时候,组件应该是存粹的,使用store.dispatch等其它变量,需要根据情况而区别的引入不同的变量,最好使用state或者props,所以需要将action和dispatch抽离封装然后赋予到组件的prop上

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

让react用起来更得心应手——(react-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
}
复制代码
让react用起来更得心应手——(react-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亲密配合,这两者的关系如下图:

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

从上面可以看出,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);
复制代码

结语:

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Kotlin实战

Kotlin实战

【美】Dmitry Jemerov(德米特里·詹莫瑞福)、【美】 Svetlana Isakova(斯维特拉娜·伊凡诺沃) / 覃宇、罗丽、李思阳、蒋扬海 / 电子工业出版社 / 2017-8 / 89.00

《Kotlin 实战》将从语言的基本特性开始,逐渐覆盖其更多的高级特性,尤其注重讲解如何将 Koltin 集成到已有 Java 工程实践及其背后的原理。本书分为两个部分。第一部分讲解如何开始使用 Kotlin 现有的库和API,包括基本语法、扩展函数和扩展属性、数据类和伴生对象、lambda 表达式,以及数据类型系统(着重讲解了可空性和集合的概念)。第二部分教你如何使用 Kotlin 构建自己的 ......一起来看看 《Kotlin实战》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具