redux && react-redux原始碼解析

渣渣的生存之道發表於2019-03-01

不能因為別人懷疑自己,但是可以通過別人啟發自己 !

昨天有人讓我把他當小白講講redux,我表示理出來的邏輯並不是很明確,他可能是在教我如何寫文章吧,我對自己寫的東西,並不是很負責,目的一直停留在增強自己的短時間記憶,我會安排時間將之前的文章做邏輯性梳理,當然先從這篇開始。

我們知道react元件傳遞是單向的,如果元件關係太遠,或者沒有關係,我們就會很麻煩,redux就是解決這個問題,他將資料儲存到倉庫,通過reducer派發action動作,來substrict

redux組成有5個

  • createStore 建立倉庫,接受reducer作為引數
  • bindActionCreator 繫結store.dispatch和action 的關係
  • combineReducers 合併多個reducers
  • applyMiddleware 洋蔥模型的中介軟體,介於dispatch和action之間,重寫dispatch
  • compose 整合多箇中介軟體

createStore

主要匯出getState,dispatch,subscribe三個方法,分別是獲取倉庫,派發動作和訂閱事件。生成store,他包含所有資料,拿資料通過getState獲取, 一個 State 對應一個 View。只要 State 相同,View 就相同。

其內部儲存啦一顆狀態樹,這個狀態樹是任意型別的,當獲取狀態樹的時候後會複製一份狀態樹嗎,並匯出,防止對狀態樹的惡意更改。

因此,只能通過派發dispatch更改狀態,dispatch會呼叫reducer處理action 動作,reducer是個函式,他有state和action兩個引數,作用是將老得state經過action處理後返回新的state,action是個有type屬性的物件,來標示他對哪個狀態進行改變

於此同時,當狀態發生改變會觸發subscribe訂閱的監聽事件

export default function createStore(reducer,initState,enchancer) {
	if (enchancer) {
		return enchancer(createStore)(reducer,initState);
	}
	//倉庫內部儲存了一顆狀態樹。可以是任意型別
	let state;
	let listeners=[];
	function getState() {
		return JSON.parse(JSON.stringify(state));
	}
	//元件可以派發動作給倉庫
	function dispatch(action) {
		//呼叫reducer進行處理,獲取老的state,計算出新的state
		state=reducer(state,action);
		//通知其他的元件
		listeners.forEach(l=>l());
	}
	//如果說其他的元件需要訂閱狀態變化時間的話,
	function subscribe(listener) {
		listeners.push(listener);
		return function () {
			listeners = listeners.filter(item=>item!==listener);
		}
	}
	dispatch({type:'@@INIT'});
	return {
		getState,
		dispatch,
		subscribe
	}
}
複製程式碼

bindActionCreator

雖然我們用dispatch派發事件,派發動作型別大概是一個有type屬性的物件,我們將這個物件的方法封一個actions的資料夾,在dispach的時候直接呼叫action,列入將dispatch(()=>{type:add}),此時我們需要建立dispatch和actions的關係

<button onClick={()=>bindActions.add1(1)}>+</button>

export default function (actions,dispatch) {
	//actions = {add(){return {type:ADD}},minus(){return {type:'MINUS'}}}
	return Object.keys(actions).reduce(
		(memo,key) => {
			memo[key]=(...args) => dispatch(actions[key](...args));
			return memo;
		},{});
}
複製程式碼

combineReducers

返回一個函式,這個函式代表合併後的reducer,那麼他一定有兩個引數,最終返回新狀態,我們迭代reducers每一個屬性

//因為redux應用只能有一個倉庫,只能有一個reducer
//把多個reducer函式合併成一個
export default function (reducers) {
	//返回的這個函式就是合併後的reducer
	
	return function (state={},action) {
		let newState={};
		for (let key in reducers) {
			// key=counter1
			//reducers[counter1]=counter那個reducer 
			//state合併後的狀態樹 
			//if (key===action.name) {
				newState[key]=reducers[key](state[key],action);//state[key]老狀態
			//}
		}
		return newState;
	}
}
複製程式碼

我們可以發現,每個元件都需要獲取倉庫,管理訂閱,向倉庫派發事件,這樣很融於,我們需要複用這樣的邏輯,可以用高階元件,或者將函式作為子元件,我們此時用這個庫

applyMiddleware

中介軟體大概是redux裡面比較難理解的部分啦

redux  && react-redux原始碼解析

他的原理如上圖,state和dispatch構成啦我們的倉庫,我們可以向倉庫傳送action通過reducer改變狀態,狀態改變之後可以修改檢視,使用者可以通過滑鼠點選檢視,檢視派發action,改變狀態,形成迴圈,,有時候我們需要釋出非同步操作,想在派發前,派發後做一些額外動作,此時我們就需要插入中介軟體,我們的方法就是得到dispatch方法,重寫dispacth,這裡用到啦我們說的原始碼解析

一個redux中介軟體大概就是如下,之後可能會將redux和express和koa中介軟體做個對比給大家總結一下


function logger(store){//getState ,新的dispatch
    return function(next){//store.dispatch舊的
        return function action(){
            console.log('old');
            next()
            console.log('new')
        }
    }
}
//解析過程
let logger = store => next =>action =>{
    
}
//以下是處理過程
function applyMiddleware(middleware){
    return function(creacteStore){
        return function(reducer){
            let store = creacteStore(reducer);
            let middleware2 = middleware(store)
            let dispatch = middleware2(store.dispatch)
        }
    }
}
let store = applyMiddleware(logger)(creacteStore)(reducer)
複製程式碼
import compose from './compose';
export default function (...middlewares) {
	return function (createStore) {
		return function (reducers) {
			let store=createStore(reducers);//這就是原始的倉庫 dispatch 就是原始的dispatch
			let dispatch;//dispatch方法指向新的dispatch方法
			let middlewares2 = middlewares.map(middleware=>middleware({
				getState: store.getState,
				dispatch:action=>dispatch(action)
			}));//呼叫第一層去掉
			dispatch=compose(...middlewares2)(store.dispatch);//再呼叫第二次把第二層去掉
			return {
				...store,
				dispatch
			}
		}
	}
}
複製程式碼

compose整合中介軟體

function add1(str) {
	return 1+str;
}
function add2(str) {
	return 2+str;
}
function sum(a,b) {
	return a+b;
}
//let ret=add1(add2(add3('zdl')));
//console.log(ret);
function compose1(...fns) {//[add1,add2,add3]
	return function (...args) {
		let last=fns.pop();
		return fns.reduceRight((val,fn)=>fn(val),last(...args));
	}
}
export default function(...fns) {
	return fns.reduce((a,b)=>(...args)=>a(b(...args)));
}
/**
 * 第一次的時候 a =add1 b=add3 let ret = add1(add2(...args))
 * 第二次的時候 (...args)=>add1(add2(sum(...args)))
 */
let ret=compose(add1,add2,sum)('a','b');
console.log(ret);//123zdl
複製程式碼

react-redux

有四個檔案

  • connect 將store和dispatch分別對映成props屬性物件,返回元件
  • context 上下文 匯出Provider,,和 consumer
  • Provider 一個接受store的元件,通過context api傳遞給所有子元件,優點類似於路由啦
  • index

index

import Provider from './Provider';
import connect from './connect';
export {
	Provider,
	connect
}
複製程式碼

Provider

/**
 * 是一個元件,用來接受store,再經過它的手通過context api傳遞給所有的子元件
 */
import React,{Component} from 'react'
import {Provider as StoreProvider} from './context';

import PropTypes from 'prop-types';
export default class Provider extends Component{
	//規定如果有人想使用這個元件,必須提供一個redux倉庫屬性
	static propTypes={
		store:PropTypes.object.isRequired
	}
	render() {
		let value={store:this.props.store};
		return (
			<StoreProvider value={value}>
				{this.props.children}
			</StoreProvider>
		)
	}
}
複製程式碼

context

import React from 'react'
let {Provider,Consumer}=React.createContext();
export {Provider,Consumer};
複製程式碼

connect

這是個高階函式,分別傳入兩個方法mapStateToProps,mapDispatchToProps,將store和dispatch分別對映成props屬性物件,這樣在頁面就不需要飲用倉庫,也不需要繫結action和dispatch,也不需要訂閱狀態,返回元件

import {Consumer} from './context';
import React,{Component} from 'react';
import {bindActionCreators} from '../redux';
/**
 * connect實現的是倉庫和元件的連線
 * mapStateToProps 是一個函式 把狀態對映為一個屬性物件
 * mapDispatchToProps 也是一個函式 把dispatch方法對映為一個屬性物件
 */
export default function (mapStateToProps,mapDispatchToProps) {
	return function (Com) {
		//在這個元件裡實現倉庫和元件的連線
		class Proxy extends Component{
			state=mapStateToProps(this.props.store.getState())
			componentDidMount() {
				this.unsubscribe = this.props.store.subscribe(() => {
					this.setState(mapStateToProps(this.props.store.getState()));
				});
			}
			componentWillUnmount = () => {
				this.unsubscribe();
			}
			
			render() {
				let actions={};
				//如果說mapDispatchToProps是一個函式,執行後得到屬性物件
				if (typeof mapDispatchToProps === 'function') {
					actions = mapDispatchToProps(this.props.store.dispatch);
				//如果說mapDispatchToProps是一個物件的話,我們需要手工繫結
				} else {
					actions=bindActionCreators(mapDispatchToProps,this.props.store.dispatch);
				}
				return <Com {...this.state} {...actions}/>
			}
		}

		return () => (
			<Consumer>
				{
					value => <Proxy store={value.store}/>
				}
			</Consumer>
		);
	}
}
複製程式碼

相關文章