提问者:小点点

如何在相关 react 组件之间进行通信?


我刚开始使用ReactJS,遇到了一个小问题。

我的应用程序本质上是一个带有过滤器的列表和一个更改布局的按钮。目前我使用三个组件:

如何使这 3 个组件相互交互,或者我是否需要某种全局数据模型来进行更改?


共3个答案

匿名用户

最佳方法取决于您计划如何排列这些组件。现在想到的几个示例场景:

  1. <代码>

可能还有其他我没有想到的情况。如果你的不适合这些,那么请告诉我。以下是我如何处理前两种情况的一些非常粗略的示例:

您可以从传递处理程序

#1的JSFiddle→

/** @jsx React.DOM */

var Filters = React.createClass({
  handleFilterChange: function() {
    var value = this.refs.filterInput.getDOMNode().value;
    this.props.updateFilter(value);
  },
  render: function() {
    return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="Filter" />;
  }
});

var List = React.createClass({
  getInitialState: function() {
    return {
      listItems: ['Chicago', 'New York', 'Tokyo', 'London', 'San Francisco', 'Amsterdam', 'Hong Kong'],
      nameFilter: ''
    };
  },
  handleFilterUpdate: function(filterValue) {
    this.setState({
      nameFilter: filterValue
    });
  },
  render: function() {
    var displayedItems = this.state.listItems.filter(function(item) {
      var match = item.toLowerCase().indexOf(this.state.nameFilter.toLowerCase());
      return (match !== -1);
    }.bind(this));

    var content;
    if (displayedItems.length > 0) {
      var items = displayedItems.map(function(item) {
        return <li>{item}</li>;
      });
      content = <ul>{items}</ul>
    } else {
      content = <p>No items matching this filter</p>;
    }

    return (
      <div>
        <Filters updateFilter={this.handleFilterUpdate} />
        <h4>Results</h4>
        {content}
      </div>
    );
  }
});

React.renderComponent(<List />, document.body);

与场景#1类似,但父组件将是将处理程序函数传递给的组件

#2的JSFiddle

/** @jsx React.DOM */

var Filters = React.createClass({
  handleFilterChange: function() {
    var value = this.refs.filterInput.getDOMNode().value;
    this.props.updateFilter(value);
  },
  render: function() {
    return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="Filter" />;
  }
});

var List = React.createClass({
  render: function() {
    var content;
    if (this.props.items.length > 0) {
      var items = this.props.items.map(function(item) {
        return <li>{item}</li>;
      });
      content = <ul>{items}</ul>
    } else {
      content = <p>No items matching this filter</p>;
    }
    return (
      <div className="results">
        <h4>Results</h4>
        {content}
      </div>
    );
  }
});

var ListContainer = React.createClass({
  getInitialState: function() {
    return {
      listItems: ['Chicago', 'New York', 'Tokyo', 'London', 'San Francisco', 'Amsterdam', 'Hong Kong'],
      nameFilter: ''
    };
  },
  handleFilterUpdate: function(filterValue) {
    this.setState({
      nameFilter: filterValue
    });
  },
  render: function() {
    var displayedItems = this.state.listItems.filter(function(item) {
      var match = item.toLowerCase().indexOf(this.state.nameFilter.toLowerCase());
      return (match !== -1);
    }.bind(this));

    return (
      <div>
        <Filters updateFilter={this.handleFilterUpdate} />
        <List items={displayedItems} />
      </div>
    );
  }
});

React.renderComponent(<ListContainer />, document.body);

当组件无法在任何类型的父子关系之间进行通信时,文档建议设置全局事件系统。

匿名用户

组件通信有多种方式。有些可以适合您的用例。以下是一些我认为有用的信息。

const Child = ({fromChildToParentCallback}) => (
  <div onClick={() => fromChildToParentCallback(42)}>
    Click me
  </div>
);

class Parent extends React.Component {
  receiveChildValue = (value) => {
    console.log("Parent received value from child: " + value); // value is 42
  };
  render() {
    return (
      <Child fromChildToParentCallback={this.receiveChildValue}/>
    )
  }
}

在这里,子组件将使用值调用父组件提供的回调,父组件将能够获取父组件中的子组件提供的值。

如果生成应用的功能/页面,则最好让单个父级管理回调/状态(也称为容器智能组件),并且所有子项都是无状态的,仅向父级报告内容。通过这种方式,您可以轻松地将父母的状态“共享”给任何需要它的孩子。

React Context允许将状态保存在组件层次结构的根部,并且能够轻松地将此状态注入到非常深嵌套的组件中,而无需将道具传递给每个中间组件。

到目前为止,上下文还是一个实验性的特性,但是React 16.3中提供了一个新的API。

const AppContext = React.createContext(null)

class App extends React.Component {
  render() {
    return (
      <AppContext.Provider value={{language: "en",userId: 42}}>
        <div>
          ...
          <SomeDeeplyNestedComponent/>
          ...
        </div>
      </AppContext.Provider>
    )
  }
};

const SomeDeeplyNestedComponent = () => (
  <AppContext.Consumer>
    {({language}) => <div>App language is currently {language}</div>}
  </AppContext.Consumer>
);

消费者正在使用渲染道具/儿童功能模式

查看此博客文章了解更多详情。

在React 16.3之前,我建议使用提供非常相似的API的react-广播,并使用以前的上下文API。

当您希望将两个组件紧密地放在一起以使它们与简单的函数进行通信时,请使用门户,就像在普通的父/子中一样,但是您不希望这两个组件在DOM中具有父/子关系,因为它暗示了可视化/ CSS约束(像z索引、不透明度...).

在这种情况下,您可以使用“门户”。有不同的反应库使用门户,通常用于模态,弹出窗口,工具提示...

考虑以下几点:

<div className="a">
    a content
    <Portal target="body">
        <div className="b">
            b content
        </div>
    </Portal>
</div>

reactAppContainer中呈现时可以生成以下DOM:

<body>
    <div id="reactAppContainer">
        <div className="a">
             a content
        </div>
    </div>
    <div className="b">
         b content
    </div>
</body>

更多详情请点击此处

你在某个地方定义一个槽,然后从你的渲染树的另一个地方填充这个槽。

import { Slot, Fill } from 'react-slot-fill';

const Toolbar = (props) =>
  <div>
    <Slot name="ToolbarContent" />
  </div>

export default Toolbar;

export const FillToolbar = ({children}) =>
  <Fill name="ToolbarContent">
    {children}
  </Fill>

这与门户有点相似,只是填充的内容将在您定义的插槽中呈现,而门户通常呈现一个新的 dom 节点(通常是 document.body 的子节点)。

检查反应槽填充库

如React留档中所述:

对于没有父子关系的两个组件之间的通信,您可以设置自己的全局事件系统。在组件中订阅事件,在组件中取消订阅,并在收到事件时调用setState()。

有很多东西可以用来设置事件总线。您可以创建一个侦听器数组,在事件发布时,所有侦听器都会接收到该事件。或者可以使用EventEmitter或PostalJs之类的东西

Flux基本上是一个事件总线,除了事件接收器是存储。这类似于基本的事件总线系统,除了状态在React之外进行管理

最初的Flux实现看起来像是以一种老套的方式进行事件来源的尝试。

Redux对我来说是最接近事件源的Flux实现,它有利于许多事件源优势,如时间旅行的能力。它与React没有严格的联系,也可以与其他功能视图库一起使用。

Egghead的Redux视频教程非常好,并解释了它是如何在内部工作的(它真的很简单)。

游标来自克洛朱尔脚本/Om,广泛用于 React 项目。它们允许在 React 外部管理状态,并允许多个组件对状态的同一部分具有读/写访问权限,而无需了解有关组件树的任何信息。

存在许多实现,包括ImmutableJS、React-cursors和omni known

编辑2016:似乎人们同意光标适用于较小的应用程序,但它不能很好地扩展复杂的应用程序。

榆树架构是一种提议由榆树语言使用的架构。即使榆树不是 ReactJS,榆树架构也可以在 React 中完成。

Redux的作者Dan Abramov使用React实现了Elm架构。

Redux和Elm都非常棒,并且倾向于在前端增强事件源概念,都允许时间旅行调试、撤消/重做、重播……

Redux和Elm的主要区别在于Elm对状态管理更加严格。在Elm中,您不能有本地组件状态或挂载/卸载挂钩,所有DOM更改都必须由全局状态更改触发。Elm架构提出了一种可扩展的方法,允许处理单个不可变对象中的所有状态,而Redux提出了一种方法,邀请您在单个不可变对象中处理大多数状态。

虽然Elm的概念模型非常优雅,并且体系结构允许在大型应用程序上进行良好扩展,但实际上,要实现简单的任务可能很困难,或者需要更多的样板来完成,例如在安装输入后将焦点放在输入上,或者与具有命令式接口的现有库集成(即JQuery插件)。相关问题。

此外,Elm体系结构涉及更多代码样板。编写起来并没有那么冗长或复杂,但我认为Elm体系结构更适合静态类型语言。

RxJS、培根 JS 或开菲尔等库可用于生成 FRP 流来处理组件之间的通信。

您可以尝试例如 Rx-React

我认为使用这些库非常类似于使用ELM语言提供的信号。

CycleJS框架不使用ReactJS,而是使用vdom。它与Elm架构有很多相似之处(但在现实生活中更容易使用,因为它允许vdom挂钩),并且它广泛使用RxJ而不是函数,如果您想在React中使用FRP,它可以成为一个很好的灵感来源。CycleJs Egghead视频很好地理解了它的工作原理。

CSP(通信顺序进程)目前很流行(主要是因为Go/goroutines和core.async/ClojureScript),但您也可以在带有JS-CSP的javascript中使用它们。

James Long制作了一段视频,解释了如何将其与React一起使用。

传奇是一个后端概念,来自DDD / EventSourcing / CQRS世界,也称为“流程管理器”。redux-saga项目正在推广它,主要是作为redux-thunk的替代品来处理副作用(例如API调用等)。大多数人目前认为它只是为副作用服务,但它实际上更多的是关于去耦组件。

它更像是对Flux架构(或Redux)的赞美,而不是一个全新的通信系统,因为传奇在最后会发出Flux动作。这个想法是,如果你有小部件1和小部件2,并且你希望它们被分离,你不能从小部件1中触发针对小部件2的动作。因此,您使 widget1 仅触发以自身为目标的操作,而 saga 是一个“后台进程”,用于侦听 widget1 操作,并可能调度以 widget2 为目标的操作。saga 是 2 个小部件之间的耦合点,但小部件保持分离。

如果你有兴趣,看看我在这里的回答

如果您想查看使用这些不同样式的同一小应用程序的示例,请检查此存储库的分支。

从长远来看,我不知道什么是最好的选择,但我真的很喜欢Flux看起来像事件溯源。

如果您不知道事件源概念,请查看这个非常具有教学意义的博客:使用apache Samza将数据库翻转过来,这是了解Flux为何如此出色的必读书籍(但这也可能适用于FRP)

我认为社区一致认为最有前途的Flux实现是Redux,由于热重载,它将逐步提供非常高效的开发人员体验。令人印象深刻的现场编码ala Bret维克多的发明原理视频是可能的!

匿名用户

好吧,有几种方法可以做到这一点,但我只想专注于使用store using Redux,这使您的生活更加轻松,而不是给你一个快速的解决方案。只有在这种情况下,使用pure React会在真正的大型应用程序中结束混乱,并且随着应用程序的增长,组件之间的通信变得越来越困难...

那么Redux为您做了什么?

Redux就像应用程序中的本地存储,当您需要在应用程序的不同位置使用数据时,就可以使用它...

基本上,Redux的想法最初来自于flux,但随着一些根本性的变化,包括通过只创建一个商店来拥有一个真相来源的概念。。。

看看下面的图表,看看Flux和Redux之间的一些区别…

如果您的应用程序需要组件之间的通信,可以考虑从一开始就在应用程序中应用Redux...

此外,从 Redux 文档中阅读这些单词可能会有所帮助:

随着对 JavaScript 单页应用程序的要求变得越来越复杂,我们的代码必须管理比以往更多的状态。此状态可以包括服务器响应和缓存的数据,以及尚未保存到服务器的本地创建的数据。UI 状态的复杂性也在增加,因为我们需要管理活动路由、所选选项卡、微调器、分页控件等。

管理这种不断变化的状态是很困难的。如果一个模型可以更新另一个模型,那么一个视图可以更新一个模型,这个模型会更新另一个模型,这反过来可能会导致另一个视图更新。在某个时候,你不再了解你的应用程序中发生了什么,因为你已经失去了对其状态的时间、原因和方式的控制。当一个系统不透明且不确定时,很难重现错误或添加新功能。

如果这还不够糟糕的话,考虑一下在前端产品开发中变得常见的新需求。作为开发人员,我们需要处理乐观更新、服务器端呈现、在执行路线转换之前获取数据等等。我们发现自己正试图处理一个我们从未处理过的复杂问题,我们不可避免地会问这样一个问题:是时候放弃了吗?答案是否定的。

这种复杂性很难处理,因为我们混合了两个人类思维很难推理的概念:突变和异步性。我称它们为门托斯和可乐。两者在分离方面都很棒,但它们一起造成了一团糟。像 React 这样的库试图通过删除异步和直接 DOM 操作来在视图层中解决此问题。但是,管理数据的状态由您决定。这是雷杜克斯进入的地方。

按照 Flux、CQRS 和事件溯源的步骤,Redux 尝试通过对更新发生的方式和时间施加某些限制来使状态突变可预测。这些限制反映在 Redux 的三项原则中。