讓react用起來更得心應手——(react-redux)
在沒有redux出來之前,父元件和子元件之間,平行元件之間傳遞和修改狀態,需要將狀態和修改狀態的方法逐級往下傳,元件巢狀過深則很容易出現管理混亂的問題。所以redux就是為了解決狀態管理而誕生的。

Redux實現
基礎框架
//用來建立Store function createStore(reducer){} // 抽離出不同的type呼叫dispatch函式的複用部分 function bindActionCreators(actions,dispatch){} // 合併reducer function combineReducers (reducers){} // 註冊中介軟體 function applyMiddleware (...middlewares){} //??????? function compose(...args){} export{ createStore, bindActionCreators, combineReducers, compose, applyMiddleware } 複製程式碼

Redux的核心createStore
- 建立一個state變數用來儲存平行元件間需要共享的變數
- 提供getState方法給外界獲取state
- 提供subscribe方法給外界訂閱回撥在dispatch修改state時執行
- 提供dispatch方法給外界呼叫使用者註冊的reucer來修改state並執行訂閱的回撥
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 } } 複製程式碼
為了理解上面的東西,看下面的用例:
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); 複製程式碼
bindActionCreators
在建立元件的時候,元件應該是存粹的,使用store.dispatch等其它變數,需要根據情況而區別的引入不同的變數,最好使用state或者props,所以需要將action和dispatch抽離封裝然後賦予到元件的prop上
- 抽離type
//action-types.js export const ADD = 'ADD'; export const MINUS = 'MINUS'; 複製程式碼
- 抽離action
//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 複製程式碼
- 批量抽離dispatch
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); 複製程式碼
combineReducers
為了更好的模組化管理,可以將每個元件的reducer分開來建立,然後再通過combineReducers將所有的reducer合併起來,其原理就是建立一個函式,dispatch的時候將所有reducer都執行一次
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; } } 複製程式碼
applyMiddleware
常用的中介軟體
中介軟體原理:在原來的dispatch方法外包裝一層函式,擴充套件其他功能,又能保證原來功能的使用。
// 列印日誌中介軟體 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); } 複製程式碼
applyMiddleware基礎原理
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); 複製程式碼
compose
compose的原理
專案中使用的外掛不止一個,在使用多個外掛的情況下,需要使用一個方法將多個外掛合併成一個。
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))) |
完善applyMiddleware
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)))); } 複製程式碼
redux完整程式碼
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 } 複製程式碼
React-redux
Redux和React的關係
Redux是一款狀態管理庫,並且提供了react-redux庫來與React親密配合,這兩者的關係如下圖:

從上面可以看出,React-redux通過Provider和connet將Redux和React聯絡起來:
React-redux框架
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 } 複製程式碼
Provider
React會提供一個createContext的API,呼叫它會生成一個Context,裡面包含了Provider元件和Consume元件,Provider提供一個狀態供跨元件使用,需要使用狀態的元件只要巢狀在Consume中就獲取Provider提供的狀態。 ofollow,noindex">讓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> } } 複製程式碼
connect
既然有掛載store,就必然有子元件獲取store,connect的作用就是獲取提供好的store
//呼叫方法: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> } } 複製程式碼
用例:
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} <buttononClick={()=>{ this.props.minus(2); }}>-</button> </div>) } } // mapStateToProps使用者自己定義需要的狀態 let mapStateToProps = (state)=>{ return {number:state.counter.number} } // action也是使用者自己定義的,可以是函式可以是物件 // 如果傳遞過來的不是方法是物件,會把這個物件自動用bindActionCreators包裝好 export default connect(mapStateToProps,actions)(Counter); 複製程式碼
結語:
個人使用一種框架時總有一種想知道為啥這樣用的強迫症,不然用框架用的不舒服,不要求從原始碼上知道其原理,但是必須得從心理上說服自己。