ReactJS两个组件通信

时间:2014-01-22 14:40:59

标签: reactjs

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

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

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

11 个答案:

答案 0 :(得分:305)

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

  1. <Filters /><List />
  2. 的子组件
  3. <Filters /><List />都是父组件的子项
  4. <Filters /><List />完全位于不同的根组件中。
  5. 可能还有其他一些我没想过的场景。如果你的不适合这些,请告诉我。以下是我如何处理前两种情况的一些非常粗略的例子:

    场景#1

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

    JSFiddle for #1 →

    /** @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 />分开。

    JSFiddle for #2 →

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

    当组件无法在任何类型的父子关系之间进行通信时,documentation recommends setting up a global event system

答案 1 :(得分:147)

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

阵营

家长/孩子直接沟通

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)以及所有孩子都可以做得更好无国籍,只向父母报告事情。通过这种方式,您可以轻松地分享&#34;任何需要它的孩子的父母的状态。

上下文

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

消费者正在使用render prop / children function pattern

查看此blog post了解详情。

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

门户

当您希望将两个组件靠近在一起以使它们与简单的功能进行通信时使用门户网站,例如普通的父/子,但您不希望这两个组件拥有父/ DOM中的子关系,因为它暗示了视觉/ CSS约束(如z-index,opacity ......)。

在这种情况下,您可以使用&#34;门户网站&#34;。使用portals有不同的反应库,通常用于modals,弹出窗口,工具提示......

请考虑以下事项:

<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>

More details here

您可以在某处定义一个插槽,然后从渲染树的另一个位置填充插槽。

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 documentation中所述:

  

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

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

之类的内容

通量

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

原始Flux实施看起来像是一种以黑客方式进行事件采购的尝试。

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

Egghead的Redux video tutorial非常好,并解释它是如何在内部工作的(它真的很简单)。

光标

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

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

编辑2016 :似乎人们认为游标适用于较小的应用程序,但它在复杂的应用程序上无法很好地扩展。 Om Next不再有游标(虽然最初引入概念的是Om)

榆树建筑

Elm architecture是一种建议由Elm language使用的架构。即使Elm不是ReactJS,Elm架构也可以在React中完成。

Redux的作者Dan Abramov使用React做了implementation Elm架构。

Redux和Elm都非常棒,并且倾向于在前端授权事件采购概念,包括时间旅行调试,撤消/重做,重播...

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

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

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

FRP

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

您可以尝试举例Rx-React

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

CycleJS框架不使用ReactJS但使用vdom。它与Elm架构有许多相似之处(但在现实生活中更容易使用,因为它允许vdom挂钩)并且它广泛使用RxJ而不是功能,如果你想使用FRP,它可以成为一个很好的灵感来源反应。 CycleJs Egghead videos很高兴了解它是如何运作的。

CSP

CSP(Communicating Sequential Processes)目前很受欢迎(主要是因为Go / goroutines和core.async / ClojureScript),但您也可以在JS-CSP的javascript中使用它们。

詹姆斯·朗已经做了一个video,解释了它如何与React一起使用。

萨加斯

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

与全新的通信系统相比,它更像是对Flux架构(或Redux)的称赞,因为该传奇最终会发出Flux动作。我们的想法是,如果您有widget1和widget2,并且希望它们分离,则无法从widget1触发针对widget2的操作。所以你让widget1只激活那些以自己为目标的动作,而传奇是一个&#34;后台进程&#34;侦听widget1操作,并可能调度目标widget2的操作。传奇是两个小部件之间的耦合点,但小部件仍然是分离的。

如果您有兴趣,请查看my answer here

结论

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

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

如果您不了解事件采购概念,请查看这个教学博客:Turning the database inside out with apache Samza,了解为什么Flux很好(但这可能适用于FRP)是必读的以及)

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

答案 2 :(得分:5)

这是我处理这个问题的方式 假设你有&lt; select&gt; 和&lt; select&gt; 。 天数取决于所选的月份。

两个列表都由第三个对象左侧面板拥有。两者都&lt; select&gt;也是leftPanel&lt; div&gt;的孩子 这是一个带有回调和 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')
    );

用于运行左面板组件的HTML 的的index.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>

答案 3 :(得分:5)

好的,有几种方法可以做到这一点,但我完全希望专注于使用 Redux 来使用商店,这样可以让您的生活更轻松,而不是给您只有这种情况下的快速解决方案,使用纯React将最终陷入真正的大型应用程序中,并且随着应用程序的增长,组件之间的通信变得越来越难......

那么 Redux 为你做什么?

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

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

请看下面的图表,看看 Flux Redux 之间的一些差异......

Redux and Flux

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

从Redux文档中读取这些单词可能有助于开始:

  

正如JavaScript单页面应用程序的要求一样   变得越来越复杂,我们的代码必须管理更多的状态   之前。此状态可以包括服务器响应和缓存数据,   以及尚未持久保存的本地创建数据   服务器。正如我们所需,UI状态的复杂性也在增加   管理活动路线,选定的标签,微调器,分页控件,   等等。

     

管理这个不断变化的状态很难。如果模型可以更新   另一个模型,然后视图可以更新模型,更新另一个模型   模型,反过来,这可能会导致另一个视图更新。在某些   请注意,您不再了解您的应用中发生的事情   失去对其状态的时间,原因和方式的控制。当一个系统   是不透明和不确定的,很难再现错误或添加   新功能。

     

好像这还不够糟糕,请考虑新的要求   在前端产品开发中很常见。作为开发人员,我们是   期望处理乐观更新,服务器端呈现,提取   执行路由转换之前的数据,等等。我们发现自己   试图管理我们从未必须处理的复杂性   之前,我们不可避免地问了一个问题:是时候放弃了吗?该   答案是否定的。

     

这种复杂性很难处理,因为我们正在混合两个概念   这是人类思维难以推理的原因:突变和   异步性。我称他们为Mentos和Coke。两者都很棒   分离,但他们在一起造成一团糟。像React这样的图书馆   尝试通过删除两者来解决视图层中的此问题   异步和直接DOM操作。但是,管理状态   您的数据由您自己决定。这是Redux进入的地方。

     

按照 Flux,CQRS和事件采购的步骤, Redux   试图通过强加某些来预测状态突变   对更新发生方式和时间的限制。这些限制   反映在Redux的三个原则中。

答案 4 :(得分:3)

我看到问题已经得到解答,但如果您想了解更多详细信息,那么组件之间总共会有<3个以上的通信案例

  • 案例1:家长与儿童的沟通
  • 案例2:孩子与家长沟通
  • 案例3:与无关的组件(任何组件的任何组件)通信

答案 5 :(得分:1)

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

如果<Filters /><TopBar />没有任何上述关系,可以像这样使用简单的全局发射器:

componentDidMount - 订阅活动

componentWillUnmount - 取消订阅活动

React.js and EventSystem code

<强> 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() {

    }
}

答案 6 :(得分:0)

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

例如,您可以使用侧栏,该侧栏依赖于组件详细信息中设置的内容。组件侧栏与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,则会使用bindListener到SidebarActions.ComponentStatusChanged。

现在在SidebarActions中

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

我们有这样的事情。它将在电话中调度该对象。并且如果将使用商店中的setSiteComponent将调用它(您可以在组件中使用,例如在按钮上的onChange期间使用的任何东西)

现在在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;博士 One Store正在侦听商店中的方法,并将触发组件操作的操作,组件操作将更新其自己的商店。

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

答案 7 :(得分:0)

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

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

有许多Flux实现,而Facebook's official implementation就是其中之一。虽然它被认为是包含大多数样板代码的那个,但它更容易理解,因为大多数事情是明确的。

其他一些替代方案是flummox fluxxor fluxibleredux

答案 8 :(得分: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'));

答案 9 :(得分: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>
        );
    }
}

答案 10 :(得分:0)

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