在 render() 方法中不使用 React.cloneElement 传递道具

Props are not passed with React.cloneElement in the render() method

我有两个 React 组件,一个 Layout class 和一个 HomePage class:

HomePage 是一个需要有 products 属性的组件。

HomePage.js

import React, { Component } from 'react';

export class HomePage extends Component {
    render() {
        if (!this.props.products) {
            return (<div>Products not loaded yet</div>);
        }
        return (<div>Products loaded!</div>);
    }
}

Layout 是一个组件,显示 children 来自 react-router 建立的路线。 此 class 负责使用 React.cloneElement

products 道具传递给 children

Layout.js

import React, { Component } from 'react';
import { NavMenu } from './NavMenu';
import { Footer } from './Footer';

export class Layout extends Component {    
    constructor(props) {
      super(props);
      this.state = {
          products: null,
          loading: true
      };
    }    

    // Make an api call when the component is mounted in order to pass
    // additional props to the children
    componentDidMount() {
      this.populateProductsData();
    }

    async populateProductsData() {
      const response = await fetch('api/products/all');
      const data = await response.json();
      this.setState({ products: data, loading: false });    
    }

    render() {
        if (this.state.loading) {
            return (<div>App loading</div>);
        }

        const childrenWithProps = React.Children.map(this.props.children, child => {
            const props = { products: this.state.products };
            if (React.isValidElement(child)) {
                return React.cloneElement(child, props);
            }
            return child;
        });

        return (
          <div>
                <NavMenu />
                {childrenWithProps}
                <Footer />
          </div>
        );
    }
}

路由是在 App 组件中进行的:

App.js

export default class App extends Component {
  render () {
    return (
        <Layout>
            <Route exact path='/'
                component={HomePage}/>
        </Layout>
    );
  }

因此,我期待

  1. 在未进行 API 呼叫时有一个包含 App loading 消息的页面
  2. 有一个包含 Products not loaded yet 消息的页面,而道具尚未传递给 Layout children
  3. 有一个包含 Products loaded! 消息的页面

但是,应用程序卡在第二步:children 组件从未接收到 products 属性。代码编译,没有运行时错误,触发 back-end Api 并发送有效响应。

为什么 product 道具在 child HomePage 组件的 render() 方法中永远不可用?


编辑:

根据@Nikita Chayka 的回答,props 应该在路由中传递:

Layout.js

export class Layout extends Component {    
    render() {
        return (
          <div>
                <NavMenu />
                {this.props.children}
                <Footer />
          </div>
        );
  }
}

App.js

export default class App extends Component {
    constructor(props) {
        super(props);

        this.state = {
            products: null,
            loading: true
        };
    }    

    componentDidMount() {
        this.populateProductsData();
    }

    async populateProductsData() {
        const response = await fetch('/api/products/all');
        const data = await response.json();
        this.setState({ products: data, loading: false });    
    }

    render() {
        if (this.state.loading)
            return (<div>App loading</div>);

        return (
        <Layout>
            <Route exact path='/'
                render={(props) => (<HomePage {...props} products={this.state.products}/>)}/>
        </Layout>
    );
  }
}

你的 Layout 组件会将 products prop 传递给 Route 组件,而不是 Home 组件,基本上你会有

<Route products={} component={Home} path="/" exact/>

但是你需要把它传递给主页,你可以在这里检查想法 - https://ui.dev/react-router-v4-pass-props-to-components/

编辑

你不应该提供组件 属性 给路由,只渲染。