【问题标题】:What is the proper way of communicating between components in react? Why not using refs everywhere?react中组件之间通信的正确方法是什么?为什么不到处使用 refs?
【发布时间】:2019-09-28 09:44:21
【问题描述】:

我知道这个问题被问了很多次,但到目前为止我找不到合理的答案。

如果我想制作一个应用程序,它有两个组件:按钮和显示时间的计时器。单击按钮时,计时器将启动/暂停/重置。对于我作为 React 的初学者来说,最明显的做法是在计时器内创建 start() pause() reset() 方法,然后单击按钮调用此函数(使用 refs)。

但根据 React 文档和所有教程,应该避免这种情况。应该是 start() pause() reset() 方法被保存在最顶层的组件(“按钮”和“计时器”组件的父级)中,它们应该从那里控制计时器。

但是如果我想要 10 个不同的计时器(相同的组件不同的设计)怎么办?在定时器内部定义 start、pause、reset 方法不就是 DRY 编码,所以顶层组件没有 30 个方法吗?

这是一个典型的例子,请不要给我解决方法,我想了解将所有逻辑都放在顶层组件中的原因,从而使子组件无法作为独立组件重用。

【问题讨论】:

    标签: javascript reactjs components ref


    【解决方案1】:

    在我看来你不必在任何顶级组件中都有定时器方法,定时器必须只知道按钮已被点击,在这种情况下你通过道具将点击事件传递给定时器,即假设定时器和按钮在同一个函数中呈现。这意味着也没有裁判。当计时器组件收到新的道具时,它将重新渲染。

    这同样适用于计时器组件中的多个计时器。

    【讨论】:

    • 是的,这听起来很合理:我将点击事件传递给计时器。因此,我创建了一个道具值,比如说布尔值,并在 componentDidUpdate() 内部的计时器中检查该布尔值是否已更改(为真或假,没关系 - 更改很重要)。谢谢,这是有道理的。但是这种编码方式看起来比使用 refs 复杂得多,而且不是很直观,我的意思是 React 可以有更好的处理事件的解决方案。
    【解决方案2】:

    鉴于您的示例,在层上“向上”移动实现的要点是能够在应用程序的不同部分使用相同的计时器组件,其中 start() 每次的行为可能都不同。 组件本身无法知道每个特定的“开始”事件应该做什么,因为组件在任何时候都不应该知道它是如何使用的。

    将回调视为事件处理程序,调用者在其中传递事件发生时应执行的操作

    这实际上强制执行 DRY,因为您需要 1 个计时器(启动、暂停、停止、计时器到达的逻辑可能等等),然后每个调用者在这些事件发生时连接他想做的事情。

    如果你想到 React 本身的结构,以及它的生命周期事件,它就是一回事。例如,“ComponentDidMount”与计时器的“开始”没有什么不同,只是上下文不同。概念思想是一样的

    编辑:重要的是要考虑在您的组件中为每个人保持固定的内容以及应该可配置的内容。 例如,您的计时器将始终每秒滴答一次,对吗?因此,时间跟踪部分对于您的应用程序将始终保持不变,它应该驻留在组件中。组件应该有一个 Start,Pause,Stop 定义的方法集,因为 stop 总是会停止计数,start 总是会恢复它。 但是您需要公开一个 OnStart、OnStop、OnPause,以便调用者可以使用您的计时器挂钩他们需要的逻辑。我不知道我能不能解释得足够好......

    编辑 2: 例如检查下面的计时器的 Start 方法:

     function Start(){
        // set the interval here or wtver way you will have to measure ticks
    // ...
    // ...
        if(this.props.OnStart)
         this.props.OnStart();
        }
    

    现在当然调用者将不负责实现实际的滴答声(尽管有一天有人可能会来请求一个与以光速或其他东西旅行的人相关的跟踪器,所以滴答声也可以配置...... ),但如果他们确实需要在 start 事件上有一个挂钩,并且他们会这样做,那么您可以通过这种方式实现它。 组件不知道回调函数的作用(业务逻辑向上移动),但是调用者现在不关心通用的“每秒一次滴答”

    编辑 3: 好的,所以.. 如果单击按钮,我们将启动计时器,然后调用道具给出的任何钩子

     function OnEveryClick(){
        // here you will write anything you want your component to do every time the button is clicked, regardless of who is the caller, like our previous Start method
    this.Start();
        }
    
    function ButtonClicked(){
    // first you call your method every time
     this.OnEveryClick();
    // then if the caller also wants to do some stuff, you evaluate their function aswell
      if(this.props.OnSubmit)
        this.props.OnSubmit();
    }
    

    然后在渲染上你做这样的事情

    <button onClick={this.ButtonClicked.bind(this)}>
    

    【讨论】:

    • "要点是能够在应用程序的不同部分使用相同的计时器组件" - 当我想要两个计时器时,我需要在顶层修改计数的函数每个计时器的时间分别。但如果计时功能在计时器内,那么我可以通过复制再次重用计时器。如果我把所有逻辑都放在最上面,那么应该显示时间的计时器组件完全无法复制它,因为它的逻辑在应用程序的主要组件中。
    • 哦不,您需要将“逻辑”部分和“特定业务”部分分开。这意味着“计算时间”功能应该在组件内,因为它不会改变!时间对我们来说总是以同样的速度流逝,因此向上移动这部分组件是没有意义的。但是你应该有一个 OnStart 例如调用者将能够挂钩他们自己的方法
    • "但是你需要公开一个 OnStart,OnStop,OnPause" -> 谢谢,所以唯一的问题是你如何做到这一点? Refs 让您可以访问这些方法。或者您按照 salomari87 的回答中的建议进行操作,将道具传递给计时器,然后在计时器中等待该道具更改?
    • 好的,谢谢,但问题是计时器如何知道开始计时?你如何在计时器内触发这个滴答声?
    • 好的,现在我看到“你的问题”,问题是定时器组件与按钮平行,所以你的 this.start() 必须告诉定时器组件运行它的 start()方法并开始打勾。在我的想法中,按钮和计时器都是子组件。您必须使用 ref 对吗?
    猜你喜欢
    • 2015-03-23
    • 2012-06-30
    • 2020-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-21
    • 2018-12-20
    相关资源
    最近更新 更多