摘要:寫法一,返回值是一個對象。我們已經知道,中間件只關注函數的傳遞,而且也不關心函數的返回值,所以只需要讓認識這個函數就可以了。
react的FLUX數據流一直搞不清楚,他不像Angular的雙向數據綁定,做一個model獲取數據,然后通過controller來管理view上的數據顯示就可以了。單項數據流引入了太多的概念,state、action、reducer、dispatch。就算看的懂圖,也不一定能coding出來。
不過我總算先搞定了Redux。
store
reducer
action
dispatch
connect
router
middleware
thunk
Basic Usage 1st 實現action方法export const addDeck = name => ({ type: "ADD_DECK", data: name });2nd 根據action方法創建reducer方法
export const showBack = (state, action) => { switch(action.type) { case "SHOW_BACK": return action.data || false; default: return state || false; } };3rd 根據reducer方法創建store
const store = createStore(combineReducers(reducers));
store.subscribe()方法設置監聽函數,一旦 State 發生變化,就自動執行這個函數。
store.subscribe(listener);
顯然,只要把 View 的更新函數(對于 React 項目,就是組件的render方法或setState方法)放入listen,就會實現 View 的自動渲染。
store.subscribe方法返回一個函數,調用這個函數就可以解除監聽。
let unsubscribe = store.subscribe(() => console.log(store.getState()) ); unsubscribe();4th 引入react-redux的
5th react組件中通過connect方法綁定store和dispatch。{...}
const mapStateToProps = (newTalks) => ({ newTalks }); const mapDispatchToProps = dispatch => ({ testFunc: () => dispatch(updataTalkLists(1)), receiveData: () => dispatch(receiveData()) }); export default connect(mapStateToProps, mapDispatchToProps)(MainPage);6th this.props中直接調用action方法。
this.props.receiveDataWith react-router
結合router使用時需要有2步。
1st 綁定routing到reducer上import { syncHistoryWithStore, routerReducer } from "react-router-redux"; import * as reducers from "./redux/reducer"; reducers.routing = routerReducer; const store = createStore(combineReducers(reducers));2nd 使用syncHistoryWithStore綁定store和browserHistory
const history = syncHistoryWithStore(browserHistory, store);Async{routes}
類似 Express 或 Koa 框架中的中間件。它提供的是位于 action 被發起之后,到達 reducer 之前的擴展。
中間件的設計使用了非常多的函數式編程的思想,包括:高階函數,復合函數,柯里化和ES6語法,源碼僅僅20行左右。
項目中主要使用了三個中間件,分別解決不同的問題。
thunkMiddleware:處理異步Action
apiMiddleware:統一處理API請求。一般情況下,每個 API 請求都至少需要 dispatch 三個不同的 action(請求前、請求成功、請求失敗),通過這個中間件可以很方便處理。
loggerMiddleware:開發環境調試使用,控制臺輸出應用state日志
實現action異步操作,必須要引入middleware。我這里用了applyMiddleware(thunkMiddleware)組件,也可以用其他的。
1st 創建store是引入Middlewareimport thunkMiddleware from "redux-thunk"; import { createStore, combineReducers, applyMiddleware } from "redux"; const store = createStore(combineReducers(reducers), applyMiddleware(thunkMiddleware));2nd 創建一個可以執行dispacth的action
這也是中間件的作用所在。
export const receiveData = data => ({ type: "RECEIVE_DATA", data: data }); export const fetchData = () => { return dispatch => { fetch("/api/data") .then(res => res.json()) .then(json => dispatch(receiveData(json))); }; };3rd 組件中對異步的store元素有相應的判斷操作。
React組件會在store值發生變化時自動調用render()方法,更新異步數據。但是我們同樣也需要處理異步數據沒有返回或者請求失敗的情況。否則渲染會失敗,頁面卡住。
if(!data.newTalks) { return(); }相關知識 Store的實現
Store提供了3個方法
import { createStore } from "redux"; let { subscribe, //監聽store變化 dispatch, //調用action方法 getState //返回當前store } = createStore(reducer);
下面是create方法的一個簡單實現
const createStore = (reducer) => { let state; let listeners = []; const getState = () => state; const dispatch = (action) => { state = reducer(state, action); listeners.forEach(listener => listener()); }; const subscribe = (listener) => { listeners.push(listener); return () => { listeners = listeners.filter(l => l !== listener); } }; dispatch({}); return { getState, dispatch, subscribe }; };combineReducer的簡單實現
const combineReducers = reducers => { return (state = {}, action) => { return Object.keys(reducers).reduce( (nextState, key) => { nextState[key] = reducers[key](state[key], action); return nextState; }, {} ); }; };中間件
createStore方法可以接受整個應用的初始狀態作為參數,那樣的話,applyMiddleware就是第三個參數了。
中間件的次序有講究,logger就一定要放在最后,否則輸出結果會不正確。
const store = createStore( reducer, initial_state, applyMiddleware(thunk, promise, logger) );
applyMiddlewares的實現,它是將所有中間件組成一個數組,依次執行
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer); 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} } }
上面代碼中,所有中間件被處理后得到一個數組保存在chain中。之后將chain傳給compose,并將store.dispatch傳給返回的函數。。可以看到,中間件內部(middlewareAPI)可以拿到getState和dispatch這兩個方法。
那么在這里面做了什么呢?我們再看compose的實現:
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } else { const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) } }
compose中的核心動作就是將傳進來的所有函數倒序(reduceRight)進行如下處理:
(composed, f) => f(composed)
我們知道Array.prototype.reduceRight是從右向左累計計算的,會將上一次的計算結果作為本次計算的輸入。再看看applyMiddleware中的調用代碼:
dispatch = compose(...chain)(store.dispatch)
compose函數最終返回的函數被作為了dispatch函數,結合官方文檔和代碼,不難得出,中間件的定義形式為:
function middleware({dispatch, getState}) { return function (next) { return function (action) { return next(action); } } } 或 middleware = (dispatch, getState) => next => action => { next(action); }
也就是說,redux的中間件是一個函數,該函數接收dispatch和getState作為參數,返回一個以dispatch為參數的函數,這個函數的返回值是接收action為參數的函數(可以看做另一個dispatch函數)。在中間件鏈中,以dispatch為參數的函數的返回值將作為下一個中間件(準確的說應該是返回值)的參數,下一個中間件將它的返回值接著往下一個中間件傳遞,最終實現了store.dispatch在中間件間的傳遞。
redux-promise中間件既然 Action Creator 可以返回函數,當然也可以返回其他值。另一種異步操作的解決方案,就是讓 Action Creator 返回一個 Promise 對象。
寫法一,返回值是一個 Promise 對象。
const fetchPosts = (dispatch, postTitle) => new Promise(function (resolve, reject) { dispatch(requestPosts(postTitle)); return fetch(`/some/API/${postTitle}.json`) .then(response => { type: "FETCH_POSTS", payload: response.json() }); });
寫法二,Action 對象的payload屬性是一個 Promise 對象。這需要從redux-actions模塊引入createAction方法,并且寫法也要變成下面這樣。
import { createAction } from "redux-actions"; class AsyncApp extends Component { componentDidMount() { const { dispatch, selectedPost } = this.props // 發出同步 Action dispatch(requestPosts(selectedPost)); // 發出異步 Action dispatch(createAction( "FETCH_POSTS", fetch(`/some/API/${postTitle}.json`) .then(response => response.json()) )); }
上面代碼中,第二個dispatch方法發出的是異步 Action,只有等到操作結束,這個 Action 才會實際發出。注意,createAction的第二個參數必須是一個 Promise 對象。
redux-promise的源碼
export default function promiseMiddleware({ dispatch }) { return next => action => { if (!isFSA(action)) { return isPromise(action) ? action.then(dispatch) : next(action); } return isPromise(action.payload) ? action.payload.then( result => dispatch({ ...action, payload: result }), error => { dispatch({ ...action, payload: error, error: true }); return Promise.reject(error); } ) : next(action); }; }
從上面代碼可以看出,如果 Action 本身是一個 Promise,它 resolve 以后的值應該是一個 Action 對象,會被dispatch方法送出(action.then(dispatch)),但 reject 以后不會有任何動作;如果 Action 對象的payload屬性是一個 Promise 對象,那么無論 resolve 和 reject,dispatch方法都會發出 Action。
mapStateToProps()mapStateToProps是一個函數。它的作用就是像它的名字那樣,建立一個從(外部的)state對象到(UI 組件的)props對象的映射關系
mapStateToProps會訂閱 Store,每當state更新的時候,就會自動執行,重新計算 UI 組件的參數,從而觸發 UI 組件的重新渲染。
mapStateToProps的第一個參數總是state對象,還可以使用第二個參數,代表容器組件的props對象。
使用ownProps作為參數后,如果容器組件的參數發生變化,也會引發 UI 組件重新渲染。
connect方法可以省略mapStateToProps參數,那樣的話,UI 組件就不會訂閱Store,就是說 Store 的更新不會引起 UI 組件的更新。
// 容器組件的代碼 //mapDispatchToProps()// All // const mapStateToProps = (state, ownProps) => { return { active: ownProps.filter === state.visibilityFilter } }
mapDispatchToProps是connect函數的第二個參數,用來建立 UI 組件的參數到store.dispatch方法的映射。也就是說,它定義了哪些用戶的操作應該當作 Action,傳給 Store。它可以是一個函數,也可以是一個對象。
mapDispatchToProps作為函數,應該返回一個對象,該對象的每個鍵值對都是一個映射,定義了 UI 組件的參數怎樣發出 Action。
const mapDispatchToProps = ( dispatch, ownProps ) => { return { onClick: () => { dispatch({ type: "SET_VISIBILITY_FILTER", filter: ownProps.filter }); } }; }
如果mapDispatchToProps是一個對象,它的每個鍵名也是對應 UI 組件的同名參數,鍵值應該是一個函數,會被當作 Action creator ,返回的 Action 會由 Redux 自動發出。
const mapDispatchToProps = { onClick: (filter) => { type: "SET_VISIBILITY_FILTER", filter: filter }; }
React-Redux 提供Provider組件,可以讓容器組件拿到state,它的原理是React組件的context屬性,請看源碼。
class Provider extends Component { getChildContext() { return { store: this.props.store }; } render() { return this.props.children; } } Provider.childContextTypes = { store: React.PropTypes.object }
上面代碼中,store放在了上下文對象context上面。然后,子組件就可以從context拿到store,代碼大致如下。
class VisibleTodoList extends Component { componentDidMount() { const { store } = this.context; this.unsubscribe = store.subscribe(() => this.forceUpdate() ); } render() { const props = this.props; const { store } = this.context; const state = store.getState(); // ... } } VisibleTodoList.contextTypes = { store: React.PropTypes.object }redux-thunk
我們知道,異步調用什么時候返回前端是無法控制的。對于redux這條嚴密的數據流來說,如何才能做到異步呢。redux-thunk的基本思想就是通過函數來封裝異步請求,也就是說在actionCreater中返回一個函數,在這個函數中進行異步調用。我們已經知道,redux中間件只關注dispatch函數的傳遞,而且redux也不關心dispatch函數的返回值,所以只需要讓redux認識這個函數就可以了。
看了一下redux-thunk的源碼:
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;
這段代碼跟上面我們看到的中間件沒有太大的差別,唯一一點就是對action做了一下如下判斷:
if (typeof action === "function") { return action(dispatch, getState, extraArgument); }
也就是說,如果發現actionCreater傳過來的action是一個函數的話,會執行一下這個函數,并以這個函數的返回值作為返回值。前面已經說過,redux對dispatch函數的返回值不是很關心,因此此處也就無所謂了。
這樣的話,在我們的actionCreater中,我們就可以做任何的異步調用了,并且返回任何值也無所謂,所以我們可以使用promise了:
function actionCreate() { return function (dispatch, getState) { // 返回的函數體內自由實現。。。 Ajax.fetch({xxx}).then(function (json) { dispatch(json); }) } }
最后還需要注意一點,由于中間件只關心dispatch的傳遞,并不限制你做其他的事情,因此我們最好將redux-thunk放到中間件列表的首位,防止其他中間件中返回異步請求。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81032.html
摘要:框架的使用詳解及教程在前段時間,我們也學習講解過框架的基本使用,但是有很多同學在交流群里給我的反饋信息說,框架理解上有難度,看了之后還是一臉懵逼不知道如何下手,很多同學就轉向選擇使用框架。 dva框架的使用詳解及Demo教程 在前段時間,我們也學習講解過Redux框架的基本使用,但是有很多同學在交流群里給我的反饋信息說,redux框架理解上有難度,看了之后還是一臉懵逼不知道如何下手,很...
摘要:利用中間件實現異步請求,實現兩個用戶角色實時通信。目前還未深入了解的一些概念。往后會寫更多的前后臺聯通的項目。刪除分組會連同組內的所有圖片一起刪除。算是對自己上次用寫后臺的一個強化,項目文章在這里。后來一直沒動,前些日子才把后續的完善。 歡迎訪問我的個人網站:http://www.neroht.com/? 剛學vue和react時,利用業余時間寫的關于這兩個框架的訓練,都相對簡單,有的...
摘要:并總結經典面試題集各種算法和插件前端視頻源碼資源于一身的文檔,優化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快速搭建項目。 本文是關注微信小程序的開發和面試問題,由基礎到困難循序漸進,適合面試和開發小程序。并總結vue React html css js 經典面試題 集各種算法和插件、前端視頻源碼資源于一身的文檔,優化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快...
摘要:并總結經典面試題集各種算法和插件前端視頻源碼資源于一身的文檔,優化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快速搭建項目。 本文是關注微信小程序的開發和面試問題,由基礎到困難循序漸進,適合面試和開發小程序。并總結vue React html css js 經典面試題 集各種算法和插件、前端視頻源碼資源于一身的文檔,優化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快...
閱讀 2461·2021-11-22 15:35
閱讀 3756·2021-11-04 16:14
閱讀 2685·2021-10-20 13:47
閱讀 2487·2021-10-13 09:49
閱讀 2064·2019-08-30 14:09
閱讀 2359·2019-08-26 13:49
閱讀 879·2019-08-26 10:45
閱讀 2762·2019-08-23 17:54