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}
          />
        );
      }
    };
  };
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章