原文在這裏
, 這是Redux官方的充電包工具集,可使Redux的用法簡單一點. 本文是文檔中的用戶指南的翻譯版本 [^譯註:這裏的包裝只是簡化了操做,概念性問題的難度並無下降,所以在沒有理解Redux核心概念以前,Redux的文檔任然是學習的敲門磚]javascript
Redux核心庫刻意安排爲沒有任何偏向性.所以可讓使用者本身來處理每一個問題,例如包含了state的State的配置,以及怎麼構建reducers. 在某些使用案例中,是很好的,由於這麼樣作給了你很大的靈活性,可是用戶並不老是須要靈活性.有時候,咱們須要的只是盡肯能簡單的開始工做,只須要有開箱即用的默認配置就能夠.又或者是,你正在編寫一個大型的應用,發現本身寫了太多相似的代碼,你很是但願能山減掉大量的手寫代碼.java
正如在快速入門
中講到的同樣,Redux Starter Kit的目標是協助簡化Redux的常見使用用例.它並無像你想象的那樣成爲一個完整的解決方案,而是使得一些Redux相關代碼的編寫變簡單(或者在某些狀況下,不折不扣的減小手寫代碼的量).git
Redux Starter Kit 導出了幾個可供使用的獨立函數,在其餘的包中添加依賴就能夠和Redux一塊兒工做.這可讓你決定,究竟是在全新的項目仍是已經進行的項目中使用Kit.github
接下來看看一些Redux Starter Kit的用法,這些用法可讓你的Redux代碼更漂亮.redux
每一個Redux App須要配置並建立一個Store,一般狀況下包含如下幾個步驟:api
Redux DevTools 擴展
下面的代碼是 Redux文檔 配置Store
中的典型代碼數組
import { applyMiddleware, compose, createStore } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunkMiddleware from 'redux-thunk'
import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'
export default function configureStore(preloadedState) {
const middlewares = [loggerMiddleware, thunkMiddleware]
const middlewareEnhancer = applyMiddleware(...middlewares)
const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
const composedEnhancers = composeWithDevTools(...enhancers)
const store = createStore(rootReducer, preloadedState, composedEnhancers)
if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('./reducers', () => store.replaceReducer(rootReducer))
}
return store
}
複製代碼
實例代碼的可讀性還能夠,可是流程不太直接明瞭:app
createStore
函數接收幾個固定位置的參數:(rootReducer,preloadedState,enhancer
).有時候很容易忘記其中的參數.configureStore
函數簡化Store的配置configureStore
在一下幾個方面對咱們有幫助:異步
applyMiddleware
和compose
函數.此外,confitureStore
默認添加了一些有特定用途的中間件:ide
redux-thunk
是在組件外執行同步和異步邏輯的經常使用中間件這麼作意味着,store的配置代碼自己更短,更容易閱讀.
最簡答的用法是隻要把頂層reducer函數做爲reducer
形參傳遞就能夠了.
import { configureStore } from 'redux-starter-kit'
import rootReducer from './reducers'
const store = configureStore({
reducer: rootReducer
})
export default store
複製代碼
也能夠傳遞分片(slice)的reducer,configureStore
會自動調用combineReducers
:
import usersReducer from './usersReducer'
import postsReducer from './postsReducer'
const store = configureStore({
reducer: {
users: usersReducer,
posts: postsReducer
}
})
複製代碼
注意,這個用法只對第一級的reducer有用.若是你想嵌套reducer,須要本身調用combineReducers
來完成更低一級的嵌套.
若是你想定製store的配置,能夠傳遞額外的選項.這裏是熱加載的實例:
import { configureStore, getDefaultMiddleware } from 'redux-starter-kit'
import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'
export default function configureAppStore(preloadedState) {
const store = configureStore({
reducer: rootReducer,
middleware: [loggerMiddleware, ...getDefaultMiddleware()],
preloadedState,
enhancers: [monitorReducersEnhancer]
})
if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('./reducers', () => store.replaceReducer(rootReducer))
}
return store
}
複製代碼
若是你提供了middleware
參數, confitureStore
就只使用你提供的中間件. 若是你想在默認中間件基礎上添加定製的中間件,能夠調用getDefaultMiddleware
,把你本身的中間件數組包含進去.
Reducers是Redux中最重要的概念,一個典型的reducer函數須要具有的功能是:
type
字段,來決定如何響應action當你在reducer中使用須要的條件邏輯是,最多見的方法是switch
聲明,由於針對單個字段執行最直接的操做.然而不少人不喜歡switch聲明.Redux文檔展現了基於action type類型的映射的用法,可是須要你本身配置.
另外一個常見的痛點是編寫reducers時,不要有不可突變的更新state.Javascript是可突變的語言.手動更新嵌套的數據很是棘手
,很容易出錯.
createReducer
函數簡化Reducer的操做由於"查表"(映射)方法很流行,Redux Starter Kit 包含了一個相似Redux 文檔中createReducer
的函數.然而咱們的createReducer
工具備一些魔法,是的Reducer的操做更好,在內部它使用'Immer'庫,Immer庫可讓你編寫假的"突變"代碼,實際上進行了不可突變動新.這麼作有效的避免了偶然的突變操做.
整體上,任何使用switch
聲明的Reducx Reducer均可以直接轉化使用createReducer
. switch中每一個case
都變成傳遞給createReducer
對象的一個鍵. 不可突變動新邏輯,例如對象展開操做,拷貝數組均可以直接轉換爲"可突變"操做 . 保持原來的不可突變操做也能夠, 只需返回更新的拷貝就行.
這裏有一些可使用createReducer
的實例. 咱們從經典的"todo list" reducer開始,使用的是switch聲明和不可突變動新
function todosReducer(state = [], action) {
switch(action.type) {
case "ADD_TODO": {
return state.concat(action.payload);
}
case "TOGGLE_TODO": {
const {index} = action.payload;
return state.map( (todo, i) => {
if(i !== index) return todo;
return {
...todo,
completed : !todo.completed
};
});
} ,
"REMOVE_TODO" : (state, action) => {
return state.filter( (todo, i) => i !== action.payload.index)
}
default : return state;
}
}
複製代碼
注意:咱們聲明調用了state.concat()
,返回包含todo新條目的通過拷貝的數組,state.map()
返回toggle分支的拷貝數組,這裏使用對象展開操做符對要更新的todo項進行了複製.
經過使用createReducer
,咱們能夠考慮簡化實例:
const todosReducer = createReducer([], {
"ADD_TODO" : (state, action) => {
// "mutate" the array by calling push()
state.push(action.payload);
},
"TOGGLE_TODO" : (state, action) => {
const todo = state[action.payload.index];
// "mutate" the object by overwriting a field
todo.completed = !todo.completed;
},
"REMOVE_TODO" : (state, action) => {
// Can still return an immutably-updated value if we want to
return state.filter( (todo, i) => i !== action.payload.index)
}
})
複製代碼
"突變"state的能力在試圖更新深度嵌套的state時特別有用.複雜而使人痛苦的代碼以下:
case "UPDATE_VALUE":
return {
...state,
first: {
...state.first,
second: {
...state.first.second,
[action.someId]: {
...state.first.second[action.someId],
fourth: action.someValue
}
}
}
}
複製代碼
能夠簡化爲下面的代碼:
updateValue(state, action) {
const {someId, someValue} = action.payload;
state.first.second[someId] = someValue;
}
複製代碼
看上去好多了 [^譯註:這裏看上去是可突變操做,實際底層使用的Immer在操做以前已經進行了拷貝操做,不會在原始內存地址作修改].
在現代Javascript中,有幾個固定好的方法能夠在對象中定義鍵和函數(並非特定針對Redux),因此你能夠混合匹配不一樣的鍵定義和函數定義. 例以下面對象中全部的函數定義方法都是合規的.
const keyName = "ADD_TODO4";
const reducerObject = {
// Explicit quotes for the key name, arrow function for the reducer
"ADD_TODO1" : (state, action) => { }
// Bare key with no quotes, function keyword
ADD_TODO2 : function(state, action){ }
// Object literal function shorthand
//對象字面量函數簡寫方式
ADD_TODO3(state, action) { }
// Computed property
[keyName] : (state, action) => { }
}
複製代碼
使用對象字面量函數簡寫方式
多是最簡短的代碼,可是你可使用以上任何一種方式.
createReducer
要考慮的因素Redux Starter Kit的createReducer
函數能夠發揮很大的做用,可是也要留心:
createReducer
函數內使用查看createReducer API 參考
,瞭解具體的詳情.
Redux鼓勵使用者編寫action creator 函數
,action creator函數封裝了建立Action 對象的流程. 可是標準的Redux用法中不是必須的.
絕大多數的action creator都很是簡單.它們接受一些參數,返回一個擁有特定type
字段和傳入參數的 action對象.這些參數一般放在payload
字段下, action隊形是Flux標準action
的傳統定義,目的是組織action對象的內容. 一個典型action creator的結構以下:
function addTodo(text) {
return {
type: 'ADD_TODO',
payload: { text }
}
}
複製代碼
createAction
函數定義Action Creators手動編寫action creators很乏味.Redux Starter Kit提供了一個函數 createAction
,用於簡化給定了action type的action對象的建立過程,傳入的參數安置在payload
字段中:
const addTodo = createAction('ADD_TODO')
addTodo({ text: 'Buy milk' })
// {type : "ADD_TODO", payload : {text : "Buy milk"}})
複製代碼
目前,creatAction
不容許你定製payload
字段. 必需要把payload
做爲整個參數來傳遞.payload
能夠是單個的值,或者一個有大量數據的對象(咱們最終會爲createAction
添加一個用於定製payload的回調函數,或者是能夠添加其餘相似meta
同樣的字段).
Redux的reducers函數須要查找特定的action types來決定該如何更新state.一般狀況下, 定義action type 字符串和定義action creator函數式分開的. Redux Starter Kit的createAction
函數使用了一套技巧讓兩個定義更容易了.
首先,createAction
在它生成的action creators上 重寫了toString()
方法. 意思是,action creators自身也能夠做爲"action type"來引用,例如能夠做爲creareReducer
的鍵.
第二點,action type也能夠定義爲action creator的一個type
字段.
const actionCreator = createAction("SOME_ACTION_TYPE");
console.log(actionCreator.toString())
// "SOME_ACTION_TYPE"
console.log(actionCreator.type);
// "SOME_ACTION_TYPE"
const reducer = createReducer({}, {
// actionCreator.toString() will automatically be called here
[actionCreator] : (state, action) => {}
// Or, you can reference the .type field:
[actionCreator.type] : (state, action) => { }
});
複製代碼
這意味着,你不須要編寫或使用獨立的action type變量,或或者重複action type的名字和值,例如const SOME_ACTION_TYPE = "SOME_ACTION_TYPE"
就不須要了.
不幸的是,隱式轉換爲字符串對於switch聲明不起做用.若是你在一個switch 聲明中使用了這些action creator,須要你本身調用actionCreator.toString()
const actionCreator = createAction('SOME_ACTION_TYPE')
const reducer = (state = {}, action) => {
switch (action.type) {
// ERROR: 這裏會出錯, 在switch中,actionCreator不會自動獲取字符串
case actionCreator: {
break
}
// CORRECT: this will work as expected
case actionCreator.toString(): {
break
}
// CORRECT: this will also work right
case actionCreator.type: {
break
}
}
}
複製代碼
若是你同時使用了Redux Starter Kit和TypeScript,要留心, 在action creator 用於對象的鍵時,TypeScript編譯器不會接受 toString()
的隱式轉換.這種狀況下,你須要手動轉換(actionCreator as string),或者使用.type
字段做爲鍵.
Redux 的state一般都以"切片"的形式組織,由reducers定義的切片 state傳遞給combineReducers
.
import { combineReducers } from 'redux'
import usersReducer from './usersReducer'
import postsReducer from './postsReducer'
const rootReducer = combineReducers({
users: usersReducer,
posts: postsReducer
})
複製代碼
在這個例子中,users
和posts
都被認爲是"切片(slices)".也都是reducers:
常規的方法是在本身獨立的文件中定義切片reducer函數,action creators在第二個文件中.由於兩個文件都須要引用一樣的action types, 又會在第三個文件中定義,並在前兩個文件中導入:
// postsConstants.js
const CREATE_POST = 'CREATE_POST'
const UPDATE_POST = 'UPDATE_POST'
const DELETE_POST = 'DELETE_POST'
// postsActions.js
import { CREATE_POST, UPDATE_POST, DELETE_POST } from './postConstants'
export function addPost(id, title) {
return {
type: CREATE_POST,
payload: { id, title }
}
}
// postsReducer.js
import { CREATE_POST, UPDATE_POST, DELETE_POST } from './postConstants'
const initialState = []
export default function postsReducer(state = initialState, action) {
switch (action.type) {
case CREATE_POST: {
// omit implementation
}
default:
return state
}
}
複製代碼
這惟一必不可少的部分只有reducer本身,想一想其餘部分:
connect
提供的mapDispatch
參數,轉而直接使用this.porps.dispatch({type:'CREATE_POST',payload:{id:123,title:"Hello World"}})
"鴨式"文件結構
建議把全部的Redux相關邏輯放到一個單一文件中,像這樣:
// postsDuck.js
const CREATE_POST = 'CREATE_POST'
const UPDATE_POST = 'UPDATE_POST'
const DELETE_POST = 'DELETE_POST'
export function addPost(id, title) {
return {
type: CREATE_POST,
payload: { id, title }
}
}
const initialState = []
export default function postsReducer(state = initialState, action) {
switch (action.type) {
case CREATE_POST: {
// Omit actual code
break
}
default:
return state
}
}
複製代碼
這樣作簡化了不少事,由於咱們不須要多個文件,因此能夠移除掉冗餘的action type 常量的導入.可是,咱們仍然要手動編寫action types和action creators.
createSlice
簡化Slices(切片)的建立爲了簡化這個過程,Redux Starter Kit 包含了一個createSlice
函數, 能夠根據你提供的reducer函數的名字自動生成action type和action creators.
這裏是pops的實例代碼:
const postsSlice = createSlice({
initialState: [],
reducers: {
createPost(state, action) {},
updatePost(state, action) {},
deletePost(state, action) {}
}
})
console.log(postsSlice)
/* { actions : { createPost, updatePost, deletePost, }, reducer } */
const { createPost } = postsSlice.actions
console.log(createPost({ id: 123, title: 'Hello World' }))
// {type : "createPost", payload : {id : 123, title : "Hello World"}}
複製代碼
createSlice
查找全部在reducers
字段中定義的函數,以及每個"case reducer"函數,用reducer的名字生成同名的action creator和action type.因此, createPost
reducer 將會返回 一個createPost
類型,和createPost()
action creator.
也能夠選擇性定義一個slice
參數用於action type的前綴:
const postsSlice = createSlice({
slice: 'posts',
initialState: [],
reducers: {
createPost(state, action) {},
updatePost(state, action) {},
deletePost(state, action) {}
}
})
const { createPost } = postsSlice.actions
console.log(createPost({ id: 123, title: 'Hello World' }))
// {type : "posts/createPost", payload : {id : 123, title : "Hello World"}}
複製代碼
大多數狀況下,須要定義一個slice,導出action creators和reducer. 推薦的方法是使用ES6的解構和導出方法:
const postsSlice = createSlice({
slice: 'posts',
initialState: [],
reducers: {
createPost(state, action) {},
updatePost(state, action) {},
deletePost(state, action) {}
}
})
// Extract the action creators object and the reducer
const { actions, reducer } = postsSlice
// Extract and export each action creator by name
export const { createPost, updatePost, deletePost } = actions
// Export the reducer, either as a default or named export
export default reducer
複製代碼
也能夠導出須要的slice部分.
經過這種方式定義的Slice和 "Redux Ducks pattern"
的概念很是相似. 然而,他們也要一些潛在的問題,在導入和導出slices時須要注意.
首先,Redux action types並非說專門只對應一個slice. 從概念上說,每一個slice reducer "擁有"他們本身的一塊state,例如,可是reducer應該能鑑監放任何action type並更新對應的state.例如, 不少不一樣的slice可能都會響應"user logger out" action, 包括清除數據,重置state爲初始值等等.在設計state和建立slices是要特別小心.
第二點,JS模塊在兩個模塊互相引用是會有"循環引用"問題. 這會致使導入變爲未定義, 由此可能會中斷須要導入的代碼, 特別是在"ducks"或者"slices"中,若是連個slice定義在不一樣的問題件中,要響應定義在另外一個文件中斷額action是可能會出問題.
若是你碰到這個問題, 能夠須要重構代碼避免循環引用.