Redux基礎學習

本文參考文檔地址:http://cn.redux.js.org/docs/introduction/Motivation.html

介紹

隨着 JavaScript 單頁應用開發日趨複雜,JavaScript 需要管理比任何時候都要多的 state (狀態)。 這些 state 可能包括服務器響應、緩存數據、本地生成尚未持久化到服務器的數據,也包括 UI 狀態,如激活的路由,被選中的標籤,是否顯示加載動效或者分頁器等等。

管理不斷變化的 state 非常困難。如果一個 model 的變化會引起另一個 model 變化,那麼當 view 變化時,就可能引起對應 model 以及另一個 model 的變化,依次地,可能會引起另一個 view 的變化。直至你搞不清楚到底發生了什麼。state 在什麼時候,由於什麼原因,如何變化已然不受控制。 當系統變得錯綜複雜的時候,想重現問題或者添加新功能就會變得舉步維艱。

如果這還不夠糟糕,考慮一些來自前端開發領域的新需求,如更新調優、服務端渲染、路由跳轉前請求數據等等。前端開發者正在經受前所未有的複雜性,難道就這麼放棄了嗎?當然不是。

這裏的複雜性很大程度上來自於:我們總是將兩個難以理清的概念混淆在一起:變化和異步。 我稱它們爲曼妥思和可樂。如果把二者分開,能做的很好,但混到一起,就變得一團糟。一些庫如 React 試圖在視圖層禁止異步和直接操作 DOM 來解決這個問題。美中不足的是,React 依舊把處理 state 中數據的問題留給了你。Redux 就是爲了幫你解決這個問題。

跟隨 FluxCQRSEvent Sourcing 的腳步,通過限制更新發生的時間和方式,Redux 試圖讓 state 的變化變得可預測。這些限制條件反映在 Redux 的三大原則中。

Redux 可以用這三個基本原則來描述:

  • 單一數據源

    整個應用的 state 被儲存在一棵 object tree 中,並且這個 object tree 只存在於唯一一個 store 中。

    這讓同構應用開發變得非常容易。來自服務端的 state 可以在無需編寫更多代碼的情況下被序列化並注入到客戶端中。由於是單一的 state tree ,調試也變得非常容易。在開發中,你可以把應用的 state 保存在本地,從而加快開發速度。此外,受益於單一的 state tree ,以前難以實現的如“撤銷/重做”這類功能也變得輕而易舉。

  • State 是隻讀的

    唯一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通對象。

    這樣確保了視圖和網絡請求都不能直接修改 state,相反它們只能表達想要修改的意圖。因爲所有的修改都被集中化處理,且嚴格按照一個接一個的順序執行,因此不用擔心競態條件(race condition)的出現。 Action 就是普通對象而已,因此它們可以被日誌打印、序列化、儲存、後期調試或測試時回放出來。

  • 使用純函數來執行修改

爲了描述 action 如何改變 state tree ,你需要編寫 reducers

Reducer 只是一些純函數,它接收先前的 state 和 action,並返回新的 state。剛開始你可以只有一個 reducer,隨着應用變大,你可以把它拆成多個小的 reducers,分別獨立地操作 state tree 的不同部分,因爲 reducer 只是函數,你可以控制它們被調用的順序,傳入附加數據,甚至編寫可複用的 reducer 來處理一些通用任務,如分頁器。

基礎學習 Redux

一、安裝

npm install --save redux 

下面在React框架下使用

二、Action

首先,讓我們來給 action 下個定義。

Action 是把數據從應用(譯者注:這裏之所以不叫 view 是因爲這些數據有可能是服務器響應,用戶輸入或其它非 view 的數據 )傳到 store 的有效載荷。它是 store 數據的唯一來源。一般來說你會通過 store.dispatch() 將 action 傳到 store。

Action 本質上是 JavaScript 普通對象。我們約定,action 內必須使用一個字符串類型的 type 字段來表示將要執行的動作。多數情況下,type 會被定義成字符串常量。當應用規模越來越大時,建議使用單獨的模塊或文件來存放 action。

Action 創建函數 就是生成 action 的方法。“action” 和 “action 創建函數” 這兩個概念很容易混在一起,使用時最好注意區分。

在 Redux 中的 action 創建函數只是簡單的返回一個 action:

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

這樣做將使 action 創建函數更容易被移植和測試。

const sendAction = () => {
    return {
        type: "send_type",
        value: "this is an action"
    }
}

module.exports = {
    sendAction
}

三、Reducer

Reducers 指定了應用狀態的變化如何響應 actions 併發送到 store 的,記住 actions 只是描述了有事情發生了這一事實,並沒有描述應用如何更新 state。

Reducer接收兩個參數

  • state
  • action

注意:

  1. 不要修改 state 使用 Object.assign() 新建了一個副本。不能這樣使用 Object.assign(state, { visibilityFilter: action.filter }),因爲它會改變第一個參數的值。你必須把第一個參數設置爲空對象。你也可以開啓對 ES7 提案對象展開運算符的支持, 從而使用 { ...state, ...newState } 達到相同的目的。
  2. **在 default 情況下返回舊的 state。**遇到未知的 action 時,一定要返回舊的 state
Object.assign 須知

Object.assign() 是 ES6 特性,但多數瀏覽器並不支持。你要麼使用 polyfill,Babel 插件,或者使用其它庫如 _.assign() 提供的幫助方法。

switch 和樣板代碼須知

switch 語句並不是嚴格意義上的樣板代碼。Flux 中真實的樣板代碼是概念性的:更新必須要發送、Store 必須要註冊到 Dispatcher、Store 必須是對象(開發同構應用時變得非常複雜)。爲了解決這些問題,Redux 放棄了 event emitters(事件發送器),轉而使用純 reducer。

很不幸到現在爲止,還有很多人存在一個誤區:根據文檔中是否使用 switch 來決定是否使用它。如果你不喜歡 switch,完全可以自定義一個 createReducer 函數來接收一個事件處理函數列表,參照"減少樣板代碼"

const initState = {
    value: "default value"
}
const rootReducer = (state = initState, action) => {
    console.log("Reducer", state, action);
    switch (action.type) {
        case "send_type":
            return Object.assign({}, state, action);
        default:
            return state;
    }
}

module.exports = {
    rootReducer
}

四、Store

在前面的章節中,我們學會了使用 action 來描述“發生了什麼”,和使用 reducers 來根據 action 更新 state 的用法。

Store 就是把它們聯繫到一起的對象。Store 有以下職責:

再次強調一下 Redux 應用只有一個單一的 store。當需要拆分數據處理邏輯時,你應該使用 reducer 組合 而不是創建多個 store。

import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)

createStore() 的第二個參數是可選的, 用於設置 state 初始狀態。這對開發同構應用時非常有用,服務器端 redux 應用的 state 結構可以與客戶端保持一致, 那麼客戶端可以將從網絡接收到的服務端 state 直接用於本地數據初始化。

let store = createStore(todoApp, window.STATE_FROM_SERVER)

案例:

import {createStore} from 'redux'
import {rootReducer} from '../reducer'

const store = createStore(rootReducer);

export default store;

五、案例演示

下面創建一個控件,點擊後修改下面的值:

import React from 'react'
import store from './store'
import { sendAction } from './action'
class ReduxDemo extends React.Component {
    handleClick = () => {
        const action = sendAction();
        store.dispatch(action);
    };

    componentDidMount() {
        store.subscribe(() => {
            console.log('subscribe', store.getState());
            this.setState({});
        })
    }

    render() {
        return (
            <div>
                <button onClick={this.handleClick}>這是ReduxDemo組件</button>
                <h2>{store.getState().value}</h2>
            </div>
        )
    }
}

export default ReduxDemo;

這裏介紹一個Chrome工具Redux DevTools插件,在谷歌商城下載,使用地址:https://github.com/zalmoxisus/redux-devtools-extension#usage

這裏修改一下

import {createStore} from 'redux'
import {rootReducer} from '../reducer'

const store = createStore(rootReducer,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

export default store;

運行一下,看它的變化
在這裏插入圖片描述
在這裏插入圖片描述

基礎學習 React Redux

這裏需要再強調一下:Redux 和 React 之間沒有關係。Redux 支持 React、Angular、Ember、jQuery 甚至純 JavaScript。

儘管如此,Redux 還是和 ReactDeku 這類庫搭配起來用最好,因爲這類庫允許你以 state 函數的形式來描述界面,Redux 通過 action 的形式來發起 state 變化。

一、安裝

Redux 默認並不包含 React 綁定庫,需要單獨安裝。

npm install --save react-redux

如果你不使用 npm,你也可以從 unpkg 獲取最新的 UMD 包(包括開發環境包生產環境包)。如果你用 <script> 標籤的方式引入 UMD 包,那麼它會在全局拋出window.ReactRedux對象。

二、Provider 標籤

React-Redux 提供<Provider/>組件,能夠使你的整個app訪問到Redux store中的數據:

import React from 'react';
import store from './pages/store'
import ComA from './pages/ComA'
import ComB from './pages/ComB'
import ComC from './pages/ComC'
import {Provider} from 'react-redux'

function App() {
    return (
        <Provider store={store}>
            <div className="App">
                <h1>Redux學習</h1>
                <ComA/>
                <ComB/>
                <ComC/>
            </div>
        </Provider>
    );
}

export default App;

在整個APP裏,組件ComAComBComC都能共享到這個store

三、connect函數

1、mapStateToProps(state, ownProps) : stateProps

這個函數允許我們將 store 中的數據作爲 props 綁定到組件上。

const mapStateToProps = (state) => {return { count: state.count } }

(1)這個函數的第一個參數就是 Redux 的 store,我們從中摘取了 count 屬性。你不必將 state 中的數據原封不動地傳入組件,可以根據 state 中的數據,動態地輸出組件需要的(最小)屬性。

(2)函數的第二個參數 ownProps,是組件自己的 props。有的時候,ownProps 也會對其產生影響。

當 state 變化,或者 ownProps 變化的時候,mapStateToProps 都會被調用,計算出一個新的 stateProps,(在與 ownProps merge 後)更新給組件。

2、mapDispatchToProps(dispatch, ownProps): dispatchProps

connect 的第二個參數是 mapDispatchToProps,它的功能是,將 action 作爲 props 綁定到組件上,也會成爲 組件的 props。

3、[mergeProps],[options]

不管是 stateProps 還是 dispatchProps,都需要和 ownProps merge 之後纔會被賦給組件。connect 的第三個參數就是用來做這件事。通常情況下,你可以不傳這個參數,connect 就會使用 Object.assign 替代該方法。

4、[options] (Object)

如果指定這個參數,可以定製 connector 的行爲。一般不用。

四、案例

下面用一個案例來說明:

問題:通過兩個按鈕來加減顯示數字,範圍是0~100,需要三個控件,控件A是加號鍵,點擊一次,B控件加1,C控件點擊一次,B控件減一。

首先我們需要三個控件

ComA

import React from "react";
import {connect} from 'react-redux'

class ComA extends React.Component {
    handleClick = () => {
        console.log('ComA',this.props);
        // 4、當點擊時發送action,也就是說發送函數會合併到props上,這樣通過props就可以獲取到發送函數了
        this.props.sendAction();
    }

    render() {
        return (<button onClick={this.handleClick}>+</button>)
    }
}

// 2、這個函數接受一個dispatch函數,它是用來發送action的
const mapDispatchToProps = (dispatch) => {
    return {
        sendAction: () => {
            // 3、這個函數的參數是一個action
            dispatch({
                type: 'add_action'
            })
        }
    }
}

// 1、這裏是要發送actoin,所以使用第二個參數,增強控件
export default connect(null, mapDispatchToProps)(ComA)

ComB

import React from "react";
import {connect} from 'react-redux'

class ComB extends React.Component{
    render() {
        console.log('ComB',this.props);
        // 3、這裏通過props可以獲取到state的屬性,也就是說state會合併到props上
        return (<div>{this.props.count}</div>)
    }
}

// 2、這個函數第一個參數是接受的state,返回值是state,當state不變的時候是不操作的
const  mapStateToProps = (state)=>{
    console.log('ComB',state)
    return state;
}

// 1、這個控件用來接受state的,使用第一個參數
export default connect(mapStateToProps)(ComB)

ComC 控件和ComA基本一致

import React from "react";
import {connect} from 'react-redux'

class ComC extends React.Component {
    handleClick = () => {
        console.log('ComC',this.props);

        this.props.sendAction();
    }

    render() {
        return (<button onClick={this.handleClick}>-</button>)
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        sendAction: () => {
            dispatch({
                type: 'sub_action'
            })
        }
    }
}

export default connect(null, mapDispatchToProps)(ComC)

然後創建store

import {createStore} from 'redux'
import {reducer} from '../reducer'
const store = createStore(reducer,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
export default store;

接着創建reducer

const initstate = {count: 0};
exports.reducer = (state = initstate, action) => {
    console.log('reducer', action)
    //這裏是業務邏輯
    switch (action.type) {
        case 'add_action':
            if (state.count===100){
                state.count = -1;
            }
            return {
                count: state.count + 1
            };
        case 'sub_action':
            if (state.count===0){
                state.count = 101;
            }
            return {
                count: state.count - 1
            };
        default:
            return state;
    }
}

最後將控件整合到APP

import React from 'react';
import store from './pages/store'
import ComA from './pages/ComA'
import ComB from './pages/ComB'
import ComC from './pages/ComC'
import {Provider} from 'react-redux'

function App() {
    return (
        //這裏要把store綁定到Provider上
        <Provider store={store}> 
            <div className="App">
                <h1>Redux學習</h1>
                <ComA/>
                <ComB/>
                <ComC/>
            </div>
        </Provider>
    );
}

export default App;

然後運行:
在這裏插入圖片描述
這樣就看到效果了,控件之間產生了互動;

下面說一下流程情況:
在這裏插入圖片描述
這裏的發送和接收都是通過connect函數增強實現的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章