ReactJS两个组件进行通信


321

我刚开始使用ReactJS,但对我遇到的问题有些困惑。

我的应用程序本质上是一个带有过滤器的列表和一个用于更改布局的按钮。目前我使用的三个组成部分:<list />< Filters /><TopBar />,现在很明显,当我更改设置,在< Filters />我想引起一些方法<list />来更新我的看法。

如何使这三个组件相互交互,或者我需要可以对其进行更改的某种全局数据模型?


是所有三个同级组件,还是一个在另一个内部?
pgreen2 2014年

它们都是三个组成部分,我已经重新安排了应用程序,以便它们现在都具有相同的父级,可以为他们提供数据。
woutr_be 2014年

4
在这里可以使用flux或pubsub模式。根据react docs中的文档,他们留下了一个含糊的句子:“对于两个没有父子关系的组件之间的通信,您可以设置自己的全局事件系统。” facebook.github.io/react/tips/...
BingeBoy

@BingeBoy是正确的Flux是编写reactjs应用程序的好方法,可以处理数据流,许多组件共享数据的问题。
Ankit Patial

4
如果您不想使用Flux或Redux,这是一篇关于该主题的很棒的文章andrewhfarmer.com/component-communication
garajo

Answers:


318

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

  1. <Filters /> 是的子组件 <List />
  2. 这两个<Filters /><List />是一个父组件的孩子
  3. <Filters /><List />完全存在于单独的根组件中。

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

场景1

您可以将处理程序从传递<List /><Filters />,然后可以在onChange事件上调用该处理函数以使用当前值过滤列表。

#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);

场景2

与方案1类似,但父组件将是将处理函数传给的组件<Filters />,并将过滤后的列表传递给<List />。我更喜欢这种方法,因为它使<List />脱离了<Filters />

#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);

场景3

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


6
以#2的好处是,他们只能依靠父母谁传递一个道具的每个组件:作为一个功能updateFilter<Filters />和阵列items<List />。您可以将这些子组件与其他具有不同行为的父母一起使用,也可以单独使用。例如,如果您想显示动态列表但不需要过滤。
Michael LaCroix 2014年

2
@woutr_be不确定是否符合您的要求,但是在类似情况下,有时我们会使用以下两个函数在子组件和父组件之间进行通信:-listenTo:function(eventName,eventCallback){$( window.document).bind(eventName,eventCallback);} triggerEvent:function(eventName,params){$ .event.trigger(eventName,params);}希望对您有所帮助!(对不起,格式化效果
不佳

29
对于方案3,是否有推荐的方法?通过生成自定义综合事件,是否有任何文档或示例?我没有在主要文档中找到任何东西。
pwray

1
情景#2 ,使有很大的意义...... 直到你有危及设计(如果只,布局) -然后你意识到一个EventHub / PubSub的必要性。
科迪

4
场景#3链接已失效,现在重定向到一个不相关的React docs页面。
beporter

170

有多种方法可以使组件进行通信。有些可以适合您的用例。这是我发现有用的一些清单。

反应

父母/子女直接沟通

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}/>
    )
  }
}

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

如果您构建应用程序的功能/页面,则最好由一个父级来管理回调/状态(也称为containersmart component),并且所有子级都必须是无状态的,只向父级报告情况。这样,您可以轻松地将“父母”的状态“共享”给需要它的任何孩子。


语境

React Context允许将状态保持在组件层次结构的根部,并且能够轻松地将此状态注入到嵌套很深的组件中,而不必麻烦将prop传递给每个中间组件。

到目前为止,上下文是一个实验性功能,但是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>
);

消费者正在使用 render prop / children功能模式

查看此博客文章以获取更多详细信息。

在React 16.3之前,我建议使用提供完全相似的API的react-broadcast,并使用以前的上下文API。


门户网站

当您希望将2个组件保持在一起以使它们与简单的功能进行通信时(如在正常的父/子中),请使用门户,但是您不希望这2个组件在DOM中具有父/子关系,因为暗示的视觉/ CSS约束(例如z-index,不透明度...)。

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

考虑以下:

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

在内部渲染时可能会产生以下DOM reactAppContainer

<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 文档中所述

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

您可以使用许多东西来设置事件总线。您可以只创建一个侦听器数组,并且在事件发布时,所有侦听器都将接收该事件。或者,您可以使用EventEmitterPostalJs之类的东西


助焊剂

助焊剂基本上是事件总线,但事件接收者是商店。这类似于基本事件总线系统,只不过状态是在React外部进行管理的

原始的Flux实现看起来像是尝试以一种怪异的方式进行事件源。

Redux对我而言,是最接近事件源的Flux实现,它受益于许多事件源优势,例如时间旅行能力。它没有严格地链接到React,也可以与其他功能视图库一起使用。

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


游标

游标来自ClojureScript / Om,并广泛用于React项目。它们允许管理React外部的状态,并允许多个组件对状态的同一部分具有读/写访问权限,而无需了解组件树的任何知识。

存在许多实现,包括ImmutableJSReact-cursorsOmniscient

编辑2016年:似乎人们同意,游标在较小的应用程序上可以正常工作,但在复杂的应用程序上无法很好地扩展。Om Next不再具有游标(这是Om最初引入的概念)


榆木建筑

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

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

Redux和Elm都非常出色,并且倾向于在前端启用事件源概念,它们都允许时间旅行调试,撤消/重做,重播...

Redux和Elm之间的主要区别在于Elm在状态管理方面趋于严格得多。在Elm中,您不能具有本地组件状态或挂接/卸载挂接,并且所有DOM更改都必须由全局状态更改触发。Elm体系结构提出了一种可扩展的方法,该方法允许处理单个不可变对象内的所有状态,而Redux提供一种方法,邀请您在单个不可变对象中处理状态的MOST

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

而且,Elm体系结构涉及更多代码样板。编写起来并不是那么冗长或复杂,但是我认为Elm体系结构更适合于静态类型的语言。


玻璃钢

可以使用RxJS,BaconJS或Kefir之类的库来生成FRP流,以处理组件之间的通信。

您可以尝试例如Rx反应

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

CycleJS框架不使用ReactJS,而是使用vdom。它与Elm架构有很多相似之处(但由于它允许使用vdom钩子,因此在现实生活中更易于使用),并且广泛使用RxJ而不是函数,并且如果您想将FRP与反应 CycleJs Egghead视频很高兴了解其工作原理。


CSP

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

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

萨加斯

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

它比起全新的通信系统更像是对Flux架构(或Redux)的补充,因为传奇最终会发出Flux动作。这样做的想法是,如果您具有widget1和widget2,并且希望将它们解耦,则无法从widget1触发定位到widget2的操作。因此,您使widget1仅触发以自身为目标的动作,并且传奇事件是侦听widget1动作的“后台进程”,并且可以调度以widget2为目标的动作。传奇是2个小部件之间的耦合点,但小部件保持解耦。

如果您有兴趣,请在这里查看我的答案


结论

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

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

如果您不了解事件源的概念,请看一下这个非常有教育意义的博客:用apache Samza将数据库内翻,这是理解Flux为何不错的必读(但这也适用于FRP) )

我认为社区同意最有前途的Flux实现是Redux,这得益于热重装,将逐步提供非常富有成效的开发人员体验。令人印象深刻的实时编码ala Bret Victor的Inventing on Principle视频是可能的!


2
优秀的答案Seb!
阿里·加贾尼

7

好的,有几种方法可以做到,但是我只想专注于使用Redux使用商店,这使您在这种情况下的生活更加轻松,而不是只为这种情况提供快速解决方案,使用纯React最终会陷入混乱真正的大型应用程序以及组件之间的通信随着应用程序的增长而越来越难...

那么,Redux为您做什么呢?

Redux就像应用程序中的本地存储,可在需要将数据用于应用程序中不同位置时使用。

基本上,Redux的想法最初来自于不断变化的概念,但是发生了一些根本性的变化,包括仅创建一家商店即可拥有一个真实来源的概念。

查看下图,了解FluxRedux之间的一些区别...

Redux和Flux

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

另外,从Redux文档中阅读这些单词可能对以下方面有所帮助:

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

管理这种不断变化的状态非常困难。如果一个模型可以更新另一个模型,那么一个视图可以更新一个模型,该模型可以更新另一个模型,这又可能导致另一个视图更新。在某些时候,您不再了解应用程序中发生的事情,因为您无法控制其状态的时间,原因和方式。当系统是不透明且不确定的系统时,很难重现错误或添加新功能。

好像还不够糟糕,请考虑一下新要求在前端产品开发中变得很普遍。作为开发人员,我们希望能够进行乐观更新,服务器端渲染,在执行路由转换之前获取数据等。我们发现自己试图管理一种从未有过的复杂性,并且我们不可避免地会问一个问题:是时候放弃了吗?答案是不。

这种复杂性很难处理,因为我们混用了两个人类大脑难以理解的概念:突变和异步性。我称他们为Mentos和可乐。两者之间的分隔可能很棒,但在一起却会造成混乱。像React这样的库试图通过消除异步和直接DOM操作来解决视图层中的这一问题。但是,管理数据状态由您自己决定。这是Redux进入的地方。

遵循Flux,CQRS和Event Sourcing的步骤之后,Redux尝试通过对更新的方式和时间施加一定的限制来使状态突变可预测。这些限制反映在Redux的三个原则中。


Redux如何提供帮助?如果我有一个datepicker作为组件的模态,并且可以从同一页面上的多个组件加载该组件,那么该datepicker组件如何知道要分派给Redux的Action?这是问题的本质,将一个组件中的动作链接到另一组件而不是其他任何组件。(考虑到datepicker它本身是模态组件内部的一个很深的组件)
vsync

@vsync不要将reudx视为单个静态值,redux实际上可以具有对象,数组...因此您可以将它们保存为对象或数组或存储中的任何内容,它可以是mapdispatchtoprops,每个都保存在对象数组中例如:[{{name:“ picker1”,value:“ 01/01/1970”},{name:“ picker2”,value:“ 01/01/1980”}],然后在parent中使用mapstatetoprops并将其向下传递给每个组件或您想要的任何位置,不确定是否能回答您的问题,但是看不到代码...如果它们位于单独的组中,您还可以提出更多详细信息...但是这完全取决于您要如何分组他们..
Alireza

问题不是关于redux什么以及可以存储什么,而是如何将操作深入传递到触发它的原因。如何做一个深刻的组件知道什么确切地需要被触发?因为在该示例中,我给出了一个通用组件,该组件应根据具体情况触发特定的减速器,因此它可以是不同的减速器,因为datepicker模态可用于任何组件。
vsync

5

这就是我处理的方式。
假设您有一个<select>代表月份,一个<select>代表Day。天数取决于所选的月份。

这两个列表均由第三个对象(左侧面板)拥有。这两个<select>也是leftPanel <div>的子代。
这是一个在LeftPanel组件中具有回调和处理程序的游戏

要对其进行测试,只需将代码复制到两个单独的文件中,然后运行index.html。然后选择一个月,看看天数如何变化。

dates.js

    /** @jsx React.DOM */


    var monthsLength = [0,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    var MONTHS_ARR = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];

    var DayNumber = React.createClass({
        render: function() {
            return (
                <option value={this.props.dayNum}>{this.props.dayNum}</option>
            );
        }
    });

    var DaysList = React.createClass({
        getInitialState: function() {
            return {numOfDays: 30};
        },
        handleMonthUpdate: function(newMonthix) {
            this.state.numOfDays = monthsLength[newMonthix];
            console.log("Setting days to " + monthsLength[newMonthix] + " month = " + newMonthix);

            this.forceUpdate();
        },
        handleDaySelection: function(evt) {
            this.props.dateHandler(evt.target.value);
        },
        componentDidMount: function() {
            this.props.readyCallback(this.handleMonthUpdate)
        },
        render: function() {
            var dayNodes = [];
            for (i = 1; i <= this.state.numOfDays; i++) {
                dayNodes = dayNodes.concat([<DayNumber dayNum={i} />]);
            }
            return (
                <select id={this.props.id} onChange = {this.handleDaySelection}>
                    <option value="" disabled defaultValue>Day</option>
                        {dayNodes}
                </select>
                );
        }
    });

    var Month = React.createClass({
        render: function() {
            return (
                <option value={this.props.monthIx}>{this.props.month}</option>
            );
        }
    });

    var MonthsList = React.createClass({
        handleUpdate: function(evt) {
            console.log("Local handler:" + this.props.id + " VAL= " + evt.target.value);
            this.props.dateHandler(evt.target.value);

            return false;
        },
        render: function() {
            var monthIx = 0;

            var monthNodes = this.props.data.map(function (month) {
                monthIx++;
                return (
                    <Month month={month} monthIx={monthIx} />
                    );
            });

            return (
                <select id = {this.props.id} onChange = {this.handleUpdate}>
                    <option value="" disabled defaultValue>Month</option>
                        {monthNodes}
                </select>
                );
        }
    });

    var LeftPanel = React.createClass({
        dayRefresh: function(newMonth) {
            // Nothing - will be replaced
        },
        daysReady: function(refreshCallback) {
            console.log("Regisering days list");
        this.dayRefresh = refreshCallback;
        },
        handleMonthChange: function(monthIx) {
            console.log("New month");
            this.dayRefresh(monthIx);
        },
        handleDayChange: function(dayIx) {
            console.log("New DAY: " + dayIx);
        },
        render: function() {
            return(
                <div id="orderDetails">
                    <DaysList id="dayPicker" dateHandler={this.handleDayChange} readyCallback = {this.daysReady} />
                    <MonthsList data={MONTHS_ARR} id="monthPicker" dateHandler={this.handleMonthChange}  />
                </div>
            );
        }
    });



    React.renderComponent(
        <LeftPanel />,
        document.getElementById('leftPanel')
    );

以及用于运行左面板组件index.html的HTML

<!DOCTYPE html>
<html>
<head>
    <title>Dates</title>

    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
    <script src="//fb.me/react-0.11.1.js"></script>
    <script src="//fb.me/JSXTransformer-0.11.1.js"></script>
</head>

    <style>

        #dayPicker {
            position: relative;
            top: 97px;
            left: 20px;
            width: 60px;
            height: 17px;
        }

        #monthPicker {
            position: relative;
            top: 97px;
            left: 22px;
            width: 95px;
            height: 17px;
        }

        select {
            font-size: 11px;
        }

    </style>


    <body>
        <div id="leftPanel">
        </div>

        <script type="text/jsx" src="dates.js"></script>

    </body>
</html>

如果您可以删除示例代码的80%并保持原样,那将是最好的选择。在此线程上下文中显示CSS无关紧要
vsync

3

我看到问题已经得到解答,但是如果您想了解更多详细信息,组件之间总共有3种通信情况

  • 案例1:父母与孩子之间的沟通
  • 案例2:孩子与父母的沟通
  • 情况3:不相关的组件(任何组件到任何组件)通信

1

当场景是组件无法在任何类型的父子关系之间进行通信时,扩展@MichaelLaCroix的答案,该文档建议设置全局事件系统。

<Filters /><TopBar />没有上述任何关系的情况下,可以这样使用一个简单的全局发射器:

componentDidMount -订阅活动

componentWillUnmount -取消订阅活动

React.js和EventSystem代码

EventSystem.js

class EventSystem{

    constructor() {
        this.queue = {};
        this.maxNamespaceSize = 50;
    }

    publish(/** namespace **/ /** arguments **/) {
        if(arguments.length < 1) {
            throw "Invalid namespace to publish";
        }

        var namespace = arguments[0];
        var queue = this.queue[namespace];

        if (typeof queue === 'undefined' || queue.length < 1) {
            console.log('did not find queue for %s', namespace);
            return false;
        }

        var valueArgs = Array.prototype.slice.call(arguments);

        valueArgs.shift(); // remove namespace value from value args

        queue.forEach(function(callback) {
            callback.apply(null, valueArgs);
        });

        return true;
    }

    subscribe(/** namespace **/ /** callback **/) {
        const namespace = arguments[0];
        if(!namespace) throw "Invalid namespace";
        const callback = arguments[arguments.length - 1];
        if(typeof callback !== 'function') throw "Invalid callback method";

        if (typeof this.queue[namespace] === 'undefined') {
            this.queue[namespace] = [];
        }

        const queue = this.queue[namespace];
        if(queue.length === this.maxNamespaceSize) {
            console.warn('Shifting first element in queue: `%s` since it reached max namespace queue count : %d', namespace, this.maxNamespaceSize);
            queue.shift();
        }

        // Check if this callback already exists for this namespace
        for(var i = 0; i < queue.length; i++) {
            if(queue[i] === callback) {
                throw ("The exact same callback exists on this namespace: " + namespace);
            }
        }

        this.queue[namespace].push(callback);

        return [namespace, callback];
    }

    unsubscribe(/** array or topic, method **/) {
        let namespace;
        let callback;
        if(arguments.length === 1) {
            let arg = arguments[0];
            if(!arg || !Array.isArray(arg)) throw "Unsubscribe argument must be an array";
            namespace = arg[0];
            callback = arg[1];
        }
        else if(arguments.length === 2) {
            namespace = arguments[0];
            callback = arguments[1];
        }

        if(!namespace || typeof callback !== 'function') throw "Namespace must exist or callback must be a function";
        const queue = this.queue[namespace];
        if(queue) {
            for(var i = 0; i < queue.length; i++) {
                if(queue[i] === callback) {
                    queue.splice(i, 1); // only unique callbacks can be pushed to same namespace queue
                    return;
                }
            }
        }
    }

    setNamespaceSize(size) {
        if(!this.isNumber(size)) throw "Queue size must be a number";
        this.maxNamespaceSize = size;
        return true;
    }

    isNumber(n) {
        return !isNaN(parseFloat(n)) && isFinite(n);
    }

}

NotificationComponent.js

class NotificationComponent extends React.Component {

    getInitialState() {
        return {
            // optional. see alternative below
            subscriber: null
        };
    }

    errorHandler() {
        const topic = arguments[0];
        const label = arguments[1];
        console.log('Topic %s label %s', topic, label);
    }

    componentDidMount() {
        var subscriber = EventSystem.subscribe('error.http', this.errorHandler);
        this.state.subscriber = subscriber;
    }

    componentWillUnmount() {
        EventSystem.unsubscribe('error.http', this.errorHandler);

        // alternatively
        // EventSystem.unsubscribe(this.state.subscriber);
    }

    render() {

    }
}

0

即使他们不是父母-孩子关系-也有这种可能性。对于Alt.JS(使用Alt-Container),有一个非常好的(对我个人而言)实现。

例如,您可以具有取决于组件详细信息中设置的补充工具栏。组件Sidebar与SidebarActions和SidebarStore连接,而Details是DetailsActions和DetailsS​​tore。

您可以像这样使用AltContainer

<AltContainer stores={{
                    SidebarStore: SidebarStore
                }}>
                    <Sidebar/>
</AltContainer>

{this.props.content}

这将保留商店(好吧,我可以使用“商店”代替“商店”道具)。现在,{this.props.content}可以是详细信息,具体取决于路线。可以说/ Details将我们重定向到该视图。例如,“详细信息”将具有一个复选框,如果将其选中,它将把补充工具栏元素从X更改为Y。

从技术上讲,它们之间没有关系,没有助焊剂就很难做到。但是,这很容易。

现在让我们转到DetailsActions。我们将在那里创建

class SiteActions {
constructor() {
    this.generateActions(
        'setSiteComponentStore'
    );
}

setSiteComponent(value) {
    this.dispatch({value: value});
}
}

和DetailsS​​tore

class SiteStore {
constructor() {
    this.siteComponents = {
        Prop: true
    };

    this.bindListeners({
        setSiteComponent: SidebarActions.COMPONENT_STATUS_CHANGED
    })
}

setSiteComponent(data) {
    this.siteComponents.Prop = data.value;
}
}

现在,这是魔术开始的地方。

如您所见,如果将使用setSiteComponent,将使用SideListenerActions.ComponentStatusChanged的bindListener。

现在在SidebarActions中

    componentStatusChanged(value){
    this.dispatch({value: value});
}

我们有这样的东西。它将在调用时分派该对象。如果将使用商店中的setSiteComponent,它将被调用(例如,您可以在onChange上的Button或其他期间在组件中使用)

现在在SidebarStore中,我们将拥有

    constructor() {
    this.structures = [];

    this.bindListeners({
        componentStatusChanged: SidebarActions.COMPONENT_STATUS_CHANGED
    })
}

    componentStatusChanged(data) {
    this.waitFor(DetailsStore);

    _.findWhere(this.structures[0].elem, {title: 'Example'}).enabled = data.value;
}

现在在这里您可以看到它将等待DetailsS​​tore。这是什么意思?或多或少意味着该方法需要先等待DetailsS​​tore更新,然后才能更新自身。

tl; dr一个商店正在监听商店中的方法,并将触发组件操作中的操作,该操作将更新其自己的商店。

希望它能以某种方式对您有所帮助。


0

如果您想探索组件之间进行通信的选项,并且感觉越来越难了,那么您可以考虑采用一种良好的设计模式:Flux

它只是规则的集合,这些规则定义了如何存储和更改应用程序范围内的状态以及如何使用该状态来呈现组件。

Flux实现有很多,Facebook的官方实现就是其中之一。尽管它被认为是包含大多数样板代码的代码,但是由于大多数事情都是显式的,因此更易于理解。

其他一些替代产品都是惊讶 fluxxor 变化无常的终极版


0

我曾经是您现在所在的位置,作为初学者,您有时会对执行此操作的反应方式感到不适应。我现在将尝试以同样的方式解决问题。

国家是沟通的基石

通常情况下,要指出的是您指出此三个组件时更改该组件状态的方式。

<List />:可能会根据过滤<Filters />器显示项目列表 :过滤器选项将改变您的数据。 <TopBar />:选项列表。

为了协调所有这些交互,您将需要一个更高的组件(称为App),该组件会将动作和数据传递给每个组件,因此看起来像这样

<div>
  <List items={this.state.filteredItems}/>
  <Filter filter={this.state.filter} setFilter={setFilter}/>
</div>

因此,何时setFilter调用它会影响filteredItem并重新渲染两个组件;如果还不清楚,我为您提供了一个带有复选框的示例,您可以将其检入单个文件:

import React, {Component} from 'react';
import {render} from 'react-dom';

const Person  = ({person, setForDelete}) => (
          <div>
            <input type="checkbox" name="person" checked={person.checked} onChange={setForDelete.bind(this, person)} />
            {person.name}
          </div>
);


class PeopleList extends Component {

  render() {

    return(
      <div>
       {this.props.people.map((person, i) => {
         return <Person key={i} person={person} setForDelete={this.props.setForDelete} />;
       })}
       <div onClick={this.props.deleteRecords}>Delete Selected Records</div>
     </div>
    );
  }

} // end class

class App extends React.Component {

  constructor(props) {
    super(props)
    this.state = {people:[{id:1, name:'Cesar', checked:false},{id:2, name:'Jose', checked:false},{id:3, name:'Marbel', checked:false}]}
  }

  deleteRecords() {
    const people = this.state.people.filter(p => !p.checked);

    this.setState({people});
 }

  setForDelete(person) {
    const checked = !person.checked;
    const people = this.state.people.map((p)=>{
      if(p.id === person.id)
        return {name:person.name, checked};
      return p;
    });

    this.setState({people});
  }

  render () {

    return <PeopleList people={this.state.people} deleteRecords={this.deleteRecords.bind(this)} setForDelete={this.setForDelete.bind(this)}/>;
  }
}

render(<App/>, document.getElementById('app'));

0

以下代码可帮助我设置两个同级之间的通信。设置是在render()和componentDidMount()调用期间在其父级中完成的。它基于https://reactjs.org/docs/refs-and-the-dom.html 希望对您有所帮助。

class App extends React.Component<IAppProps, IAppState> {
    private _navigationPanel: NavigationPanel;
    private _mapPanel: MapPanel;

    constructor() {
        super();
        this.state = {};
    }

    // `componentDidMount()` is called by ReactJS after `render()`
    componentDidMount() {
        // Pass _mapPanel to _navigationPanel
        // It will allow _navigationPanel to call _mapPanel directly
        this._navigationPanel.setMapPanel(this._mapPanel);
    }

    render() {
        return (
            <div id="appDiv" style={divStyle}>
                // `ref=` helps to get reference to a child during rendering
                <NavigationPanel ref={(child) => { this._navigationPanel = child; }} />
                <MapPanel ref={(child) => { this._mapPanel = child; }} />
            </div>
        );
    }
}

这是TypeScript,可能应该在您的答案中提及。好主意。
serraosays

0

奇怪的是没有人提到mobx。这个想法类似于redux。如果我有一个已订阅了多个组件的数据,则可以使用此数据来驱动多个组件。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.