1. 程式人生 > >react-redux的使用從action規劃到reducer實現及完整案例

react-redux的使用從action規劃到reducer實現及完整案例

網路中介紹redux和react-redux的文章非常非常多我為什麼要寫這篇文章呢?因為他們都寫得不好。好與不好的標準就是對於一個需要學習react redux的人能不能在讀完文章後順利理解和完成可執行案例。由於時間關係和演示專案對檔案命名不是很合理,請諒解。 redux作為一個狀態管理庫其實是獨立的。可以在angular,vue,react或者jQuery中使用。當然使用redux你需要遵循一定的規律或者標準,這會在專案和程式碼中體現。

規劃結構

在本案例中我要做一個使用者管理的功能,用redux來管理使用者的資料。規劃的結構如下:

{
	users:[
	 { name: 'xxx', gender: 'm' },
	 { name: 'yyy', gender: 'f' },
	],
	selectedUser: { name: '魏永強', gender: 'm' }
}

這一步非常重要,你需要先自己構思下你的應用的單一狀態樹。這個結構請大家記住了後邊在構建action, reducer時你就會覺得非常清晰;

功能定位

  1. 我們需要可以新增使用者 ADD_USER
  2. 我們需要在列表中點選檢視使用者詳情 SELECT_USER
  3. 我們需要雙擊列表中資料刪除該使用者 DELETE_USER

用程式碼描述功能[action編寫]

redux官網對action的描述是:把資料從應用傳入store的有效載荷。 其實這個解釋很難理解,我覺得在我們開發的視角應該覺得action是對功能的描述。

action.js檔案

export const ADD_USER = "ADD_USER"
; export const SELECT_USER = "SELECT_USER"; export const DELETE_USER = "DELETE_USER"; export function addUser(user) { return { type: ADD_USER, payload: user }; }; export function selectUser(user) { return { type: SELECT_USER, payload: user }; }; export function deleteUser(
id) { return { type: DELETE_USER, payload: id }; };

注意每個物件都有一個type就是我們的功能描述和載荷payload其實就是我們的功能需要的資料

用程式碼實現功能[reducer編寫]

redux官方的解釋是:指定了應用狀態的變化如何響應 actions 併發送到 store 的 也就是實現action對功能的描述 這兒我們要思考下怎麼樣來劃分reducer了。回到文章剛開始規劃結構我們知道本應用就涉及到兩個資料一個是users也就是使用者列表,另一個是selectedUser也就是使用者詳情。那我們就建立兩個js: reducers/ |----user_list.js |----user_selected.js |----index.js

先看程式碼:

user_list.js

/*
 @Author: weiyognqiang<[email protected]>
 @Date: 2018/10/17
 */
import { ADD_USER, DELETE_USER } from '../actions';

export default function (state = [], action) {
    switch (action.type) {
      case ADD_USER:
        return [...state, action.payload];
      case DELETE_USER:
        if (action.payload >= state.length) {
          return state;
        }
        state.splice(action.payload, 1);
        return [...state];
      default:
        return state;
    }
}

user_selected.js

/*
 @Author: weiyognqiang<[email protected]>
 @Date: 2018/10/17
 */

import { SELECT_USER } from '../actions';

export default function (state = null, action) {
  switch (action.type) {
    case SELECT_USER:
      return action.payload;
    default:
      return state;
  }
}

index.js

import { combineReducers } from "redux";
import Users from "./user_lists";
import SelectedUser from "./user_selected";

const rootReducer = combineReducers({
  users: Users,
  selectedUser: SelectedUser
});

export default rootReducer;

注意: 在reducer的處理函式中傳入了state和action。需要注意的是reducer是純函式那什麼事純函式呢,就是你傳入的引數相同了他返回的結果是確定的。不產生副作用(其實我覺得這個形容很彆扭)。舉一個老生常談的例子。比如在函式中有Math.Random(), Date.Now()之類的是不行的。因為隨機和與時間相關讓我們對函式的結果沒法預知。 以下兩點非常重要:

  1. 不要去改變state的值
  2. 如果有未知的action請一定返回舊的state

注意我在ADD_USER和DELETE_USER的處理,一個要想users陣列物件增加元素,一個要刪除陣列物件的某個值。但是以上兩點必須遵循所以我們用了...物件展開運算子。當然你也可以使用Object.assign({},{},{})深拷貝

以上程式碼對reducer進行了拆分但是在index.js中使用redux提供的combineReducers進行了合併。該函式返回的結果是和combineReducers傳入物件結構完全一致的state,這裡需要注意下。當然你可以自己處理combineReducers但是官方的combineReducers還進行了reducer檢查,比如是否在action未匹配時返回了state等。

構建操作介面[container和component以及connect]

在我們設計中可以把頁面元素拆分成元件component但是在真實處理中這也存在問題,就是元件中資料處理邏輯混在一起。所以有些人抽象了container這個東西。把這個東西成為資料元件,顧名思義就是專門為元件提供資料。給元件提供資料那麼就是元件間通訊就要使用props。 那麼我們同樣需要兩個container一個用來顯示列表一個用來顯示詳情

如何讓store中的資料和元件溝通

其實到了這一步很多人發現其實缺了什麼。也就是我們就資料如何進入store的操作描述(action)及操作實現(reducer)都完成了。但是如何在介面使用這些資料呢? 顯然,要讓他們關聯起來。這個就是react-redux提供的connect把store中的資料以及操作跟我們的檢視元件連線起來這個過程很抽象其實不難理解

建立元件及連線[connect]

containers/user-list.js

/*
 @Author: weiyognqiang<[email protected]>
 @Date: 2018/10/17
 */
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Input, Button, List } from 'antd';
import {selectUser, addUser, deleteUser} from "../actions";

class UserList extends Component{

  constructor(props) {
    super(props);

  }

  showUserLists()
  {
    return this.props.users.map((user, index) => (
      <li
        onClick={() => this.props.selectUser(user)}
        onDoubleClick={() => this.props.deleteUser(index)}
        key={index}>{user.name}</li>
    ));
  }

  render() {
    return (
      <div>
        <Button onClick={() => this.props.addUser({name: '魏永強', gender: 'f'})}>新增</Button>
        <ul>
          {this.showUserLists()}
        </ul>
      </div>
    );
  }

}

function mapStateToProps(state) {
  return {
    users: state.users
  };
};

function mapDispatchToProps(dispatch) {
  return bindActionCreators({
    addUser: addUser,
    selectUser: selectUser,
    deleteUser: deleteUser
  }, dispatch)
};


export default connect(
  mapStateToProps,
  mapDispatchToProps
)(UserList);


containers/user-detail.js

/*
 @Author: weiyognqiang<[email protected]>
 @Date: 2018/10/18
 */

import React, { Component } from 'react';
import { connect } from 'react-redux';

class UserDetail extends Component{

  constructor(props) {
    super(props);

  }

  render() {
    if (!this.props.selectUser) {
      return (<p>請單擊使用者姓名檢視詳情:</p>);
    }

    return (
      <div>
        <p>name: {this.props.selectUser.name}</p>
        <p>gender: {this.props.selectUser.gender}</p>
      </div>
    );
  }

}

function mapStateToProps(state) {
  return {
    selectUser: state.selectedUser
  }
}

export default connect(
  mapStateToProps
)(UserDetail);

注意 每個container前面的class元件大家都很容易理解。在元件中需要的資料我們都是使用this.props屬性來引用。那麼我們必須讓他和我們的store中的資料對應起來。這兒有兩個函式需要注意。 mapStateToProps(state):從這個函式的結構我們就能看出來其實這個是將store的state和我們的props對應起來了而已,很好理解。 mapDispatchToProps(dispatch):這個就是把dispatch和props對應起來了而已。(注意我通篇沒有講解dispatch這個東西,這個就是把我們的操作描述傳遞給reducer的一個函式而已。當然具體的api請查閱官方手冊)

完了之後呢把props和元件連線起來: export default connect()(UserDetail);

在頁面中使用元件(通用)

pages/UserList/index.js

import React, { Component } from 'react';
import UserLists from '../../models/containers/user-list';
import UserDetail from '../../models/containers/user-detail';

export default class UserList extends Component{

  constructor(props) {
    super(props);
  }
  
  render() {
    return (
      <div>
        <UserLists />
        <UserDetail />
      </div>
    );
  }

}

讓所有容器元件訪問store[非常重要]

以上都做完了還不算晚。我們為了讓所有容器元件訪問store我們要使用react-redux提供的Provider 元件來包裹APP元件。當然如果要結合react-router使用的話還要包裹BrowserRouter或其他。

index.js修改

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from './models/reducers'

const store = createStore(rootReducer);

store.subscribe(() => {console.log(store.getState())});

ReactDOM.render(<Provider store={store}>
  <BrowserRouter>
    <App />
  </BrowserRouter>
</Provider>, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();