目錄結(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
放在 store
的 module
目錄下,最開始的理解是拇囊,每個模塊都應該有自己獨立的 action迂曲、reducer、state
寥袭,但因為對 react
和 redux
的理解太淺路捧,導致整個項目維護成本巨大,各個組件嵌套 props
傳遞传黄,各種交集杰扫,新需求下來經(jīng)常牽一發(fā)動全身。
而現(xiàn)在將所有的 action
統(tǒng)一放在 actions
中進行管理膘掰,業(yè)務組件在需要的時候加載對應的 action
方法章姓, reducer
監(jiān)聽對應的 action
下發(fā)新的 state
。
這種較為統(tǒng)一的 redux
管理中识埋,學習切分 state
凡伊,降低 redux
和 react
組件的耦合,維護起來思路比較清晰窒舟。
store/types
統(tǒng)一管理 action
的 type
系忙,避免重復開發(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);
}
)
代碼邏輯也很簡單管嬉, 在 action
的 payload
參數(shù)中指定一個 Promise
,中間件判斷如果是 Promise
則對相應的 result
和 error
發(fā)送 dispatch
來達到效果朗鸠。
State 維護
如何維護 state
一直是困擾我的一大難題蚯撩,要分的多細,如何劃分組件的 state
烛占,如何使用 reducer
更新 state
胎挎,玩了快3個月的 react
仍然一頭霧水。
State
的設計原則
- 是否是從父級通過
props
傳入的忆家?如果是犹菇,可能不是state
。
- 可以通過外部傳入的數(shù)據(jù)芽卿,不應該使用
state
管理
- 是否會隨著時間改變揭芍?如果不是,可能不是
state
卸例。
- 不會改變的數(shù)據(jù)称杨,不應該使用
state
管理
- 能根據(jù)組件中其它
state
數(shù)據(jù)或者props
計算出來嗎肌毅?如果是,就不是state
姑原。
- 能夠通過
props
計算出來的不應該使用state
管理
對于應用中的每一個 state 數(shù)據(jù):
- 找出每一個基于那個 state 渲染界面的組件悬而。
- 找出共同的祖先組件(某個單個的組件,在組件樹中位于需要這個 state 的所有組件的上面)页衙。
- 要么是共同的祖先組件,要么是另外一個在組件樹中位于更高層級的組件應該擁有這個 state 阴绢。
- 如果找不出擁有這個 state 數(shù)據(jù)模型的合適的組件店乐,創(chuàng)建一個新的組件來維護這個 state ,然后添加到組件樹中呻袭,層級位于所有共同擁有者組件的上面眨八。
可以看出 state
的設計原則是不可預測的變量,但是如何構(gòu)建 state
樹呢左电?
- 需要對業(yè)務深入了解
- 要有一定的設計基礎
胡亂的設計廉侧,到最后會發(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
的使用筆記