redux7 - 手写实现 react - redux 基本功能 Provider 和 connect

参考reac-redux 库 源码
以下实现的功能代码演示地址

使用react-redux库

官方 react-redux 库,使用
index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './reducers/store';
ReactDOM.render(
  <Provider store={store}>
    <CoutA />
    <CoutB />
  </Provider>,
  document.getElementById('root'),
);

  1. 该库先导出一个 Provider 组件, 传入一个 store 属性, 值是 reduxcreateStore(reducer) 方法的返回值

在组件中使用 react-redux, 需要导出一个 connect 函数, 进行连接组件和仓库
CoutA.js

import { connect } from 'react-redux';
class CoutA extends Component {
  render() {
    const { add, num } = this.props;
    return (
      <>
        <p>CoutA组件:{num}</p>
        <button onClick={add}>CoutA组件++</button>
      </>
    );
  }
}
const mapStateToProps = (state) => state.count1;
const mapDispatchToProps = (dispatch) => ({
  add() {
    dispatch({type: ADD1});
  },
});
export default connect(mapStateToProps, mapDispatchToProps)(CoutA);

流程图


  • 通过 reduxcreateStore(reducer) 创建store对象
  • 通过 react-reduxPeovider 组件 包裹所有的 react 组件, 通过 store属性把 上边的 store 对象传入
  • 通过 react-redux 提供的 connect 方法连接 组件与数据源仓库

在这里插入图片描述

实现 Provider 组件

在src / react-redux文件夹下:

该文件木有啥逻辑, 就是导出 connect 方法, Provider 组件


index.js

import Provider from './Provider';
import connect from './connect';
export { Provider, connect };

context.js


创建react 的上下文对象

import React from 'react';
export default React.createContext(null);

Provider.js


这个组件拿到外部提供的 store属性, 把所有的子组件通过this.props.children渲染

import React, { PureComponent } from 'react';
import Context from './context';
export default class extends PureComponent {
  render() {
    // 拿到顶级组件传入的 store 属性
    const { store, children } = this.props;
    return <Context.Provider value={store}>{children}</Context.Provider>;
  }
}

接着看 connect 方法

const mapStateToProps = (state) => state;
const mapDispatchToProps = (dispatch) => ({
  add() {
    dispatch({type: ADD1});
  },
});
export default connect(mapStateToProps, mapDispatchToProps)(CoutA);

这个方法传入俩参数,执行后,又返回一个函数,接着把组件传进去, 可能刚接触的感到挺难理解的,从函数层面上讲呢,算是高阶函数,同时又使用了函数的科里化,从react 上说,这就是个高阶组件,

  • 先执行connect(mapStateToProps, mapDispatchToProps) ,执行完后,它返回一个函数,所以你需要再次调用 传入组件, 可以拆分成以下两步,比较好理解
// 1. 先执行第一步,返回一个函数
let connectFn = connect(mapStateToProps, mapDispatchToProps)
// 2. 再次调用这个函数,返回一个包装后的组件(就是高阶组件)
export default connectFn (CoutA)

根据以上梳理逻辑,接着实现这个 connect 方法

// connect(mapStateToProps, mapDispatchToProps)(CoutA)
// 第一层: 导出一个函数接收俩参数
export default function connect(mapStateToProps, mapDispatchToProps) {
  // 第二层,返回一个函数,接收组件作为参数,同时最终渲染的还是你传入的这个组件,所以直接在render 返回
  return function (WarpComponent) {
    return class extends PureComponent {
      render() {
        return <WarpComponent  />;
      }
    };
  };
}

先让包装后的组件拿到状态


通过 react 的 context 上下文对象

export default function connect(mapStateToProps, mapDispatchToProps) {
  // 第二层,返回一个函数,接收组件作为参数,同时最终渲染的还是你传入的这个组件,所以直接在render 返回
  return function (WarpComponent) {
    return class extends PureComponent {
      static contextType = Context;
      constructor(props, context) {
        super(props);
        this.state = context.getState();
      }
      render() {
        return <WarpComponent  {...state}/>;
      }
    };
  };
}

到这里,你在组件中直接这样调用,就可以通过props 拿到值了

connect()(CoutA)

再看传入connect刚调用时传入的参数格式, 传入了俩函数,俩函数的返回值都是个对象

const mapStateToProps = (state) => state
const mapDispatchToProps = (dispatch) => ({
  add() {
    dispatch({type: ADD1});
  },
});
connect(mapStateToProps, mapDispatchToProps)(CoutA)

处理上边俩参数

export default function connect(mapStateToProps, mapDispatchToProps) {
  return function (WarpComponent) {
    return class extends PureComponent {
      static contextType = Context;
      constructor(props, context) {
        super(props);
        this.state = context.getState();
      }
      UNSAFE_componentWillMount() {
        // 判断传入的第一个参数是不是函数,用户可能传,或者不传,需要判断处理
        if (typeof mapDispatchToProps === 'function') {
          // 这里是把 你传入的自定义的方法,绑定到props 上
          this.setState({
            ...this.state,
            ...mapDispatchToProps(this.context.dispatch),
          });
          // 把dispacth 当作它的返回值
          mapDispatchToProps(this.context.dispatch);
        }
        if (typeof mapStateToProps === 'function') {
          // 把传入的数据替换到 state 上
          this.setState({
            ...this.state,
            ...mapStateToProps(this.state),
          });
          // 把 this .state 当作它的返回值
          mapStateToProps(this.state);
        }
      }
      render() {
        return <WarpComponent {...this.state} />;
      }
    };
  };
}

接着进行订阅数据的变化

export default function connect(mapStateToProps, mapDispatchToProps) {
  return function (WarpComponent) {
    return class extends PureComponent {
      // .... 略
      // 订阅数据
      componentDidMount() {
        this.unsubscribe = this.context.subscribe(() =>
          this.setState(this.context.getState()),
        );
      }
      // 组件销毁前,取消订阅
      componentWillUnmount() {
        this.unsubscribe();
      }
      // .... 略
    };
  };
}

到这已经基本实现了,

优化简化代码

希望只获取自己组件依赖的数据,而不关心其它的数据, 可以这样写

const mapStateToProps = (state) => state.count1;
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(CoutA);

但上边的代码是不支持这样的写法,需要进行优化, 既然 第二个参数,是动作,返回的恰好是个对象
可以考虑使用 reduxbindActionCreators 这个方法,它刚好适用于这种对象的语法

import React, { PureComponent } from 'react';
import Context from './context';
import { bindActionCreators } from './../redux';
export default function connect(mapStateToProps, mapDispatchToProps) {
  return function (WarpComponent) {
    return class extends PureComponent {
      static contextType = Context;
      constructor(props, context) {
        super(props);
        // 这里 直接执行 mapStateToProps 函数,把默认的 值返回给回调函数
        this.state = mapStateToProps ? mapStateToProps(context.getState()) : context.getState();
      }
      componentDidMount() {
        this.unsubscribe = this.context.subscribe(() =>
          this.setState(mapStateToProps(this.context.getState())),
        );
      }
      componentWillUnmount() {
        this.unsubscribe();
      }
      render() {
        // 使用 bindActionCreators 集中处理 所有的 actions,
        let boundActions = bindActionCreators(
          mapDispatchToProps(),
          this.context.dispatch,
        );
        return <WarpComponent {...this.state} {...boundActions} />;
      }
    };
  };
}

所以组件中就简写了

import React, { Component } from 'react';
import { ADD1 } from '../reducers/type';
import { connect } from './../react-redux';
class CoutA extends Component {
  render() {
    const { add, num } = this.props;
    return (
      <>
        <p>CoutA组件:{num}</p>
        <button onClick={add}>CoutA组件++</button>
      </>
    );
  }
}
const mapStateToProps = (state) => state.count1;
// 这里不在dispatch 了
const mapDispatchToProps = () => ({
  add() {
    return { type: ADD1 };
  },
});
export default connect(mapStateToProps, mapDispatchToProps)(CoutA);

但是使用官方,它是需要在组件中手动dispacth 的, 所以再接着简化,改成官方的那种

import React, { PureComponent } from 'react';
import Context from './context';
export default function connect(mapStateToProps, mapDispatchToProps) {
  return function (WarpComponent) {
    return class extends PureComponent {
      static contextType = Context;
      componentDidMount() {
        this.unsubscribe = this.context.subscribe(() =>
          this.setState(mapStateToProps(this.context.getState())),
        );
      }
      componentWillUnmount() {
        this.unsubscribe();
      }
      render() {
        const { getState, dispatch } = this.context;
        return (
          <WarpComponent
            {...(mapStateToProps && mapStateToProps(getState()))}
            {...(mapDispatchToProps && mapDispatchToProps(dispatch))}
          />
        );
      }
    };
  };
}

这时组件就需要手动 dispatch 了,但是现在还有个问题, 不管哪个组件的数据变化了,每个组件都会再执行一次render
(演示地址代码,为了重现这个问题, 没有改)
在这里插入图片描述
那这就需要处理了,因为原先每次render的时候都新建了函数,所以 PureComponent 浅比较的话, 就是不相等的 , 所以
shoudComponentUpdate 就会返回true,导致每个都会更新

import React, { PureComponent } from 'react';
import Context from './context';
export default function connect(mapStateToProps, mapDispatchToProps) {
  return function (WarpComponent) {
    return class extends PureComponent {
      static contextType = Context;
      constructor(props, context) {
        super(props);
        // 构造函数里定义死
        this.boundActions =
          mapDispatchToProps && mapDispatchToProps(context.dispatch);
      }
      render() {
        const { getState } = this.context;
        return (
          <WarpComponent
            {...(mapStateToProps && mapStateToProps(getState()))}
            {...this.boundActions}
          />
        );
      }
    };
  };
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章