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結構就是我專案中正在使用的專案結構,用起來還是很順暢的,歡迎大家提出改進建議!

相關推薦

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

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

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

React + Redux 是React生態中使用最頻繁的技術棧,但關於如何組織React+Redux的專案結構,一直都有多種聲音。本文將討論其中最常用的3種專案結構,並給出個人的最佳實踐。按照型別這裡的型別指的是一個檔案在專案中充當的角色型別,即這個檔案是一個component,還是一個container,

【 D3.js 系列1.0 】 CSV 表格檔案的讀取

在入門系列的教程中,我們常用 d3.json() 函式來讀取 json 格式的檔案。json 格式很強大,但對於普通使用者可能不太適合,普通使用者更喜歡的是用 Microsoft Excel 或 OpenOffice Calc 等生成的表格檔案,因為簡單易懂,容易編輯。

【 D3.js 系列1.1 】 其他表格檔案的讀取

CSV 表格檔案是以逗號作為單元分隔符的,其他還有以製表符 Tab 作為單元分隔符的 TSV 檔案,還有人為定義的其它分隔符的表格檔案。本文將說明在 D3 中如何讀取它們。   1. TSV 表格檔案是什麼 TSV(Tab Separated Values),製表分隔值,它

SilkTest高階系列1-用textract來識別文字

在以前的文章中,我簡單介紹過如何使用SilkTest中的OCR功能識別介面或者是bmp圖片上的文字內容。也提到過silktest自帶一個ocr識別的pattern庫,該庫可以識別windows下的某些字型。不過這些字型都是常見的字型,種類有限,對於某些特殊的字型,預設是無法

react-redux

react-redux進階一、 安裝: npm install redux react-redux 二、入口文件:index.jsx: 引入: 創建reducer: 創建容器: 將容器綁定到屬性: 完整代碼: var React = require(‘react‘) var ReactDom = re

React (5) Redux

1.  UI 元件 render()函式放在另外一個單獨的UI.js 檔案和動作行為分開 UI 上面所用的資料和函式, 通過父元件傳遞過來,用 this.props 來接收 UI 元件下面: import 'antd/dist/antd.css';

Redux---異步和中間件

src clas 復制 undle 行處理 pre reducer 明顯 dom   Redux中間件,其實就是一個函數, 當我們發送一個action的時候,先經過它,我們就可以對action進行處理,然後再發送action到達reducer, 改變狀態,這時我們就可以在中

Python系列連載(1)——那些容易被忽略的問題(上)

本篇我們來聊一聊一些在入門部分容易被忽略的問題 int()強制轉換浮點數 在int()的強制轉換浮點數時候,不管是正數還是負數,只取整數部分。 注意:這裡不是向上或者向下取整,也不是四捨五入。 無限遞迴 還記得我們講的俄羅斯套娃麼,還記得

Flutter redux

目的 認識Flutter Redux侷限性 引入Middleware必要性 全方位整合UT Flutter Redux初代實現侷限性 UT不好覆蓋 頁面 初代實現一個頁面的結構是這樣的: class XXXScreen extends StatefulWidget {

Redux(Immutable.js)

更好的閱讀體驗 更好的閱度體驗 Immutable.js Immutable的優勢 1. 保證不可變(每次通過Immutable.js操作的物件都會返回一個新的物件) 2. 豐富的API 3. 效能好 (通過字典樹對資料結構的共享) Immutable的問題 1. 與原生JS互動不友好

Redux(像VUEX一樣使用Redux

更好的閱度體驗 前言 redux的問題 方案目標 如何實現 思考 前言 Redux是一個非常實用的狀態管理庫,對於大多數使用React庫的開發者來說,Redux都是會接觸到的。在使用Redux享受其帶來的便利的同時, 我們也深受其問題的困擾。 redux的問題 之前在另

Redux(一)

State的不可變化帶來的麻煩 在用Redux處理深度複雜的資料時會有一些麻煩。由於js的特性,我們知道當對一個物件進行復制時實際上是複製它的引用,除非你對這個物件進行深度複製。Redux要求你每次你返回的都是一個全新的State,而不是去修改它。這就要求我們要對原來的State進行深度複製。這往往帶來複雜的

【 D3.js 系列 — 2.1 】 力學圖的事件 + 頂點的固定

本章討論在力學圖中常用到的事件( Event ),然後對【進階 - 第 2.0 章】的人物關係圖進行改進,使使用者能夠固定拖拽的物件。 force.on("tick", function(){ });這裡的 force 是之前程式碼中定義的佈局( Layout )

HTML5 系列:indexedDB 數據庫

連接數據庫 function request html5 客戶端 前言在 HTML5 的本地存儲中,有一種叫 indexedDB 的數據庫,該數據庫是一種存儲在客戶端本地的 NoSQL 數據庫,它可以存儲大量的數據。從上篇:HTML5 進階系列:web Storage ,我們知道

C#系列——WebApi 異常處理解決方案(轉)

機制 輸出 ges 如果 但是 rom lba slist 解決 出處:http://www.cnblogs.com/landeanfen/p/5363846.html 閱讀目錄 一、使用異常篩選器捕獲所有異常 二、HttpResponseException自

C#系列——WebApi 接口測試工具:WebApiTestClient

spa type 區域 all 手動 shee 找到 網絡 打開文件 C#進階系列——WebApi 接口測試工具:WebApiTestClient 前言:這兩天在整WebApi的服務,由於調用方是Android客戶端,Android開發人員也不懂C#語法,API裏

C#系列——WebApi 路由機制剖析:你準備好了嗎?

事先 blank path can tex 全局配置 dex 找不到 save 前言:從MVC到WebApi,路由機制一直是伴隨著這些技術的一個重要組成部分。 它可以很簡單:如果你僅僅只需要會用一些簡單的路由,如/Home/Index,那麽你只需要配置一個默認路由就能簡

C#系列——WebApi 跨域問題解決方案:CORS

dea ati ice pro target default 異常 測試工具 復雜 前言:上篇總結了下WebApi的接口測試工具的使用,這篇接著來看看WebAPI的另一個常見問題:跨域問題。本篇主要從實例的角度分享下CORS解決跨域問題一些細節。 WebApi系列文章

Jenkins系列之——09配置Linux系統ssh免密碼登陸

dom pub tar finger cnblogs pan 改變 art home ssh認證的完整描述:https://www.ibm.com/developerworks/cn/linux/security/openssh/part1/ 說明:點我去查看 今天我們只說