天天看點

【前端,幹貨】react and redux教程學習實踐(二)。

前言

  這篇博文接 

【前端】react and redux教程學習實踐,淺顯易懂的實踐學習方法。

 ,上一篇簡略的做了一個redux的初級demo,今天深入的學習了一些新的、有用的,可以在生産項目中使用的前端架構,我将盡量以最簡單的語言描述,如果有童鞋看不懂,也可以私下問我。

複習

  前一節我們已經知道,一個redux應用,主要有幾個概念,它們的共同作用都是管理一個全局state,使react元件的state集中處理,想一下你在寫react元件的時候,元件的state總是或多或少與父級元件有關聯,一般情況下我們都是用props處理的 —— 即在父級元件傳入state,子元件使用props管理狀态state。然而經過redux的處理,state被提升到最高等級的元件,它被具體化成一個對象,變得更加容易管理,可以直接在後端讀取state改變前端互動。複習一下兩個概念:

  1.store 

  Store 就是儲存狀态資料對象的地方,你可以把它看成一個容器。整個應用隻能有一個 Store。這個對象通過createStore建立,它的第一個參數傳入一個函數,這個函數就是唯一能處理改變state的地方。

import { createStore } from 'redux';
const store = createStore(fn);      

  2、action

   action是一個通知,例如我有一個元件,裡面有一個click事件改變了目前的狀态,那麼就要通過這個click事件的回調,向store發出要更改state的action,store傳入的那個函數接到通知以後,改變state,最後更新視圖。發出action的方法是,一般action是一個對象,官方規定它必須有一個type屬性,用來規定現在這個action是誰發出的。

store.dispatch(action);      

好,就複習這兩個概念,這幾個流程可以概括為:

【前端,幹貨】react and redux教程學習實踐(二)。

首先元件A通過回調發出action,然後reducers接受到這個action,處理完之後傳回state對象更改,最後觸發視圖更新。一個redux互動的流程大概就是這樣。

要看得懂下面的東西,你需要懂上一節寫過的代碼。

先實踐

還是實踐那個案例,一個number的加減:

【前端,幹貨】react and redux教程學習實踐(二)。

目标是點選對應的按鈕,數字加減,輸入時值也随state變化。github位址:https://github.com/294678380/redux_demo   (demo2)

先來看看上次我們是怎麼發送action的:

我們通過直接在元件上挂props實作的向store發出action

const render = ()=> ReactDOM.render(
    <div>
        <Number
            value={store.getState()}
            add={(action)=>store.dispatch(action)}  //傳入一個函數,傳入發送過來的action,由reducers處理之後傳回state,
            less={(action)=>store.dispatch(action)}
        />
    </div>,
    content
);
      
class Number extends Component{
    add(){
        this.props.add({type:"NUMBER_ADD"}) //調用app.js中傳過來的箭頭函數,傳入了type為NUMBER_ADD的action
    }
  //.....省略
}      

通過傳過來的add箭頭函數,發出了type是NUMBER_ADD的action,然後reducers接受到了這個action,更改了state引發視圖更新。

現在我們要改造這份代碼

為什麼這樣的方式有問題?

現在這樣的代碼能看到的至少有三點問題:

  1.發出的那個action是元件内确定的,結果就是,我需要在元件内寫一個type,比如那個“NUMBER_ADD”,在reducers處理函數中還要再判斷type等于“NUMBER_ADD”,如果需要更改,增加了應用耦合。

  2.監聽是全局的,也就是說每次更新都是全局的。這影響效率。

  3.代碼也不夠優雅,要填寫很多重複的props。

讓我們來看代碼:

  app.js

import React from "react";
import ReactDOM from "react-dom";
import {InitReducers} from "./reducers";
import {createStore} from "redux";
//這個是react-redux提供的管理state的元件,把它放在最高層就可以實作統一管理state。
import {Provider} from "react-redux";
import Number from "./number";
//createStore生成store,InitReducers在收到action時調用的函數
const store = createStore(InitReducers);
const content = document.querySelector(".content");
//直接render
ReactDOM.render(
    <Provider store={store}>
        <Number />
    </Provider>,
    content
);      

入口做了更改吧監聽函數store.subscribe()移除,使用Provider包裹的方式釋出state,注意 createStore(InitReducers); 傳入的這個函數是從reducers.js導出的InitReducers函數。

現在我們看reduces.js

import * as Actions from "./actions"; 
const ReducersActions = {
    [Actions.NUMBER_ADD]:(state,action)=>{
        state.number++;
        return state;
    },
    [Actions.NUMBER_LESS]:(state,action)=>{
        state.number--;
        return state;
    },
    [Actions.NUMBER_VALUE]:(state,action)=>{
        state.number = action.value;
        return state;
    }

}
function InitReducers(state={},action){
    state.number == undefined?state.number=0:state.number;
    if(action.type === "@@redux/INIT"){
        return state;
    }
    state = ReducersActions[action.type](state,action);
    return Object.assign({},state);
}
export {InitReducers}      

引入了actions.js檔案,這裡就是要解決前面說的,action需要一個統一入口的問題。這個actions.js定義了所有action.type,函數 InitReducers被觸發時,ReducersActions[action.type](state,action);會被調用,這個action.type是元件發過來的action。

現在我們看actions.js

export const NUMBER_ADD = "NUMBER_ADD";
export const NUMBER_LESS = "NUMBER_LESS";
export const NUMBER_VALUE = "NUMBER_VALUE";
export function setNumber(type,value=false){
    switch(type){
        case "add":
        return {type:NUMBER_ADD}
        case "less":
        return {type:NUMBER_LESS}
        case "value":
        return {type:NUMBER_VALUE,value:value}
    }
}      

我在這裡定義了三個action,

NUMBER_ADD     增加

NUMBER_LESS  減少      
NUMBER_VALUE  輸入

一個函數      
setNumber  這個函數是給number元件調用的。

      

然後number.js

import React,{Component} from "react";
import {connect} from "react-redux";
//bindActionCreators
import {bindActionCreators} from "redux";
//引入actions
import * as Actions from "./actions";

const propTypes = {
    actions:React.PropTypes.object.isRequired,
    number:React.PropTypes.number.isRequired
}
class Number extends Component{
    setNumber(event){
        let {target} = event;
        let    type = target.getAttribute("data-type");
        if(type != "value"){
            this.props.actions.setNumber(type);
        }else{
            this.props.actions.setNumber(type,target.value);
        }
    }
    render(){
        let props = this.props,
        setNumber = this.setNumber.bind(this);
        return <div>
                    <input type="text" onChange={setNumber} data-type="value" value={props.number}/>
                    <button data-type="add" onClick={setNumber}>+</button>
                    <button data-type="less" onClick={setNumber}>-</button>
                </div>
    }
}
Number.propTypes = propTypes;
//定義元件内需要使用的state某個值
function mapStateToProps(state){
    return {
        number:state.number
    }
}
//定義元件内需要調用回調改變state的props參數
function mapDispatchToProps(dispatch){
    return {
        //這就是合并actions.js中定義的函數,在元件内this.props.actions.xxx調用bindActionCreators生成的dispatch函數
        actions:bindActionCreators(Actions,dispatch)
    }
}
export default connect(mapStateToProps,mapDispatchToProps)(Number);      

先把視線轉到button的click:

 調用了setNumber方法

 setNumber調用了  this.props.actions.setNumber(type,target.value); 傳入type,如果是輸入則傳入value,這裡調用的就是actions.js中export出來的setNumber函數,那麼它是怎麼傳進來的呢?

  

//定義元件内需要使用的state某個值
function mapStateToProps(state){
    return {
        number:state.number
    }
}
//定義元件内需要調用回調改變state的props參數
function mapDispatchToProps(dispatch){
    return {
        //這就是合并actions.js中定義的函數,在元件内this.props.actions.xxx調用bindActionCreators生成的dispatch函數
        actions:bindActionCreators(Actions,dispatch)
    }
}
export default connect(mapStateToProps,mapDispatchToProps)(Number);      

就是這一段,

第一個函數,mapStateToProps 這個是定義元件内需要擷取的,state的值,就是你全局的狀态會被傳入這個函數,你需要指定哪一個屬性你需要放到元件的props裡面。

第二個函數,mapDispatchToProps 這個是定義元件内需要擷取的,能調用到 dispatch函數的回調。它也是放到props裡面,和第一個state的差別是,這裡放進去的是函數,是能夠調用actions.js裡定義的函數。

  這裡的

return {
        //這就是合并actions.js中定義的函數,在元件内this.props.actions.xxx調用bindActionCreators生成的dispatch函數
        actions:bindActionCreators(Actions,dispatch)
    }      

這個actions就是那句 this.props.actions對象,它的值是 bindActionCreators(Actions,dispatch)。這個函數把actions.js中所有導出的函數轉換成能夠觸發dispatch的函數,在元件裡調用this.props.actions.setNumber時,實際上在setNumber處理完action之後,又會自動調用store.dispatch這個方法發出action。

然後總結要點

  要點一:Provider元件包裹整個應用,這樣就能通過這一個超級父元件管理所有子元件的state。

  要點二:bindActionCreators函數,綁定action,建立dispatch。傳回一個actions對象,然後傳入元件中,最後元件調用actions裡面定義的方法,方法處理action調用store.dispatch,這是整個架構的關鍵點

看一點别人的項目實踐

  最近在看一個項目:superset。 這是一個大資料可視化內建軟體,它的前端架構是基于react+redux的,裡面的整體架構,可以看到如圖:

【前端,幹貨】react and redux教程學習實踐(二)。

app.jsx:

【前端,幹貨】react and redux教程學習實踐(二)。

看看它的做法,它将actions對象分發到需要全局state的所有元件中,然後在這些元件中就不需要定義任何與redux相關的代碼了:

【前端,幹貨】react and redux教程學習實踐(二)。
【前端,幹貨】react and redux教程學習實踐(二)。

最後總結

打字到這裡,手有點酸,先不總結啦。

demo代碼github:https://github.com/294678380/redux_demo

=============================

轉載需在明顯處注明作者與出處連結。

========================================================

轉載請注明出處。

繼續閱讀