Redux是JS
的狀態管理器,便於咱們更加清晰管理追蹤應用程序中變化莫測的狀態變動。Redux採用 的方式對數據進行管理,這種方式的好處在於只能從單一的方向進行數據變動,剔除了數據能五花八門改變的方式,有利於咱們對數據的變化的追蹤,同時下降項目後期的維護成本。react
Redux狀態管理器的核心思想:git
store
狀態樹action
行爲狀態對象reducer
行爲狀態的處理簡單的說就是首先使用定義狀態樹中全部須要發生變化的狀態,而後使用定義對應的行爲的處理並將處理後的狀態返回,最後將各個處理後的狀態按照必定的規律組合成對象就造成了store
狀態樹。github
所以store
狀態樹就是一個對象,定義了對象裏某個遞歸字段對應值須要發生變化時須要的信息,而後通過處理,最終使狀態樹中對應的遞歸字段的值發生變化npm
文章中展現的實例demo有些並未能直接在界面上體現,須要配合redux-tool工具觀看狀態樹的變化,瀏覽器中安裝相應的redux調試工具方法redux
示例 redux-1:api
//定義一個加法行爲須要的參數
const addAction = (num1,num2) => {
return ({
type: 'ADD',
num1,
num2,
})
}
//定義處理加法的行爲邏輯
const addReducer = (state,action) => {
if(action.type === 'ADD') {
return {
result: action.num1 + action.num2
}
}
}
//生成整個狀態樹
const store = createStore(addReducer,{})
//獲取整個狀態樹對象
store.getState() //undefined
//此時因爲須要觸發某個行爲
store.dispatch(addAction(1,2))
//獲取整個狀態樹對象
store.getState() //{result:3}
以上簡單的幾個操做就是Redux的整個實現思想
複製代碼
state
,通過處理後返回新的state
。不容許返回undefined
或者null
dispatch
方法觸發更新操做dispatch
觸發更新,狀態樹也不會發生任何的變化redux
是同步進行的,所以建立行爲、觸發更新操做dispatch
等方法都必須是同步操做,若須要支持異步操做時,須要增長中間件的支持,好比 redux-promise、redux-thunk 等建立狀態樹數組
@params reducer 處理行爲的函數
@params preloadedState 初始化默認狀態樹
@params enhancer 中間件,用於增長redux的處理能力
@return Object store對象
createStore(reducer: Function, preloadedState?: Object, enhancer?: Function) => Object
複製代碼
狀態對象promise
獲取狀態樹中的全部狀態信息瀏覽器
@return Object 狀態樹信息
getState() => Object
複製代碼
惟一能觸發狀態樹中相關狀態變化的方法bash
dispatch會校驗對象參數的正確性,當對象沒有type
字段時,會形成程序出錯
@params action 建立行爲的對象
@return Object action對象,非對象時會觸發程序錯誤,除非使用中間件
dispatch(action: Object) => Object
複製代碼
狀態樹發生變化的監聽,該方法只單單監聽到狀態樹發生變化,未能得知變化的內容。目前以爲做用並不大
@params listener 監聽的回調
@params Function 取消監聽的方法
subscribe(listener: Function) => Function
複製代碼
變動狀態樹中的處理方式,好比在建立createStore後,須要增長某個的,這個時候就須要用到該方法
前面的使用原則中闡述過,
store
狀態樹的內容是由的返回值組成。所以store
狀態樹的內容隨着的變化而變化
@params nextReducer reducer處理函數
replaceReducer(nextReducer)
複製代碼
將多個不一樣的處理函數做爲對象某個key的值,合成一個函數,做爲createStore
方法的參數傳遞。該方法能夠嵌套使用
@params reducers 由各個reducer合成的對象
@return Function reducer處理函數
combineReducers(reducers: Object) => Function
複製代碼
用於擴展redux的處理能力,做用於createStore
函數的第三個參數傳遞
@params ...middleware 中間件參數
@return Function store事件的處理函數
applyMiddleware(...middleware) => Function
複製代碼
主要用戶簡化dispatch的調用,通過該方法處理後,能夠直接調用該方法返回的函數或者對象中的方法觸發store的更新,而不用使用dispatch
。
@params actionCreators action行爲對象或者函數
@params dispatch 觸發更新的方法
@return Object | Function
bindActionCreators(actionCreators: Object | Function, dispatch) => Object | Function
複製代碼
API使用示例 redux-2
//定義action
export const add = (text) => ({
type: 'ADD',
text
})
export const extendFilter = () => ({
type: 'EXTENDSHOW'
})
export const reduce = ()=> ({
type: 'REDUCE'
})
export const filter = ()=> ({
type: 'SHOW'
})
//reducer的處理
const list = (state = [], action) => {
switch (action.type) {
case "ADD":
return [
...state,
{
id:state.length,
text: action.text
}
]
case "REDUCE":
if (state.length === 0) {
return state
}else {
state.pop()
return [...state]
}
default:
return state //必須有返回內容,卻不能undefined、null
}
}
const show = (state = false, action) => {
if (action.type === 'SHOW') {
return !state
}
return true
}
const extendShow = (state = false, action) => {
if (action.type === 'EXTENDSHOW') {
return !state
}
return true
}
export const rootReducer = combineReducers({
list,
show
})
const showReducer = combineReducers ({
extendShow
})
export const secondReducer = combineReducers({
list,
'show': show,
showReducer
})
//進過bindActionCreators處理後,能夠直接調用filterDispatch觸發更新
const filterDispatch = bindActionCreators(filter,store.dispatch)
//獲取狀態樹中的指定內容
showLabel.innerText = store.getState().show ? "顯示" : '隱藏'
複製代碼
該插件主要有助於開發過程當中,查看狀態樹的狀態以及變化過程
使用方式:
//安裝
npm i --save-dev redux-devtools-extension
//使用
import { composeWithDevTools } from 'redux-devtools-extension'
const store = createStore(rootReducer,composeWithDevTools())
複製代碼
redux-thunk
中間件使redux
在dispatch
方法中支持異步操做。
實現原理是向傳遞給dispatch
的函數參數注入Store
對象的dispatch
與getState
方法,使得能夠在函數內部調用dispatch
與getState
方法。同時經過支持使用注入額外的一個自定義參數
使用示例 redux-3:
const customObj1 = {
name: 'Tom',
age: 18
}
const customObj2 = {
name: 'Chen',
age: 20
}
const store = createStore(rootReducer,composeWithDevTools(applyMiddleware(thunk.withExtraArgument(customObj1,customObj2))))
addElement.onclick = () => {
if (inputElement.value.length <= 0) {
return
}
let value = inputElement.value
store.dispatch(function(dispatch,getState) {
//從這裏log輸出可知,經過withExtraArgument只支持傳遞一個參數
console.log(arguments)// [ƒ, ƒ, {…}]
console.log(getState()) // {list: Array(0)}
setTimeout(() => {
dispatch(add(value))
console.log(getState()) // {list: Array(1)}
}, 1000)
})
// store.dispatch(addHandle(value))
inputElement.value = ""
}
function addHandle(value) {
return function(dispatch,getState) {
console.log(arguments)
setTimeout(() => {
dispatch(add(value))
}, 1000)
}
}
複製代碼
redux-promise
中間件與redux-thunk
做用相同,使redux
支持異步操做的能力。
在redux
中行爲建立函數中,只容許返回同步的對象,然而redux-promise
使得行爲建立函數中支持返回promise
對象,當promise
執行成功時,觸發狀態樹的更新;當promise
執行失敗時,狀態樹不會發生任何的變化,也不會致使程序出錯
使用示例 redux-4:
export const add = (text) => {
return new Promise((fulfill,reject) => {
setTimeout(() => {
fulfill({
type: 'ADD',
text
})
}, 1000);
})
}
export const reduce = ()=> {
return new Promise((fulfill,reject) => {
setTimeout(() => {
reject()
}, 500);
})
}
複製代碼
redux-actions
擴展主要便於編寫和,從而簡化redux的使用
@params type action中的type字段
@params payloadCreator payloadCreator的處理返回結果,將成爲action的payload字段的內容
@params metaCreator metaCreator的處理返回結果,將成爲action的meta字段的內容
createAction(type, payloadCreator?: Function, metaCreator?: Function) => Function`
複製代碼
:
export default function createAction(
type,
payloadCreator = value => value, //當未設置該參數時,會默認返回傳遞的參數
metaCreator
) {
invariant(
isFunction(payloadCreator) || isNull(payloadCreator),
"Expected payloadCreator to be a function, undefined or null"
);
/*
此處檢查payloadCreator函數的第一個參數若是是error將不執行該函數,直接返回error
*/
const finalPayloadCreator =
isNull(payloadCreator) || payloadCreator === identity
? identity
: (head, ...args) =>
head instanceof Error ? head : payloadCreator(head, ...args);
//metaCreator 只要是函數時就會執行
const hasMeta = isFunction(metaCreator);
const typeString = type.toString();
const actionCreator = (...args) => {
const payload = finalPayloadCreator(...args);
//建立action對象
const action = { type };
if (payload instanceof Error) {
action.error = true;
}
if (payload !== undefined) {
//若是是error,則添加特殊的字段
action.payload = payload;
}
if (hasMeta) {
action.meta = metaCreator(...args);
}
return action;
};
actionCreator.toString = () => typeString;
return actionCreator;
}
複製代碼
示例:
const todo = createAction('TODO', name => {
return {name: 'action' + name}
}, name => {
return {age: 18}
});
console.log(todo('name'))
結果:
{type: "TODO", payload: {name: "actionname"}, meta: {age: 18}}
/*
當不須要對action的行爲參數進行處理時,
能夠將payloadCreator設置爲undefined或者null。
同理不須要額外處理其餘數據時,metaCreator也能夠忽略
*/
const todo = createAction('TODO',undefined, name => {
return {age: 18}
})
console.log(todo('name'))
結果:
{type: "TODO", payload: 'name', meta: {age: 18}}
const todo = createAction('TODO')
console.log(todo('name'))
結果:
{type: "TODO", payload: 'name'}
/*
當action行爲的參數是error時,action返回的對象中會額外增長error字段,
而且其值爲true,並且不會調用payloadCreator方法
*/
const todo = createAction('TODO', name => {
return {name: 'action' + name}
}, name => {
return {age: 18}
});
console.log(todo(new Error('error')))
結果:
{type: "TODO", payload: Error: error, error: true, meta: {age: 18}}
複製代碼
@params actionMap action的集合
@params ...identityActions action的type,單獨定義該字段至關於action的參數不須要進過處理轉換
@params options action中type的前綴
createActions(actionMap, ...identityActions?, options?) => Object
複製代碼
1.
createActions
中的actionMap
與...identityActions?
參數,,不然會忽略actionMap
(見示例及源碼分析)
2.同時實現payloadCreator
與metaCreator
方法時,須要將其歸入[]
數組中(以下示例)
3.當沒有定義options
,而且建立的action遞歸時,默認的使用/
4.createActions
生成的遞歸對象的key
中使用to-camel-case插件,將其轉換爲駝峯的方式,但具體action
中type保持不變(見示例)
:
function createActions(actionMap, ...identityActions) {
const options = isPlainObject(getLastElement(identityActions))
? identityActions.pop()
: {};
//限制createActions參數的規則,若是不符合條件則拋出異常
invariant(
identityActions.every(isString) &&
(isString(actionMap) || isPlainObject(actionMap)),
'Expected optional object followed by string action types'
);
//若是以identityActions開頭,則直接返回identityActions所生成的
if (isString(actionMap)) {
return actionCreatorsFromIdentityActions(
[actionMap, ...identityActions],
options
);
}
return {
...actionCreatorsFromActionMap(actionMap, options),
...actionCreatorsFromIdentityActions(identityActions, options)
};
}
複製代碼
示例:
//先identityActions在actionMap的狀況
export const list = createActions('ADD',{
'REDUCE': value => value
})
console.log(list)
結果:{add: ƒ}
const todos = createActions(
{
profile: {
add: name => name,
DELETE_ITEM: [name => ({ name, age: 18 }), name => ({ gender: 'female' })]
},
show: name => name
},
'hidden',
{ prefix: 'todo', namespace: '-' }
)
console.log(todos.show('name'))
結果:
{type: "todo-show", payload: "name"}
console.log(todos.profile.deleteItem('name'))
結果:
{type: "todo-profile-delete", payload: {name: "name", age: 18}, meta: {gender: "female"}}
const todos = createActions('ADD','DELETE')
console.log(todos.add('name'))
結果:
{type: "ADD", payload: "name"}
複製代碼
@params type action中的type
@params reducer 行爲處理函數
@params defaultState 默認值(必須有默認值,當state未null時,使用defaultState)
handleAction(type,reducer | reducerMap = Identity, defaultState)
複製代碼
使用
next
、throw
處理action
行爲邏輯是時,能夠有效處理action
對象中出現Error的狀況(createAction中已經介紹如何出現Error)
:
export default function handleAction(type, reducer = identity, defaultState) {
const types = toString(type).split(ACTION_TYPE_DELIMITER);
//defaultState指定該參數是必須的
invariant(
!isUndefined(defaultState),
`defaultState for reducer handling ${types.join(', ')} should be defined`
);
//reducer參數必須是對象或者函數
invariant(
isFunction(reducer) || isPlainObject(reducer),
'Expected reducer to be a function or object with next and throw reducers'
);
const [nextReducer, throwReducer] = isFunction(reducer)
? [reducer, reducer]
: [reducer.next, reducer.throw].map(
aReducer => (isNil(aReducer) ? identity : aReducer)
);
//此時解析了爲什麼須要defaultState
return (state = defaultState, action) => {
const { type: actionType } = action;
if (!actionType || types.indexOf(toString(actionType)) === -1) {
return state;
}
//當acton出現Error時使用throwReducer函數處理
return (action.error === true ? throwReducer : nextReducer)(state, action);
};
}
複製代碼
示例:
const todoReducer = handleAction('TODO',(state,action)=> ({
name: action.payload
}),'default')
//next、throw的方式
export const err = createAction('ERROR')
export const flag = handleAction('ERROR',{
next(state,action){
console.log("next",state,action)
return !state
},
throw(state,action){
console.log("throw",state,action)
return !state
}
},true)
err(new Error('自定義錯誤')) //此時會執行throw方法
複製代碼
@params reducerMap reducer處理函數
@params defaultState 默認值(必須有默認值)
@params options 定義遞歸的前綴(同createActions)
handleActions(reducerMap, defaultState, options?)
複製代碼
示例:
const todos = handleActions({
'ADD_TODO': (state = [],action) => {
return [
...state,
{
id: action.payload.id,
text: action.payload.text,
completed: false
}
]
},
'TOGGLE_TODO': (state = [],action) => {
return state.map(todo => {
console.log(todo,action)
return (todo.id === action.payload)
? {...todo, completed: !todo.completed}
: todo
}
)
},
'DELETE_TODO':(state = [], action) => {
state.pop()
return state
}
},[])
//使用map的方式處理
const todos = handleActions(
new Map([
[
// "ADD_TODO",//使用action的type方式
addTodo,//使用action的方式
(state = [], action) => {
return [
...state,
{
id: action.payload.id,
text: action.payload.text,
completed: false
}
];
}
],
[
"TOGGLE_TODO",
(state = [], action) => {
return state.map(todo => {
console.log(todo, action);
return todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo;
});
}
]
]),
[]
);
複製代碼
示例 redux-5:
export const promiseAction = createAction('PROMISE', (value) => {
console.log(value)
return new Promise((fulfill,reject) => {
setTimeout(() => {
fulfill(value)
}, 1000);
})
})
複製代碼
React框架提供的只是一個抽象的DOM
層,組件間的通信處理麻煩。react-redux
有效的協助咱們處理這些難題。有關 React 介紹能夠自行瀏覽官網。
該插件所展現的示例來自於
Redux
官方提供的todos
項目改造而來
使最終的store
狀態樹 在任何被嵌套的組件中都能獲取
示例 redux-6:
const store = createStore(rootReducer)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
複製代碼
將React組件鏈接到store
狀態樹
@params mapStateToProps 須要獲取的狀態樹相關信息
@params mapDispatchToProps dispath相關觸發更新
@params mergeProps 自定義映射到組件props字段的處理
@params options 自定義選項
connect(mapStateToProps?: Function, mapDispatchToProps?: Function | Object, mergeProps?: Function, options?: Object)
複製代碼
根據須要從store
中獲取相關字段信息與調用組件時所傳遞參數構建對象,對象中的每一個字段都將成爲組件的prop,同時字段中的值也將肯定該組件是否須要從新渲染.若是定義mergeProps
函數(見mergeProps
方法說明),則做爲stateProps參數
當store
發生變化時會回調該函數,若是不須要訂閱變化,可設置爲null或undefined
方法內不能存在異步的行爲,全部的操做都應該保持同步
@params state 整個store的狀態樹
@params ownProps 調用該組件時傳遞的參數
@return Object
mapStateToProps?: (state, ownProps?) => Object
複製代碼
示例 redux-6:
const mapStateToProps = (state, ownProps) => ({
//獲取狀態樹中的filter字段信息
active: ownProps.filter === state.visibilityFilter
})
複製代碼
用於定義觸發store
更新的操做也就是dispatch
, 返回對象中的每一個字段都將成爲組件的prop中的字段。若是定義mergeProps函數(見mergeProps
方法說明),則做爲dispatchProps參數
當未定義該方法時,組件中默認接收dispatch參數;一旦定義了該方法,組件中將不接收
dispatch
參數。但能夠經過手動注入的方式向props傳遞dispatch(見示例)
@params dispatch store中觸發更新的操做
@parmas ownProps 調用該組件時傳遞的參數,當接收到新的props時會回調該函數
@return Object 必須返回一個對象,該對象中定義觸發store更新的操做
mapDispatchToProps?: Object | (dispatch, ownProps?) => Object
複製代碼
示例 redux-6:
const mapDispatchToProps = (dispatch, ownProps) => ({
onClick: () => dispatch(setVisibilityFilter(ownProps.filter)),
dispatch //因爲定義了mapDispatchToProps函數,組件默認不會接收dispatch參數,所以手動傳入
})
//當不須要獲取相關信息時,也能夠直接返回對象的方式
const increment = () => ({ type: 'INCREMENT' })
const decrement = () => ({ type: 'DECREMENT' })
const reset = () => ({ type: 'RESET' })
const mapDispatchToProps = {
increment,
decrement,
reset
}
複製代碼
自定義映射組件props的操做,若是未實現該方法,則默認實現{ ...ownProps, ...stateProps, ...dispatchProps }
@params stateProps mapStateToProps方法返回對象
@params dispatchProps mapDispatchToProps方法返回對象
@params ownProps 調用該組件時傳遞的參數
@return Object
mergeProps?: (stateProps, dispatchProps, ownProps) => Object
複製代碼
一些經常使用選項,具體說明見官方文檔
{
context?: Object,
pure?: boolean,
areStatesEqual?: Function,
areOwnPropsEqual?: Function,
areStatePropsEqual?: Function,
areMergedPropsEqual?: Function,
forwardRef?: boolean,
}
複製代碼