React 不会渲染递归反应式组件

React does not render recursive reactive components

本文关键字:递归 反应式 组件 React      更新时间:2023-09-26

给定此组件:

import React, { Component } from 'react';
import TrackerReact from 'meteor/ultimatejs:tracker-react';
export default class SubscriptionView extends TrackerReact(Component) {
  constructor(props) {
    super(props);
    let params = props.params || [];
    if (!Array.isArray(params)) {
      params = [params];
    }
    this.state = {
      subscription: {
        collection: Meteor.subscribe(props.subscription, ...params)
      }
    };
  }
  componentWillUnmount() {
    this.state.subscription.collection.stop();
  }

  render() {
    let loaded = this.state.subscription.collection.ready();
    if (!loaded) {
      return (
        <section className="subscription-view">
          <h3>Loading...</h3>
        </section>
      );
    }
    return (
      <section className="subscription-view">
        { this.props.children }
      </section>
    );
  }
};

另一个组件:

import SubscriptionView from './SubscriptionView.jsx';
export const Foo = () => (
  <SubscriptionView subscription="allFoo">
    <SubscriptionView subscription="singleBar" params={ 123 }>
      <div>Rendered!</div>
    </SubscriptionView>
  </SubscriptionView>
);

第一个Subscription在数据可用时重新呈现,但第二个只呈现一次,仅此而已。如果我在 SubscriptionView 的渲染函数中放置一个console.log(this.props.subscription, ready);,我会看到

allFoo false
allFoo true
singleBar false

仅此而已。

在服务器端,两种发布方法都是

Meteor.publish('allFoo', function () {
  console.log("Subscribing foos");
  return Foos.find();
});
Meteor.publish('singleBar', function (id) {
  console.log("Subscribing bar", id);
  return Bars.find({ _id: id });
});

正在调用这两个发布方法。

为什么第二SubscriptionView不是被动的?


*溶液*

这是基于alexi2的评论:

import React, { Component } from 'react';
import TrackerReact from 'meteor/ultimatejs:tracker-react';
export default class SubscriptionLoader extends TrackerReact(Component) {
  constructor(props) {
    super(props);
    let params = props.params || [];
    if (!Array.isArray(params)) {
      params = [params];
    }
    this.state = {
      done: false,
      subscription: {
        collection: Meteor.subscribe(props.subscription, ...params)
      }
    };
  }
  componentWillUnmount() {
    this.state.subscription.collection.stop();
  }
  componentDidUpdate() {
    if (!this.state.done) {
      this.setState({ done: true });
      this.props.onReady && this.props.onReady();
    }
  }

  render() {
    let loaded = this.state.subscription.collection.ready();
    if (!loaded) {
      return (
        <div>Loading...</div>
      );
    }
    return null;
  }
};

然后,在父组件的 render 方法中:

<section className="inventory-item-view">
  <SubscriptionLoader subscription='singleBar' params={ this.props.id } onReady={ this.setReady.bind(this, 'barReady') } />
  <SubscriptionLoader subscription='allFoos' onReady={ this.setReady.bind(this, 'foosReady') } />
  { content }
</section>

其中setReady仅设置组件的状态,并且仅当this.state.barReady && this.state.foosReady为 true 时,content才具有值。

它有效!

尝试像这样分离出SubscriptionView组件:

import SubscriptionView from './SubscriptionView.jsx';
export const Foo = () => (
    <div>
      <SubscriptionView subscription="singleBar" params={ 123 }>
        <div>Rendered!</div>
      </SubscriptionView>
      <SubscriptionView subscription="allFoo">
        <div>Rendered Again!</div>
      </SubscriptionView>
    </div>
);

从评论对话编辑

不确定我是否走在正确的轨道上,但您可以将 Foo 构建为一个"智能"组件,根据需要将 props 传递给每个 SubscriptionView,然后将 Foo 用作可重用组件。

假设我需要渲染的是FooBarForm,它要求在该特定用例中同时注册Foos和Bars。你会怎么做?

您可以根据需要创建组件FoosBars,并创建一个包含这些组件并传递必要数据的父组件FooBarForm

FooBarForm将处理状态,并在更改时将其传递给其子组件的 props。

现在,状态由父组件集中管理,子组件使用从父组件传递的 props 进行渲染。

子组件将在其 props 更改时重新渲染,具体取决于从父组件传递的状态是否已更改。