Redux進階系列1: React+Redux項目結構最佳實踐

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

1.按照類型react

這裏的類型指的是一個文件在項目中充當的角色類型,即這個文件是一個component,仍是一個container,或者是一個reducer等,充當component、container、action、reducer等不一樣角色的文件,分別放在不一樣的文件夾下,這也是Redux官網示例所採用的項目結構。這種結構以下所示:git

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文件。因此,開發一個功能時,你須要頻繁的切換路徑,修改不一樣的文件。當項目逐漸變大時,這種項目結構是很是不方便的。github

2.按照功能redux

一個功能模塊對應一個文件夾,這個功能所用到的container、component、action、reducer等文件,都存放在這個文件夾下。以下所示:spa

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-boil...。但這種結構也有一個問題,Redux會將整個應用的狀態做爲一個store來管理,不一樣的功能模塊之間能夠共享store中的部分狀態(項目越複雜,這種場景就會越多),因而當你在feature1的container中dispatch一個action,極可能會影響feature2的狀態,由於feature1和feature2共享了部分狀態,會響應相同的action。這種狀況下,不一樣模塊間的功能被耦合到了一塊兒。code

3.Duckscomponent

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結構就是我項目中正在使用的項目結構,用起來仍是很順暢的,歡迎你們提出改進建議!


歡迎關注個人公衆號:老幹部的大前端,領取21本大前端精選書籍!

3808299627-5a93ba468b59a

相關文章
相關標籤/搜索