redux && react-redux源码解析

2,888 阅读5分钟

react组件传递是单向的,如果组件关系太远,或者没有关系,就会很麻烦,redux就是解决这个问题,他将数据存储到仓库,通过reducer派发action动作,来subscribe

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里面比较难理解的部分啦

他的原理如上图,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>
		);
	}
}