[译]React高级话题之Refs and the DOM

栏目: IOS · Android · 发布时间: 5年前

内容简介:本文为意译,翻译过程中掺杂本人的理解,如有误导,请放弃继续阅读。原文地址:Refs and the DOMRefs提供了一种访问在render方法里面创建的React element或者原生DOM节点的方法。

本文为意译,翻译过程中掺杂本人的理解,如有误导,请放弃继续阅读。

原文地址:Refs and the DOM

正文

Refs提供了一种访问在render方法里面创建的React element或者原生DOM节点的方法。

在典型的React数据流中(自上而下的数据流),props是父组件与子组件打交道的唯一途径。为了与子组件交互,你需要给子组件传递一个新的props,促使它重新渲染。然而,有不少的场景需要我们在这种props主导型的数据流之外去命令式地去修改子组件里面的东西。被修改的子组件有可能是一个React component的实例,也有可能是一个原生DOM元素。对于这两种情况,React都提供了一个“安全舱口”去访问它们。

什么时候用Refs呢?

以下几个业务场景是挺合适的:

  • 手动管理聚焦(focus),文本选择或者视频,音频的回放。
  • 命令式地触发动画。
  • 与第三方的DOM类库进行整合。

如果能用声明式的方式去实现的,就不要用Refs去实现。举个例子说,能通过传递一个 isOpen prop给 Dialog 组件来实现弹窗的打开和关闭,就不用通过暴露“open”和“close”方法来实现弹窗的打开和关闭(在结合redux数据流的背景下,我不太认同这句话所表达的观点)。

不要滥用Refs

在实际开发的过程中,如果实现上遇到困难了,你的惯性思维可能是,先使用Refs实现了它(这个功能),管它三七二十一呢。在这种情况下,你不妨先让你的头脑冷静下来,以更缜密的思维去想想,能不能通过state来实现呢?如果能,state应该存放在组件树层级中的哪个层级呢?一般来说,公认为合适存放state的层级是顶级组件,也就是我们以前说的“container component”。查看提升你的state看看该怎么做。

注意,接下来的例子已经被更新过了。更新过后的例子使用了React.createRef()这个API。这个API在React 16.3中就引入了。假如你还在使用较早的React版本,那么我们推荐你使用callback refs来代替。

创建Refs

使用React.createRef()来创建Refs,并通过ref属性来attached to React element。一般的做法是,在组件的constructor里面,将通过React.createRef()来创建的Refs直接赋值给组件的一个实例属性。这样一来,当组件实例化的时候,你就可以在组件的其他地方使用这个引用。(也就是说,组件实例的某个属性是 引用着 通过React.createRef()来创建的Refs的,而这个Refs又是通过ref属性附加在原生DOM元素或者子组件实例上的。故通过这个实例属性,我们是可以访问到原生DOM元素或者子组件实例的)

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}
复制代码

访问Refs

上面讲了如何创建Refs,这一小节我们就来讲如何访问Refs。其实上面已经讲到了,我们通过组件的实例属性来保存着Refs的引用的。Refs是一个对象,它有一个current属性。我们正是通过这个current属性来访问原生DOM元素或者子组件实例的。

const node = this.myRef.current;
复制代码

current的属性值因不同的情况而异。这个不同的情况指的是ref属性所在的React component的类型。

  • DOM component。当ref属性是负载在DOM component上的话,通过React.createRef()来创建的Refs对象的current属性的值将会是原生的DOM元素。
  • custom component。custom component又可以分为class component 和function component。注意,因为function component是没有实例的,所以,它是不能通过这种方式来使用ref属性的(这里,这种方式是指上面“创建Refs”这一小节所说的方法。实际上function component也是可以消费ref属性的,这得使用后面提到的“ ref forwarding”技术)。所以这里,custom component指的是class component。当ref属性是挂载在custom component上的话,通过React.createRef()来创建的Refs对象的current属性将会指向custom component的组件实例。

下面的例子将会演示这两者之间的不同。

1)把ref属性挂载在DOM component上

下面的代码使用了ref属性来保存DOM元素的引用:

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // create a ref to store the textInput DOM element
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // Explicitly focus the text input using the raw DOM API
    // Note: we're accessing "current" to get the DOM node
    this.textInput.current.focus();
  }

  render() {
    // tell React that we want to associate the <input> ref
    // with the `textInput` that we created in the constructor
    return (
      <div>
        <input
          type="text"
          ref={this.textInput} />

        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
复制代码

当ref属性所在的那个组件挂载到页面后,current属性将会被赋值为指向原生DOM元素的引用。当这个组件被卸载后,current属性又会被重置为null。ref属性值的更新发生在组件生命周期函数 componentDidMountcomponentDidUpdate 之前。

2)把ref属性挂载在class component上

如果你想把上面的 <CustomTextInput> 包裹在父组件中,并想模拟组件挂载后就自动获取焦点。那么,我们可以通过ref去访问那个 <CustomTextInput> 的实例,通过这个实例的 focusTextInput 方法手动地让对应的组件内部的input框获得焦点。

class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

  componentDidMount() {
    // 在这里this.textInput.current指向的是
    // CustomTextInput组件的实例
    this.textInput.current.focusTextInput();
  }

  render() {
    return (
      <CustomTextInput ref={this.textInput} />
    );
  }
}
复制代码

注意, <CustomTextInput> 组件是class component时,这种写法才会有用。

class CustomTextInput extends React.Component {
  // ...
}
复制代码

3)Refs与function component的关联

注意,第三点,我们已经不使用“把ref属性挂载在xxx组件上”这个说法了。因为把ref属性直接挂载function component是没有什么用的。这里用“关联”一词,只不过在表达,Refs还是可以跟function component结合起来使用的。 再次强调,应为function component没有实例,所以不要直接在function component上挂载ref属性:

function MyFunctionComponent() {
  return <input />;
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }
  render() {
    // This will *not* work!
    return (
      <MyFunctionComponent ref={this.textInput} />
    );
  }
}
复制代码

如果你想在一个组件上直接挂载ref属性,那么你需要将这个组件转化为class component。这种转化,就像你如果需要使用生命周期函数或者state,你也会将组件转化为class component一样。

然而,正如我们上面提到的,ref属性还是可以跟function component结合使用的。如何结合法呢? 那就是在function component的实现代码体里面使用 。值得注意的是,即使在function component里面去使用,ref属还是要挂载在DOM component或者class component上:

function CustomTextInput(props) {
  // textInput must be declared here so the ref can refer to it
  let textInput = React.createRef();

  function handleClick() {
    textInput.current.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={textInput} />

      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );
}
复制代码

将DOM Refs暴露给父组件

在很少的情况下,你可能想直接从父组件来访问子组件里面的DOM元素。而一般情况下,我们是不推荐大家这么做的。因为这么做会打破组件封装的完整性。但是呢,偶尔这么做还是挺管用的。比如想手动让输入框获取焦点或者测量子组件中DOM元素的位置和尺寸大小。

如果你正在使用React 16.3或者以上,我们推荐你使用ref forwarding来满足你的需要。 Ref forwarding技术让子组件自己选择是否要把ref引用暴露给父组件(Ref forwarding lets components opt into exposing any child component’s ref as their own) 。你可以查阅一下ref forwarding文档。这里的例子将会给你演示如何地将子组件中DOM元素的ref引用暴露给父组件的。

如果你再用React 16.2或者以下,或者需要一个比ref fowarding更加灵活的方案,你可以使用 这个方案 。通过一个不同与ref的prop名,显式地将ref引用传递下去。

我们建议尽可能少地去暴露DOM元素给外界。但是,它确实可以是一个很有用的(访问原生DOM)“安全舱口”。注意,这种访问原生DOM的方案需要你往子组件中添加一些代码。假如你对子组件的实现没有控制权(即往里面插入一些代码),那么这个时候你只剩下最后的选择了-使用findDOMNode()。原则上,findDOMNode()已经不被鼓励使用了。在StrictMode下,这个API已经被废弃了。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

代码整洁之道:程序员的职业素养

代码整洁之道:程序员的职业素养

罗伯特·C.马丁 (Robert C.Martin) / 余晟、章显洲 / 人民邮电出版社 / 2016-9-1 / 49.00元

1. 汇聚编程大师40余年编程生涯的心得体会 2. 阐释软件工艺中的原理、技术、工具和实践 3. 助力专业软件开发人员具备令人敬佩的职业素养 成功的程序员在以往的工作和生活中都曾经历过大大小小的不确定性,承受过永无休止的压力。他们之所以能够成功,是因为拥有一个共同点,都深切关注创建软件所需的各项实践。他们将软件开发视为一种需要精雕细琢加以修炼的技艺,他们以专业人士的标准要求自己,......一起来看看 《代码整洁之道:程序员的职业素养》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换