1. 程式人生 > >Redux進階系列1: React+Redux專案結構最佳實踐

Redux進階系列1: React+Redux專案結構最佳實踐

React + Redux 是React生態中使用最頻繁的技術棧,但關於如何組織React+Redux的專案結構,一直都有多種聲音。本文將討論其中最常用的3種專案結構,並給出個人的最佳實踐。

  1. 按照型別

    這裡的型別指的是一個檔案在專案中充當的角色型別,即這個檔案是一個component,還是一個container,或者是一個reducer等,充當component、container、action、reducer等不同角色的檔案,分別放在不同的資料夾下,這也是Redux官網示例所採用的專案結構。這種結構如下所示:

    actions/
     a.js
     b.js
    components/
     a1.js
    a2.js b1.js constainers/ a.js b.js reducers/ a.js b.js index.js

    使用這種結構組織專案,每當增加一個新功能時,需要在containers和components資料夾下增加這個功能需要的元件,還需要在actions和reducers資料夾下,分別新增Redux管理這個功能使用到的action和reducer,如果action type是放在另外一個資料夾的話,還需要在這個資料夾下增加新的action type檔案。所以,開發一個功能時,你需要頻繁的切換路徑,修改不同的檔案。當專案逐漸變大時,這種專案結構是非常不方便的。

  2. 按照功能

    一個功能模組對應一個資料夾,這個功能所用到的container、component、action、reducer等檔案,都存放在這個資料夾下。如下所示:

    feature1/
     components/
     actions.js
     container.js
     index.js
     reducer.js
    feature2/
     components/
     actions.js
     container.js
     index.js
     reducer.js
    index.js
    rootReducer.js

    這種專案結構的好處顯而易見,一個功能中使用到的元件、狀態和行為都在同一個資料夾下,方便開發,易於功能的擴充套件,Github上很多腳手架也選擇了這種目錄結構,如

    https://github.com/react-boilerplate/react-boilerplate。但這種結構也有一個問題,Redux會將整個應用的狀態作為一個store來管理,不同的功能模組之間可以共享store中的部分狀態(專案越複雜,這種場景就會越多),於是當你在feature1的container中dispatch一個action,很可能會影響feature2的狀態,因為feature1和feature2共享了部分狀態,會響應相同的action。這種情況下,不同模組間的功能被耦合到了一起。

  3. Ducks

    Ducks其實是對一種新的Redux專案結構的提議。它提倡將相關聯的reducer、action types和action寫到一個檔案裡。本質上是以應用的狀態作為模組的劃分依據,而不是以介面功能作為劃分模組的依據。這樣,管理相同狀態的依賴都在同一個檔案中,不管哪個容器元件需要使用這部分狀態,只需要在這個元件中引入這個狀態對應的檔案即可。這樣的一個檔案(模組)如下:

    // widget.js
    
    // Actions
    const LOAD   = 'widget/LOAD';
    const CREATE = 'widget/CREATE';
    const UPDATE = 'widget/UPDATE';
    const REMOVE = 'widget/REMOVE';
    
    const initialState = {
     widget: null,
     isLoading: false,
    }
    
    // Reducer
    export default function reducer(state = initialState, action = {}) {
     switch (action.type) {
       LOAD: 
         //...
       CREATE:
         //...
       UPDATE:
         //...
       REMOVE:
         //...
       default: return state;
     }
    }
    
    // Action Creators
    export function loadWidget() {
     return { type: LOAD };
    }
    
    export function createWidget(widget) {
     return { type: CREATE, widget };
    }
    
    export function updateWidget(widget) {
     return { type: UPDATE, widget };
    }
    
    export function removeWidget(widget) {
     return { type: REMOVE, widget };
    }

    整體的目錄結構如下:

    components/  (應用級別的通用元件)
    containers/  
     feature1/
       components/  (功能拆分出的專用元件)
       feature1.js  (容器元件)
       index.js     (feature1對外暴露的介面)
    redux/
     index.js (combineReducers)
     module1.js (reducer, action types, actions creators)
     module2.js (reducer, action types, actions creators)
    index.js

    在前兩種專案結構中,當container需要使用actions時,可以通過import * as actions from 'path/to/actions.js'方式,一次性把一個action檔案中的所有action creators都引入進來。但在使用Ducks結構時,action creators和reducer定義在同一個檔案中,import *的匯入方式會把reducer也匯入進來(如果action types也被export,那麼還會匯入action types)。我們可以把action creators和action types定義到一個名稱空間中,解決這個問題。修改如下:

    // widget.js
    
    // Actions
    export const types = {
     const LOAD   : 'widget/LOAD',
     const CREATE : 'widget/CREATE',
     const UPDATE : 'widget/UPDATE',
     const REMOVE : 'widget/REMOVE'
    }
    
    const initialState = {
     widget: null,
     isLoading: false,
    }
    
    // Reducer
    export default function reducer(state = initialState, action = {}) {
     switch (action.type) {
       types.LOAD: 
         //...
       types.CREATE:
         //...
       types.UPDATE:
         //...
       types.REMOVE:
         //...
       default: return state;
     }
    }
    
    // Action Creators
    export const actions = {
     loadWidget: function() {
       return { type: types.LOAD };
     },
     createWidget: createWidget(widget) {
       return { type: types.CREATE, widget };
     },
     updateWidget: function(widget) {
       return { type: types.UPDATE, widget };
     },
     removeWidget: function(widget) {
       return { type: types.REMOVE, widget };
     }
    }

    這樣,我們在container中使用actions時,可以通過import { actions } from 'path/to/module.js'引入,避免了引入額外的物件,也避免了import時把所有action都列出來的繁瑣。

    現在的Ducks結構就是我專案中正在使用的專案結構,用起來還是很順暢的,歡迎大家提出改進建議!

相關推薦

no