相关仓库: github.com/mcuking/min…javascript
本讲主要解决如何在 react 中更优雅的使用 redux,即实现 react-reduxvue
在实现 react-redux 以前,咱们首先须要了解 react 的 context 机制。当须要将某个数据设置为全局,便可使用 context 在父组件声明,这样其下面的全部子组件均可以获取到这个数据。java
(注意,在最新的 react 16.3(.0-alpha)中,context 机制已经更新,功能更增强大,详情请参考个人译文 React 16.3(.0-alpha)新特性-译)react
基于 context 机制,咱们定义一个 Provider,做为应用的一级组件,专门负责将传入的 store 放到 context 里,全部子组件都可以直接获取 store,并不渲染任何东西。git
// Provider 负责将store放到context里,全部子组件都可以直接获取store
export class Provider extends Component {
// 使用context须要使用propType进行校验
static childContextTypes = {
store: PropTypes.object
};
constructor(props, context) {
super(props, context);
this.store = props.store; // 将传进来的store做为自己的store进行管理
}
// 将传进来的store放入全局context,使得下面的全部子组件都可得到store
getChildContext() {
return { store: this.store };
}
render() {
return this.props.children;
}
}
复制代码
对应业务代码以下:github
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from './mini-redux';
import { Provider } from './mini-react-redux';
import { counter } from './index.redux';
import App from './App';
const store = createStore(counter);
ReactDOM.render(
<Provider store={store}> <App /> </Provider>,
document.getElementById('root')
);
复制代码
connect 负责链接组件,将 redux 中的数据传入组件的属性里,所以须要完成下面两件事:redux
所以 connect 自己是一个高阶组件,首先接收下面两个参数,而后再接收一个组件:bash
const mapStateToProps = state => {
return {
num: state
};
};
复制代码
const mapDispatchToProps = {
buyHouse,
sellHouse
};
// action creator
export function buyHouse() {
return { type: BUY_HOUSE };
}
export function sellHouse() {
return { type: SELL_HOUSE };
}
复制代码
第一步,咱们将 mapStateToProps 的返回值,即组件须要的全局状态 state 中的某个状态,以参数的形式传给新构建的组件,代码以下:dom
export const connect = (
mapStateToProps = state => state,
mapDispatchToProps = {}
) => WrapComponent => {
// 高阶函数,连续两个箭头函数意味着嵌套两层函数
return class ConnectComponent extends Component {
// 使用context须要使用propType进行校验
static contextTypes = {
store: PropTypes.object
};
constructor(props, context) {
super(props, context);
this.state = {
props: {}
};
}
componentDidMount() {
this.update();
}
// 获取mapStateToProps返回值,放入到this.state.props
update() {
const { store } = this.context; // 获取放在全局context的store
const stateProps = mapStateToProps(store.getState());
this.setState({
props: {
...this.state.props,
...stateProps
}
});
}
render() {
return <WrapComponent {...this.state.props} />; } }; }; 复制代码
第二步,咱们须要将 mapDispatchToProps 这个对象中修改全局状态的方法传入给组件,可是直接将相似 buyHouse 方法传给组件,并在组件中执行 buyHouse()方法并不能改变全局状态。ide
联想到上一讲 redux 中,修改全局状态,须要使用 store 的 dispatch 方法,dispatch 对应代码以下:
function dispatch(action) {
// reducer根据老的state和action计算新的state
currentState = reducer(currentState, action);
// 当全局状态变化时,执行传入的监听函数
currentListeners.forEach(v => v());
return action;
}
复制代码
其中须要外部传入 action,即一个对象,例如{type: BUY_HOUSE}
。所以咱们须要将 buyHouse 方法的返回值 action 对象,传给 store.dispatch 方法,执行后才能改变全局状态。对应代码以下:
buyHouse = () => store.dispatch(buyHouse());
复制代码
对此,咱们封装一个方法 bindActionCreators,入参为 mapDispatchToProps 和 store.dispatch,返回相似 buyHouse = () => store.dispatch(buyHouse())的方法的集合,即便用 dispatch 将 actionCreator 的返回值包一层,代码以下:
// 将 buyHouse(...arg) 转换为 (...arg) => store.dispatch(buyHouse(...arg))
function bindActionCreator(creator, dispatch) {
return (...arg) => dispatch(creator(...arg)); // 参数arg透传
}
// creators 示例 {buyHouse, sellHouse, buyHouseAsync}
export function bindActionCreators(creators, dispatch) {
let bound = {};
for (let v in creators) {
bound[v] = bindActionCreator(creators[v], dispatch);
}
return bound;
}
复制代码
所以,咱们就能够第一步的基础上,将 store.dispatch 包装后的 actionCreator 集合对象,传给组件,代码以下:
export const connect = (
mapStateToProps = state => state,
mapDispatchToProps = {}
) => WrapComponent => {
// 高阶函数,连续两个箭头函数意味着嵌套两层函数
return class ConnectComponent extends Component {
// 使用context须要使用propType进行校验
static contextTypes = {
store: PropTypes.object
};
constructor(props, context) {
super(props, context);
this.state = {
props: {}
};
}
componentDidMount() {
const { store } = this.context;
store.subscribe(() => this.update()); // 每当全局状态更新,均须要更新传入组件的状态和方法
this.update();
}
// 获取mapStateToProps的返回值 和 mapDispatchToProps,放入到this.state.props
update() {
const { store } = this.context; // 获取放在全局context的store
const stateProps = mapStateToProps(store.getState());
// 方法不能直接传,由于须要dispatch
// 例如直接执行addHouse()毫无心义,须要buyHouse = () => store.dispatch(addHouse())才有意义
// 其实就使用diapatch将actionCreator包了一层
const dispatchProps = bindActionCreators(
mapDispatchToProps,
store.dispatch
);
this.setState({
props: {
...this.state.props,
...stateProps,
...dispatchProps
}
});
}
render() {
return <WrapComponent {...this.state.props} />; } }; }; 复制代码
注意,除了将 dispatchProps 传给组件以外,上面代码还在组件的 componentDidMount 生命周期中,将 update 函数设置为监听函数,即
store.subscribe(() => this.update())
复制代码
从而,每当全局状态发生变化,都会从新获取最新的传入组件的状态和方法,实现组件状态与全局状态同步的效果。
另外最近正在写一个编译 Vue 代码到 React 代码的转换器,欢迎你们查阅。