ReactJS 两个组件通信

IT技术 javascript reactjs
2021-02-09 00:36:00

我刚开始使用 ReactJS,但对我遇到的一个问题有点卡住了。

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

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

6个回答

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

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

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

场景#1

您可以从<List />to传递一个处理程序<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

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

场景 #3 链接已失效,现在重定向到一个不相关的 React 文档页面。
2021-03-11 00:36:00
情景#2 ,使有很大的意义......直到你有危及设计(如果只,布局) -然后你意识到一个EventHub / PubSub的必要性。
2021-03-20 00:36:00
@woutr_be 不确定它是否符合您的要求,但在类似情况下,我们使用以下两个函数在子组件和父组件之间进行通信:- listenTo: function(eventName, eventCallback) { $( window.document).bind(eventName,eventCallback);} triggerEvent: function(eventName, params) { $.event.trigger(eventName, params);} 希望有帮助!(抱歉不能更好地格式化)
2021-03-29 00:36:00
以#2的好处是,他们只能依靠父母谁传递一个道具的每个组件:作为一个功能updateFilter<Filters />和阵列items<List />您可以在其他具有不同行为的父组件中一起或单独使用这些子组件。例如,如果您想显示动态列表但不需要过滤。
2021-04-01 00:36:00
对于场景 3,是否有推荐的方法?通过生成自定义合成事件的任何文档或示例?我在主要文档中没有找到任何内容。
2021-04-07 00:36:00

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

react

亲子直接沟通

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 允许在组件层次结构的根部保存状态,并且能够轻松地将此状态注入到非常深的嵌套组件中,而无需将 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>
);

消费者正在使用render prop/children 函数模式

查看此博客文章了解更多详细信息。

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


传送门

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

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

考虑以下:

<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 实现看起来像是试图以一种骇人听闻的方式进行事件溯源。

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 提出一种方法,邀请您在单个不可变对象中处理大部分状态。

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

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


玻璃钢

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

您可以尝试例如Rx-React

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

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


光热发电

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

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

传记

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

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

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


结论

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

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

如果您不了解事件溯源的概念,请查看这个非常有教育意义的博客:使用 apache Samza 将数据库翻过来,这是了解 Flux 为什么很好的必读之物(但这也适用于 FRP )

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

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

那么Redux为你做了什么?

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

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

查看下图,了解FluxRedux之间的一些差异......

Redux 和 Flux

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

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

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

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

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

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

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

@vsync 不认为 reudx 是一个单一的静态值,redux 实际上可以有对象、数组……所以你可以将它们保存为对象或数组或商店中的任何内容,它可以是 mapdispatchtoprops 并且每个都保存在对象数组中像: [{name: "picker1", value:"01/01/1970"}, {name: "picker2", value:"01/01/1980"}] 然后在 parent 中使用 mapstatetoprops 并将其传递给每个组件或任何你想要的地方,不确定它是否能回答你的问题,但没有看到代码......如果它们在不同的组中,你也可以反对更多的细节......但这完全取决于你想如何分组他们..
2021-03-14 00:36:00
问题不在于redux您可以存储什么以及您可以存储什么,而是如何将操作深入传递到需要触发它的内容。如何做一个深刻的组件知道什么确切地需要被触发?因为在示例中我给出了一个应该触发特定减速器的通用组件,具体取决于场景,所以它可能是不同的减速器,因为日期选择器模式可用于任何组件。
2021-03-21 00:36:00
如何Redux的帮助?如果我有一个datepicker(作为组件)的模态,并且该组件可以从位于同一页面上的多个组件加载,那么该datepicker组件如何知道将哪个Action分派到 redux?这是问题的本质,将一个组件b 中的动作链接到另一个组件,而不是任何其他组件(考虑到datepicker它本身是模态组件本身内的一个很深很深的组件)
2021-04-07 00:36:00

这就是我处理这件事的方式。
假设您有一个 <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>
如果您可以删除 80% 的示例代码并仍然保留您的观点,那就最好了。在此线程的上下文中显示 CSS 无关紧要
2021-04-02 00:36:00

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

  • 案例一:亲子沟通
  • 案例 2:子对父通信
  • 案例 3:不相关的组件(任何组件到任何组件)通信