首页 文章

ReactJS两个组件通信

提问于
浏览
265

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

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

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

11 回答

  • 294

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

  • 132

    OK, 有几种方法可以做到这一点,但我完全希望专注于使用商店使用 Redux 这使得你的生活更容易适应这些情况,而不是只为这种情况提供快速解决方案,使用纯粹的React将最终陷入困境随着应用程序的增长,真正的大应用程序和 Components 之间的通信变得越来越难......

    那么 Redux 为你做什么?

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

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

    请看下面的图表,看看 FluxRedux 之间的一些区别......

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

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

    随着JavaScript单页面应用程序的要求变得越来越复杂,我们的代码必须管理比以往更多的状态 . 此状态可以包括服务器响应和缓存数据,以及尚未持久保存到服务器的本地创建的数据 . UI状态的复杂性也在增加,因为我们需要管理活动路由,选定的选项卡,微调器,分页控件等 . 管理这个不断变化的状态很难 . 如果模型可以更新另一个模型,那么视图可以更新模型,该模型会更新另一个模型,而这反过来可能会导致另一个视图更新 . 在某些时候,您不再理解您的应用中发生了什么,因为您已经失去了对其状态的时间,原因和方式的控制 . 当系统不透明且不确定时,很难重现错误或添加新功能 . 好像这还不够糟糕,考虑新要求在前端产品开发中变得普遍 . 作为开发人员,我们期望处理乐观更新,服务器端呈现,在执行路由转换之前获取数据等等 . 我们发现自己试图管理以前从未有过的复杂性,我们不可避免地提出这样一个问题:是时候放弃了吗?答案是不 . 这种复杂性很难处理,因为我们混合了人类思维难以推理的两个概念:变异和异步性 . 我称他们为Mentos和Coke . 两者都可以很好地分离,但它们一起创造了一团糟 . 像React这样的库试图通过删除异步和直接DOM操作来解决视图层中的这个问题 . 但是,管理数据的状态取决于您 . 这是Redux进入的地方 . 在Flux,CQRS和Event Sourcing的步骤之后,Redux尝试通过对更新发生的方式和时间施加某些限制来使状态突变可预测 . 这些限制反映在Redux的三个原则中 .

  • 0

    我看到问题已经回答了,但是如果你想了解更多细节,总共有 3 cases of communication between components

    • 案例1:家长与儿童的沟通

    • 案例2:儿童与家长的沟通

    • 案例3:与无关的组件(任何组件的任何组件)通信

  • 1

    这是我处理这个问题的方式 .
    假设你有 Month 的<select>和 Day 的<select> . 天数取决于所选月份 .

    两个列表都由第三个对象(左侧面板)拥有 . 两个<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')
        );
    

    用于运行左面板组件的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>
    
  • 5

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

    • <Filters /><List /> 的子组件

    • <Filters /><List /> 都是父组件的子项

    • <Filters /><List /> 完全位于单独的根组件中 .

    可能还有其他我不想的场景 . 如果你的不适合这些,请告诉我 . 以下是我如何处理前两个场景的一些非常粗略的例子:

    场景#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 .

  • 0

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

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

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

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

  • 0

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

    例如,您可以使用侧栏,该侧栏依赖于组件详细信息中设置的内容 . 组件侧栏与SidebarActions和SidebarStore连接,而Details是DetailsActions和DetailsStore .

    你可以像那样使用AltContainer

    <AltContainer stores={{
                        SidebarStore: SidebarStore
                    }}>
                        <Sidebar/>
    </AltContainer>
    
    {this.props.content}
    

    哪个会保留商店(我可以使用“商店”代替“商店”道具) . 现在,可以是细节,具体取决于路线 . 让我们说/ Details将我们重定向到该视图 . 例如,如果要检查侧边栏元素,则将详细信息设置为将侧边栏元素从X更改为Y.

    从技术上讲,它们之间没有任何关系,如果没有变化,就很难做到 . 但这很容易 .

    现在让我们来看看DetailsActions . 我们将在那里创造

    class SiteActions {
    constructor() {
        this.generateActions(
            'setSiteComponentStore'
        );
    }
    
    setSiteComponent(value) {
        this.dispatch({value: value});
    }
    }
    

    和DetailsStore

    class SiteStore {
    constructor() {
        this.siteComponents = {
            Prop: true
        };
    
        this.bindListeners({
            setSiteComponent: SidebarActions.COMPONENT_STATUS_CHANGED
        })
    }
    
    setSiteComponent(data) {
        this.siteComponents.Prop = data.value;
    }
    }
    

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

    正如您所看到的,将使用bindListener到SidebarActions.ComponentStatusChanged,如果将使用setSiteComponent将使用它 .

    现在在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;
    }
    

    现在,您可以看到,它将等待DetailsStore . 这是什么意思?或多或少意味着此方法需要等待DetailsStore更新才能更新自身 .

    tl; dr One Store正在侦听商店中的方法,并将触发组件操作的操作,组件操作将更新其自己的商店 .

    我希望它可以帮到你不知何故 .

  • 0

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

    <Filters /><TopBar /> 的情况下没有任何上述关系,可以使用一个简单的全局 Launcher ,如下所示:

    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() {
    
        }
    }
    
  • 3

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

    反应

    家长/孩子直接沟通

    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 ),并且所有子级都是无状态的,只向父级报告内容 . 通过这种方式,您可以轻松"share"任何需要它的孩子的父级状态 .


    上下文

    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 .


    门户网站

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

    在这种情况下,您可以使用"portal" . 有不同的反应库使用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


    活动 Bus

    如React _98996中所述:

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

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


    Flux

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

    原始Flux实现看起来像是试图以hacky方式进行事件采购 .

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

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


    游标

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

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

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


    榆树建筑

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

    Redux的作者Dan Abramov使用React做了一个榆树架构 .

    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中使用它们 .

    James Long做了video解释它如何与React一起使用 .

    Sagas

    传奇是一种来自DDD / EventSourcing / CQRS世界的后端概念,也称为"process manager" . 它正在被redux-saga项目推广,主要是作为redux-thunk的替代品来处理副作用(即API调用等) . 目前大多数人认为它只是副作用的服务,但实际上更多的是解耦组件 .

    与全新的通信系统相比,它更像是对Flux架构(或Redux)的称赞,因为saga在最后发出了Flux动作 . 我们的想法是,如果您有widget1和widget2,并且希望它们分离,则无法从widget1触发针对widget2的操作 . 因此,您只使widget1触发针对其自身的操作,并且该传奇是一个侦听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是可能的!

  • 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'));
    
  • 3

    以下代码帮助我设置两个兄弟之间的通信 . 在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>
            );
        }
    }
    

相关问题