【问题标题】:How does Provider and connect work in react?Provider 和 connect 是如何工作的?
【发布时间】:2018-06-21 23:17:47
【问题描述】:

1-Provider

为什么我们需要将所有组件都包装在 Provider 中?

2-connect:

connect 如何将 redux store 作为props 传递给组件?

3- 我们可以建立自己的Providerconnect 吗?

下面是一个完整工作的简单 React Redux 示例,它仅显示来自 redux 存储的名称并使用 React Redux connectProvider,那么 ConnectedComponent 如何可以简单地访问 this.props.name

import React, { Component } from "react";
import { render } from "react-dom";
import { createStore } from "redux";
import { Provider, connect } from "react-redux";

var defaultState = {
  name: "Amr"
};

function rootReducer(state = defaultState, action) {
  return state;
}

var store = createStore(rootReducer);


class ConnectedComp extends Component {
  render() {
    return (
        <h2>{this.props.name}</h2>
    );
  }
}
function mapStateToProps(state) {
  return {
    name: state.name
  };
}
ConnectedComp = connect(mapStateToProps)(ConnectedComp);



class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <ConnectedComp />
      </Provider>
    );
  }
}

render(<App />, document.getElementById("root"));

这里有一个完整的示例https://codesandbox.io/s/lpvnxro7n7

【问题讨论】:

    标签: javascript reactjs redux react-redux


    【解决方案1】:

    为了能够理解 Providerconnect 的工作原理,我们需要理解 React 中的两个概念

    1- 上下文 API:

    上下文是一种通过组件树传递数据的方式,无需在每一层手动向下传递道具,您可以了解更多关于context here

    2- 高阶组件 (HOC):

    高阶组件是一个函数,它接受一个组件并返回一个新组件,但在返回新组件之前你可以传递额外的自定义 props 然后返回它,你可以了解更多关于HOC here


    3- 构建我们自己的 Providerconnect

    既然我们了解了上下文和高阶组件,我们将使用它们在问题中创建相同的完整工作示例,但使用我们自己构建的 myProvidermyConnect


    MyProvider

    //This is how we utilize React Context and create MyProvider component that will pass store to all its child components automatically
    //This is also known by Provider pattern
    class MyProvider extends Component {
      //By adding the getChildContext function and childContextTypes, React passes the information down automatically to any component in the subtree
      getChildContext() {
        const { store } = this.props
        return { store }
      }
    
      render() {
        return this.props.children;
      }
    }
    
    MyProvider.childContextTypes = {
      store: PropTypes.object.isRequired,
    }
    

    myConnect

    //This is the Higher Order Component
    function myConnect(mapStateToPropsFunc) {
      return function (WrappedComp) {
        var myHOC = class HOC extends Component {
          render() {
            //Now we access redux store using react context api as it will be passed by MyProvider automatically to all child components
            var myStore = this.context.store.getState();
            //mapStateToPropsFunc is just used to structure the props required by the component so we pass to mapStateToPropsFunc the whole store and then it returns a mapped object with required props thats why it is well known by mapStateToProps
            var storeToBePassed = mapStateToPropsFunc(myStore);
            return (
              //We pass the result from executing mapStateToPropsFunc to the wrapped component and this is how the components get passed props from redux store
              <WrappedComp {...storeToBePassed} />
            )
          }
        }
    
        //We need to define contextTypes otherwise context will be empty
        myHOC.contextTypes = {
          store: PropTypes.object
        };
    
        //return new component that has access to redux store as props mapped using mapStateToProps function
        return myHOC;
      }
    }
    

    同样的简单示例使用我们自己的MyProvidermyConnect

    //Note that we removed react-redux library
    import React, { Component, Children } from "react";
    import { PropTypes } from "prop-types";
    import { render } from "react-dom";
    import { createStore } from "redux";
    
    var defaultState = {
      name: "Amr"
    };
    
    function rootReducer(state = defaultState, action) {
      return state;
    }
    
    var store = createStore(rootReducer);
    
    class App extends Component {
      render() {
        //Here we use our own built myProvider and pass store
        return (
          <MyProvider store={store}>
            <ConnectedComp />
          </MyProvider>
        );
      }
    }
    
    class ConnectedComp extends Component {
      render() {
        return (
          <h2>{this.props.name}</h2>
        );
      }
    }
    
    //mapStateToProps is a normal function that get store as parameter and return the required props by that component, btw this function can have any name
    function mapStateToProps(state) {
      return {
        name: state.name
      };
    }
    
    //Here we use our own built myConnect
    ConnectedComp = myConnect(mapStateToProps)(ConnectedComp);
    
    render(<App />, document.getElementById("root"));
    

    你可以在这里测试上面的实现https://codesandbox.io/s/727pl0mqoq


    这个例子简单说明了如何使用 React ContextHOC 构建 react-redux Providerconnect

    这让我们更好地理解我们为什么使用ProviderconnectmapStateToProps

    【讨论】:

    • 好东西。我有一个问题。您能否解释一下store(通过MyProvider)中的更新如何导致“连接”组件中发生更新/重新渲染?如果store 是自定义对象(即不使用redux)怎么办?
    • MyProvider 的唯一目的是将 redux 存储或任何自定义对象自动传递给所有子级,但请记住,传递的存储只能通过上下文 api 访问,如您在答案这部分 this.context.store.getState(); ,这就是 MyConnect 变得方便的地方,因为它会从上下文中读取数据并将其作为 props 传递给连接的组件,最后组件将更新/渲染,因为反应会一旦组件 propsstate 更新后触发渲染,在我们的例子中,原因是从 MyConnect HOC 传递的 props 更新
    • 对于MyProvider函数,为什么getChildContext不简单地返回this.props.store..
    • 你需要从getChildContext返回一个对象,它需要匹配我们定义的childContextTypes结构,如果你返回this.props.store,无论从这行返回的值var store = createStore(rootReducer);将被返回,它会是这样的{ getState: function, dispatch: function, ...etc },这是不正确的,你需要返回的是这个{ store: { getState: function, dispatch: function, ... }},因为稍后你将通过以下方式在myConnect中使用上下文this.context.store.getState()
    • 我个人认为这样会更好,connect(stateProps, dispatchProps, state)(Component) 而不是提供者。
    猜你喜欢
    • 2017-05-23
    • 2020-01-13
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    • 2020-09-17
    • 2012-06-08
    • 2017-03-22
    • 1970-01-01
    相关资源
    最近更新 更多