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

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

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

Actions、Reducers

初期搭建的項目是將 action置媳、reducer于樟、state 放在 storemodule 目錄下,最開始的理解是拇囊,每個模塊都應該有自己獨立的 action迂曲、reducer、state寥袭,但因為對 reactredux 的理解太淺路捧,導致整個項目維護成本巨大,各個組件嵌套 props 傳遞传黄,各種交集杰扫,新需求下來經(jīng)常牽一發(fā)動全身。

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

這種較為統(tǒng)一的 redux 管理中识埋,學習切分 state凡伊,降低 reduxreact 組件的耦合,維護起來思路比較清晰窒舟。

store/types

統(tǒng)一管理 actiontype系忙,避免重復開發(fā) action

api

Ajax 分裝惠豺,統(tǒng)一管理請求银还,在項目開發(fā)的時候,發(fā)現(xiàn)洁墙,因為思路不清晰见剩,很多 action 有重復的 Ajax,在服務端接口改動的時候扫俺,修改接口 url 和是一件很痛苦的事情苍苞,所以統(tǒng)一管理請求,在 action 中調(diào)用狼纬,便于項目維護羹呵。

components、views

區(qū)分普通組件和業(yè)務組件疗琉,業(yè)務組件整合需要的普通組件冈欢,通過 redux 管理 props,使得組件邏輯更加清晰盈简。

Immutable

剛開始對其本身的用法不明確的情況下胡亂使用 immutable凑耻,導致項目維護時各種問題太示,反思了下,好的技術(shù)只有在需要他并且能夠用好它的情況下在整合到項目中香浩,胡亂的添加各種技術(shù)类缤,只會把自己推往更深的坑里。

createReducer

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

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

middlewares

中間件是 redux 針對 flux 不足囱晴,產(chǎn)出的一個很有趣的工具膏蚓,如何用好它是一門藝術(shù),可以添加各種 filter middlewares 來使得整體功能變得更強大畸写。

Redux 提供了 applyMiddleware(...middlewares) 來將中間件應用到 createStore驮瞧。applyMiddleware 會返回一個函數(shù),該函數(shù)接收原來的 creatStore 作為參數(shù)枯芬,返回一個應用了 middlewares 的增強后的 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)
    }

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

    //返回經(jīng)middlewares增強后的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;

可以看出來破停, thunk 處理異步的邏輯就是判斷返回的 action 是否是 function翅楼,來執(zhí)行 function,或者執(zhí)行 action 真慢。

redux-logger

日志打印中間件

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

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

Promise

處理 action 中的 Promise

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

代碼邏輯也很簡單管嬉, 在 actionpayload 參數(shù)中指定一個 Promise,中間件判斷如果是 Promise 則對相應的 resulterror 發(fā)送 dispatch 來達到效果朗鸠。

State 維護

如何維護 state 一直是困擾我的一大難題蚯撩,要分的多細,如何劃分組件的 state烛占,如何使用 reducer 更新 state胎挎,玩了快3個月的 react 仍然一頭霧水。

State 的設計原則

  1. 是否是從父級通過 props 傳入的忆家?如果是犹菇,可能不是 state
  • 可以通過外部傳入的數(shù)據(jù)芽卿,不應該使用 state 管理
  1. 是否會隨著時間改變揭芍?如果不是,可能不是 state 卸例。
  • 不會改變的數(shù)據(jù)称杨,不應該使用 state 管理
  1. 能根據(jù)組件中其它 state 數(shù)據(jù)或者 props 計算出來嗎肌毅?如果是,就不是 state 姑原。
  • 能夠通過 props 計算出來的不應該使用 state 管理

對于應用中的每一個 state 數(shù)據(jù):

  • 找出每一個基于那個 state 渲染界面的組件悬而。
  • 找出共同的祖先組件(某個單個的組件,在組件樹中位于需要這個 state 的所有組件的上面)页衙。
  • 要么是共同的祖先組件,要么是另外一個在組件樹中位于更高層級的組件應該擁有這個 state 阴绢。
  • 如果找不出擁有這個 state 數(shù)據(jù)模型的合適的組件店乐,創(chuàng)建一個新的組件來維護這個 state ,然后添加到組件樹中呻袭,層級位于所有共同擁有者組件的上面眨八。

可以看出 state 的設計原則是不可預測的變量,但是如何構(gòu)建 state 樹呢左电?

  1. 需要對業(yè)務深入了解
  2. 要有一定的設計基礎

胡亂的設計廉侧,到最后會發(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 的更新看起來是相互獨立的篓足。有時 state 中的字段是相互依賴的段誊,需要認真考慮,但在這個案例中我們可以把 todos 更新的業(yè)務邏輯拆分到一個單獨的函數(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栈拖,但它變成了一個數(shù)組连舍!現(xiàn)在 todoApp 只把需要更新的一部分 state 傳給 todos 函數(shù),todos 函數(shù)自己確定如何更新這部分數(shù)據(jù)涩哟。這就是所謂的 reducer 合成索赏,它是開發(fā) Redux 應用最基礎的模式。

在目前無法達到準確的分化 state 時贴彼,先將 reducer 對于 state 的操作放在一起潜腻,然后在對業(yè)務有更深刻的理解時,慢慢的分化器仗,來達到 state 維護的目的

下面深入探討一下如何做 reducer 合成融涣。能否抽出一個 reducer 來專門管理 visibilityFilter?當然可以:

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

現(xiàn)在我們可以開發(fā)一個函數(shù)來做為主 reducer精钮,它調(diào)用多個子 reducer 分別處理 state 中的一部分數(shù)據(jù)暴心,然后再把這些數(shù)據(jù)合成一個大的單一對象。主 reducer 并不需要設置初始化時完整的 state杂拨。初始時专普,如果傳入 undefined, 子 reducer 將負責返回它們的默認值。

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)
  }
}

注意每個 reducer 只負責管理全局 state 中它負責的一部分弹沽。每個 reducer 的 state 參數(shù)都不同檀夹,分別對應它管理的那部分 state 數(shù)據(jù)筋粗。

現(xiàn)在看過起來好多了!隨著應用的膨脹炸渡,我們還可以將拆分后的 reducer 放到不同的文件中, 以保持其獨立性并用于專門處理不同的數(shù)據(jù)域娜亿。

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

import { combineReducers } from 'redux';

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp;

注意上面的寫法和下面完全等價:

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

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

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

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)容合作請聯(lián)系作者
  • 序言:七十年代末性雄,一起剝皮案震驚了整個濱河市没卸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌秒旋,老刑警劉巖约计,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異迁筛,居然都是意外死亡病蛉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門瑰煎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铺然,“玉大人,你說我怎么就攤上這事酒甸∑墙。” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵插勤,是天一觀的道長沽瘦。 經(jīng)常有香客問我,道長农尖,這世上最難降的妖魔是什么析恋? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮盛卡,結(jié)果婚禮上助隧,老公的妹妹穿的比我還像新娘。我一直安慰自己滑沧,他們只是感情好并村,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布巍实。 她就那樣靜靜地躺著,像睡著了一般哩牍。 火紅的嫁衣襯著肌膚如雪棚潦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天膝昆,我揣著相機與錄音丸边,去河邊找鬼。 笑死荚孵,一個胖子當著我的面吹牛妹窖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播处窥,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼嘱吗,長吁一口氣:“原來是場噩夢啊……” “哼玄组!你這毒婦竟也來了滔驾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤俄讹,失蹤者是張志新(化名)和其女友劉穎哆致,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體患膛,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡摊阀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了踪蹬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胞此。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖跃捣,靈堂內(nèi)的尸體忽然破棺而出漱牵,到底是詐尸還是另有隱情,我是刑警寧澤疚漆,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布酣胀,位于F島的核電站,受9級特大地震影響娶聘,放射性物質(zhì)發(fā)生泄漏闻镶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一丸升、第九天 我趴在偏房一處隱蔽的房頂上張望铆农。 院中可真熱鬧,春花似錦狡耻、人聲如沸顿涣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涛碑。三九已至精堕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蒲障,已是汗流浹背歹篓。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留揉阎,地道東北人庄撮。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像毙籽,于是被迫代替她去往敵國和親洞斯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 做React需要會什么? react的功能其實很單一毅否,主要負責渲染的功能亚铁,現(xiàn)有的框架,比如angular是一個大而...
    蒼都閱讀 14,759評論 1 139
  • http://gaearon.github.io/redux/index.html 螟加,文檔在 http://rac...
    jacobbubu閱讀 79,951評論 35 198
  • 學習必備要點: 首先弄明白徘溢,Redux在使用React開發(fā)應用時,起到什么作用——狀態(tài)集中管理 弄清楚Redux是...
    賀賀v5閱讀 8,896評論 10 58
  • 前言 本文 有配套視頻捆探,可以酌情觀看然爆。 文中內(nèi)容因各人理解不同,可能會有所偏差黍图,歡迎朋友們聯(lián)系我討論曾雕。 文中所有內(nèi)...
    珍此良辰閱讀 11,904評論 23 111
  • 或許,我對于你雌隅,只是一個熟悉的陌生人翻默。那棵記憶里的木棉,花開滿了枝頭恰起,紅得如同那時我們的血液修械。這段單純的回憶只有我...
    布小夭閱讀 668評論 0 0