React+redux+webpack 項(xiàng)目構(gòu)建:初具規(guī)模

目錄結(jié)構(gòu)

.
├── dist                              # 所有打包配置項(xiàng)
├── src                               # 程序源文件
│   ├── actions                       # actions 管理
│   ├── api                           # superagent 處理,基于node的Ajax組件
│   ├── components                    # 可復(fù)用的直觀組件(Presentational Components)
│   ├── constants                     # 常量管理
│   ├── reducers                      # reducers 管理
│   ├── routes                        # 主路由和異步分割點(diǎn)
│   │   └── index.js                  # 用store啟動(dòng)主程序路由
│   ├── static                        # 靜態(tài)資源文件
│   ├── store                         # Redux指定塊
│   │   ├── middlewares               # 中間件管理
│   │   │   ├── afterApiMiddleware.js # 處理用戶登錄中間件
│   │   │   └── promiseMiddleware.js  # 處理 Pormise 中間件
│   │   ├── createStore.js            # 創(chuàng)建和使用redux store
│   │   ├── reducers.js               # Reducer注冊(cè)和注入
│   │   └── types.js                  # 管理 Action、Reducer 公用的 types
│   │── util                          # 工具包
│   │── views                         # 業(yè)務(wù)頁(yè)面管理
│   │   └── Home                      # 不規(guī)則路由
│   │       ├── Home.js               # 將組件集成成為業(yè)務(wù)模塊
│   │       ├── Home.less             # 業(yè)務(wù)模塊對(duì)應(yīng)的css
│   │       └── index.js              # 導(dǎo)入業(yè)務(wù)模塊,使用 redux 將模塊需要的 porps 傳入
│   ├── index.htlm                    # 主入口 HTML 文件
│   ├── index.js                      # 主要 JS 文件
│   └── index.less                    # 主入口 css 文件
└── tests                             # 測(cè)試

Actions、Reducers

初期搭建的項(xiàng)目是將 action、reducer、state 放在 storemodule 目錄下,最開(kāi)始的理解是,每個(gè)模塊都應(yīng)該有自己獨(dú)立的 action、reducer、state,但因?yàn)閷?duì) reactredux 的理解太淺,導(dǎo)致整個(gè)項(xiàng)目維護(hù)成本巨大,各個(gè)組件嵌套 props 傳遞,各種交集,新需求下來(lái)經(jīng)常牽一發(fā)動(dòng)全身。

而現(xiàn)在將所有的 action 統(tǒng)一放在 actions 中進(jìn)行管理,業(yè)務(wù)組件在需要的時(shí)候加載對(duì)應(yīng)的 action 方法, reducer 監(jiān)聽(tīng)對(duì)應(yīng)的 action 下發(fā)新的 state

這種較為統(tǒng)一的 redux 管理中,學(xué)習(xí)切分 state,降低 reduxreact 組件的耦合,維護(hù)起來(lái)思路比較清晰。

store/types

統(tǒng)一管理 actiontype,避免重復(fù)開(kāi)發(fā) action。

api

Ajax 分裝,統(tǒng)一管理請(qǐng)求,在項(xiàng)目開(kāi)發(fā)的時(shí)候,發(fā)現(xiàn),因?yàn)樗悸凡磺逦?,很?action 有重復(fù)的 Ajax,在服務(wù)端接口改動(dòng)的時(shí)候,修改接口 url 和是一件很痛苦的事情,所以統(tǒng)一管理請(qǐng)求,在 action 中調(diào)用,便于項(xiàng)目維護(hù)。

components、views

區(qū)分普通組件和業(yè)務(wù)組件,業(yè)務(wù)組件整合需要的普通組件,通過(guò) redux 管理 props,使得組件邏輯更加清晰。

Immutable

剛開(kāi)始對(duì)其本身的用法不明確的情況下胡亂使用 immutable,導(dǎo)致項(xiàng)目維護(hù)時(shí)各種問(wèn)題,反思了下,好的技術(shù)只有在需要他并且能夠用好它的情況下在整合到項(xiàng)目中,胡亂的添加各種技術(shù),只會(huì)把自己推往更深的坑里。

createReducer

// reducer生成器
export function createReducer (initialState, reducerMap) {
  return (state = initialState, action) => {
    const reducer = reducerMap[action.type]
    return reducer ? reducer(state, action) : state
  }
}

reducer 通過(guò) switch case 來(lái)判斷 action.type 針對(duì)指定的 type 構(gòu)建出新的 state,使用這段 js 構(gòu)建出的 reducer 代碼減少了 switch case 的模板代碼,使得 reducer 看起來(lái)更加清晰。

middlewares

中間件是 redux 針對(duì) flux 不足,產(chǎn)出的一個(gè)很有趣的工具,如何用好它是一門(mén)藝術(shù),可以添加各種 filter middlewares 來(lái)使得整體功能變得更強(qiáng)大。

Redux 提供了 applyMiddleware(...middlewares) 來(lái)將中間件應(yīng)用到 createStore。applyMiddleware 會(huì)返回一個(gè)函數(shù),該函數(shù)接收原來(lái)的 creatStore 作為參數(shù),返回一個(gè)應(yīng)用了 middlewares 的增強(qiáng)后的 creatStore。

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    //接收createStore參數(shù)
    var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch
    var chain = []

    //傳遞給中間件的參數(shù)
    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }

    //注冊(cè)中間件調(diào)用鏈
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    //返回經(jīng)middlewares增強(qiáng)后的createStore
    return {
      ...store,
      dispatch
    }
  }
}

redux-thunk

處理異步 action 的中間件,

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

可以看出來(lái), thunk 處理異步的邏輯就是判斷返回的 action 是否是 function,來(lái)執(zhí)行 function,或者執(zhí)行 action 。

redux-logger

日志打印中間件

export default function({getState,dispatch}) {
    return (next) => (action) => {
        console.log('pre state', getState());
        // 調(diào)用 middleware 鏈中下一個(gè) middleware 的 dispatch。
        next(action);
        console.log('after dispatch', getState());
    }
}

日志打印其實(shí)就是在 action 執(zhí)行前后分別打印需要的信息,觀測(cè)每個(gè) action 的變化對(duì)發(fā)開(kāi)的幫助是很大的。

Promise

處理 action 中的 Promise

action.payload.then(
    result => dispatch({ ...action, payload: result }),
    error => {
        dispatch({ ...action, payload: error, error: true });
        return Promise.reject(error);
    }
)

代碼邏輯也很簡(jiǎn)單, 在 actionpayload 參數(shù)中指定一個(gè) Promise,中間件判斷如果是 Promise 則對(duì)相應(yīng)的 resulterror 發(fā)送 dispatch 來(lái)達(dá)到效果。

State 維護(hù)

如何維護(hù) state 一直是困擾我的一大難題,要分的多細(xì),如何劃分組件的 state,如何使用 reducer 更新 state,玩了快3個(gè)月的 react 仍然一頭霧水。

State 的設(shè)計(jì)原則

  1. 是否是從父級(jí)通過(guò) props 傳入的?如果是,可能不是 state 。
  • 可以通過(guò)外部傳入的數(shù)據(jù),不應(yīng)該使用 state 管理
  1. 是否會(huì)隨著時(shí)間改變?如果不是,可能不是 state
  • 不會(huì)改變的數(shù)據(jù),不應(yīng)該使用 state 管理
  1. 能根據(jù)組件中其它 state 數(shù)據(jù)或者 props 計(jì)算出來(lái)嗎?如果是,就不是 state
  • 能夠通過(guò) props 計(jì)算出來(lái)的不應(yīng)該使用 state 管理

對(duì)于應(yīng)用中的每一個(gè) state 數(shù)據(jù):

  • 找出每一個(gè)基于那個(gè) state 渲染界面的組件。
  • 找出共同的祖先組件(某個(gè)單個(gè)的組件,在組件樹(shù)中位于需要這個(gè) state 的所有組件的上面)。
  • 要么是共同的祖先組件,要么是另外一個(gè)在組件樹(shù)中位于更高層級(jí)的組件應(yīng)該擁有這個(gè) state 。
  • 如果找不出擁有這個(gè) state 數(shù)據(jù)模型的合適的組件,創(chuàng)建一個(gè)新的組件來(lái)維護(hù)這個(gè) state ,然后添加到組件樹(shù)中,層級(jí)位于所有共同擁有者組件的上面。

可以看出 state 的設(shè)計(jì)原則是不可預(yù)測(cè)的變量,但是如何構(gòu)建 state 樹(shù)呢?

  1. 需要對(duì)業(yè)務(wù)深入了解
  2. 要有一定的設(shè)計(jì)基礎(chǔ)

胡亂的設(shè)計(jì),到最后會(huì)發(fā)現(xiàn),只是在給自己埋坑...

拆分 Reducer

原文:Reducer

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    case TOGGLE_TODO:
      return Object.assign({}, state, {
        todos: state.todos.map((todo, index) => {
          if(index === action.index) {
            return Object.assign({}, todo, {
              completed: !todo.completed
            })
          }
          return todo
        })
      })
    default:
      return state
  }
}

這里的 todos 和 visibilityFilter 的更新看起來(lái)是相互獨(dú)立的。有時(shí) state 中的字段是相互依賴的,需要認(rèn)真考慮,但在這個(gè)案例中我們可以把 todos 更新的業(yè)務(wù)邏輯拆分到一個(gè)單獨(dú)的函數(shù)里:

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
    case TOGGLE_TODO:
      return Object.assign({}, state, {
        todos: todos(state.todos, action)
      })
    default:
      return state
  }
}

注意 todos 依舊接收 state,但它變成了一個(gè)數(shù)組!現(xiàn)在 todoApp 只把需要更新的一部分 state 傳給 todos 函數(shù),todos 函數(shù)自己確定如何更新這部分?jǐn)?shù)據(jù)。這就是所謂的 reducer 合成,它是開(kāi)發(fā) Redux 應(yīng)用最基礎(chǔ)的模式。

在目前無(wú)法達(dá)到準(zhǔn)確的分化 state 時(shí),先將 reducer 對(duì)于 state 的操作放在一起,然后在對(duì)業(yè)務(wù)有更深刻的理解時(shí),慢慢的分化,來(lái)達(dá)到 state 維護(hù)的目的

下面深入探討一下如何做 reducer 合成。能否抽出一個(gè) reducer 來(lái)專門(mén)管理 visibilityFilter?當(dāng)然可以:

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
  case SET_VISIBILITY_FILTER:
    return action.filter
  default:
    return state
  }
}

現(xiàn)在我們可以開(kāi)發(fā)一個(gè)函數(shù)來(lái)做為主 reducer,它調(diào)用多個(gè)子 reducer 分別處理 state 中的一部分?jǐn)?shù)據(jù),然后再把這些數(shù)據(jù)合成一個(gè)大的單一對(duì)象。主 reducer 并不需要設(shè)置初始化時(shí)完整的 state。初始時(shí),如果傳入 undefined, 子 reducer 將負(fù)責(zé)返回它們的默認(rèn)值。

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}

function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}

注意每個(gè) reducer 只負(fù)責(zé)管理全局 state 中它負(fù)責(zé)的一部分。每個(gè) reducer 的 state 參數(shù)都不同,分別對(duì)應(yīng)它管理的那部分 state 數(shù)據(jù)。

現(xiàn)在看過(guò)起來(lái)好多了!隨著應(yīng)用的膨脹,我們還可以將拆分后的 reducer 放到不同的文件中, 以保持其獨(dú)立性并用于專門(mén)處理不同的數(shù)據(jù)域。

最后,Redux 提供了 combineReducers() 工具類來(lái)做上面 todoApp 做的事情,這樣就能消滅一些樣板代碼了。有了它,可以這樣重構(gòu) todoApp:

import { combineReducers } from 'redux';

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp;

注意上面的寫(xiě)法和下面完全等價(jià):

export default function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}

這塊代碼讓我更加確定,先統(tǒng)一管理 reducer 后在慢慢分化,原先為了分化而分化,坑苦了自己

你也可以給它們?cè)O(shè)置不同的 key,或者調(diào)用不同的函數(shù)。下面兩種合成 reducer 方法完全等價(jià):

const reducer = combineReducers({
  a: doSomethingWithA,
  b: processB,
  c: c
})
function reducer(state = {}, action) {
  return {
    a: doSomethingWithA(state.a, action),
    b: processB(state.b, action),
    c: c(state.c, action)
  }
}

持續(xù)更新,下一篇 webpack 的使用筆記

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 做React需要會(huì)什么? react的功能其實(shí)很單一,主要負(fù)責(zé)渲染的功能,現(xiàn)有的框架,比如angular是一個(gè)大而...
    蒼都閱讀 14,961評(píng)論 1 139
  • http://gaearon.github.io/redux/index.html ,文檔在 http://rac...
    jacobbubu閱讀 80,422評(píng)論 35 198
  • 學(xué)習(xí)必備要點(diǎn): 首先弄明白,Redux在使用React開(kāi)發(fā)應(yīng)用時(shí),起到什么作用——狀態(tài)集中管理 弄清楚Redux是...
    賀賀v5閱讀 9,074評(píng)論 10 58
  • 前言 本文 有配套視頻,可以酌情觀看。 文中內(nèi)容因各人理解不同,可能會(huì)有所偏差,歡迎朋友們聯(lián)系我討論。 文中所有內(nèi)...
    珍此良辰閱讀 12,195評(píng)論 23 111
  • 或許,我對(duì)于你,只是一個(gè)熟悉的陌生人。那棵記憶里的木棉,花開(kāi)滿了枝頭,紅得如同那時(shí)我們的血液。這段單純的回憶只有我...
    布小夭閱讀 707評(píng)論 0 0

友情鏈接更多精彩內(nèi)容