Redux筆記
參考理解
嚴格的單向數(shù)據(jù)流是Rduex設計核心。
Redux簡單概括:單一的state是存儲在store中再扭,當要對state進行更新的時候泛范,首先要發(fā)起一個action(通過dispatch
函數(shù))敦跌,action的作用就相當于一個消息通知柠傍,用來描述發(fā)生了什么(比如:增加一個TODO)惧笛,然后reducer會根據(jù)action來
進行對state更新患整,這樣就可以更新新的state去渲染View.
從不直接修改 state 是 Redux 的核心理念之一, 所以你會發(fā)現(xiàn)自己總是在使用 Object.assign() 創(chuàng)建對象拷貝, 而拷貝中會包含新創(chuàng)建或更新過的屬性值各谚。在下面的 todoApp 示例中, Object.assign() 將會返回一個新的 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適用場景
- 用戶的使用方式復雜(Ul界面復雜昌渤,操作內(nèi)容多)
- 不同身份的用戶有不同的使用方式(普通用戶和管理員)
- 多個用戶之間可以協(xié)作
- 與服務器有大量交互
- View要從多個來源獲取數(shù)據(jù)
從組件看膀息,存在以下場景可以考慮使用Redux
- 某個組件的狀態(tài)需要共享
- 某個狀態(tài)需要在任何地方可以拿到
- 一個組件需要改變?nèi)譅顟B(tài)
- 一個組件需要改變另一個組件的狀態(tài)
要點
- 應用中所有的state都以一個對象樹的形式存儲在一個單一的store中
- 唯一改變state的辦法就是觸發(fā)action,一個描述發(fā)生什么的對象
- 為了描述action如何改變state樹潜支,需要編寫reducers.
store
store 充當一個容器冗酿,用來保存數(shù)據(jù)的地方鸠窗。也可以理解成存儲state的地方胯究。整個應用 只能 有一個Store
由于整個應用只有一個store裕循,所以store保存了所有的數(shù)據(jù)≈暧ぃ可以通過store.getState()獲取當前時刻的state.
store 里能直接通過 store.dispatch() 調(diào)用 dispatch() 方法暑认,但是多數(shù)情況下你會使用 react-redux 提供的 connect() 幫助器來調(diào)用蘸际。
action
Action 本質(zhì)上是 JavaScript 普通對象粮彤。我們約定导坟,action 內(nèi)必須使用一個字符串類型的 type 字段來表示將要執(zhí)行的動作。多數(shù)情況下尘惧,type 會被定義成字符串常量褥伴。當應用規(guī)模越來越大時重慢,建議使用單獨的模塊或文件來存放 action似踱。
action是由用戶操作view產(chǎn)生的核芽。view的改變產(chǎn)生action,action的改變會傳到store驰坊,進而影響state的改變拳芙。
import { createStore } from 'redux';
const store = createStore(fn);
store.dispatch({
type: 'ADD_TODO',
payload: 'Learn Redux'
});
通過store.dispatch()將action傳遞到store里面舟扎。
reducer
store 接收到action后睹限,必須給出一個新的state,從而是view做出變化羡疗。這樣的一個計算過程叫做Reducer.
Reducer是一個 函數(shù)顺囊,接收action和當前的state作為參數(shù),然后返回一個新的state.
const reducer = function (state, action) {
// ...
return new_state;};
因為Reducer函數(shù)負責生成state午乓,而整個應用只有一個state,所以當state非常大的時候益愈,導致Reduce函數(shù)也非常的大蒸其,
根據(jù)action不同的種類摸袁,我們可以將reducer拆分為多個小的reducer义屏,最后再合成一個大的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;
}};
上面這個Reducer包含了3個action,顯得比較大蜂大,比較臃腫。
我們可以對其進行拆分函數(shù):
const chatReducer = (state = defaultState, action = {}) => {
return {
chatLog: chatLog(state.chatLog, action),
statusMessage: statusMessage(state.statusMessage, action),
userName: userName(state.userName, action)
}};
這樣一個大的Reducer函數(shù)就拆分成三個小函數(shù)蝶怔,每個小函數(shù)負責生成對應屬性奶浦。這樣就可以把小的函數(shù)理解成子reducer函數(shù)。
這就與react的根組件與子組件的概念吻合踢星,根組件對應最終生成的大的Reducer澳叉,而子組件對應每個子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()來實現(xiàn)上面todoApp做的事情
import { combineReducers } from 'redux';
const todoApp = combineReducers({
visibilityFilter,
todos
})
上面的代碼等價于下面的代碼
export default function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
}}
export default todoApp;
Redux提供combineReducers方法,用于將定義的各個子Reducer函數(shù)合并成一個大的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)建一個 store 斩狱,每當我們在 store 上 dispatch 一個 action 秕岛,store 內(nèi)的數(shù)據(jù)就會相應地發(fā)生變化。
Store提供了三種方法:store.getState(),store.dispatch(),store.subscribe().
getState()用來獲取store里面當前的state。
dispatch()用來傳遞action到store里面,可以在任何地方調(diào)用 store.dispatch(action),包括組件中诱贿、XHR 回調(diào)中焙蹭、甚至定時器中烟馅。
subscribe()用來監(jiān)聽事件,接受的參數(shù)應當是個函數(shù),該函數(shù)應當是在這里獲得store的最新state然后用來改變組件state的。
正常的思路應該是view發(fā)出action通過dispatch()傳遞給reducer進行相關(guān)的計算從而得出新的state。觀察上面創(chuàng)建store的代碼击儡,可以發(fā)現(xiàn)我們是
先把reducer這個計算函數(shù)“放入”store里面,所以我們就可以實現(xiàn)當我們把action通過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)的任何一個組件(比如這里的 Comp ),如果需要使用 state 中的數(shù)據(jù)龙考,就必須是「被 connect 過的」組件——使用 connect 方法對「你編寫的組件( MyComp )」進行包裝后的產(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的第一個參數(shù)是mapStateToProps函數(shù)内狗, 該函數(shù)允許我們將我們將store中的數(shù)據(jù)作為props綁定到組件上偎行。
mapStateToProps(state, ownProps) : stateProps。
const mapStateToProps = (state) => {
return {
count: state.count
}
}
- mapStateToProps函數(shù)的第一個參數(shù)就是Redux的store(整個state)從上面的示例我們可以看出我們沒有將store中所有的數(shù)據(jù)(state)全部
傳入connect的組件矗漾,我們可以根據(jù)所要連接組件所需的props,從store中的state動態(tài)的輸出該組件需要的props. - mapStateToProps函數(shù)的第二個參數(shù)ownProps,是連接組件的props.
使用ownProps示例:(比如點擊人員列表查看人員詳細信息巩梢,點擊事件傳遞組件(該組件只維護一個用戶信息)人員Id屬性props,然后可以通過這個組件自己的props去store獲取對應數(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);
當Redux中的store(state)變化或者ownProps變化的時候感混,mapStateToProps都會被調(diào)用犀忱,計算出一個新的stateProps,(再與ownProps merge后)更新給組件.如果省略了這個參數(shù)哥倔,你的組件將不會監(jiān)聽 Redux store.
mapDispatchToProps(dispatch, ownProps): dispatchProps
connect 的第二個參數(shù)是 mapDispatchToProps芽突,它的功能是艘刚,將 action 作為 props 綁定到組件上.如果省略這個 mapDispatchToProps 參數(shù)荚斯,默認情況下,dispatch 會注入到你的組件 props 中胡岔。該函數(shù)如果傳遞的是一個對象,那么每個定義在該對象的函數(shù)都將被當作 Redux action creator,而且這個對象會與 Redux store 綁定在一起,其中所定義的方法名將作為屬性名,合并到組件的 props 中与斤。如果傳遞的是一個函數(shù),該函數(shù)將接收一個 dispatch 函數(shù),然后由你來決定如何返回一個對象锭弊,這個對象通過 dispatch 函數(shù)與 action creator 以某種方式綁定在一起
不管是 stateProps 還是 dispatchProps爽醋,都需要和 ownProps merge 之后才會被賦給組件。connect 的第三個參數(shù)就是用來做這件事。通常情況下惋啃,你可以不傳這個參數(shù)哼鬓,connect 就會使用 Object.assign 替代該方法
代碼示例
import React, {Component} from 'react'
class Counter extends Component {
render() {
//從組件的props屬性中導入四個方法和一個變量
const {increment, decrement, counter} = this.props;
//渲染組件,包括一個數(shù)字边灭,四個按鈕
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ā)起之后绒瘦,到達reducer之前的擴展點
Redux moddleware provides a third-party extension point between dispatching an action, and the moment it reaches the reducer
理解:在action和reducer中間插入middleware称簿,通過改變數(shù)據(jù)流扣癣,實現(xiàn)異步action.
可以干什么:進行日志記錄、創(chuàng)建崩潰報告憨降、調(diào)用異步接口或者路由
怎么使用Middleware:redux 提供了applyMiddleware這個api來加載middleware
項目中使用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ā)出以后莱衩,過一段時間再執(zhí)行reducer。怎樣是reducer在異步操作結(jié)束后自動執(zhí)行娇澎?使用的解決方法就是:中間件笨蚁。
同步action只要發(fā)出一種action,異步action一般要發(fā)出三種action。
異步action一般要發(fā)出的三種action:
- 操作發(fā)起時的action
- 操作成功時的action
- 操作失敗時的action
同步action的執(zhí)行流程:
action-->dispatch-->reducers
當我們執(zhí)行異步action的時候可能還需要在action傳遞到reducer這個過程中處理一些其他的事趟庄,執(zhí)行一些額外的函數(shù)括细。
執(zhí)行流程:
action-->dispatch-->額外的函數(shù)代碼-->reducer
引入中間件,將額外需要執(zhí)行的函數(shù)放到中間件中執(zhí)行
action-->dispatch-->middleware1-->...-->middleware3-->reducer
我們都知道action creator是一個純js函數(shù)岔激,返回的是一個對象勒极。將action creator通過dispacth傳遞到reducer從而改變state.
而通過使用中間件,action creator返回的將是一個函數(shù)(或者其他的類似Promise對象等)虑鼎,返回的函數(shù)的參數(shù)是dispatch和getState這個兩個redux方法辱匿。執(zhí)行異步請求的時候,第一個action表示操作開始炫彩,該操作類似同步action操作匾七。第二個action的發(fā)出是在返回的函數(shù)中(因為返回的函數(shù)的參數(shù)含有g(shù)etState,所有state是最新的江兢。而參數(shù)中又含有dispatch昨忆,所有可以通過該方法執(zhí)行第二個action)。