React 入門-redux 和 react-redux

React 將頁面元素拆分成組件,通過組裝展示數據。組件又有無狀態有狀態之分,所謂狀態,可以簡單的認爲是組件要展示的數據。React 有個特性或者說是限制單向數據流,組件的狀態數據只能在組件內部修改,對於其他組件是只讀的,想要修改只能通過組件提供的接口回調。

隨着組件數量的增多,組件間狀態數據共享的複雜性也會隨之增加,如果僅使用 React 組件內的 State 可能會導致程序的流程混亂,代碼難以維護。

原文鏈接:https://www.chuonye.com/archives/react-redux.html

1. 引入 Redux

爲什麼這麼說呢,我們來看一個圖:

redux

原始的 React,當最底層組件需要改變數據時,如果數據在父組件,回調方法層層傳遞即可;如果在兄弟組件,那就需要藉助一箇中間組件,當然了也有辦法直接與兄弟組件通信。可以想象,隨着應用複雜程度的提高,組件間通信使用的各種直接或間接回調,可能會導致代碼亂成一團。

此時,Redux 就派上用場了,如上圖所示,它把應用的所有狀態-數據存儲在一個地方,並稱之爲Store,組件間不直接通信,而是把變化的數據推給 Store,需要根據狀態變化重新渲染的組件通過訂閱 Store 來實現。

Redux 在設計與實現時,遵循三大原則或者說規範限制

1.1 唯一數據源

整個應用程序的狀態數據僅存儲在一個 Store 中,數據就存儲在一個大的對象樹(object tree)中。

1.2 只讀的 Store

唯一的 Store 也是隻讀的,應用程序無法直接修改狀態數據。Store 對象本身的 API 非常少,僅有四個方法:

  • getState() : 獲取當前狀態數據
  • dispatch(action) : 推送觸發變化
  • subscribe(listener) : 訂閱數據變化
  • replaceReducer(nextReducer)

顯而易見,沒有提供設置狀態的方法。其中的 dispatch(action) 是唯一改變數據的方法,比如:

var action = {
  type: 'ADD_USER',
  user: {name: 'chuonye.com'}
};

store.dispatch(action);

dispatch 方法會將 action 傳遞給 Redux,action 就是一個普通對象,包含觸發的操作類型以及數據。

1.3 使用純函數更改數據

Redux 接收到 action 後,會使用一個純函數來處理,這些函數被稱爲 Reducers

var someReducer = function(state, action) {
  ...
  return state;
}

Reducer 接收當前的 state 和 action 作爲參數,它也不能直接修改原有的數據,而是通過返回一個新的 state 來修改。

總的來說,Redux 是一個幫助應用統一管理狀態數據的工具,它遵循嚴格的單向數據流(Store 只讀)設計,使得應用的行爲變得可預測且容易理解。

react-redux-stream

2. React-Redux

Redux 一般和 React 這類框架搭配使用,爲了方便與 React 集成,Redux 官方提供了一個 react-redux 綁定庫。react-redux 將組件劃分爲容器組件UI組件其他組件,其中:

  • 容器組件:與 Redux-Store 交互,分派 Action,監聽 state 變化,負責數據管理和業務邏輯
  • UI 組件:無狀態,負責數據的展示,樣式,排版,數據來源於 props 屬性
  • 其他組件:無法明確區分是容器還是 UI 的組件,或者本身比較簡單沒有拆分必要的組件

從整體來看,在使用上,應用代碼分層設計,結構如下:

react-redux

我們不妨結合着數據庫,來理解下每個層次的意思,從下往上:

  • Store 可以理解成數據庫服務,其中的 State 是一個數據庫,State 對象樹的各個部分,可以理解爲對應不同的
  • Reducer 就是實際對 State 增刪改的,它按邏輯拆分成多個 子reducer,分別對對象樹的不同部分或者說對不同的表進行操作,就如同一個 SQL 執行引擎
  • Action 就是一系列觸發改變的動作以及數據,就如同一條條 SQL 語句
  • 容器組件和 UI 組件就如同 Java 中設計的 DAO 和 Controller

這些設計怎麼有點熟悉,跟 Java 是越來越像了。下面簡單寫個例子,看看到底怎麼用。

3. 實例:Counter 計數器

接下來我們按照使用原生 React,使用 Redux 和使用 React-Redux的順序分別實現一個 Counter 計數器的功能。

3.1 原生 React 實現

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class Counter extends Component {
    constructor(props) {
        super(props);
        this.state = { value: 0 }; // 狀態數據
    }
    render() {
        return (
            <div>
                <p>state: {this.state.value}</p>
                <button onClick={() => this.handleIncrement()}>+1</button>
                <button onClick={() => this.handleDecrement()}>-1</button>
            </div>
        );
    }
    // 處理方法
    handleIncrement() {
        let curVal = this.state.value + 1;
        this.setState({value: curVal});
    }
    handleDecrement() {
        let curVal = this.state.value - 1;
        this.setState({value: curVal});
    }
}
// 渲染
ReactDOM.render(<Counter />, document.getElementById('root'))

3.2 使用 Redux 實現

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';

// 接收 action 並處理,簽名:(state, action) => newState
function reducer(state = {value: 0}, action) {
    let curVal = state.value;
    switch (action.type) {
        case 'INCREMENT':
            return {value: curVal+1};
        case 'DECREMENT':
            return {value: curVal-1};
        default:
            return state;
    }
}
// 創建 Redux store
const store = createStore(reducer);

const render = () => ReactDOM.render(
    <div>
        <p>state: {store.getState().value}</p>
        {/* dispatch 通知 store 改變數據 */}
        <button onClick={()=>{store.dispatch({type: 'INCREMENT'})}}>+1</button>
        <button onClick={()=>{store.dispatch({type: 'DECREMENT'})}}>-1</button>
    </div>,
    document.getElementById('root')
)

render();
// 訂閱 store,狀態改變時更新 UI
store.subscribe(render);

3.3 使用 React-Redux 實現

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider, connect } from 'react-redux';

// 接收 action 並處理
function reducer(state = {value: 0}, action) {
    let curVal = state.value;
    switch (action.type) {
        case 'INCREMENT':
            return {value: curVal+1};
        case 'DECREMENT':
            return {value: curVal-1};
        default:
            return state;
    }
}
// 定義一個 UI 組件,用於展示數據
class CounterUI extends Component {
    render() {
        // 數據來源於 props
        const {value, handleIncrement, handleDecrement} = this.props;
        return (
            <div>
                <p>state: {value}</p>
                <button onClick={handleIncrement}>+1</button>
                <button onClick={handleDecrement}>-1</button>
            </div>
        );
    }
}
// 定義一個 容器組件,用於與 Redux store 交互
// 將 Redux state 按需注入到 UI 組件的 props
function mapStateToProps(state) {
    return { value: state.value }
}
  
// 將 Redux actions 按需注入到 UI 組件的 props
function mapDispatchToProps(dispatch) {
    return {
        handleIncrement: () => dispatch({type: 'INCREMENT'}),
        handleDecrement: () => dispatch({type: 'DECREMENT'})
    }
}
// 使用 connect 方法基於 UI組件 生成一個 容器組件
const CounterContainer = connect(mapStateToProps, mapDispatchToProps)(CounterUI);

const store = createStore(reducer);

// 使用 Provider 作爲根組件,使得所有子組件都能訪問到 store
ReactDOM.render(
    <Provider store={store}>
      <CounterContainer />
    </Provider>,
    document.getElementById('root')
)

4. 小結

Redux 和 React-Redux 專門設計了約束和約定,導致代碼量不見得減少,甚至還可能增加了。簡單的項目看不出有什麼優勢,但在構建一定規模的企業級項目時,這些約束和條條框框,會有利於項目的維護和管理。正如 Java,有些人認爲過於囉嗦,但這正是它嚴謹的體現!

下一篇將會結合 antd UI框架模仿實現官方提供的購物車實例。

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