在沒有redux出來以前,父組件和子組件之間,平行組件之間傳遞和修改狀態,須要將狀態和修改狀態的方法逐級往下傳,組件嵌套過深則很容易出現管理混亂的問題。因此redux就是爲了解決狀態管理而誕生的。 react
//用來建立Store function createStore(reducer){} // 抽離出不一樣的type調用dispatch函數的複用部分 function bindActionCreators(actions,dispatch){} // 合併reducer function combineReducers (reducers){} // 使用中間件造成一個新的dispatch覆蓋原來的dispatch,並返回store function applyMiddleware (...middlewares){} //合併中間件 function compose(...args){} export { createStore, bindActionCreators, combineReducers, compose, applyMiddleware } 複製代碼
function createStore(reducer){ let state; //存放狀態 let listeners = []; //存放訂閱的回調 function dispath(action){ state = reducer(state, action); // 調用reducer返回新的state listeners.forEach(fn=>fn()); // 發佈全部訂閱的回調 } // 派發初始動做,type爲reducer中沒有的類型,目的是初始化狀態爲用戶設置的狀態 dispatch({type:'@INIT'}); function getState(){ // 暴露的state屬性不但願別人能改,改了也不會影響原有的狀態 return JSON.parse(JSON.stringify(state)); } function subscribe(fn){ //訂閱回調,並返回一個從listeners刪除回調的函數 listeners.push(fn); return ()=>{listeners = listeners.filter(l=>l!=fn)}; } } return { getState, dispatch, subscribe } } 複製代碼
爲了理解上面的東西,看下面的用例:redux
let initState = { title: { color: "red", text: "kbz" } }; function reducer(state = initState, action) { switch (action.type) { case "CHANGE_TITLE_COLOR": return { ...state, title: { ...state.title, color: action.color } }; break; case "CHANGE_TITLE_TEXT": return { ...state, content: { ...state.title, text: action.text } }; break; } return state; } let store = createStore(reducer); let unsubcribe = store.subscribe(function() { console.log(store.getState().title.color); }); setTimeout(() => { store.dispatch({ type: "CHANGE_TITLE_COLOR", color: "yellow" }); }, 1000); setTimeout(() => { unsubcribe(); store.dispatch({ type: "CHANGE_TITLE_COLOR", color: "blue" }); }, 2000); 複製代碼
在建立組件的時候,組件應該是存粹的,使用store.dispatch等其它變量,須要根據狀況而區別的引入不一樣的變量,最好使用state或者props,因此須要將action和dispatch抽離封裝而後賦予到組件的prop上promise
//action-types.js export const ADD = 'ADD'; export const MINUS = 'MINUS'; 複製代碼
//actions.js import * as types from '../action-types'; let actions = { add(count){ return {type:types.ADD,count} }, minus(count){ return {type:types.MINUS,count} } } export default actions 複製代碼
function bindActionCreators(actions,dispatch){ let obj = {} for(let key in actions){ obj[key] = (...args)=>dispatch(actions[key](...args)) } return obj; } 複製代碼
import React,{Component} from 'react'; import {render} from 'react-dom'; import Counter from './components/Counter'; import {bindActionCreators} from 'redux'; import store from './store'; inmport action form './action' let props= bindActionCreators(actions,store.dispatch) render(<Counter {...props}></Counter>,window.root); 複製代碼
爲了更好的模塊化管理,能夠將每一個組件的reducer分開來建立,而後再經過combineReducers將全部的reducer合併起來,其原理就是建立一個函數,dispatch的時候將全部reducer都執行一次bash
function combineReducers (reducers){ //返回一個總的totalReducer,和全部的reducer同樣接收state和action return (state={},action)=>{ // totalState登記每個組件的state // 遍歷執行全部的reducer,將返回的state從新登記在totalState中 let obj = {}; for(let key in reducers){ obj[key] = reducers[key](state[key],action) } return obj; } } 複製代碼
中間件原理:在原來的dispatch方法外包裝一層函數,擴展其餘功能,又能保證原來功能的使用。markdown
// 打印日誌中間件 let reduxLogger = (store)=>(dispatch)=>(action)=>{ console.log('prev',store.getState()); dispatch(action) console.log('next',store.getState()); } // let reduxThunk = (store)=>(dispatch)=>(action)=>{ // 若是是函數將正真的dispatch傳給用戶,用戶抉擇是否要派發 if(typeof action === 'function'){ return action(dispatch,store.getState); } dispatch(action); // 直接把對象派發便可 } let reduxPromise = (store)=>(dispatch)=>(action)=>{ // 判斷當前action是否是一個promise,若是是promise就執行,執行的時候只會管成功的結果 if( action.then &&typeof(action.then) == 'function'){ return action.then(dispatch); }else if(action.payload && action.payload.then){ //action.payload是否爲promise return action.payload.then(data=>{ dispatch({...action,payload:data}); },err=>{ dispatch({...action,payload:err}); return Promise.reject(err); // 對外拋出錯誤 }) } return dispatch(action); } 複製代碼
let applyMiddleware = (middleware)=> (createStore)=> (reducer)=>{ let store = createStore(reducer); // 返回新的dispatchL:(action)=>{xxxxxx} let fn = middleware(store); let newDispatch = fn(store.dispatch); //覆蓋原有的dispatch,返回{getState,dispatch:newDispatch,subscribe} return {...store,dispatch:newDispatch}; } // 典型的柯里化,把多個middleware連起來,後面compose會介紹 export default applyMiddleware(reduxLogger)(createStore)(reducer); 複製代碼
項目中使用的插件不止一個,在使用多個插件的狀況下,須要使用一個方法將多個插件合併成一個。react-router
function add(a,b){ return a+b; } function toUpperCase(str){ return str.toUpperCase(); } function len(str){ return str.length } function compose(...args){ return args.reduce((a,b)=>{(...args)=>a(b(...args))}); } compose(len,toUpperCase,add)(a,b); //(a,b) => len(toUpperCase(add(a,b))) 複製代碼
a | b | 返回函數 |
---|---|---|
len | toUpperCase | (...args)=>len(toUpperCase(...args)) |
(...args)=>{len(toUpperCase(...args)} | add | (...args)=>len(toUpperCase(add(...args))) |
let reduxLogger = (store)=>(dispatch)=>(action)=>{ console.log('prev',store.getState()); dispatch(action) console.log('next',store.getState()); } let applyMiddleware = (...middlewares)=> (createStore)=> (reducer)=>{ let store = createStore(reducer); let fns = middlewares.map(middleware=>{ return middleware(store) //返回的函數接受disopatch用於在原來的基礎上擴展 }); // compose(fn1,fn2)(store.dispatch) //fn執行返回一個新的包裝dispatch函數傳給fn1 let newDispatch = compose(...fns)(store.dispatch); return {...store,dispatch:newDispatch}; //將合併後的dispatch覆蓋原來的最初的dispatch } function compose(...args){ return args.reduce((a,b)=>((...args)=>a(b(...args)))); } 複製代碼
function createStore(reducer,fn) { let state; let listeners = []; let dispatch = (action) => { state = reducer(state,action); listeners.forEach(fn=>fn()); } dispatch({type:'@INIT'}); // createStore(reducer,applyMiddleware(...middlewares))一步到位 // 在內部使用applyMiddleware(...middlewares)(createStore)(reducer) if(typeof fn === 'function'){ return fn(createStore)(reducer); } let getState = ()=> JSON.parse(JSON.stringify(state)); let subscribe = (fn)=>{ listeners.push(fn); return ()=>{ listeners = listeners.filter(l=>l!=fn); } } return {getState,subscribe,dispatch} } function bindActionCreators(actions,dispatch){ let obj = {} for(let key in actions){ obj[key] = (...args)=>dispatch(actions[key](...args)) } return obj; } let combineReducers = (reducers)=>{ return (state={},action)=>{ let obj = {} for(let key in reducers){ obj[key] = reducers[key](state[key],action) } return obj; } } let applyMiddleware = (...middlewares)=> (createStore)=> (reducer)=>{ let store = createStore(reducer); let fns = middlewares.map(middleware=>{ return middleware(store) }); let newDispatch = compose(...fns)(store.dispatch); return {...store,dispatch:newDispatch}; } function compose(...args){ return args.reduce((a,b)=>((...args)=>a(b(...args)))); } export { createStore, bindActionCreators, combineReducers, compose, applyMiddleware } 複製代碼
Redux是一款狀態管理庫,而且提供了react-redux庫來與React親密配合,這二者的關係以下圖: app
從上面能夠看出,React-redux經過Provider和connet將Redux和React聯繫起來:import React,{Component} from 'react'; import {bindActionCreators} from './redux' let Context = React.createContext(); //將store掛載在contex上,供嵌套組件使用 class Provider extends Component{} // connect的做用就是獲取store,子組件獲取contex上的store let connect = (mapStateToProps,mapDispatchToProp)=>{} export { Provider, connect } 複製代碼
React會提供一個createContext的API,調用它會生成一個Context,裏面包含了Provider組件和Consume組件,Provider提供一個狀態供跨組件使用,須要使用狀態的組件只要嵌套在Consume中就獲取Provider提供的狀態。讓react用起來更駕輕就熟——(react基礎解析)裏面有介紹,這裏不贅述。框架
let Context = React.createContext(); class Provider extends Component{ // 將React-redux中的Provide包裝了react提供的API生成的Context.Provider //<Provider store={xxxx}></Provider>,將store掛載在contex上 render(){ return <Context.Provider value={{store:this.props.store}}> {this.props.children} //子組件 </Context.Provider> } } 複製代碼
既然有掛載store,就必然有子組件獲取store,connect的做用就是獲取提供好的storedom
//調用方法:connect(mapStateToProps,mapDispatchToProp)(Com) // connect是一個高階組件,調用後的返回一個組件 let connect = (mapStateToProps,mapDispatchToProp)=>(Com) =>{ return ()=>{ // 高階組件的特色就是把組件中公用的邏輯抽取來,返回一個通過處理的組件 class Proxy extends Component{ state = mapStateToProps(this.props.store.getState()) componentWillMount(){ this.unsub = this.props.store.subscribe(()=>{ this.setState(mapStateToProps(this.props.store.getState())) }) } componentWillUmount(){ this.unsub() } //mapStateToProps就是將state中的部分或所有狀態映射到須要的組件中做爲其props //mapDispatchToProp就是將action中已經綁定成dispatch形式的action按需求映射到須要的組件做爲其props render(){ let b if(typeof mapDispatchToProp === 'function'){ b = mapDispatchToProp(this.props.store.dispatch); }else{ // bindActionCreators把直接將全部action的綁定成diapatch(action)形式組成一個對象 b = bindActionCreators(mapDispatchToProp,this.props.store.dispatch) } //將全部的state和修改state的方法以props的方式傳入 return <Com {...this.state} {...b}></Com> } } //調用Consumer將獲取到的store傳給包裝Com的Proxy return <Context.Consumer> {({store})=>{ return <Proxy store={store}></Proxy> }} </Context.Consumer> } } 複製代碼
用例:ide
import React,{Component} from 'react'; import actions from '../store/actions/counter'; import {connect} from 'react-redux'; class Counter extends Component{ render(){ return (<div> <button onClick={()=>{ this.props.add(2); }}>+</button> {this.props.number} <button onClick={()=>{ this.props.minus(2); }}>-</button> </div>) } } // mapStateToProps用戶本身定義須要的狀態 let mapStateToProps = (state)=>{ return {number:state.counter.number} } // action也是用戶本身定義的,能夠是函數能夠是對象 // 若是傳遞過來的不是方法是對象,會把這個對象自動用bindActionCreators包裝好 export default connect(mapStateToProps,actions)(Counter); 複製代碼
我的使用一種框架時總有一種想知道爲啥這樣用的強迫症,否則用框架用的不舒服,不要求從源碼上知道其原理,可是必須得從心理上說服本身。