Redux筆記
參考理解
嚴(yán)格的單向數(shù)據(jù)流是Rduex設(shè)計(jì)核心。
Redux簡單概括:單一的state是存儲在store中,當(dāng)要對state進(jìn)行更新的時(shí)候,首先要發(fā)起一個(gè)action(通過dispatch
函數(shù)),action的作用就相當(dāng)于一個(gè)消息通知,用來描述發(fā)生了什么(比如:增加一個(gè)TODO),然后reducer會根據(jù)action來
進(jìn)行對state更新,這樣就可以更新新的state去渲染View.
從不直接修改 state 是 Redux 的核心理念之一, 所以你會發(fā)現(xiàn)自己總是在使用 Object.assign() 創(chuàng)建對象拷貝, 而拷貝中會包含新創(chuàng)建或更新過的屬性值。在下面的 todoApp 示例中, Object.assign() 將會返回一個(gè)新的 state 對象, 而其中的 visibilityFilter 屬性被更新了:
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}
Redux適用場景
- 用戶的使用方式復(fù)雜(Ul界面復(fù)雜,操作內(nèi)容多)
- 不同身份的用戶有不同的使用方式(普通用戶和管理員)
- 多個(gè)用戶之間可以協(xié)作
- 與服務(wù)器有大量交互
- View要從多個(gè)來源獲取數(shù)據(jù)
從組件看,存在以下場景可以考慮使用Redux
- 某個(gè)組件的狀態(tài)需要共享
- 某個(gè)狀態(tài)需要在任何地方可以拿到
- 一個(gè)組件需要改變?nèi)譅顟B(tài)
- 一個(gè)組件需要改變另一個(gè)組件的狀態(tài)
要點(diǎn)
- 應(yīng)用中所有的state都以一個(gè)對象樹的形式存儲在一個(gè)單一的store中
- 唯一改變state的辦法就是觸發(fā)action,一個(gè)描述發(fā)生什么的對象
- 為了描述action如何改變state樹,需要編寫reducers.
store
store 充當(dāng)一個(gè)容器,用來保存數(shù)據(jù)的地方。也可以理解成存儲state的地方。整個(gè)應(yīng)用 只能 有一個(gè)Store
由于整個(gè)應(yīng)用只有一個(gè)store,所以store保存了所有的數(shù)據(jù)。可以通過store.getState()獲取當(dāng)前時(shí)刻的state.
store 里能直接通過 store.dispatch() 調(diào)用 dispatch() 方法,但是多數(shù)情況下你會使用 react-redux 提供的 connect() 幫助器來調(diào)用。
action
Action 本質(zhì)上是 JavaScript 普通對象。我們約定,action 內(nèi)必須使用一個(gè)字符串類型的 type 字段來表示將要執(zhí)行的動作。多數(shù)情況下,type 會被定義成字符串常量。當(dāng)應(yīng)用規(guī)模越來越大時(shí),建議使用單獨(dú)的模塊或文件來存放 action。
action是由用戶操作view產(chǎn)生的。view的改變產(chǎn)生action,action的改變會傳到store,進(jìn)而影響state的改變。
import { createStore } from 'redux';
const store = createStore(fn);
store.dispatch({
type: 'ADD_TODO',
payload: 'Learn Redux'
});
通過store.dispatch()將action傳遞到store里面。
reducer
store 接收到action后,必須給出一個(gè)新的state,從而是view做出變化。這樣的一個(gè)計(jì)算過程叫做Reducer.
Reducer是一個(gè) 函數(shù),接收action和當(dāng)前的state作為參數(shù),然后返回一個(gè)新的state.
const reducer = function (state, action) {
// ...
return new_state;};
因?yàn)镽educer函數(shù)負(fù)責(zé)生成state,而整個(gè)應(yīng)用只有一個(gè)state,所以當(dāng)state非常大的時(shí)候,導(dǎo)致Reduce函數(shù)也非常的大,
根據(jù)action不同的種類,我們可以將reducer拆分為多個(gè)小的reducer,最后再合成一個(gè)大的reducer。
const chatReducer = (state = defaultState, action = {}) => {
const { type, payload } = action;
switch (type) {
case ADD_CHAT:
return Object.assign({}, state, {
chatLog: state.chatLog.concat(payload)
});
case CHANGE_STATUS:
return Object.assign({}, state, {
statusMessage: payload
});
case CHANGE_USERNAME:
return Object.assign({}, state, {
userName: payload
});
default: return state;
}};
上面這個(gè)Reducer包含了3個(gè)action,顯得比較大,比較臃腫。
我們可以對其進(jìn)行拆分函數(shù):
const chatReducer = (state = defaultState, action = {}) => {
return {
chatLog: chatLog(state.chatLog, action),
statusMessage: statusMessage(state.statusMessage, action),
userName: userName(state.userName, action)
}};
這樣一個(gè)大的Reducer函數(shù)就拆分成三個(gè)小函數(shù),每個(gè)小函數(shù)負(fù)責(zé)生成對應(yīng)屬性。這樣就可以把小的函數(shù)理解成子reducer函數(shù)。
這就與react的根組件與子組件的概念吻合,根組件對應(yīng)最終生成的大的Reducer,而子組件對應(yīng)每個(gè)子reducer。
拆開成子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)
}}
通過使用redux提供的combinReducers()來實(shí)現(xiàn)上面todoApp做的事情
import { combineReducers } from 'redux';
const todoApp = combineReducers({
visibilityFilter,
todos
})
上面的代碼等價(jià)于下面的代碼
export default function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
}}
export default todoApp;
Redux提供combineReducers方法,用于將定義的各個(gè)子Reducer函數(shù)合并成一個(gè)大的Reducer.
import { combineReducers } from 'redux';
const chatReducer = combineReducers({
chatLog,
statusMessage,
userName
})
理解
import { createStore } from 'redux';
const store = createStore(reducer);
store.subscribe(listener);
通過 reducer 創(chuàng)建一個(gè) store ,每當(dāng)我們在 store 上 dispatch 一個(gè) action ,store 內(nèi)的數(shù)據(jù)就會相應(yīng)地發(fā)生變化。
Store提供了三種方法:store.getState(),store.dispatch(),store.subscribe().
getState()用來獲取store里面當(dāng)前的state。
dispatch()用來傳遞action到store里面,可以在任何地方調(diào)用 store.dispatch(action),包括組件中、XHR 回調(diào)中、甚至定時(shí)器中。
subscribe()用來監(jiān)聽事件,接受的參數(shù)應(yīng)當(dāng)是個(gè)函數(shù),該函數(shù)應(yīng)當(dāng)是在這里獲得store的最新state然后用來改變組件state的。
正常的思路應(yīng)該是view發(fā)出action通過dispatch()傳遞給reducer進(jìn)行相關(guān)的計(jì)算從而得出新的state。觀察上面創(chuàng)建store的代碼,可以發(fā)現(xiàn)我們是
先把reducer這個(gè)計(jì)算函數(shù)“放入”store里面,所以我們就可以實(shí)現(xiàn)當(dāng)我們把a(bǔ)ction通過dispatch()傳遞給store后,不需要自己手動去調(diào)用reducer,
store會自己自動調(diào)用。
怎么使用subscribe()訂閱來更新ui,connect如何使用
使用方法
明智的做法是只在最頂層組件
使用React-redux
首先在最外層容器中,把所有內(nèi)容包裹在 Provider 組件中,將之前創(chuàng)建的 store作為 prop 傳給 Provider 。
const App = () => {
return (
<Provider store={store}>
<Comp/>
</Provider>
)
};
Provider 內(nèi)的任何一個(gè)組件(比如這里的 Comp ),如果需要使用 state 中的數(shù)據(jù),就必須是「被 connect 過的」組件——使用 connect 方法對「你編寫的組件( MyComp )」進(jìn)行包裝后的產(chǎn)物。
class MyComp extends Component {
// content...
}
const Comp = connect(...args)(MyComp);
connect的使用方法 參考理解
connect([mapStateToProps], [mapDispatchToProps], [mergeProps],[options])
connect的作用:連接React組件與Redux store會自己自動調(diào)用。
connect的第一個(gè)參數(shù)是mapStateToProps函數(shù), 該函數(shù)允許我們將我們將store中的數(shù)據(jù)作為props綁定到組件上。
mapStateToProps(state, ownProps) : stateProps。
const mapStateToProps = (state) => {
return {
count: state.count
}
}
- mapStateToProps函數(shù)的第一個(gè)參數(shù)就是Redux的store(整個(gè)state)從上面的示例我們可以看出我們沒有將store中所有的數(shù)據(jù)(state)全部
傳入connect的組件,我們可以根據(jù)所要連接組件所需的props,從store中的state動態(tài)的輸出該組件需要的props. - mapStateToProps函數(shù)的第二個(gè)參數(shù)ownProps,是連接組件的props.
使用ownProps示例:(比如點(diǎn)擊人員列表查看人員詳細(xì)信息,點(diǎn)擊事件傳遞組件(該組件只維護(hù)一個(gè)用戶信息)人員Id屬性props,然后可以通過這個(gè)組件自己的props去store獲取對應(yīng)數(shù)據(jù))
const mapStateToProps = (state, ownProps) => {
// state 是 {userList: [{id: 0, name: '王二'}]}
return {
user: _.find(state.userList, {id: ownProps.userId})
}
}
class MyComp extends Component {
static PropTypes = {
userId: PropTypes.string.isRequired,
user: PropTypes.object
};
render(){
return <div>用戶名:{this.props.user.name}</div>
}
}
const Comp = connect(mapStateToProps)(MyComp);
當(dāng)Redux中的store(state)變化或者ownProps變化的時(shí)候,mapStateToProps都會被調(diào)用,計(jì)算出一個(gè)新的stateProps,(再與ownProps merge后)更新給組件.如果省略了這個(gè)參數(shù),你的組件將不會監(jiān)聽 Redux store.
mapDispatchToProps(dispatch, ownProps): dispatchProps
connect 的第二個(gè)參數(shù)是 mapDispatchToProps,它的功能是,將 action 作為 props 綁定到組件上.如果省略這個(gè) mapDispatchToProps 參數(shù),默認(rèn)情況下,dispatch 會注入到你的組件 props 中。該函數(shù)如果傳遞的是一個(gè)對象,那么每個(gè)定義在該對象的函數(shù)都將被當(dāng)作 Redux action creator,而且這個(gè)對象會與 Redux store 綁定在一起,其中所定義的方法名將作為屬性名,合并到組件的 props 中。如果傳遞的是一個(gè)函數(shù),該函數(shù)將接收一個(gè) dispatch 函數(shù),然后由你來決定如何返回一個(gè)對象,這個(gè)對象通過 dispatch 函數(shù)與 action creator 以某種方式綁定在一起
不管是 stateProps 還是 dispatchProps,都需要和 ownProps merge 之后才會被賦給組件。connect 的第三個(gè)參數(shù)就是用來做這件事。通常情況下,你可以不傳這個(gè)參數(shù),connect 就會使用 Object.assign 替代該方法
代碼示例
import React, {Component} from 'react'
class Counter extends Component {
render() {
//從組件的props屬性中導(dǎo)入四個(gè)方法和一個(gè)變量
const {increment, decrement, counter} = this.props;
//渲染組件,包括一個(gè)數(shù)字,四個(gè)按鈕
return (
<p>
Clicked: {counter} times
{' '}
<button onClick={increment}>+</button>
{' '}
<button onClick={decrement}>-</button>
{' '}
</p>
)
}
}
export default Counter;
import { connect } from 'react-redux'
import Counter from '../components/Counter'
import actions from '../actions/counter';
//將state.counter綁定到props的counter. 哪些 Redux 全局的 state 是我們組件想要通過 props 獲取的?
const mapStateToProps = (state) => {
return {
counter: state.counter
}
};
//將action的所有方法綁定到props上.哪些 action 創(chuàng)建函數(shù)是我們想要通過 props 獲取的?
const mapDispatchToProps = (dispatch, ownProps) => {
return {
increment: (...args) => dispatch(actions.increment(...args)),
decrement: (...args) => dispatch(actions.decrement(...args))
}
};
//通過react-redux提供的connect方法將我們需要的state中的數(shù)據(jù)和actions中的方法綁定到props上
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
Middleware
作用的位置:位于action被發(fā)起之后,到達(dá)reducer之前的擴(kuò)展點(diǎn)
Redux moddleware provides a third-party extension point between dispatching an action, and the moment it reaches the reducer
理解:在action和reducer中間插入middleware,通過改變數(shù)據(jù)流,實(shí)現(xiàn)異步action.
可以干什么:進(jìn)行日志記錄、創(chuàng)建崩潰報(bào)告、調(diào)用異步接口或者路由
怎么使用Middleware:redux 提供了applyMiddleware這個(gè)api來加載middleware
項(xiàng)目中使用Middleware的方式:
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'
import rootReducer from './reducers'
const loggerMiddleware = createLogger()
const createStoreWithMiddleware = applyMiddleware(
thunkMiddleware,
loggerMiddleware
)(createStore)
export default function configureStore(initialState) {
return createStoreWithMiddleware(rootReducer, initialState)
}
applyMiddleware源碼:
export default function applyMiddleware(...middlewares) { return (next) =>
(reducer, initialState) => {
var store = next(reducer, initialState);
var dispatch = store.dispatch;
var chain = [];
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
};
chain = middlewares.map(middleware =>
middleware(middlewareAPI));
dispatch = compose(...chain, store.dispatch);
return {
...store,
dispatch
};
};
}
applyMiddleware源碼使用了柯里化
(理解:將多參數(shù)函數(shù)轉(zhuǎn)化為單參數(shù)函數(shù)執(zhí)行,然后返回結(jié)果接受剩下的參數(shù)繼續(xù)執(zhí)行)。
柯里化
的過程存在閉包,因而store是最新并且相同的。
// 柯里化簡單示例
function add(x){
return function(y){
return x+y;
}
}
console.log(add(5)(10)); // 15
對于中間件的理解:同步action:action發(fā)出后,reducer立即算出state。異步action:action發(fā)出以后,過一段時(shí)間再執(zhí)行reducer。怎樣是reducer在異步操作結(jié)束后自動執(zhí)行?使用的解決方法就是:中間件。
同步action只要發(fā)出一種action,異步action一般要發(fā)出三種action。
異步action一般要發(fā)出的三種action:
- 操作發(fā)起時(shí)的action
- 操作成功時(shí)的action
- 操作失敗時(shí)的action
同步action的執(zhí)行流程:
action-->dispatch-->reducers
當(dāng)我們執(zhí)行異步action的時(shí)候可能還需要在action傳遞到reducer這個(gè)過程中處理一些其他的事,執(zhí)行一些額外的函數(shù)。
執(zhí)行流程:
action-->dispatch-->額外的函數(shù)代碼-->reducer
引入中間件,將額外需要執(zhí)行的函數(shù)放到中間件中執(zhí)行
action-->dispatch-->middleware1-->...-->middleware3-->reducer
我們都知道action creator是一個(gè)純js函數(shù),返回的是一個(gè)對象。將action creator通過dispacth傳遞到reducer從而改變state.
而通過使用中間件,action creator返回的將是一個(gè)函數(shù)(或者其他的類似Promise對象等),返回的函數(shù)的參數(shù)是dispatch和getState這個(gè)兩個(gè)redux方法。執(zhí)行異步請求的時(shí)候,第一個(gè)action表示操作開始,該操作類似同步action操作。第二個(gè)action的發(fā)出是在返回的函數(shù)中(因?yàn)榉祷氐暮瘮?shù)的參數(shù)含有g(shù)etState,所有state是最新的。而參數(shù)中又含有dispatch,所有可以通過該方法執(zhí)行第二個(gè)action)。