【问题标题】:What's a bad practice with refs in React?React 中的 refs 有什么不好的做法?
【发布时间】:2016-11-12 22:27:38
【问题描述】:

我开始学习 React。不同网站的一些人告诉大家,使用 refs 是一种不好的做法(是的,完全使用它们)。

真正的处理是什么?我将它附加到子组件(这样我可以访问内部的东西)是不是很糟糕?

谢谢!

【问题讨论】:

  • 是的,它破坏了封装。为什么要通过 ref 访问子组件?你应该只通过道具在组件之间说话。如果它变得混乱,添加 Redux 来帮助管理状态。
  • @ZekeDroid 主要是做一些非数据驱动的活动。 “刷新”是最常见的一种。另一种方法是使用某种合成状态传递给孩子(如hasToRefresh 或类似的)。至少可以说也不理想。
  • @ZenMaster 必须刷新 IS 数据驱动。您只会在某些状态更改后刷新,因此您不应该直接访问 DOM。一旦您手动刷新某些内容,您的状态就不再受控制,您必须开始阅读 DOM 才能了解内容(即,它是否完成刷新?)
  • @ZekeDroid 绝对不是。父级上可能只有一个“刷新”按钮。在这一点上 - 需要刷新孩子。
  • 老实说,我喜欢你的观点。我讨厌把事情看成表面上的价值,即使在这种情况下我已经亲身体验过,我很想听听更多。您介意回答这个问题并举一个您认为可以使用 refs 的示例吗?

标签: javascript reactjs refs


【解决方案1】:

React 要求你 think the react way 并且 refs 是一种你几乎不需要使用的 DOM 的后门。为了彻底简化,react 的思维方式是,一旦状态发生变化,您将重新渲染 所有 依赖于该状态的 UI 组件。 React 将确保只更新 DOM 的正确部分,使整个事情变得高效并隐藏 DOM(有点)。

例如,如果您的组件托管一个 HTMLInputElement,那么在 React 中,您将连接一个事件处理程序来跟踪对输入元素的更改。每当用户键入一个字符时,事件处理程序就会触发,并且在您的处理程序中,您将使用输入元素的新值更新您的状态。对状态的更改会触发托管组件重新呈现自身,包括具有新值的输入元素。

这就是我的意思

import React from 'react';
import ReactDOM from 'react-dom';

class Example extends React.Component {

    state = {
      inputValue: ""
    }

    handleChange = (e) => {
      this.setState({
        inputValue: e.target.value
      })
    }

    render() {
        const { inputValue } = this.state
        return ( 
          <div>
            /**.. lots of awesome ui **/
            /** an input element **/
            <input value={inputValue} onChange={this.handleChange}} />
            /** ... some more awesome ui **/
          </div>
       )
  }
}


ReactDOM.render( <Example />, document.getElementById("app") );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>


<div id="app">
</div>

注意任何时候输入值发生变化,处理程序被调用,setState 被调用并且组件将重新完全呈现自己。

考虑 refs 通常是不好的做法,因为您可能会想只使用 refs 并按照 JQuery 方式做事,这不是 React 架构/思维模式的意图。

真正更好地理解它的最好方法是构建更多的 React 应用和组件。

【讨论】:

  • 您好,谢谢您的回答。我理解这种反应思维方式,我确实采用构建容器 + 组件的方式。但是,几天前我遇到了这个问题 - 我有一个 Container 组件,它有其他组件作为子组件。这个子组件有 2 个单选按钮和 2 个按钮(+ 和 -)。因此,容器(父级)必须检查选中了哪些单选按钮以及哪个按钮(+ 或 -)。所以我认为最好的方法是将 ref 放在孩子身上,这样我就可以跟踪单选按钮。
  • @skav: 嘿,如果我运行你的代码,它的抛出错误......你能解决它吗:(
  • 我在使用动态表单生成器时有一个类似的实例。基本上,工作流程是我进行 API 调用并根据用户交互取回字段列表。我相信我可以为每个视图制作一个特定的表单,但我只是在谈论 30-40 种不同的表单视图就维护而言,t 认为可行。我发现在表单上使用 1 ref 就足够了,因为我可以解析表单并获取所有值。我不确定这是否仍然“正确”,但我认为这是一个有效的用例。
  • 在上述情况下输入元素不会在 DOM 级别被替换是非常重要的,因为 React 检测到它与状态同步。因此,它保持焦点、光标位置、打开自动完成等。
【解决方案2】:

嗯...不确定它是否适合作为答案,但它变得太长,无法发表评论。

假设您有一个仪表板,其中包含显示系统各种状态的小部件。每个小部件都有自己的数据源和自己的控件。也许他们甚至不时刷新。但是,当用户想要查看系统的更新视图时,仪表板级别有一个“刷新”按钮。实现这样的按钮并非易事。

如果您在 Redux 应用程序中 - 您可以选择 - 为所有孩子“伪造”dispatch('refresh')。为了解耦它,每个小部件在加载时注册一个动作,以便父级简单地遍历所有动作并在需要命令性刷新时触发它们。

在非 Redux/Flux 系统中,或者在更复杂/动态的场景中,这可能是不可能的,也可能不是那么简单。 然后,在所有小部件上公开refresh 方法,然后从父级(或者更确切地说,所有者)访问它可能会更好,更复杂的是:

class WidgetA extends React.Component {
    refresh() {
        console.log('WidgetA refreshed');
    }

    render() {
      return (
        <h3>WidgetA</h3>
      );
    }
}  

class WidgetB extends React.Component {
    refresh() {
        console.log('WidgetB refreshed');
    }

    render() {
      return (
        <h3>WidgetB</h3>
      );
    }
}  

class Dashboard extends React.Component {
    constructor() {
        super();

        this.onRefresh = this.handleRefresh.bind(this);
        this.onRegister = this.handleRegister.bind(this);
        this.widgets = [];
    }

    handleRegister(widget) {
        this.widgets.push(widget);
    }

    handleRefresh() {
        this.widgets.forEach((widget) => {
            widget.refresh();
        });
    }

    render() {
        return (
            <div>
                <button onClick={this.onRefresh}>Refresh</button>
                <hr />
                <WidgetA ref={this.onRegister} />
                <WidgetB ref={this.onRegister} />
            </div>
        );
    }
}

当然是这样,不那么冗长。

作为旁注,我赞成 @skav 的回答并认为这些情况 应该避免。这是一个例外。

CodePen Example

【讨论】:

  • 有趣的例子。我可以看到,如果没有 redux,这将是要走的路。使用 redux 就像您说的那样,就像确保每个小部件的 reducer 案例都是 REFRESH_ALL 之类的案例一样简单,仅由高级仪表板触发。但是,是的,关于在可能的情况下尽量避免裁判的最后一点很好
  • 我不确定是否所有孩子都需要发送“刷新”操作。我认为父组件可以调度“刷新”动作,每个子组件减速器可以响应“刷新”动作并返回必要的状态。
  • @dblay 这些是单独开发的,并且可能已交付(pacakge-wise)小部件。让他们的父母“为他们”进行刷新会有点问题,因为它必须密切了解什么是行动发送一个(它们可能是一个复杂的构成刷新的动作的 thunk-net)。
猜你喜欢
  • 2019-12-18
  • 1970-01-01
  • 2018-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-27
相关资源
最近更新 更多