帶你瞭解redux與react-redux

romin發表於2018-04-18

1、redux

1.1)Action Creator

export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
----
import * as types from '../action-types';
//actionCreator 建立action的函式
export default {
    increment(){
        return {type:types.INCREMENT}
    },
    decrement(){
        return {type:types.DECREMENT}
    }

}
複製程式碼

看著寫了這麼多,其實就是為了拿到字串INCREMENTDECREMENT

1.2)reducer

reducer是一個純函式,相同的輸入有相同的輸出,不同的輸入得到不同的輸出。它必須遵守以下幾點

  • 不得改寫引數
  • 不能呼叫系統 I/O 的API
  • 不能呼叫Date.now()或者Math.random()等不純的方法,因為每次會得到不一樣的結果

reducer中不能改變state,在保證舊的狀態不變的情況下,返回一個全新的狀態

const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
let initState = { number: 0 }
export default function (state = initState, action) {
    switch (action.type) {
        case INCREMENT:
            return { number: state.number + 1 };
        case DECREMENT:
            return { number: state.number - 1 };
        default:
            return state;
    }
}
複製程式碼

其實就相當於state變成了一個只是可讀的狀態,不可更改,返回了計算後的新的狀態,原狀態不變。

1.3)createStore

const createStore = (reducer) => {
  let state;
  let listeners = [];
  
  const getState = () => state;// 因為dispatch的執行,拿到的是初始狀態
  
  //訂閱,供外界訂閱本倉庫中狀態的變化 ,如果狀態變化 了會執行訂閱的邏輯 
  const subscribe = (listener) => {
  listeners.push(listener);
    return () => {//subscribe的返回結果是一個unsubscribe
      listeners = listeners.filter(l => l !== listener);
    }
  };

  const dispatch = (action) => {
  //接收新的動作後,通過 才狀態 和新動作計算出新狀態
    state = reducer(state, action);
    listeners.forEach(listener => listener());
  };

  dispatch({});
  // 這裡執行dispatch的原因是,為了執行reducer函式,最終獲取到裡面的初始 state,也就是說createStore()執行後,立馬拿到初始狀態。
  return { getState, dispatch, subscribe };
};
複製程式碼

1.4)combinReducers

將兩個reducer合併

 function combineReducers(reducers) {
     //返回合併後的reducer,這個函式將被傳入createStore
     return function (state = {}, action) {
         let newState = {};
         for (let attr in reducers) {
             let reducer = reducers[attr];
             newState[attr] = reducer(state[attr], action);
         }
         return newState;
     }
 }
 export default combineReducers({
    reducer1,
    reducer2
});
複製程式碼

另外一種寫法:

export default reducers => (state = {}, action) => Object.keys(reducers).reduce((currentState, key) => {
    currentState[key] = reducers[key](state[key], action);
    return currentState;
}, {});
複製程式碼

1.5)bindActionCreator

我們做下面一個操作

<button onClick={()=>store.dispatch(actions.increment())}>-</button>
複製程式碼

bindActionCreator用來把actiondispatch繫結在一起,讓書寫變得更方便

<button onClick={newActions.increment}>-</button>
複製程式碼

這是bindActionCreators的實現原理:

function bindActionCreators(actions,dispatch){
    let newActions = {};
    for(let attr in actions){
     newActions[attr] = function(){
         dispatch(actions[attr].apply(null,arguments));
     }
    }
    return newActions;
 }
複製程式碼

1.6) 我們可以來生成redux了

import createStore from './createStore';
import combineReducers from './combineReducers';
import bindActionCreators from './bindActionCreators';
export {
    createStore,
    combineReducers,
    bindActionCreators
}
複製程式碼

二、react-redux

1、Provider

provider的作用是讓每個元件都擁有呼叫store的能力

Provider 本身並沒有做很多事情,只是把store放在 context 裡罷了。實際上如果你用 react-redux,那麼連線檢視和資料層最好的辦法是使用 connect 函式。本質上 Provider就是給 connect提供store用的。

//用來通過上下文物件向下層元件傳遞資料 store
import React,{Component} from 'react';
import propTypes from 'prop-types';
export default class Provider extends Component{
    static childContextTypes = {
        store:propTypes.object.isRequired
    }
    getChildContext(){
        return {store:this.props.store};
    }
    render(){
        return this.props.children;
    }
}
複製程式碼

一般我們在跟元件上使用

const store = configureStore();
render(
   <Provider store={store}>
      <Root />
   </Provider>,
   document.getElementById('root')
);
複製程式碼

2、connect

React-Redux 提供connect方法,用於從 UI 元件生成容器元件。connect的意思,就是將這兩種元件連起來。

作用:

  • (1)輸入邏輯:外部的資料(即state物件)如何轉換為UI 元件的引數

  • (2)輸出邏輯:使用者發出的動作如何變為 Action物件,從 UI 元件傳出去

connect的使用

import { connect } from 'react-redux'

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)
複製程式碼

connect有兩個引數,分別是 mapStateToProps,mapDispatchToProps

第一個引數負責輸入邏輯,即將state對映到 UI 元件的引數(props),第二個引數負責輸出邏輯,即將使用者對UI 元件的操作對映成 Action

2.1 ) mapStateToProps

返回值,是一個物件.

從字面上理解,就是把狀態(store中的state)對映成屬性(元件中的props)

const mapStateToProps = (state) => ( // 正常我們在react-redux中會這樣書寫
  {number: state.number}
  )
複製程式碼

2.2)mapDispatchToProps

mapDispatchToPropsconnect函式的第二個引數,用來建立 UI 元件的引數到store.dispatch方法的對映。也就是說,它定義了哪些使用者的操作應該當作 Action,傳給 Store。它可以是一個函式,也可以是一個物件。

如果mapDispatchToProps是一個函式,會把dispatch當做一個引數傳進去。

const mapDispatchToProps = (dispatch) => {
  return {
    onClick: () => {
      dispatch({
        type: 'ADD'
      });
    }
  };
}
// 返回了一個物件,該物件的每個鍵值對都是一個對映,定義了 UI 元件的引數怎樣發出 Action。
複製程式碼

如果mapDispatchToProps是一個物件,它的每個鍵名也是對應 UI 元件的同名引數,鍵值應該是一個函式,會被當作 Action creator ,返回的 Action 會由 Redux 自動發出。

bindActionCreators(mapDispatchToProps,this.store.dispatch)
複製程式碼

2.3) connect 的實現

在connect中實現了元件的更新操作。

import React,{Component} from 'react';
import {bindActionCreators} from 'redux';
import propTypes from 'prop-types';
export default function(mapStateToProps,mapDispatchToProps){
   return function(WrapedComponent){
      class ProxyComponent extends Component{
          static contextTypes = {
              store:propTypes.object
          }
          constructor(props,context){
            super(props,context);
            this.store = context.store;//這裡的store是從Prodiver傳過來的
            this.state = mapStateToProps(this.store.getState());//{color:state.color}
          }
          componentWillMount(){
              this.unsubscribe = this.store.subscribe(()=>{
                  this.setState(mapStateToProps(this.store.getState()));// 根據store中的state來更新檢視。
              });
          }
          componentWillUnmount(){
              this.unsubscribe();
          }
          render(){
              let actions= {};
              if(typeof mapDispatchToProps == 'function'){
                actions = mapDispatchToProps(this.store.disaptch);
              }else if(typeof mapDispatchToProps == 'object'){
                actions = bindActionCreators(mapDispatchToProps,this.store.dispatch);
              }
                return <WrapedComponent {...this.state} {...actions}/>
         }
      }
      return ProxyComponent;
   }
}
複製程式碼

2.4) 最後我們來生成react-redux

import connect from './connect';
import Provider from './Provider';
export {
    connect,
    Provider
}
複製程式碼

相關文章