React: 实施横向数据加载

创建于 2015-03-13  ·  136评论  ·  资料来源: facebook/react

这是一个一流的 API,用于从全局存储/网络/资源中横向加载无状态(尽管可能是记忆化的)数据,可能使用 props/state 作为输入。

type RecordOfObservables = { [key:string]: Observable<mixed> };

class Foo {

  observe(): RecordOfObservables {
    return {
      myContent: xhr(this.props.url)
    };
  }

  render() {
    var myContent : ?string = this.data.myContent;
    return <div>{myContent}</div>;
  }

}

observe() 在 componentWillMount/componentWillUpdate 之后但在渲染之前执行。

对于记录中的每个键/值。 订阅值中的 Observable。

subscription = observable.subscribe({ onNext: handleNext });

我们允许从订阅同步调用 onNext。 如果是,我们设置:

this.data[key] = nextValue;

否则,对于初始渲染,我们将其保留为未定义。 (也许我们将其设置为空?)

然后渲染照常进行。

每次调用 onNext 时,我们都会安排一个新的“this.data[key]”,它会有效地触发该组件上的强制更新。 如果这是唯一的更改,则不会重新执行观察 (componentWillUpdate -> render -> componentDidUpdate)。

如果 props / state 发生变化(即从 recieveProps 或 setState 更新),则重新执行 observe()(在协调期间)。

此时我们循环遍历新记录,并订阅所有新的 Observables。

之后,取消订阅之前的 Observables。

subscription.dispose();

这种排序很重要,因为它允许数据提供者对其缓存进行引用计数。 即,只要没人听,我就可以缓存数据。 如果我立即取消订阅,那么在我再次订阅相同的数据之前,引用计数将下降到零。

卸载组件时,我们会自动取消订阅所有活动订阅。

如果新订阅没有立即调用 onNext,那么我们将继续使用之前的值。

因此,如果我的示例中的this.props.url发生更改,并且我正在订阅一个新 URL,myContent 将继续显示前一个 url 的内容,直到下一个 url 完全加载。

这与<img />标记具有相同的语义。 我们已经看到,虽然这可能会造成混淆并导致不一致,但它是一个相当合理的默认设置,并且显示微调器比使用相反的默认设置更容易。

如果您没有缓存数据,最佳做法可能是立即发送“空”值。 另一种选择是 Observable 在结果中提供 URL(或 ID)和内容。

class Foo {

  observe() {
    return {
      user: loadUser(this.props.userID)
    };
  }

  render() {
    if (this.data.user.id !== this.props.userID) {
      // Ensure that we never show inconsistent userID / user.name combinations.
      return <Spinner />;
    }
    return <div>Hello, {this.data.user.name} [{this.props.userID}]!</div>;
  }

}

我们应该使用 Observable 的 RxJS 合约,因为它更常用并且允许同步执行,但是一旦@jhusain的提议更常用,我们将切换到该合约。

var subscription = observable.subscribe({ onNext, onError, onCompleted });
subscription.dispose();

如果需要,我们可以添加更多响应这些事件的生命周期钩子。

注意:这个概念允许横向数据表现得像“行为”——就像道具一样。 这意味着我们不必为这些东西重载概念状态。 它允许进行优化,例如丢弃数据以便稍后重新订阅。 它是可恢复的。

Component API Big Picture

最有用的评论

如果有人想玩这种 API,我为observe做了一个非常愚蠢的 polyfill 作为高阶组件:

import React, { Component } from 'react';

export default function polyfillObserve(ComposedComponent, observe) {
  const Enhancer = class extends Component {
    constructor(props, context) {
      super(props, context);

      this.subscriptions = {};
      this.state = { data: {} };

      this.resubscribe(props, context);
    }

    componentWillReceiveProps(props, context) {
      this.resubscribe(props, context);
    }

    componentWillUnmount() {
      this.unsubscribe();
    }

    resubscribe(props, context) {
      const newObservables = observe(props, context);
      const newSubscriptions = {};

      for (let key in newObservables) {
        newSubscriptions[key] = newObservables[key].subscribe({
          onNext: (value) => {
            this.state.data[key] = value;
            this.setState({ data: this.state.data });
          },
          onError: () => {},
          onCompleted: () => {}
        });
      }

      this.unsubscribe();
      this.subscriptions = newSubscriptions;
    }

    unsubscribe() {
      for (let key in this.subscriptions) {
        if (this.subscriptions.hasOwnProperty(key)) {
          this.subscriptions[key].dispose();
        }
      }

      this.subscriptions = {};
    }

    render() {
      return <ComposedComponent {...this.props} data={this.state.data} />;
    }
  };

  Enhancer.propTypes = ComposedComponent.propTypes;
  Enhancer.contextTypes = ComposedComponent.contextTypes;

  return Enhancer;
}

用法:

// can't put this on component but this is good enough for playing
function observe(props, context) {
  return {
    yourStuff: observeYourStuff(props)
  };
}

class YourComponent extends Component {
  render() {
    // Note: this.props.data, not this.data
    return <div>{this.props.data.yourStuff}</div>;
  }
}

export default polyfillObserve(YourComponent, observe);

所有136条评论

undefined可能是分配给data的最安全值,直到 observable 通过onNext提供其第一个值。 例如,在 Relay 中,我们为null (数据不存在)和undefined (尚未获取)赋予不同的含义,因此我们理想的默认数据值为undefined 。 另一种方法是提供一种新方法,例如getInitialData ,但我怀疑这是不必要的/矫枉过正。

这很有趣,但是从静态类型的角度来看,我对键/值系统不太满意,它们的类型几乎无法表达。
为什么不让observe返回单个 observable 并设置/合并解析为this.data的值:

class Foo {

  observe() {
    return (
      loadUser(this.props.userID)
        .map(user => { user })
  }

  render() {
    if (this.data.user.id !== this.props.userID) {
      // Ensure that we never show inconsistent userID / user.name combinations.
      return <Spinner />;
    }
    return <div>Hello, {this.data.user.name} [{this.props.userID}]!</div>;
  }

}

对于多次获取的情况,例如:

class Foo {

  observe() {
    return (
     combineLatest(
      loadUser(this.props.userID),
      loadSomethingElse(this.props.somethingElseId),
      (user, somethingElse) => ({ user, somethingElse})
     )
  }

  render() {
    ..
  }

}

这可能有点冗长,但它允许有很好的静态类型:

interface Comp<T> {
  observe(): Observable<T>;
  data: T;
}

此外,当 props/state 发生变化时,我们可以访问 'props' 'state' 作为 observable ,而不是重新执行observe

class Foo {

  observe(propsStream) {
    return (
      propsStream
        .flatMap(({ userID }) => loadUser(userId))
        .map(user => { user })
    );
  }

  render() {
    if (this.data.user.id !== this.props.userID) {
      // Ensure that we never show inconsistent userID / user.name combinations.
      return <Spinner />;
    }
    return <div>Hello, {this.data.user.name} [{this.props.userID}]!</div>;
  }
}

原因是因为我们不想要求使用组合器和理解 RxJS 才能订阅(多个)Observables。 以这种方式组合两个 Observable 非常令人困惑。 事实上,至少对于我们的数据源,我们可能会实现订阅 API,但甚至不包括 Observables 原型中的组合器。 这不是必需的,但如果需要,您可以自由使用组合器。

但是,要订阅一个简单的 Flux 商店,您不需要这样做。

我认为 Flow 可能能够使用约束来处理这种静态类型,但我会与这些人核实以确保。 我认为键入数据属性就足够了,然后可以隐含观察类型。

class Foo extends React.Component {
  data : { user : User, content : string };
  observe() /* implied as { user : Observable<User>, content : Observable<string> } */ {
  }
}

这种变化并不是要全部使用 Observables 作为描述应用程序状态的一种方式。 这可以在此之上实现,就像您之前所做的那样。 这显然与应用程序状态无关,因为此方法是幂等的。 该框架可以根据需要免费取消订阅和重新订阅。

抄送@ericvicenti

至少在打字稿的情况下,没有办法根据data的类型来限制observe的返回类型,至少直到像https://github.com/这样的东西

我很想将它用于下一个版本的 React DnD,但显然这需要等待 React 0.14。
我想知道我是否可以暂时使用一个在ref实例上设置this.data的高阶组件来“填充”它。不过可能太疯狂了。

是否有可能遵守 Promise? 然后可以在第一次渲染之前使用 Promises 树来解析整个组件树的数据! 这对于服务器端 React 非常有用。

使它成为一流的 API 有什么好处? 它基本上可以使用“高阶组件”来完成。

使它成为一流的 API 有什么好处? 它基本上可以使用“高阶组件”来完成。

包装 5 个 HOC 以获得 5 个订阅对于初学者来说有点笨拙且难以理解。 理解componentWillReceiveProps也很重要。 这解决了这两个问题。

我,一方面,欢迎我们新的可观察的霸主。

我想知道这是否有助于使https://github.com/chenglou/react-state-stream更接近 React 的香草 API

不是只需要一个 HOC 吗? 在您的 Medium 帖子中的示例中,您遍历stores并订阅每个。

stores.forEach(store =>
  store.addChangeListener(this.handleStoresChanged)
);

@aaronshaf 当然取决于用例。 有时是不同种类的状态资源,而不仅仅是“几家商店”。 但我不能代表 React 团队说,让我们听听@sebmarkbage 怎么说。

现在会喜欢某种 polyfill 来玩这个。 我还没有完全理解这个想法。 处理未来更新所涉及的机制是什么。 我会花更多的时间来理解它。 我认为它应该可以通过一个简单的 mixin 来实现。

@vjeux告诉我我应该

我并不是要宣传我自己的工作,但我认为这个钩子与React Nexus 中getNexusBindings钩子非常相似。 您可以通过生命周期挂钩(可以依赖于 props)在组件级别声明数据 deps。

API 如下所示:

class UserDetails {
  getNexusBindings(props) {
    return {
      // binding to data in the datacenter
      posts: [this.getNexus().remote, `users/${this.props.userId}/posts`],
      // binding to data in the local flux
      mySession: [this.getNexus().local, `session`],
    }
  }
}

componentDidMountcomponentWillReceiveProps期间应用/更新绑定。 在后一种情况下,下一个绑定与之前的绑定不同; 取消订阅删除的绑定,订阅添加的绑定。 Nexus Flux的实现中描述了底层的获取/更新机制。 基本上使用相同的 API,您可以订阅本地数据(传统的本地存储)或远程数据(使用 GET 获取并通过 Websockets/polyfill 接收补丁)。 您实际上可以从另一个窗口(使用 postWindow)或 WebWorker/ServiceWorker 订阅数据,但我仍然没有找到真正有用的用例。

长话短说,您使用 Flux 抽象在组件级别同步描述数据依赖,并且挂钩确保您的依赖项自动订阅、更新时注入和取消订阅。

但它也有一个很好的特性:利用完全相同的生命周期函数在服务器端渲染时执行数据预取。 基本上,从根开始并从那里递归,React Nexus 预取绑定,渲染组件,并继续处理后代,直到所有组件都被渲染。

@aaronshaf @gaearon成为

1) 它不会侵蚀 props 命名空间。 例如,高阶组件不需要从你的 props 对象中声明一个不能用于其他任何东西的名称,如data 。 链接多个高阶组件会不断占用更多名称,现在您必须找到一种方法来保持这些名称的唯一性。 如果您正在编写一些可能已经编写好的东西,而现在您遇到了名称冲突怎么办?

此外,我认为高阶组件的最佳实践应该是避免更改包装组件的合同。 即从概念上讲,它应该是与输出相同的道具。 否则,当消费者提供与收到的完全不同的一组道具时,使用和调试会令人困惑。

2) 我们不必使用state来存储最后一个值。 data概念类似于props ,因为它纯粹是一种记忆。 如果我们需要回收内存,我们可以随时将其丢弃。 例如,在无限滚动中,我们可能会自动清理不可见的子树。

@RickWong是的,支持 Promises 是相当简单的,因为它们是 Observables 的一个子集。 我们可能应该这样做以保持冷静。 但是,我仍然可能建议不要使用它们。 我发现它们不如 Observables 的原因如下:

A)它们不能被框架自动取消。 我们能做的最好的事情就是忽略迟到的解决方案。 与此同时,Promise 保留了潜在的昂贵资源。 很容易陷入 subscribe/cancel/subscribe/cancel... 长时间运行的计时器/网络请求的糟糕情况,如果您使用 Promises,它们不会从根节点取消,因此您必须等待资源完成或超时。 这可能不利于大型桌面页面(如 facebook.com)的性能或内存受限环境(如 react-native)中的延迟关键应用程序。

B)您将自己锁定为只获得一个值。 如果该数据随时间变化,您不能使您的视图无效,并且您最终会处于不一致的状态。 它不是反应性的。 但是,对于可能很好的单个服务器端渲染,在客户端上,理想情况下您应该以一种可以将新数据流式传输到 UI 并自动更新以避免过时数据的方式来设计它。

因此,我发现 Observable 是构建的高级 API,因为如果您需要,它不会锁定您来解决这些问题。

@elierotenberg感谢您的参与! 它确实看起来非常相似。 一样的好处。 你觉得我的提议有什么限制吗? 即 React Nexus 缺少一些东西,你不能在此基础上构建它吗? 如果我们不将自己锁定在重要的用例之外,那就太好了。 :)

从服务器渲染的角度来看,重要的是我们能够推迟最终的 renderToString,直到 Observable/Promise 被解析为可以异步获取的数据。 否则,我们仍然不得不在 React 之外进行所有异步数据获取,而不知道哪些组件将在页面上。

我相信 react-nexus 确实允许在继续渲染树之前在组件内进行异步加载。

是的,react-nexus 明确区分:
1) 绑定声明为getNexusBindings (这是一种同步的、无副作用的生命周期方法,类似于渲染 - 实际上它曾经被命名为 renderDependencies 但我认为它很混乱),
2) 将订阅/更新绑定为applyNexusBindings (它是同步的,并区分以前的 nexus 绑定以确定必须订阅哪些新绑定以及必须取消订阅哪些新绑定)
3) 将预取绑定为prefetchNexusBindings (这是异步的,并在“初始”(无论这意味着什么)值准备好时解析)

ReactNexus.prefetchApp(ReactElement)返回Promise(String html, Object serializableData) 。 这个钩子模仿 React 树的构造(使用instantiateReactComponent )并递归地构造/预取/渲染组件。 当整个组件树“准备好”时,它最终调用React.renderToString ,知道所有数据都准备好了(模数错误)。 一旦解决,这个 Promise 的值就可以被注入到服务器响应中。 在客户端,常规的React.render()生命周期照常工作。

如果有人想玩这种 API,我为observe做了一个非常愚蠢的 polyfill 作为高阶组件:

import React, { Component } from 'react';

export default function polyfillObserve(ComposedComponent, observe) {
  const Enhancer = class extends Component {
    constructor(props, context) {
      super(props, context);

      this.subscriptions = {};
      this.state = { data: {} };

      this.resubscribe(props, context);
    }

    componentWillReceiveProps(props, context) {
      this.resubscribe(props, context);
    }

    componentWillUnmount() {
      this.unsubscribe();
    }

    resubscribe(props, context) {
      const newObservables = observe(props, context);
      const newSubscriptions = {};

      for (let key in newObservables) {
        newSubscriptions[key] = newObservables[key].subscribe({
          onNext: (value) => {
            this.state.data[key] = value;
            this.setState({ data: this.state.data });
          },
          onError: () => {},
          onCompleted: () => {}
        });
      }

      this.unsubscribe();
      this.subscriptions = newSubscriptions;
    }

    unsubscribe() {
      for (let key in this.subscriptions) {
        if (this.subscriptions.hasOwnProperty(key)) {
          this.subscriptions[key].dispose();
        }
      }

      this.subscriptions = {};
    }

    render() {
      return <ComposedComponent {...this.props} data={this.state.data} />;
    }
  };

  Enhancer.propTypes = ComposedComponent.propTypes;
  Enhancer.contextTypes = ComposedComponent.contextTypes;

  return Enhancer;
}

用法:

// can't put this on component but this is good enough for playing
function observe(props, context) {
  return {
    yourStuff: observeYourStuff(props)
  };
}

class YourComponent extends Component {
  render() {
    // Note: this.props.data, not this.data
    return <div>{this.props.data.yourStuff}</div>;
  }
}

export default polyfillObserve(YourComponent, observe);

除了库实现之外,Observable 是一个具体的、商定的东西吗? 合约是什么,实现起来是否足够简单,不需要使用 bacon 或 Rxjs? 与用于侧载数据的一流 api 一样好,考虑到 React 向纯 js 的稳步发展,React 添加基于未指定/非常初始指定原语的 api 似乎很奇怪。 这样的事情会将我们与特定的用户土地实现联系起来吗?

顺便说一句,为什么不是 Streams? 我没有马参加比赛,但老实说我很想知道; 已经在网络流上完成了工作,当然还有节点

另外两个要考虑: https : https://github.com/caolan/highland

@jquense关于将 Observable 添加到 ECMAScript 7(+) 的提案正在积极开展工作,因此理想情况下这将成为纯 JS。 https://github.com/jhusain/asyncgenerator (目前已过时。)

我们不会依赖 RxJS。 无需使用 RxJS 就可以轻松实现 API。 RxJS 是最接近活跃的 ECMAScript 提案的。

most.js 似乎也可行。

Bacon.js 的 API 似乎很难在不依赖 Bacon 的情况下使用,因为使用了Bacon.Event类型来分隔值。

Stream API 太高级了,与这个用例相去甚远。

是否有某种“在渲染前等待”选项?我的意思是在客户端上没有必要在渲染之前等待所有 Observable,但在服务器上你希望等待它们解决,以便每个组件的 render()完整的,而不是部分的。

[parent] await observe(). full render(). -> [foreach child] await observe(). full render().

在我所有的探索中,我发现是服务器端 React 中最重要的生命周期钩子。

在这个讨论之后,我试图在下面的帖子中总结 React Nexus 所做的事情:

使用 React Nexus 正确完成同构应用程序

下面是核心预取例程的示意图:

React Nexus

我们不会依赖 RxJS。 无需使用 RxJS 就可以轻松实现 API。 RxJS 是最接近活跃的 ECMAScript 提案的。

:+1: 这是我最关心的问题,想想说,除非你知道自己在做什么,否则实现你自己的承诺是非常令人担忧的。 我认为否则你最终会对生态系统中的特定库产生隐含的要求。 切线......来自promise世界的一个好东西是A +测试套件,所以即使跨库也至少可以保证.then的通用功能,这有助于promise interop之前标准化。

这是我最关心的问题,想想说,除非你知道自己在做什么,否则实现你自己的承诺是非常令人担忧的。 我认为否则你最终会对生态系统中的特定库产生隐含的要求。

完全同意。 值得庆幸的是,observables 有一个非常简单的契约,甚至没有像then这样的内置方法,所以在某种程度上它们比 Promise 更简单。

如果委员会坚持调用next安排像 Promises 这样的微任务,它们可能会变得更复杂(并且更慢)。

这会打扰一些很多模式是基于 onNext 在 RxJS 中是同步的事实:/

我认为常见的 Flux 存储模式可能是在每个键的基础上保留一个可观察对象的 Map,以便它们可以被重用。 然后在每个人都退订时清理它们。

这样,您可以执行以下操作: MyStore.get(this.props.someID)并始终返回相同的 Observable。

这样,您可以执行以下操作: MyStore.get(this.props.someID) 并始终返回相同的 Observable。

使用this.props.key (我知道了)有意义吗? 在大多数情况下,您已经使用<... key={child.id} .. />传递了这样的唯一标识符。

这样,您可以执行以下操作: MyStore.get(this.props.someID) 并始终返回相同的 Observable。

这也是我用于 React Nexus 的模式。 Store#observe 返回一个记忆化的、不可变的观察者; 当所有订阅者都离开至少一个滴答声时,它被清理(包括相关的后端特定清理机制,例如发送实际的“取消订阅”消息或其他)。

@sebmarkbage @gaearon如何在 v0.14 中观察服务器上的工作?
它是否能够正确地等待所有观察者解决,然后再渲染为类似于 react-nexus 的字符串(但内置于反应)?

IMO 如果组件在“准备好”在服务器上渲染之前等待第一个观察到的值,那就太好了。

@gaearon :IMO 如果组件在“准备好”在服务器上渲染之前等待第一个观察到的值,那就太好了。

是的,:+1: 用于异步渲染。 与此同时, @andreypopp 的react-async是一种替代方案,但它需要fibers才能“破解”React。 如果 React 可以开箱即用地支持异步渲染,那就太好了。

异步渲染是我们想要支持的东西,但不是这个问题的一部分。 大号

不幸的是,可能不会在 0.14 中实现。 需要考虑和重构许多不同的设计。

随意创建和发布描述实现这一目标所需的内部架构更改。

我和@gaearon re: react-streaming-state有同样的想法。 考虑到除侧载之外的所有潜在应用程序,是否有比data更好的名称? 例如, observed会更清楚地将其与方法相关联。

并不是要让自行车脱轨,而是想把它扔出去。

等不及 React 中的 observables。 据我了解,这应该使 React 具有反应性

在重写react-async时,我正在尝试类似的想法,请参阅README

显着的区别是我引入了显式的可观察/进程身份来协调进程,类似于 React 使用key prop 和有状态组件的方式。

当命名流程的id发生更改时,React Async 会停止旧流程实例并启动一个新流程实例。

API 如下所示:

import React from 'react';
import Async from 'react-async';

function defineFetchProcess(url) {
  return {
    id: url,
    start() {
      return fetch(url)
    }
  }
}

function MyComponentProcesses(props) {
  return {
    user: defineFetchProcess(`/api/user?user${props.userID}`)
  }
}

@Async(MyComponentProcesses)
class MyComponent extends React.Component {

  render() {
    let {user} = this.props
    ...
  }
}

进程 API 现在在语法和名称上遵循 ES6 Promises API,但在语义上,不希望每个进程只调用一次process.then(onNext, onError) 。 它旨在适应最流行的(?)通过承诺获取数据的用例。 但老实说,现在我认为我需要更改它以防止混淆。

我可能弄错了,但我认为唯一阻止在用户空间中实现提议的(在这个问题中)API 是缺少生命周期钩子,它在渲染之前执行,如componentWillUpdate但使用新的props并且state已经安装在实例上。

尚未讨论的一件事是onError回调的处理。 如果 observable 产生错误,那么该信息应该以某种方式可供组件使用。 因为 React 处理实际的subscribe(callbacks)调用,我们需要一个标准化的方法将它注入到回调对象中。

为了为应用程序开发人员提供最大的灵活性,我看到了两种方法。 第一个是将错误放在顶级属性上,类似于this.data 。 这似乎令人难以置信的沉重,并进一步吞噬了组件的命名空间。
第二个将允许开发人员将自己的onError回调定义为生命周期函数。 如果我想对我的 observables 进行自定义错误处理,我可以添加类似

onObserveError(key, error) {
  // do something with the error
  this.state.errors[key] = error;
  this.setState({ errors: this.state.errors });
}

这类似于我们在即将到来的 Parse+React 迭代中所做的。 我们创建了自己的错误处理来生成我们想要的 API。 错误被添加到私有 { name => error } 映射中,并且组件具有顶级公共方法queryErrors() ,如果它不为空,则返回映射的克隆,并且null否则(允许一个简单的if (this.queryErrors())

当然,定义新​​的保留方法也是一件棘手的事情; 我不会假装不是。 但是我们需要一种方法在渲染时隐式或显式地使组件可以使用错误。

@andrewimm这个想法是将其输入到一个通用的错误传播系统中,该系统会在层次结构中冒泡错误,直到它被错误边界处理。 https://github.com/facebook/react/issues/2928

这也应该处理方法抛出和优雅恢复中的错误。 就像render()方法抛出一样。 这是更多的工作,需要一些时间才能正确实施,但想法是以这种方式统一错误处理。

我认为这应该被排除在适当的反应之外,并且应该与 react-async 和 react-nexus 等项目协调 1 或 2 个关键集成点,以便可以在适当的 React 之上干净地完成......

我同意,似乎有一个建议的方法来做到这一点比
将其烘焙到框架本身

2015 年 4 月 21 日星期二晚上 11:38 Rodolfo Hansen [email protected]
写道:

我认为这应该被排除在适当的反应之外,并且 1 或 2 键
集成点应该与 react-async 和
react-nexus 所以这可以在 React 适当的基础上干净地完成......


直接回复此邮件或在 GitHub 上查看
https://github.com/facebook/react/issues/3398#issuecomment -95048028。

周末,我构建了另一个 Flux 实现,称为Flexy 。 在此,查看商店的代码。 它公开了一个符合 observable API 的.getObservable方法,即使它确实没有使用 observables 或其他 Reactive 框架。

所以,我想说 API 很容易用实际的 Observables 创建。

也就是说,不要对代码做出苛刻的判断,它是在一个周末完成的:

  • 乐趣
  • 理解
  • 使用 js-csp
  • 使用观察 API

旁注,像这样的系统和 Flux 实际上使服务器端渲染不那么痛苦。 使用与 React-Nexus 类似的系统,我们可以初始化存储并将它们传递给 React 应用程序。 然后,我们可以监视存储和调度程序并继续重新渲染,直到不再触发任何操作(所有必需的数据都已经在存储中)。

我认为这是获得无状态数据订阅新语义的最小集成点。 您会建议哪些其他集成点? 不包括异步渲染,这是一个更复杂的问题,值得拥有自己的线程,无论如何,这个钩子都可以与异步渲染一起使用。

其他一切都可以在每个组件的基础上在 React 之上实现。 请注意,我们不会对插件进行全局注入,因为这会破坏跨环境的组件重用,因此构建在 React 之上的框架应该与各个组件相关联。

我们缺少什么钩子?

嘿,

老实说,虽然新的钩子会让实现变得更容易,但如果没有它们,我们当然可以实现横向数据加载,正如我们用react-asyncreact-nexus演示的那样。

如果有的话,在已安装的 React 层次结构之外公开和支持维护 React 组件实例的生命周期会有所帮助。 在react-nexus我使用内部instanciateReactComponent并自己调用componentWillMountcomponentWillUnmount等,我认为这种方法很脆弱(如果instanciateReactComponents依赖于在下一个版本的 React 上改变的内部不变量?)。

至于无状态方法,在我看来,获取_is_ 有状态的异步数据,从而在某些组件的状态中存储挂起/完成/失败状态是相关的。 当我们在现实世界的应用程序中使用react-nexus时,我们有高阶组件来执行数据获取并将它们的获取状态作为 props 注入到它们的子组件中。 因此,内部组件是“无状态的”(这是理想的),而外部组件是“有状态的”(这也是理想的,例如,显示加载微调器或占位符)。

您会建议哪些其他集成点?

在我看来, @ANDREYPOPP提出了正确的问题。 我们需要在渲染前的生命周期钩子中实现这一点吗? 这似乎是所需的最小 API 更改,其余的是在您根据任何输入流/发射器/可观察的情况更改data适当地设置和触发 forceUpdate。 除非我错过了其他特别的东西(完全有可能)?

我不会在这里卷入更大的讨论。
但是要回答@sebmarkbage ,我认为我想要的最重要的钩子之一是仅在不使用真正的可观察对象时才需要的东西。

  • 一个钩子,可以提供可以处理由可观察的值推送的函数_before_它们被设置为数据。 对于真正的 observables,这只是.map

如果情况稍微开放一点,我认为应该有钩子来用自定义钩子替换 Observable 特定的行为。 这样我们就可以使用事件发射器或 CSP 通道。

在我看来,最后几条评论说最小的扩展点实际上是“连接”和“断开”生命周期挂钩 - 这使得将异步数据附加到组件并管理这些订阅变得容易?

Observables 然后可以相当简单地建立在此之上(在核心内部或外​​部) - 但是暴露这些生命周期点具有更广泛的吸引力?

这是一个合理的总结吗?

我还不相信这需要在 React 本身中解决。 我更倾向于认为 React 应该提供任何必要的钩子来在 React 之上构建这个功能。

但是暂时,假设我们要使用observe() 。 一些想法:

我们有this.propsthis.statethis.context和现在的this.data ,它们都是render()中新数据的潜在来源。 这对我来说似乎太过分了。 是将应用程序状态与组件状态分开的想法吗? 这可能会澄清和分离围绕状态的一些问题,但我觉得引入新输入的成本可能不会超过收益。 如果我们希望this.state只关注组件状态,为什么不让this.data的字段添加到this.propsthis.context呢?

this.data这个名字太笼统了。 道具是数据,状态是数据,任何局部变量都是数据。 这个名字没有增加任何意义并且混淆了现有的意义。 我更喜欢this.observed或任何其他真正有意义的名称。 所以 +1 @matthewwithanm的评论:

可能有比data更好的名字吗? 例如, observed会更清楚地将其与方法相关联。

如果我们让observe()在服务器上执行,我们需要某种钩子来清除这可能导致的任何内存泄漏,因为卸载永远不会发生。

如果我们每次propsstate更改(和context ?)时再次调用observe() ,那么observe必须针对性能进行优化,理想情况下不能一不小心就变贵了。 它成为热路径的一部分。 我喜欢 React Nexus 的这个:

下一个绑定与以前的绑定不同; 取消订阅删除的绑定,订阅添加的绑定。

我开始相信状态会使组件复杂化,我一直试图在我自己的组件中减少它的使用,而是将其提升。 这就是为什么除了@fisherwebdev提出的担忧(我现在同意)之外,我不相信让observe依赖于state是一个好主意。 state 取决于 props,observed 取决于 state _and_ props.. 是不是太复杂了? 我宁愿有observe(props)

我也开始意识到观察不应该依赖于state ,主要是因为它不需要。 正如@gaearon指出的那样,将状态提升到上一层很容易,在分离关注点后感觉更干净。 当observe可能依赖于state ,组件内处理更新的逻辑会变得更加复杂; 当它仅依赖于props ,您可以在componentDidMount / componentWillReceiveProps使用无叉处理程序。 关键路径中更少的代码转化为更简单的渲染周期,并且它还减少了重新触发订阅的意外更新的数量。

+1 观察(道具)

我们处理的状态越少,IMO 就越好。

我认为,作为一个库,React 应该尽可能地灵活。 我同意观察依赖于状态不是一个好主意。 是的,它可能很复杂。 是的,最佳实践应该是不依赖于状态。
但这是应该允许 React 用户做出的选择。
我建议保持当前 API 不变(除了任何可能的钩子以增加更多灵活性,使其不仅仅适用于可观察对象),并证明文档说明不建议在观察方法中使用状态。

我相信每个人都想做正确的事,我们希望被指向正确的方向。 观察不接受状态比在文档中意外发现“这是一种反模式”之类的内容对用户来说更容易。

编辑:抱歉,刚刚意识到我在寻找flatMap 。 我很困惑,因为我认为它会使消息数组变平,但它在更高级别上运行(消息可观察)。

提议的 API 会处理一个数据字段的结果依赖于另一个数据字段的结果的情况吗? 即你映射第一个结果并返回一个可观察的:

observe(props, context) {
  if (!props.params.threadID) {
    return {};
  }

  const observeThread = ThreadStore.observeGetByID(
    {id: props.params.threadID}
  );
  return {
    thread: observeThread,
    messages: observeThread.map(thread => {
      return MessageStore.observeGetByIDs({ids: thread.messageIDs});
    })
  };
}

一般来说,我对 observables 还是很陌生,所以我可能完全错了。 在 promise-land 中,这非常简单,因为从then返回一个 promise 将导致后续的then s 基于该 promise。

我不明白它不应该依赖于this.state 。 封装状态确实使 React 变得更加复杂,但仅此而已。 如果没有封装状态,我们只需要一个记忆的立即模式库。 如果你全神贯注于“Stores”,那么是的,你不需要状态,但这不是 React 规定的。

我们有几种模式需要您创建额外的包装器,但对于正常使用,您不需要这样做。 除了商店,你观察到的数据总是依赖于状态,即使是间接的。 我认为在观察中依赖它根本不是坏习惯。 例如

observe() {
  return { items: Items.getPagedItems({ pageIndex: this.state.currentPage }) };
}

即使observe不依赖于state它仍然依赖于propscontext 。 即使它不依赖于context你仍然会有一个间接使用context来渲染在observe中使用它的组件的道具。

observe需要在每次渲染过程结束时重新评估,因为道具可能已经改变。 但是,如果返回相同的 observable,我们肯定会区分结果的 observable,而不是取消订阅/重新订阅。 但是,我们不能对单个属性进行差异化(除了 shouldComponentUpdate 之外),因此理想情况下,您应该使用Map作为强大功能来实现自己的缓存。 这样,您可以将相同的 observable 返回给树中的多个组件。 例如,多个组件加载同一个用户。 即使您不这样做,您也只需重新创建 Observable 并最终到达底部缓存。 无论如何,这就是 React 协调工作的方式。 它不是那么慢。

这个observe钩子并不是为了让 React 完全响应式的,因为状态是在 Observables 中捕获的。 当前的一个关键设计目标是避免在闭包和组合器中捕获状态,而是拥有一个可以冻结和恢复的漂亮干净的独立状态树,并可能在工作人员之间共享。

这让我想到了最后一点……

我们当然不需要_将它添加到核心库中。 最初的“公共”接口是 mountComponent/receiveComponent,您可以在它之上构建整个复合组件系统。 然而,没有多少人使用提高抽象条更强大的功能,因为我们现在可以构建由更高抽象条启用的其他东西。 例如组件范围的优化。

React 的主要目的是在组件之间创建契约,以便生态系统中的不同抽象可以共存。 该角色的一个重要部分是提高通用概念的抽象级别,以便我们可以启用新的跨组件功能。 例如保存子树的所有状态,然后恢复子树。 或者可能包括在服务器上自动卸载或更改服务器上协调的时间方面。

提供一些包括在内的电池以使所有这些都变得可口和均匀也很重要。

重要的是要意识到微模块化(比如添加一个新的生命周期钩子)严格来说并不是完全胜过将其构建到框架中。 这也意味着不再可能对系统范围的抽象进行推理。

我希望像这样的东西在文档中作为“哲学/设计目标/非目标”。

React 的主要目的是在组件之间创建契约,以便生态系统中的不同抽象可以共存。 该角色的一个重要部分是提高通用概念的抽象级别,以便我们可以启用新的跨组件功能。

喜欢这个。 我同意@gaearon的观点,如果这是在某种文档中会很好。

我们当然不需要将它添加到核心库中......但是,没有多少人使用它来提高抽象条更强大,因为我们现在可以构建由更高抽象条启用的其他东西。 例如组件范围的优化。

我觉得沉默(至少对我来说)不是添加另一个 API,而是添加一个依赖于非语言(仍在定义)构造工作的 API。 这完全可以解决问题,但我担心 Promise 库所面临的问题,即没有 Promise 库(甚至是规范投诉库)可以相互信任,这会导致不必要的包装和防御工作以确保它们正确解决,这会限制优化机会. 或者更糟糕的是,你会像 jQuery 一样被卡住,实现永远无法改变。

@jquense我完全同意。 我很久以前就想添加这个钩子。 (原实验:https://github.com/reactjs/react-page/commit/082a049d2a13b14199a13394dfb1cb8362c0768a)

两年前的犹豫是离标准化还太远。 在我们将其添加为核心之前,我想要一个标准协议。

我认为我们已经到了这样一个地步,许多框架都同意需要像 Observables 这样的东西,而标准化正在达到提出可口 API 的地步。 我确信我们最终需要对其进行一些调整,但只要高级架构有效,它就应该是可交换的并最终收敛。

我认为 Promises 发生的事情是 API 和调试故事在 Observables 不受影响的某些领域严重缺乏。 这是一个开箱即用的更完整的故事,因为 Promises 必须标准化一个最小的不完整解决方案。

意见的唯一区别是:我观察到的 Observables(无法抗拒,抱歉)是 Zalgo 潜力。 Observables 是否可以同步推送值以响应订阅。 有些人似乎反对它,但据我了解,React 对 Observables 的使用将取决于此。 你能对此发表评论吗?

一般来说,我不认为 Zalgo 是 Observables 的问题,因为消费者始终处于控制之中,并且可能会选择使用observeOn之类的始终异步。

很高兴终于在这方面达成了一些共识。 我个人更喜欢通道而不是 Observables,但如果 Observables 将被添加到语言中,我同意不再需要等待。

也就是说,让我们确保我们保持 API 足够开放,以便与符合基本 API 的非 Observable 一起工作。

我也不认为 Zalgo 是个问题。 但是,使用 Observables,您可以根据需要使用调度程序来确保异步,默认调度程序现在是异步的,因此您可以根据需要使用它。

@sebmarkbage我认为您已经解决了我的大部分问题,我现在看到了将其添加到框架中的好处。 但是,您能否评论一下this.data - (1) 我们可以/应该将这些字段折叠到 props/context/state 中还是 (2) 我们可以重命名它吗?

有点像自行车棚,但无论如何都要去做……Zalgo 问题并不是关于它在 API 期望方面是否重要的​​问题,它是一个可观察的互操作和易于实现的问题。 没有早日就 Zalgo 达成一致,这使 Promise 库世界处于令人讨厌的境地,在处理来自其他库的 Promise 时必须超级防御。 (我的上述观点在下面重复)

...没有承诺库(甚至是规范投诉库)可以相互信任,这会导致不必要的包装和防御工作,以确保它们正确解决

因为早期的承诺并不都符合异步解析,所以我们现在处于这样一个位置,即使它是指定的库也不能假设thenables是值得信赖的,从而扼杀了潜在的优化。 这让我觉得在这里特别重要,React 不会提供 Observable 实现来使用(谁会想要那个?),而且我们很可能需要几年才能完全依赖浏览器提供的 Observable,如此简单的库互操作很重要。 再加上@gaearon的观点,如果 React 依赖于同步调用,并且它被指定为始终是异步的,这会使我们陷入类似 jquery 的位置,从而陷入流氓实现的困境。

我完全同意。 我很久以前就想添加这个钩子。 两年前的犹豫是离标准化还太远。 在我们将其添加为核心之前,我想要一个标准协议。

我很高兴它也被参加并考虑过,这当然令人欣慰。 :) 总的来说,我认为早期采用 Promise 值得我在这里讨论的缺点,所以不要把我的担心视为不喜欢或不赞成,我对一流 API 的前景感到非常兴奋而且我还看到 Observable 在这里真的是一个很好/最合理的选择。

“我们应该使用 Observable 的 RxJS 合约,因为它更常用并允许同步执行,但一旦@jhusain的提议更常用,我们将改用该合约。”

只是为了增加一点上下文。 有一个 Reactive Streams 倡议 (http://www.reactive-streams.org/) 为具有非阻塞背压的异步流处理提供标准。 这包括针对运行时环境(JVM 和 JavaScript)以及网络协议的努力。

当前领先的实现是 fe Akka Streams 或 RxJava。 我不知道 RxJs 现在是否已经符合相同的接口,订阅者的当前接口是 onSubscribe(Subscription s), onNext(T t), onCompleted(), onError(Throwable t)。

您能否进一步了解@jhusain的建议是什么?

我不知道 React 是否应该严格遵守这一倡议,因为如果我需要,我可能会在两者之间放置 RxJs(假设它会遵守)并适应 React 接口并让更高级的概念,如对 RxJs 的背压(尽管我会宁愿不必适应太多)。

对这项倡议有什么立场或目标吗?

@vladap我相信是@jhusain 提到的提议

我已经阅读了@jhusain ,但我不确定将来可能转向此规范的动机。 有什么特别的优势吗?

Reactive-streams 规范有更大的支持,并且已经在1.0 版本上。 因为 RxJava 已经实现了这个规范,所以我假设 RxJs 将遵循(但尚未检查)。

本博客通过一些使用 Akka 流的示例来总结接口。

我可以看到在后端和前端拥有相同接口的一些优势,主要是因为我在两者上都工作。 可能有助于后端和前端组之间的合作,但另一方面,我假设 websocket 或 sse 是流的实际集成点。

我现在在www.reactive-streams.org上找不到实施者列表,但我最后一次检查它是:

Björn Antonsson – Typesafe Inc.
Gavin Bierman – 甲骨文公司
Jon Brisbin – Pivotal Software Inc.
乔治坎贝尔 – Netflix, Inc
本·克里斯滕森 – Netflix, Inc
Mathias Doenitz – spray.io
Marius Eriksen – Twitter Inc.
Tim Fox – 红帽公司
Viktor Klang – Typesafe Inc.
Roland Kuhn 博士 – Typesafe Inc.
Doug Lea – 纽约州立大学奥斯威戈分校
Stephane Maldini – Pivotal Software Inc.
Norman Maurer – 红帽公司
Erik Meijer – Applied Duality Inc.
Todd Montgomery – Kaazing Corp.
Patrik Nordwall – Typesafe Inc.
约翰内斯·鲁道夫 – spray.io
Endre Varga – Typesafe Inc.

也许我在这里走得太远了,但我相信更大的背景可以帮助未来的决定。

@vladap根据我的理解和我在 github 上看到的问题, @jhusain已经在与他们合作,所以我想我们不会有这么多问题。
从接口的角度来看,从我也可以在不同的 github 问题和其他规范文档中掌握的内容来看,观察者肯定会尊重生成器接口,例如:

{
  next(value),
  throw(e),
  return(v)
}

简单地使用一个尊重该接口的“订阅”方法实现一个非常基本的可观察对象,对于反应来说应该是安全的。

它看起来只是具有相同功能的不同命名,从这个意义上说它很好。 我可能更喜欢与规范中相同的命名,但最后我并不关心这些方法是否相同。

我无法评估与 onSubscribe() 等效的缺失值。 在我提到的博客中,作者说这是控制背压的关键。 我不知道它还有其他用例。 据此,我假设 React 不关心控制背压,或者有另一种策略。 这是一件复杂的事情,因此我理解这不是 React 问题。

我是否正确理解该策略是大意的——要么应用程序很复杂,可能会出现背压问题,然后在两者之间使用一些东西来解决它,比如 RxJS,或者你将 React 组件直接连接到 fe websocket 并且你不没有背压问题,因为该应用程序简单且更新缓慢。

我在哪里可以找到为未来的 ECMAScript 提出的可观察接口? 如果已经有的话。

当前的提案可以在这里找到:

https://github.com/jhusain/asyncgenerator

JH

2015 年 5 月 7 日凌晨 2:32,vladap [email protected]写道:

我在哪里可以找到为未来的 ECMAScript 提出的可观察接口? 如果已经有的话。


直接回复此邮件或在 GitHub 上查看。

Reactive Streams 提案 (RSP) 比 TC-39 提案更进一步,因为它引入了一个处理背压的 Observable。 RSP Observable 已针对通过网络高效发送流进行了优化,同时尊重背压。 它部分基于在 RxJava 中完成的工作,这是一项非常令人印象深刻的工程(完全披露:它是由 Netflix 的同事 Ben Christensen 设计的)。

决定标准化更原始的 Observable 类型的主要原因是谨慎。 更原始的 Observable 是 ES2015 Iterable 合约的对偶,它为我们提供了有价值的保证,即该类型至少与 ES2015 中已经标准化的类型一样灵活。 此外,在 JS 中有很多不需要背压的 Observables 用例。 在浏览器中,DOM 是推送流最常见的接收器,并且有效地充当缓冲区。 鉴于 RSP 类型更复杂,我们的方法是先标准化更原始的类型,然后留出空间稍后实现更高级的类型。 理想情况下,我们会等到它在用户领域得到验证。

仅供参考,RxJS 目前没有实施 RSP Observable 的计划。

JH

2015 年 5 月 7 日凌晨 2:30,vladap [email protected]写道:

它看起来只是具有相同功能的不同命名,从这个意义上说它很好。 我可能更喜欢与规范中相同的命名,但最后我并不关心这些方法是否相同。

我无法评估与 onSubscribe() 等效的缺失值。 在我提到的博客中,作者说这是控制背压的关键。 我不知道它还有其他用例。 据此,我假设 React 不关心控制背压,或者有另一种策略。 这是一件复杂的事情,因此我理解这不是 React 问题。

我是否正确理解该策略是大意的——要么应用程序很复杂,可能会出现背压问题,然后在两者之间使用一些东西来解决它,比如 RxJS,或者你将 React 组件直接连接到 fe websocket 并且你不没有背压问题,因为该应用程序简单且更新缓慢。


直接回复此邮件或在 GitHub 上查看。

非常感谢您提供的宝贵细节。 这很有道理。

@gaearon我有点抄袭了你。 我想将 ParseReact 与 ES6 类一起使用,因此我需要将 mixin 的观察 api 重新实现为高阶组件。

https://gist.github.com/amccloud/d60aa92797b932f72649 (用法如下)

  • 我允许在组件上定义观察或传递给装饰器。 (我可能会合并两者)
  • 我将 observables 合并为道具(没有 this.data 或 this.props.data)

@aaronshaf @gaearon成为头等舱的好处是:

1) 它不会侵蚀 props 命名空间。 例如,高阶组件不需要从你的 props 对象中声明一个不能用于其他任何东西的名称。 链接多个高阶组件会不断占用更多名称,现在您必须找到一种方法来保持这些名称的唯一性。 如果您正在编写一些可能已经编写好的东西,而现在您遇到了名称冲突怎么办?

此外,我认为高阶组件的最佳实践应该是避免更改包装组件的合同。 即从概念上讲,它应该是与输出相同的道具。 否则,当消费者提供与收到的完全不同的一组道具时,使用和调试会令人困惑。

它可以是常规组件,而不是 HOC:

import Observe from 'react/addons/Observe';

class Foo {
  render() {
    return (
      <Observe
        render={this.renderData}
        resources={{
          myContent: xhr(this.props.url)
        }} />
    );
  }

  renderData({ myContent }) {
    if (myContent === null) return <div>Loading...</div>;
    return <div>{myContent}</div>;
  }
}

因为您控制了作为render属性传递的函数,所以名称不会发生冲突。 另一方面,它不会污染所有者的状态。

我在这里想念什么?

如果Observe组件只是从它的 props 中抓取 Observables,这甚至可以不那么冗长:

render() {
  return (
    <Observe myContent={Observable.fetch(this.props.url)}
             render={this.renderData} />
  );
}

renderData({ myContent }) {
  if (myContent === null) return <div>Loading...</div>;
  return <div>{myContent}</div>;
}

这样做的另一个好处是,如果shouldComponentUpdate返回 false,它将避免重新订阅,因为我们根本不会进入render

最后,可以为render编写一个装饰器,将其包装成一个Observe组件:

@observe(function (props, state, context) {
  myContent: Observable.fetch(props.url)
})
render({ myContent }) {
  if (myContent === null) return <div>Loading...</div>;
  return <div>{myContent}</div>;
}

我宁愿将render保留为纯渲染函数,而不是在其中注入数据获取逻辑。
在我看来,最初的提议似乎不错。 状态与rx-react的工作方式非常接近,它将允许将状态管理与看起来非常连贯的数据获取逻辑分开。

唯一困扰我的是使用 map of observable 而不是一个 observable,因为它不允许用户选择 observable 的组成方式,但这是一个小问题。

它并没有真正注入数据获取逻辑,只是节省了一些输入。 它将脱糖到上面的版本,它只呈现<Observe />组件。 渲染有状态的组件并不罕见,所以我认为它不会让render比现在更纯净。

我试图将#3858 中最好的和这个提案结合起来。

在任何 HOC 方法中,优点是明确性,但缺点由 @sebmarkbage 描述:道具名称可能在某些时候发生冲突。

在当前的提案中,好处是明确性,但不利的一面是更复杂的生命周期和更大的核心组件 API 表面。

在 #3858 中,好处是将“memoized render”依赖项与渲染本身放在一起(它们没有在其他任何地方使用,所以这是有道理的),但我担心“看起来同步但异步”不了解它是如何实现

在我的建议中,我保持了托管和明确性,但是:

  • <Observe /> (或用<Observe />包装渲染的observe()装饰器)只是一个附加组件,我不建议对 React 核心进行任何_更改。
  • 每个人都可以为他们的用例实现自己的观察逻辑。 你可以拥有自己的<Observe /> ,它只使用一个 observable,如果你喜欢的话。 您可能会也可能不会使用装饰器。
  • 组件生命周期保持不变。
  • 没有 prop 冲突,因为数据是作为参数传递的。
  • 我们正在通过使用我们拥有的工具解决问题来进行测试。
  • 为了减少样板,我们使用样板减少工具(装饰器)而不是引入新的核心概念。

在这里进行了很好的讨论和工作,非常尊重。 :)

我同意这不值得成为核心。 也许一个附加组件给了这个提议足够的牵引力,人们可以在更充分地承诺之前尝试收敛和标准化它。 话虽如此,我发现这个提议比https://github.com/facebook/react/issues/3858https://github.com/facebook/react/pull/3920更好,因为它的极简主义。

这是我在辅助项目中一直使用的东西(所以是盐粒)——它类似于@elierotenberg 的出色工作,但不会接管生命周期,因为这个应用程序不是 100% 使用 React 并且必须互操作。

为 CoffeeScript 和 mixins 做好准备,或者如果你愿意,可以一直眯着眼睛直到它看起来像 ES6。 :)

_ = require 'lodash'

module.exports = DeclareNeedsMixin = 
  componentDidMount: ->
    <strong i="12">@needsConsumerId</strong> = _.uniqueId @constructor.displayName
    <strong i="13">@sinkNeeds</strong> <strong i="14">@props</strong>, <strong i="15">@state</strong>

  componentWillUpdate: (nextProps, nextState) ->
    <strong i="16">@sinkNeeds</strong> nextProps, nextState

  componentWillUnmount: ->
    @props.flux.declareNeeds <strong i="17">@needsConsumerId</strong>, []

  sinkNeeds: (props, state) ->
    if not @declareNeeds?
      return console.warn 'Missing method required for DeclareNeedsMixin: `declareNeeds`', @

    needs = <strong i="18">@declareNeeds</strong> props, state
    props.flux.declareNeeds <strong i="19">@needsConsumerId</strong>, needs

  # Intended to be overridden by the host class.
  # Returns a set of facts, stored as an array.
  # Yes, immutable data is awesome, that's not the point here though. :)
  # Facts are serializable data, just values.
  # declareNeeds: (props, state) ->
  #   []

并像这样使用:

module.exports = EmailThreads = React.createClass
  displayName: 'EmailThreads'
  mixins: [DeclareNeedsMixin]

  propTypes:
    flux: PropTypes.flux.isRequired

  declareNeeds: (props, state) ->
    [Needs.GmailData.myThreads({ messages: 20 })]

  ...

所以declareNeeds是 props 和 state 的单向函数来描述这个组件需要什么。 在这里的实际实现中, @props.flux.declareNeeds的接收端设置在顶层组件中,将这些需求下沉到ProcessSink对象中。 它随心所欲地进行批处理,在共享相同flux组件之间对needs进行重复数据删除,然后执行副作用来满足这些需求(例如连接到套接字或发出 HTTP 请求)。 当不再需要组件时,它使用引用计数来清理有状态的事物,例如套接字连接。

数据从诸如套接字事件和请求之类的状态位流到调度程序(然后到 Store 或其他任何地方)并返回到组件以满足他们的需求。 这不是同步的,因此当数据尚不可用时,所有组件都会处理渲染。

我在这里分享这个只是作为另一种声音,即在用户空间中探索这些解决方案是可能的,并且当前的 API 非常好地服务于这种实验。 就核心可以采取的最小步骤来支持不同方法之间的互操作而言,我认为@elierotenberg做到了:

如果有的话,在已安装的 React 层次结构之外公开和支持维护 React 组件实例的生命周期会有所帮助。

至于无状态方法,在我看来,异步数据获取是有状态的,因此在某些组件的状态中存储挂起/完成/失败状态是相关的。

@elierotenberg@andrewimm对一流的错误处理提出了很好的观点。 @sebmarkbage我认为您对最小互操作点的直觉是正确的,但是我不确定如何为错误添加语义以使组件树冒泡是否符合该要求。 这里需要一个同样简单的故事来说明如何从onErroronCompleted访问值,即使只是this.observed中的插槽保存了next/error/completed # 的最后一个值{ next: "foo" } 。 在不支持将可观察合同作为一流功能的情况下,我对这个提议是否能被淘汰持怀疑态度。

因为这是互联网,也是我第一次在这里插话:React 问题提要是一些最好的阅读材料,也是伟大工作和想法的全方位来源。 :+1:

闻起来像我的束缚。

我不确定这与当前的提议/实现有何关系,但我发现启用组合和高阶操作实际上是数据依赖跟踪的一个关键特性,特别是如果您使用反应式数据源(通量、通量通过网络或任何其他方式为您提供更新)。

react-nexus@^3.4.0为例:

// the result from this query...
@component({
  users: ['remote://users', {}]
})
// is injected here...
@component(({ users }) =>
  users.mapEntries(([userId, user]) =>
    [`user:${userId}`, [`remote://users/${userId}/profile`, {}]]
  ).toObject()
))
class Users extends React.Component {
  // ... this component will receive all the users,
  // and their updates.
}

总而言之,我想知道组件 API 中是否应该有数据绑定。 在我看来,返回高阶组件的装饰器提供了一种非常好的方式来表达数据绑定,而不会污染组件方法的命名空间。

但是,正如@sebmarkbage指出的那样,存在污染 props 命名空间的风险。 目前,我正在使用道具转换器装饰器( react-transform-props )在将道具传递给内部组件之前清理/重命名道具,但我承认如果高阶组件成为未来可能会出现问题更普遍,名称冲突的风险增加。
这可以通过使用符号是属性键来解决吗? propTypes检查是否支持Symbol -keyed props? JSX 是否支持计算的内联 props 键(尽管总是可以使用计算属性 + 对象扩展)?

抱歉,如果这有点离题,但在我看来,我们仍然必须找到合适的抽象/API 来在组件级别表达数据依赖。

我的 2¢

对于随时间变化的值,我一直很喜欢“儿童作为函数”模式。 我相信@elierotenberg 是第一次想出来的? 我目前的用途是在反应弹簧上模拟弹簧(通过反弹)。 例子 -

<Springs to={{x: 20, y: 30}} tension={30}>
  {val => <div style={{left: val.x, top: val.y}}>moving pictures</div>}
</Springs>

没有道具碰撞,也没有使用所有者的状态。 我还可以嵌套多个弹簧,并且 react 管理所有的硬位。 这些'observables'(哈!)也可以接受onErroronComplete和其他道具(graphql查询?)。

粗略地尝试勾勒出“通量”

<Store 
  initial={0}
  reduce={(state, action) => action.type === 'click'? state+1 : state} 
  action={{/* assume this comes as a prop from a 'Dispatcher' up somewhere */}}> 
    {state => <div onClick={() => dispatch({type: 'click'})}> clicked {state} times</div>}
</Store>

我们在 Asana 使用了这种模式,我们称之为 _render callbacks_,但最终远离它。

优点

  • 没有道具碰撞的可能性。
  • 作为StoreComponent的作者和StoreComponent的用户都易于实现

缺点

  • shouldComponentUpdate非常难以实现,因为渲染回调可能会关闭状态。 想象一下,我们有一个A -> Store -> B的组件树。 A在其状态中有一个计数器,在渲染回调期间作为B的道具访问。 如果A因计数器而更新,而Store未更新,则B将具有旧版本的计数器。 结果,我们被迫总是更新商店。
  • 孤立地测试组件变得非常困难。 不可避免地,开发人员将复杂的逻辑放在要渲染的组件的渲染回调中,并想要测试逻辑。 执行此操作的自然方法是渲染整个树并_through_ 测试 store 组件。 这使得无法使用浅渲染器。

我们现在转移到一个模型,其中StoreComponent采用ReactElement并且当商店接收到新数据时,商店克隆 ReactElement 覆盖特定的道具。 有很多方法可以实现这种模式,例如采用 Component 构造函数和 props。 我们采用这种方法是因为它最容易在 TypeScript 中建模。

很棒的一点,在不打破“横向”要求的情况下也想不出办法。

@threepointone听起来与支持https://github.com/reactjs/react-future/pull/28的实施提案之一完全一样

@pspeter3在你的例子中,让 Store 总是更新 / shouldComponentUpdate: ()=> true有一个严重的缺点吗? 我认为它的“孩子”无论如何都会在没有Store情况下被渲染。 谢谢你的时间!

@threepointone这正是我们为边界所做的。 目前尚不清楚具体的影响是什么,但团队成员对此表示担忧。 担心加上难度测试迫使转而使用React.cloneElement(this.props.child, {data: this.state.data})

@pspeter3测试角度绝对是个问题。 如果 shallowRenderer 识别出“渲染回调”怎么办? 那会有帮助吗?

Ps- '渲染回调' :thumbs_up:

@sebmarkbage目前关于es-observable 的讨论是subscribe将保证异步,并提供[Symbol.observer]方法来同步和订阅快捷方式,尽管同时拥有同步/异步 api 的有效性是目前正在讨论中。

我在上面提到的支持同步订阅的用例中加入了这张票,不知道你是否有什么要添加的。

我认为这里的想法是处理外部状态的一种非常简洁的模式,尽管在玩了一段时间之后,我倾向于支持 HOC 方法。

@gaearon我也简化了上面的 HOC 位,使用静态 - ComposedComponent.observe并使用this.state而不是this.state.data - https://gist.github.com/tgriesser/d5d80ade6f895c28e659

提议的 es7 装饰器看起来非常好:

<strong i="20">@observing</strong>
class Foo extends Component {
  static observe(props, context) {
    return {
      myContent: xhr(props.url)
    };
  }
  render() {
    var myContent = this.props.data.myContent;
    return <div>{myContent}</div>;
  }
}

类装饰器甚至可以为data添加一个 getter,使其非常接近最初提议的 API(减去影响可观察订阅的本地状态,我同意这是一件好事 - 周围的噪音少得多订阅/取消订阅)。

缺乏同步订阅可能是一个大问题。

我想我已经知道如何以一致的方式解决“状态问题”和“横向数据加载”(衍生数据)。 它以一种无状态的“反应方式”。 我找到了一种如何在任何时间点保持状态一致性的方法,它符合UI = React(state)模式。 不太可能我无法让它完全防弹,添加更多示例并进行良好的演示。 https://github.com/AlexeyFrolov/slt 。 另一方面,它经过了很好的测试,我在我的生产项目中反复使用它。 欢迎有头脑的人投稿。

大家好,

有趣的是我以前没有在这个线程中绊倒,因为在我们公司,我们在半年前遇到了这个提案所解决的同样问题。
我们开始将 React 用于大型项目(想想具有 Microsoft Visio 复杂性的编辑器,具有非常循环的数据)。 香草反应计数跟不上我们的性能要求,
Flux 对我们来说有点不可行,因为它有大量的样板文件和所有订阅的错误倾向。 所以我们发现我们也需要可观察的数据结构。

由于我们找不到任何可以使用的东西,我们构建了自己的 observables 库,基于淘汰 observables 的原则(特别是:自动订阅)。
这实际上非常适合当前 React 组件的生命周期,我们不需要奇怪的 hack 甚至子渲染回调(下面使用的 ObserverMixin 大约是 10 loc)。
这极大地改进了我们的 DX,并且对我们的团队非常有效,因此我们决定将其作为开源发布。 同时,它经过了实战证明(例如,它提供了一个 ES7 可观察数组 polyfill)并且高度优化。
这是一个简短的计时器示例,(也可用作JSFiddle ),恕我直言,DX 再好不过了……:放心:

var store = {};
// add observable properties to the store
mobservable.props(store, {
    timer: 0
});

// of course, this could be put flux-style in dispatchable actions, but this is just to demo Model -> View
function resetTimer() {
    store.timer = 0;
}

setInterval(function() {
    store.timer += 1;
}, 1000);

var TimerView = React.createClass({
    // This component is actually an observer of all store properties that are accessed during the last rendering
    // so there is no need to declare any data use, nor is there (seemingly) any state in this component
    // the combination of mobservable.props and ObserverMixin does all the magic for us.
    // UI updates are nowhere forced, but all views (un)subscribe to their data automatically
    mixins: [mobservable.ObserverMixin],

    render: function() {
        return (<span>Seconds passed: {this.props.store.timer}</span>);
    }
});

var TimerApp = React.createClass({
    render: function() {
        var now = new Date(); // just to demonstrate that TimerView updates independently of TimerApp
        return (<div>
            <div>Started rendering at: {now.toString()}</div>
            <TimerView {...this.props} />
            <br/><button onClick={resetTimer}>Reset timer</button>
        </div>);
    }
});

// pass in the store to the component tree (you could also access it directly through global vars, whatever suits your style)
React.render(<TimerApp store={store} />, document.body);

有关此方法的更多详细信息,请参阅此博客。 顺便说一句,对于那些使用 ES6 类的人,我会确保将装饰器和/或容器添加到lib中。

可悲的是,在 react-europe 之前我没有看到这个帖子,否则我们将有一个很好的机会来讨论 React 和 observables。 但非常感谢鼓舞人心的演讲! :+1: 我特别喜欢 GraphQL 的抽象和 Redux 背后的思想工作 :)

@mweststrate我觉得社区最终需要在“可观察”和“不可变数据”解决方案之间做出选择。 也许我们需要以某种方式混合以在一个解决方案中同时拥有两种方法的优势(https://github.com/AlexeyFrolov/slt/issues/4)。 在我的解决方案中,我实现了“不可变数据”方法,重点关注任何时间点的状态一致性。 我也计划支持 Observables 和 Generators。 这是一个规则示例,用于获取“衍生”或“补充”数据(例如页面正文、资产、推荐、评论、喜欢)并保持应用程序状态的一致性。

https://github.com/AlexeyFrolov/slt#rules -example

这是一个复杂的现实世界(我的生产代码)示例,展示了如何使用 Location 标头处理 API 重定向。

import r from "superagent-bluebird-promise";
import router from "./router";

export default {
    "request": function (req)  {
        let route = router.match(req.url);
        let session = req.session;
        route.url = req.url;
        return this
            .set("route", route)
            .set("session", req.session);
    },
    "route": {
        deps: ["request"],
        set: function (route, request) {
            let {name, params: { id }} = route;
            if (name === "login") {
                return this;
            }
            let url = router.url({name, params: {id}});
            let method = request.method ? request.method.toLowerCase() : "get";
            let req = r[method]("http://example.com/api/" + url);
            if (~["post", "put"].indexOf(method)) {
                req.send(request.body);
            }
            return req.then((resp) => {
                let ctx = this.ctx;
                let path = url.substr(1).replace("/", ".");
                if (!resp.body) {
                    let location = resp.headers.location;
                    if (location) {
                        ctx.set("request", {
                            method: "GET",
                            url: location.replace('/api', '')
                        });
                    }
                } else {
                    ctx.set(path, resp.body);
                }
                return ctx.commit();
            });
        }
    }
}

其余的方法与您的方法相同,只是没有直接绑定到 React(在我的情况下不需要)。 我相信我们需要以某种方式联合起来以实现共同目标。

我认为这是一个坏主意,因为会有很多难以考虑的边缘情况。

从上面的评论中,我可以列出最终用户每次想要创建“数据”组件时需要考虑的可能范围:

  • 服务器端渲染
  • .state.data初始化
  • .context.props.state.observe纠缠
  • 异步渲染

我认为这个提议会导致组件容易出错、不稳定且难以调试。

我同意@glenjamin connectdisconnect生命周期钩子的提议。

抱歉,如果这是题外话(我不太清楚)。 但这里有一个示例,说明为什么我认为公开 API 以与组件树交互将是解决此问题的一种有趣方式: https :

这种方法是我走树的技巧,所以我的问题是,有一个公共 API 来做这种事情,如何在“横向数据加载”中实现创新,我认为这可以归结为“一个没有的渲染器”自上而下工作”: https :

但这里有一个例子,说明为什么我认为公开 API 以与组件树交互将是解决这个问题的一种有趣方式。

我相信@swannodette在 ReactConf 上谈到了这个。 我想知道 Om Next 是否做到了?

是的,他在第一次 ReactConf 上提出了同样的建议。 我还没有看到最新的 Om.next,也没有听过 EuroClojure 的演讲,但以前 Om 会使用自己的结构,用户必须建立它才能做到这一点。

这种方法是我走树的技巧,所以我的问题是,有一个公共 API 来做这种事情,如何在“横向数据加载”中实现创新,我认为这可以归结为“一个没有的渲染器”不能自上而下地工作”

我认为这大致相当于在测试工具中发现的浅层渲染——我提到使它成为一流的 API,作为ReactEurope的 @sebmarkbage 的 DOM 和字符串渲染的对等

最初的反应是它可能有点低级 - 但与可扩展网络的东西类似,我认为这将更容易在用户空间中进行实验。

我同意,如果我们有办法渲染来获取树,那么我们可以遍历它并找到所有需要的数据并将它们传递下去。

访问整个虚拟 DOM 树是一个非常强大且有用的功能,我很想访问它,即使这被视为一个完全独立的问题。
考虑到 React 从 0.14 开始采用的路径,我认为这很有意义。

鉴于 observables 的复杂性,我谦虚地建议这个线程中的绅士们看看 Meteor 的反应数据实现: https://github.com/meteor/meteor/wiki/Tracker-Manual。 将它与 React 协调需要在componentWillMount方法中使用 3-5 行代码,例如:

  componentWillMount() {
    if (typeof this.getState === 'function') {
      Tracker.autorun(() => {
        // Assuming this.getState() calls some functions that return
        // reactive data sources
        this.setState(this.getState());
      });
    }
  }

我不知道 Tracker(很容易作为一个单独的库提取)是否消除了对 React 中可观察支持的需求,但看起来确实如此。

一旦某些可观察值发生更改,MOBservable 遵循非常相似的模式来刷新组件,因此当前的生命周期方法 + 装饰器似乎为第三方库提供了足够的灵活性来表达这些模式,并且恕我直言第三方数据源概念只会使事情过于复杂。

    componentWillMount: function() {
        var baseRender = this.render;
        this.render = function() {
            if (this._watchDisposer)
                this._watchDisposer();
            var[rendering, disposer] = mobservableStatic.watch(() => baseRender.call(this), () => {
                    this.forceUpdate();
            });
            this._watchDisposer = disposer;
            return rendering;
        }
    },

@Mitranim我同意,这是一本非常好的读物,感谢您找到它! 这实际上是https://github.com/facebook/react/pull/3920的建议。

我们不确定哪个提案更好。 在玩过两者之后,我主要相信反应式编程(如您所链接的那样; https://github.com/meteor/meteor/wiki/Tracker-Manual )是要走的路,但我们还没有达成共识,并且我们仍在尝试找出最有意义的方法,因此欢迎提供反馈。

@Mitranim @jimfb我多年来一直是 Meteor 的忠实粉丝。 Tracker 真的很酷,也很迷人。 我创建了一个演示文稿,演示了一个非常简单的跟踪器版本是如何工作的:

https://github.com/ccorcos/meteor-track/blob/master/client/main.js

我什至用 Tracker 创建了一个 observable 流库:

https://github.com/ccorcos/meteor-tracker-streams

但是当我完全过渡到使用 React 而不是 Blaze 时,我开始意识到 Tracker 太复杂了,有时一个简单的发布/订阅/onChange 方法只是简单 100 倍。

此外,对于所有函数式编程爱好者来说,Tracker 附带了一些在 99% 的情况下都有意义的副作用,但有时它们会让你受益。 这是它在 React 组件中的外观

componentWillMount: ->
  <strong i="15">@c</strong> = Tracker.autorun =>
    @setState({loading: true})
    Meteor.subscribe 'users', => @setState({loading: false})
    Tracker.autorun =>
      @setState({users: Users.find({}, {sort:{name:-1}}).fetch()})
componentWillUnmount: ->
  @c.stop()

我关于副作用的观点是c.stop()将停止订阅和内部自动运行。

@ccorcos是的,在https://github.com/facebook/react/pull/3920中,所有的副作用都完全在 React 内部。 事实上,React 核心可以以完全不可变/功能性的方式实现它们,不会执行任何突变。 但这是一个实现细节,因为无论如何副作用都不会在外部可见。

有趣...那么为什么不直接使用 onChange 回调呢? 我发现那些是最兼容的。 无论您使用的是 Meteor (Tracker)、RxJS、Highland.js 等,您都可以通过事件回调轻松地与它们集成。 与 React 高阶组件相同。

我喜欢 Redux 的地方在于它将这个逻辑排除在框架之外,并将 React 组件保持为纯函数。

@ccorcos主要问题是,当组件实例被销毁时,所有“订阅”都需要清理。 组件作者经常忘记这种清理,从而造成内存泄漏。 我们希望它是自动的,这使得编写更容易(样板更少)并且更不容易出错(清理是自动的)。

@jimfb没错。 自动取消订阅是 Tracker 方法中真正巧妙的事情之一。 每次调用响应式数据源都会重新建立订阅,一旦组件停止调用数据,就没有什么可以清理的了!

是的,但它并不是真正“自动”取消订阅。 您必须在组件中调用c.stop()才能卸载。 不过,您可以使用 mixin 将其抽象出来。

componentWillMount: ->
  <strong i="7">@autorun</strong> =>
    @setState({loading: true})
    Meteor.subscribe 'users', => @setState({loading: false})
    Tracker.autorun =>
      @setState({users: Users.find({}, {sort:{name:-1}}).fetch()})

但这真的与任何其他 api 没有什么不同。 您可以创建一个内置方法,在卸载等时自动为您取消订阅。看,我是 Meteor 的忠实粉丝。 所以我不想跟你说这件事。 只是有时候,我发现很难向不熟悉的人解释这些东西。 同时,使用简单的侦听器/事件发射器更易于理解和实现,并且它们往往与您想要使用的任何反应系统非常兼容......

@ccorcos我们可能在谈论不同的机制。 最后我检查了一下,Tracker 没有永久订阅; 每个建立对响应式数据源的依赖关系(通过访问它)的函数在它发生变化时都会重新运行一次,这就是订阅的结束。 如果它再次访问数据源,这将重新建立一次更改的“订阅”。 等等。

@Mitranim是正确的,#3920 的语义比 Meteor 更“自动”(取消订阅确实是自动的),而且更简单,因为在常见用例中 API 表面积实际上为零。

@ccorcos @Mitranim对于准备使用 Tracker / Vue.js 启发的库,您可以尝试Mobservable ,它观察在 _render_ 期间访问的所有数据,并在卸载时处理所有订阅(直到订阅保持活动状态)。 到目前为止,我们在 Mendix 成功地将其应用于相当大的项目。 它对您的数据非常不显眼,并且它装饰现有对象而不是提供自己的模型对象。

最后我检查了一下,Tracker 没有永久订阅

@Mitranim订阅可以是永久的。 看一下这个。

sub = Meteor.subscribe('chatrooms')
# this subscription lasts until...
sub.stop()

现在有了 Tracker 的一些有趣的东西。 看一下这个。

comp = Tracker.autorun ->
  Meteor.subscribe('chatrooms')
# this subscription lasts until...
comp.stop()

最后一个例子虽然不是非常有用。 直到我们做这样的事情。

roomId = new ReactiveVar(1)
comp = Tracker.autorun ->
  Meteor.subscribe('messages', roomId.get())
# when I change the roomId, the autorun will re-run
roomId.set(2)
# the subscription to room 1 was stopped and now the subscription to room 2 has started
# the subscription is stopped when I call stop...
comp.stop()

每个建立对响应式数据源的依赖关系(通过访问它)的函数在它发生变化时都会重新运行一次,这就是订阅的结束。

订阅一直持续到重新运行自动运行(更改了反应性依赖项)或它所在的计算停止。 两者都调用了 compute.onInvalidate 钩子。

这是一个超级做作的版本,完成了完全相同的事情。 希望它能帮助您了解 Tracker 的工作原理。 也许您还会看到它会变得多么混乱。

comp = Tracker.autorun ->
  Meteor.subscribe('chatrooms')

# is the same as

comp = Tracker.autorun (c) ->
  sub = null
  Tracker.nonreactive ->
    # dont let comp.stop() stop the subscription using Tracker.nonreactive
    sub = Meteor.subscribe('chatrooms')
  c.onInvalidate ->
    # stop the subscription when the computation is invalidated (re-run)
    sub.stop()
# invalidate and stop the computation
comp.stop()

@ccorcos我现在看到了混乱的根源; 这是我的词汇。 在谈论订阅时,我不是指_Meteor 订阅_。 它们确定从服务器推送到客户端的数据,但与视图层更新无关,这是本次讨论的主题。 在说 _subscription_ 时,我将传统事件侦听器与 Tracker 重新运行依赖于响应式数据源的函数的能力相提并论。 对于 React 组件,这将是一个获取数据然后调用setStateforceUpdate的组件方法。

@mweststrate感谢您的示例,它看起来很有趣。

没错。 所以他们有一个聪明的方法。

componentWillMount: function() {
  this.comp = Tracker.autorun(() => {
    let sub = Meteor.subscribe('messages')
    return {
      loading: !sub.ready(),
      messages: Messages.find().fetch()
    }
  })
componentWillUnmount: function() {
  this.comp.stop()
}

订阅只会重新订阅每次都没有问题。 sub.ready 和 Messages.find.fetch 都是“反应式”的,并且会在它们发生变化时触发自动运行重新运行。 Tracker 最酷的地方在于,当您开始隐藏自动运行并在文档中显示某个功能位于“反应式上下文”中时

你可以把它扔进一个mixin

componentWillMount: function() {
  this.comp = Tracker.autorun(() => {
    return this.getReactiveData()
  })
componentWillUnmount: function() {
  this.comp.stop()
}

然后你就剩下这个神奇的反应函数了!

getReactiveData: function() {
  let sub = Meteor.subscribe('messages')
  return {
    loading: !sub.ready(),
    messages: Messages.find().fetch()
  }
}

像这样的追踪器很酷...

@ccorcos原来我在使用 Tracker 风格的事件时自动取消订阅有点错误。 您仍然需要在componentWillUnmount中停止侦听器,否则它将继续访问数据源并调用setState (除非您使用现在已弃用的isMounted()进行检查)。

附带说明一下,您可以创建优雅的装饰器以使组件方法具有反应性。 这里有一些例子: [1][2] 。 看起来像这样:

export class Chat extends React.Component {
  <strong i="13">@reactive</strong>
  updateState () {
    this.setState({
      auth: auth.read(),
      messages: messages.read()
    })
  }

  /* ... */
}

@Mitranim非常简洁——不过我更喜欢高阶函数。 ;)

@sebmarkbage @jimfb我已经关注这个线程和 alt 线程(#3858)几个月了,我很好奇核心团队是否就这个问题达成了任何共识,或者至少是一个大方向。

@oztune没有更新; 我们一直专注于其他优先事项。 当有关此主题的更新时,我们将发布到其中一个线程。

伙计们,我创建了一个通用 API 来组合容器。 检查react-komposer 。 也就是说,我们可以创建具有更高阶函数的容器。

import { compose } from `react-komposer`;

// Create a component to display Time
const Time = ({time}) => (<div>{time}</div>);

// Create the composer function and tell how to fetch data
const composerFunction = (props, onData) => {
    const handler = setInterval(() => {
    const time = new Date().toString();
    onData(null, {time});
  }, 1000);

  const cleanup = () => clearInterval(handler);
  return cleanup;
};

// Compose the container
const Clock = compose(composerFunction)(Time);

// Render the container
ReactDOM.render(<Clock />, document.getElementById('react-root'));

这是现场版本: https :

我们还提供了一些简单的方法来使用Promises、Rx.js Observables 和 Meteor 的 Tracker组合容器。

另请查看我的文章:让我们编写一些 React 容器

@arunoda我们最终做了非常相似的事情。 我想知道的一件事是你如何防止 composerFunction 在每次道具更改时被调用?

@oztune实际上现在它会再次运行。 我们使用这个Lokka和 Meteor。 这两个都有本地缓存​​,即使我们多次调用 composerFunction 也不会访问服务器。

但我认为我们可以这样做:

const options =  {propsToWatch: ["postId"]};
const Clock = compose(composerFunction, options)(Time);

有任何想法吗?

@arunoda这也是我们尝试过的,但是它在操作及其依赖项之间造成了一些脱节。 我们现在做一些类似于 react-async 的事情,其中​​ composerFunction 将返回一个函数和一个键,而不是立即执行给定的操作。 如果 key 与 composerFunction 返回的先前 key 不同,则将执行新函数。 我不确定这是否与这个 github 线程相切,所以我很乐意继续使用 Twitter(相同的用户名)。

@oztune我创建了一个新的 GH 问题,让我们继续在那里聊天。 我猜比推特好多了。

为什么不让 JSX 直接理解和渲染 Observables,这样我就可以在 props 中传递 Observable,例如 this.props.todo$ 并将它们嵌入 JSX。 然后我不需要任何 API,其余的在 React 之外进行管理,而 HoC 用于填充 observables。 props 是否包含纯数据或 observable 无关紧要,因此不需要特殊的 this.data。

{this.props.todo$

此外,React 渲染可以渲染 Oservable[JSX],这将允许链接中描述的设计,而无需额外的库。

https://medium.com/@milankinen/containers -are-dead-long-live-observable-combinators-2cb0c1f06c96#.yxns1dqin

https://github.com/milankinen/react-combinators

你好,
就目前而言,我们可以轻松地将无状态组件与 rxjs 流一起使用。
我不明白需要另一个 API。
我写了一个例子 - 你可以将鼠标移到它的板上,当它达到 26 时它会更改为重新启动。
我很想听听你对这种方式的看法。
这是代码:
https://jsfiddle.net/a6ehwonv/74/

@giltig :我最近也在学习这种方式并且喜欢它。 与 Cycle.js 一起。

我希望能够以某种方式轻松地收听在组件上定义的事件处理程序,而无需传入主题进行桥接。 如果我理解正确,这与此处建议的完全相反。 或者,如果我们可以观察 React vdom 的合成事件,也许“refs”可以用于观察,以避免只有 CSS 标签来观察。

进一步的 RxJs 可以支持 combineLatest 中的对象,这样我们就可以直接使用 React 功能组件来创建更大的组件。

const myFancyReactComponent = ({surface, number, gameover}) => (
        <div> 
          {gameover ? gameover : surface}
          {number}
        </div>
)

const LiveApp = Rx.Observable.combineLatest(
    LiveSurface, DynamicNumberView, DynamicGameOver,
    myFancyReactComponent
)

你好,
我们不使用 Cycle 或任何其他框架的原因是我更喜欢库而不是框架(对开发人员有更多的控制权),而且我想享受 React 所拥有的社区力量。
现在为 React 开发了很多渲染引擎,可惜没有使用它(ReactNative、ReactDom、ReactThree 等)。 仅使用 Rxjs 并按照我上面展示的示例进行响应非常简单。

如果 React 组件可以接受 pojo 和 observables 作为 props 一样,那会更容易思考。 至于现在这是不可能的,所以我上面展示的是我们选择的方式。

顺便说一句,您对 MyFancyReactComponent 所做的事情是可能的,在某些情况下我们实际上也这样做了,尽管您也可以直接编写 jsx。

关于主题 - 我认为这是一种有效的方式,因为最终在 React 组件中我只是使用了一个可以是任何东西的处理程序函数。 我选择使用内部接收事件的主题来实现它,但其他人可以选择其他方式 - 它很灵活。

如果 React 组件可以接受 pojo 和 observables 作为 props 一样,那会更容易思考。 至于现在这是不可能的,所以我上面展示的是我们选择的方式。

从长远来看,可观察的道具没有任何意义。 在这种情况下,它根本没有意义,实际上..

在我看来,我们最终使用了 Suspense(缓存 + 上下文)的不同模型。 缓存本身可能会支持订阅。 您可以在https://github.com/facebook/react/issues/13206 中跟踪 Suspense 的剩余工作

我们还为更多孤立案例提供订阅套餐。

此页面是否有帮助?
0 / 5 - 0 等级