React中如何使用Redux管理資料

外星人180發表於2017-09-16

內容簡介

本內容是基於實實在在的專案開發提煉出來的解決方案,手把手教你如何在React中使用Redux來進行資料管理,該案例可以直接在專案中使用;本文中不會介紹原理什麼之類的,網上一大堆。本內容適合有react開發基礎的人學習,希望對大家有幫助。

目錄結構

  • 1 按型別分
    這裡的型別指的是一個檔案在專案中充當的角色型別,即這個檔案是一個component,還是一個container,或者是一個reducer等,充當component、container、action、reducer等不同角色的檔案,分別放在不同的資料夾下,這也是Redux官網示例所採用的專案結構。這種結構如下所示:
    actions/
    a.js
    b.js
    components/
    a1.jsx
    a2.jsx
    b1.jsx
    constainers/
    a.jsx
    b.jsx
    reducers/
    a.js
    b.js
    index.js複製程式碼
    當專案比較小時按型別來分是完全沒問題的,當專案越來越龐大你會發現要修改某資料結構時很麻煩。
  • 2 按功能分
    我們在專案開發中總結了一種比較合適的目錄結構,components裡存放所有的view元件和元件對應的樣式檔案,constainers裡存放所有的容器元件,為了開發效率和減少檔案數量,我們把容器元件的action和reducer放在同一個檔案(XXXRedux.js)裡面。這種結構如下所示::
    components/
    componentA/
      a.jsx
      a.less
    componentB/
      b.jsx
      b.less
    constainers/
    constainer1/
       X.jsx
       X.less
       XRedux.js
    constainer2/
       XX.jsx
       XX.less
       XXRedux.js複製程式碼
    顯而易見採用這樣的目錄結構要修改某一個元件時很方便,每一個容器元件對應的action、reducer、component、css都同一個檔案目錄下,操作很方便。

    開發步驟

    因為涉及到公司資料安全問題,真實專案是沒法給各位看官look look了;以下都用經典的todolist程式碼做展示。
  • 1 設計reducer
    設計store有很多講究,不能把服務給的資料結構直接作為store,也不能按照view結構來設計store;對於初學者來說是一件很煩惱的事,社群很多都推薦按照設計資料庫表結構來設計store,但是很多以專案交付為目標的不可能做得這麼標準,所以大家都儘量向標準看齊-_-。
    曾經在文章看到設計store的標準,給大家參考參考:

    • 1) 把整個應用的狀態按照領域(Domain)分成若干子State,子State之間不能儲存重複的資料。
    • 2) State以鍵值對的結構儲存資料,以記錄的key/ID作為記錄的索引,記錄中的其他欄位都依賴於索引。
    • 3)State中不能儲存可以通過已有資料計算而來的資料,即State中的欄位不互相依賴。

    在業務比較複雜的情況下強力建議大家使用immutable,在專案初期還沒有感覺,但是專案越來越大,業務越來越複雜時就能完全感受到它的好處了。
    以下程式碼是一個reducer檔案,我們把該reducer需要的所有type定義在一個常量types物件中,initialState是最初state沒有資料時顯示的預設資料。

const types = {
  SELECT_ENTRY: 'select_entry',
  CREATE_NEW_ENTRY: 'create_new_entry',
  EDIT_ENTRY: 'edit_entry',
  CANCEL_EDIT: 'cancel_edit',
  UPDATE_ENTRY_LIST: 'update_entry_list',
  UPDATE_SAVED_ENTRY: 'updadte_saved_entry'
}

const initialState = {
  selectedId: null,
  isEditing: false,
  big: '測試',
};

export default function editor(state = initialState, action) {
  switch (action.type) {
    case types.SELECT_ENTRY:
      return Object.assign({}, state, { selectedId: action.id });
    case types.CREATE_NEW_ENTRY:
      return Object.assign({}, state, { selectedId: null, isEditing: true });
    case types.EDIT_ENTRY:
      return Object.assign({}, state, { selectedId: action.id, isEditing: true });
    case types.CANCEL_EDIT:
      return Object.assign({}, state, { isEditing: false });
    default:
      return state;
  }
}複製程式碼
  • 2 action
    為了快速開發,方便修改和減少檔案數量,把action也寫在reducer裡面;一般每reducer裡面程式碼最多也不會超過400行,如果程式碼量太多你應該考慮你元件是否寫得有問題了;大家把storage操作就當作是與後臺服務的互動吧。
    可能已有人注意到使用dispatch分發action時已經使用非同步資料流了,是的,但是使用非同步操作之前還是需要在Redux裡新增一些非同步操作中間價,不用捉雞,這個後面程式碼會講到。

    (注意⚠️)在真正的專案開發時候因為業務需要,所以store沒有這麼簡單;很多時候互動都是在props裡獲取的物件(變數非引用無所謂),若要對從props裡獲取的物件進行某些業務操作時一定要深拷貝一份進行操作,否則你會遇到意想不到的結果。

export function selectEntry(id) {
  return { type: types.SELECT_ENTRY, id };
}

export function createNewEntry() {
  return { type: types.CREATE_NEW_ENTRY };
}

export function editEntry(id) {
  return { type: types. EDIT_ENTRY, id };
}

export function cancelEdit() {
  return { type:  types.CANCEL_EDIT };
}

function updateEntryList(items) {
  return { type:  types.UPDATE_ENTRY_LIST, items };
}

export function deleteEntry(id) {
  return dispatch => {
    storage.deleteEntry(id)
    .then(() => storage.getAll())
    .then((items) => dispatch(updateEntryList(items)));
  };
}

export function fetchEntryList() {
  return dispatch => {
    storage.getAll()
      .then(items => dispatch(updateEntryList(items)));
  };
}

function updateSavedEntry(id) {
  return { type: types.UPDATE_SAVED_ENTRY, id };
}

export function saveEntry(item) {
  const { title, content, id } = item;
  return dispatch => {
    if (id) {
      // 更新流程
      storage.updateEntry(id, title, content)
        .then(() => dispatch(updateSavedEntry(id)))
        .then(() => storage.getAll())
        .then(items => dispatch(updateEntryList(items)));
    } else {
      // 建立流程
      storage.insertEntry(title, content)
        .then(inserted => dispatch(updateSavedEntry(inserted.id)))
        .then(() => storage.getAll())
        .then(items => dispatch(updateEntryList(items)));
    }
  };
}複製程式碼
  • 3 Redux 與元件
    在專案中使用Redux做資料管理時,我們把元件分為容器元件和展示元件,一般容器元件就負責資料是這麼更新的,不會包含任何Virtual DOM的修改和組合以及元件樣式;而展示元件是view相關的,一般會寫成無狀態元件,但是在專案中很多時候展示元件需要有生命週期,但是建議儘量使用無狀態元件。

    import React, { PropTypes } from 'react';
    import { connect } from 'react-redux';
    import CreateBar from '../CreateBar';
    import List from '../List';
    import ItemShowLayer from '../ItemShowLayer';
    import ItemEditor from '../ItemEditor';
    
    import './style.scss';
    /**
       * Deskmark是一個容器元件
       */
    class Deskmark extends React.Component {
      constructor(props) {
        super(props);
      }
      static defaultProps = {} 
    
      static propTypes = {
        state: PropTypes.object.isRequired,
        actions: PropTypes.object.isRequired,
      }
      componentDidMount() {
        this.props.actions.fetchEntryList();
      }
    
      render() {
        const { state, actions } = this.props;
        const { isEditing, selectedId } = state.editor;
        const items = state.items;
        const item = items.find(
          ({ id }) => id === selectedId
        );
    
        const mainPart = isEditing
          ? (
            <ItemEditor
              item={item}
              onSave={actions.saveEntry}
              onCancel={actions.cancelEdit}
            />
          )
          : (
            <ItemShowLayer
              item={item}
              onEdit={actions.editEntry}
              onDelete={actions.deleteEntry}
            />
          );
    
        return (
          <section className="deskmark-component">
            <nav className="navbar navbar-fixed-top navbar-dark bg-inverse">
              <a className="navbar-brand" href="#">Deskmark App</a>
            </nav>
            <div className="container">
              <div className="row">
                <div className="col-md-4 list-group">
                  <CreateBar onClick={actions.createNewEntry} />
                  <List
                    items={items}
                    onSelect={actions.selectEntry}
                  />
                </div>
                {mainPart}
              </div>
            </div>
          </section>
        );
      }
    }
    /**
     * mapStateToProps可以過濾該元件需要的資料
     * @param {*} state Redux管理的store
     */
    function mapStateToProps(state) {
      const { state, actions } = state;
      return {
        state,
        actions
      };
    }
    export default connect(mapStateToProps)(Deskmark);複製程式碼
  • 4 入口檔案
    app.js檔案是專案的入口檔案,也是在React中使用Redux的最關鍵的一步,該檔案只需要配置一次,以後都可以拿來直接用。

    外掛說明

    • 1) redux
    • 2) react-redux 該外掛讓Redux在React中的使用爽得不要不要的,在React中只需要使用一個元件(Provider)和一個高價函式(connect)就可以用Redux來管理React的資料了。
    • 3) react-router 把router的資料給Redux管理
    • 4) redux-thunk 可以在Redux中使用非同步操作,網上也有很多類似比較牛X非同步中間價

    大家如果有興趣可以看看redux-arena,專案中沒有使用過,也就不做過多介紹了。

    • 1) app.js

      import React from 'react';
      import ReactDom from 'react-dom';
      import {Provider} from 'react-redux';
      import Deskmark from '../containers/Main/index';
      import mainStore from '../containers/Main/store';
      import { Router, Route, hashHistory, browserHistory }       from 'react-router';
      //獲取store
      const store = mainStore();
      
      ReactDom.render(
        <Provider store={store}>
            <Router history={hashHistory}>
                <Route path="/" component={Deskmark} />
            </Router>
        </Provider>,
        document.getElementById('react-content')
      );複製程式碼
    • 2) store.js

      import { createStore, applyMiddleware, compose } from 'redux';
      import thunkMiddleware from 'redux-thunk';
      import rootReducer from './mainReducer';
      
      //thunkMiddleware非同步操作中介軟體
      const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);
      //建立一個store
      export default function mainStore() {
        const store = createStoreWithMiddleware(rootReducer, compose(applyMiddleware(thunkMiddleware))
         );
      
        return store;
      }複製程式碼
    • 3) mainReducer.js

      import {
        combineReducers
      } from 'redux';
      
      //combineReducers把多個子reducer合併成一個reducer
      const rootReducer = combineReducers({
        editor,
        index,
        items
      });
      
      export default rootReducer;複製程式碼

好了,到此為止在React中使用Redux就告一段落,謝謝閱讀,如果有問題可以一起討論,若文中有錯誤歡迎批評指出或者給出建議。

相關文章