356

我刚开始使用 ReactJS 并且有点卡在我遇到的问题上。

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

我怎样才能让这 3 个组件相互交互,或者我是否需要某种全局数据模型来进行更改?

4

11 回答 11

339

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

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

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

情景#1

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

#1 的 JSFiddle →</a>

/** @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 →</a>

/** @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

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

于 2014-01-24T00:54:29.790 回答
207

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

反应

父母/孩子直接沟通

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

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

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


语境

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

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


事件总线

如 React文档中所述:

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

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


通量

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

最初的 Flux 实现看起来像是试图以一种 hacky 的方式进行事件溯源。

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

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


光标

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

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

2016 年编辑:似乎人们同意游标适用于较小的应用程序,但它不能很好地适用于复杂的应用程序。Om Next 不再有游标(虽然最初是 Om 引入了这个概念)


榆树架构

Elm 架构是Elm 语言提议使用的架构。即使 Elm 不是 ReactJS,Elm 架构也可以在 React 中完成。

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

Redux 和 Elm 都非常棒,并且倾向于在前端赋予事件源概念,两者都允许时间旅行调试、撤消/重做、重放......

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

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

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


玻璃钢

RxJS、BaconJS 或 Kefir 等库可用于生成 FRP 流以处理组件之间的通信。

您可以尝试例如Rx-React

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

CycleJS框架不使用 ReactJS 而是使用vdom。它与 Elm 架构有很多相似之处(但在现实生活中更容易使用,因为它允许 vdom 挂钩)并且它广泛使用 RxJs 而不是函数,如果您想将 FRP 与做出反应。CycleJs Egghead 视频很容易理解它是如何工作的。


CSP

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

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

传说

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

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

如果你有兴趣看看我的回答here


结论

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

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

如果你不知道事件溯源的概念,看看这个非常有教学意义的博客:Turning the database inside out with apache Samza,这是理解为什么 Flux 很好的必读之物(但这也适用于 FRP )

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

于 2015-07-22T12:48:28.467 回答
9

好的,有几种方法可以做到这一点,但我只想专注于使用Redux使用 store ,这会让你在这些情况下的生活更轻松,而不是只为这种情况提供快速解决方案,使用纯 React 最终会搞砸随着应用程序的增长,真正的大型应用程序和组件之间的通信变得越来越难......

那么Redux为您做了什么?

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

基本上,Redux 的想法最初来自 Flux,但发生了一些根本性的变化,包括通过只创建一个商店来拥有一个事实来源的概念......

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

Redux 和 Flux

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

从 Redux 文档中阅读这些话也可能会有所帮助:

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

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

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

这种复杂性很难处理,因为我们混合了人类大脑很难推理的两个概念:突变和异步。我称它们为曼妥思和可乐。两者都可以很好地分离,但它们在一起会造成混乱。像 React 这样的库试图通过删除异步和直接 DOM 操作来解决视图层中的这个问题。但是,管理数据的状态由您决定。这就是 Redux 进入的地方。

遵循Flux、CQRS 和 Event Sourcing的步骤,Redux 试图通过对更新发生的方式和时间施加某些限制来使状态突变可预测。这些限制体现在 Redux 的三个原则中。

于 2017-12-08T10:03:53.663 回答
6

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

这两个列表都属于第三个对象,即左侧面板。<select> 也是 leftPanel 的子元素 <div>这是一个在LeftPanel组件
中包含回调和处理程序的游戏。

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

日期.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>
于 2014-09-03T17:34:22.180 回答
4

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

  • 案例 1:父子之间的通信
  • 案例 2:孩子与父母的沟通
  • 案例 3:不相关的组件(任何组件到任何组件)通信
于 2016-11-12T21:02:52.010 回答
3

我曾经是你现在所处的位置,作为一个初学者,你有时会觉得反应方式是如何做到这一点的。我将尝试以我现在想的相同方式解决问题。

状态是沟通的基石

通常归结为在您指出三个组件的情况下更改此组件中状态的方式。

<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'));
于 2017-04-18T23:28:23.243 回答
2

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

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

componentDidMount- 订阅活动

componentWillUnmount- 取消订阅活动

React.js 和 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);
    }

}

通知组件.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() {

    }
}
于 2016-11-08T06:25:59.240 回答
0

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

它只是一组规则,定义了如何存储和改变应用程序范围的状态,并使用该状态来呈现组件。

Flux 的实现有很多,Facebook 的官方实现就是其中之一。虽然它被认为是包含最多样板代码的一个,但由于大部分内容都是明确的,因此更容易理解。

其他一些替代品flummoxfluxxorfluxible redux

于 2015-12-17T15:38:45.437 回答
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>
        );
    }
}
于 2017-10-18T14:00:29.993 回答
0

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

于 2018-09-07T17:35:12.643 回答
0

即使他们不是父子关系,也有这种可能性——这就是 Flux。有一个非常好的(对我个人而言)被称为 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,则将使用到 SidebarActions.ComponentStatusChanged 的​​ bindListener。

现在在 SidebarActions

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

我们有这样的东西。它将在调用时调度该对象。如果将使用 store 中的 setSiteComponent ,它将被调用(您可以在组件中使用,例如在 onChange on 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 One Store 正在监听 store 中的方法,并将触发来自组件 action 的 action,该 action 将更新其自己的 store。

我希望它能以某种方式帮助你。

于 2015-12-13T21:53:55.390 回答