什麼問題?
組件的樹形結構決定了數據的流向,致使的數據傳遞黑洞javascript
怎麼解決?
全部組件都經過中介者傳遞共享數據
方案:
中介者:html
(function createStore() { var store; return function() { if(!store) { store = new Regular; } return store; } })()
組件A修改數據前端
define(['./store.js'], function(createStore) { var A = Regular.extend({ name: "組件A", data: { title: '標題' }, getData: function() { this.data.title = createStore().data.title; }, setData: function() { store.data.title = '新標題' //通知全部其餘組件 store.$emit('change', { title: '新標題' }) } }); return A; });
其餘組件能夠監聽,也能夠主動拿:java
define(['./store.js'], function(store) { var B = Regular.extend({ name: "組件B", init: function() { createStore().$on('change', function(newTitle){ this.data.title = newTitle }) } }); return B; });
兩個問題:
1 store.data能夠直接被訪問和修改->data和store分開&經過接口拿
2 只須要訂閱和派發(派發的時候會把數據存起來並通知其餘訂閱者)git
(function createStore() { var store; return function() { if(!store) { var store = new Regular; var state = {}; store.getState = function(){ return state; }; store.subscribe = function(listener) { store.$on('change', listener); } store.dispatch = function(action) { if(action.type == 'changeTitle') { state.title = action.data.title; } store.$emit('change', state); } } return store; } })() define(['./store.js'], function(createStore) { var A = Regular.extend({ name: "組件A", data: { title: '標題' }, getData: function() { this.data.title = createStore().data.title; }, setData: function() { store.dispatch({ type: 'changeTitle', data: {title: '新標題'} }) }); return A; }); define(['./store.js'], function(store) { var B = Regular.extend({ name: "組件B", init: function() { createStore().subscribe(mapState) }, mapState: function(state) { this.data.title = state.title } }); return B; });
這個就是基本的redux雛形。後面的其實都是一些改進。
改進1: 數據處理耦合在store當中->抽出reducer並能傳入初始statees6
(function createStore(reducer, initState) { var store; return function() { if(!store) { var store = new Regular; var state = initState; store.getState = function(){ return state; }; store.subscribe = function(listener) { store.$on('change', listener); } store.dispatch = function(action) { state = reducer(state, action); store.$emit('change', state); } } return store; } })()
reducer長這樣:github
function reducer1(state, action) { switch(action.type) { case 'CHANGE_TITLE': //es6寫法 //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator //return { //...state, //title: action.data.title //} return Object.assign({}, state, {title: action.data.title}); break; } }
reducer說是一個純函數,本質上就是它不改變輸入的參數,最後返回了一個新對象(規約)。也解決了可變數據結構的問題。
改進2:每一個組件都須要依賴store,而且須要調用store的subscribe和dispatch方法->建立頂層容器而且擴展它的組件的能力redux
const App = Regular.extend({ name: 'App', template: ` <StoreProvider> <A /> <B /> </StoreProvider> ` }); var StoreProvider = Regular.extend({ template: '{#include this.$body}', config: function(){ this.store = createStore(); }, modifyBodyComponent: function( component ){ component.dispatch = this.store.dispatch.bind(store) //把訂閱這個工做給作了 this.store.subscribe(function () { var state = this.store.getState(); component.mapState(state); }.bind(this)); component.getState = this.store.getState.bind(store) } });
而後子組件內就能夠這樣調用了:數組
define([], function() { var B = Regular.extend({ name: "組件B", init: function() { this.subscribe(mapState) }, mapState: function(state) { this.data.title = state.title } }); return B; });
改進3: 這裏的store無法傳reducer和初始狀態,由於你是裏面調用的->createStore在外面作,而後把store傳給頂層容器數據結構
const App = Regular.extend({ name: 'App', template: ` <StoreProvider store={store}> <A /> <B /> </StoreProvider> ` config(data) { data.store = createStore(reducers, { title: "標題" }) } }); var StoreProvider = Regular.extend({ template: '{#include this.$body}', config: function(data){ this.store = data.store; }, modifyBodyComponent: function( component ){ component.dispatch = this.store.dispatch.bind(store) //把訂閱這個工做給作了 this.store.subscribe(function () { var state = this.store.getState(); component.mapState(state); }.bind(this)); //這個方法其實不用了。由於數據經過connect得到了,而初始數據經過一開始就傳到store進去了 component.getState = this.store.getState.bind(store) } });
改進3: 而且這樣每一個組件都有一個mapState方法,並且作得事都比較相似就是把state的數據過來-> 抽出一個connect函數
function connect(config, B) { B.implement({ mapState: function(state) {//this指向B const mappedData = config.mapState.call(this, state); mappedData && Object.assign(this.data, mappedData); } }); } connect({ //es6簡寫 //mapState(state) { //... //} mapState: function(state) { return { title: state.title } }, A);
以上,就是redux核心的基本實現原理。
中間件是幹嗎用的?
https://guoyongfeng.github.io/book/15/04-redux-logger%E7%9A%84%E5%BA%94%E7%94%A8%E5%92%8C%E7%90%86%E8%A7%A3.html
好比上面的logger中間件,就是想在store的dipatch方法裏面作點其餘事,好比打印下個性化的日誌。
怎麼實現?
正常想法:代理dispatch方法
function applyMiddleware() { let store = createStore(reducer1, initState); store.dispatch = function(action) { console.log("....") store.dispatch(action); console.log("....") } return store; }
後面呢也是改進。。
改進1:不但願改變原先store&dispatch內打印日誌部分但願能作其餘事情->傳遞一個middleware回調進去
function logger(dispatch, action) { console.log("....") dispatch(action); console.log("....") } function applyMiddleware(middleware) { let store = createStore(reducer1, initState); let dispatch = function(action) { middleware(store.dispatch, action) } return Object.assign({}, store, {dispatch: dispatch}); } var store = applyMiddleware(logger)
改進2: 如何處理多箇中間件(每一箇中間件作的事不同)並且store的dispatch應該只被執行一次
var store = applyMiddlewares(logger, someMiddleware) function applyMiddlewares(logger, someMiddleware) { let store = createStore(reducer1, initState); let dispatch = function(action) { //但願一層層代理地執行中間件,最左邊的先執行 someMiddleware(logger(store.dispatch, action), action) } return Object.assign({}, store, {dispatch: dispatch}); } //因此logger必須return一個函數,給其餘middleware執行 function logger(dispatch) { return function(action) { console.log("....") dispatch(action); console.log("....") } }
改進3: 仔細看這裏面的實現發現就是數組的reduceRight方法
function applyMiddlewares(middlewares) { let store = createStore(reducer1, initState); let dispatch = middlewares.reduceRight(function(dispatch, middeware) { return middeware(dispatch); }, store.dispatch) return Object.assign({}, store, {dispatch: dispatch}); }
改進4: 中間件裏面的next函數是幹嗎用的?->其實就是傳進去的dispatch方法
function logger(next) { return function(action) { console.log("....") next(action); console.log("....") } }
以上,就是redux中間件實現的基本原理。
Action Creator是什麼?
是對dispatch函數參數(也就是action)的一種抽象,便於Action的複用
好比咱們這麼寫:
this.$dispatch({ action: 'CHANGE_TITLE', data: { title: '新標題' } })
可能其餘組件也須要寫類似的代碼,你須要複製代碼。其實咱們能夠抽出一個creator。
//這部分能夠被複用 const CHANGE_TITLE = 'CHANGE_TITLE'; function changeTitle(newTitle) { return { type: CHANGE_TITLE , data: { title: newTitle } } } this.$dispatch(changeTitle('新標題'))
最後的一張圖總結:
做者知乎/公衆號:前端瘋