目錄結(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 放在 store 的 module 目錄下,最開(kāi)始的理解是,每個(gè)模塊都應(yīng)該有自己獨(dú)立的 action、reducer、state,但因?yàn)閷?duì) react 和 redux 的理解太淺,導(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,降低 redux 和 react 組件的耦合,維護(hù)起來(lái)思路比較清晰。
store/types
統(tǒng)一管理 action 的 type,避免重復(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)單, 在 action 的 payload 參數(shù)中指定一個(gè) Promise,中間件判斷如果是 Promise 則對(duì)相應(yīng)的 result 和 error 發(fā)送 dispatch 來(lái)達(dá)到效果。
State 維護(hù)
如何維護(hù) state 一直是困擾我的一大難題,要分的多細(xì),如何劃分組件的 state,如何使用 reducer 更新 state,玩了快3個(gè)月的 react 仍然一頭霧水。
State的設(shè)計(jì)原則
- 是否是從父級(jí)通過(guò)
props傳入的?如果是,可能不是state。
- 可以通過(guò)外部傳入的數(shù)據(jù),不應(yīng)該使用
state管理
- 是否會(huì)隨著時(shí)間改變?如果不是,可能不是
state。
- 不會(huì)改變的數(shù)據(jù),不應(yīng)該使用
state管理
- 能根據(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ù)呢?
- 需要對(duì)業(yè)務(wù)深入了解
- 要有一定的設(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的使用筆記