在沒有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方法外包裝一層函數,擴展其餘功能,又能保證原來功能的使用。react-router
// 打印日誌中間件
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);
複製代碼
項目中使用的插件不止一個,在使用多個插件的狀況下,須要使用一個方法將多個插件合併成一個。app
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親密配合,這二者的關係以下圖: 框架
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基礎解析)裏面有介紹,這裏不贅述。dom
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的做用就是獲取提供好的storeide
//調用方法: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}
<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);
複製代碼
我的使用一種框架時總有一種想知道爲啥這樣用的強迫症,否則用框架用的不舒服,不要求從源碼上知道其原理,可是必須得從心理上說服本身。