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

news/2024/7/5 12:49:14

前言

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

原文地址:Refs and the DOM

正文

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

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

什么时候用Refs呢?

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

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

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

不要滥用Refs

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

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

Object 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已经被废弃了。

Callback Refs

除了上面提到的方法外,React也支持别的方式去设置refs,其中一个就叫“callback refs”。“callback refs”能够在refs赋新值和重置的时候给你更小粒度的控制权。

相比于使用createRef()来创建refs并将它传递给ref属性,“callback refs”传递给ref属性的是一个函数,准备来说是一个callback函数。在这个callback函数里面,你可以通过参数获得一个访问React component实例或者原生的DOM元素的引用。一般的做法,使用一个组件的实例属性来保存这个引用,方便到处使用:

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);

    this.textInput = null;

    this.setTextInputRef = element => {
      this.textInput = element;
    };

    this.focusTextInput = () => {
      // Focus the text input using the raw DOM API
      if (this.textInput) this.textInput.focus();
    };
  }

  componentDidMount() {
    // autofocus the input on mount
    this.focusTextInput();
  }

  render() {
    // Use the `ref` callback to store a reference to the text input DOM
    // element in an instance field (for example, this.textInput).
    return (
      <div>
        <input
          type="text"
          ref={this.setTextInputRef}
        />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}
复制代码

当组件挂载到页面后,React将会给这个callback函数传入个一个组件实例或者原生DOM的引用;当组件卸载后,React再次给callback函数传入null(这里说的“给callback函数传入”代表着一次callback的调用)。React保证refs的更新会在componentDidMount和componentDidUpdate调用之前发生。

正如由React.createRef()创建出来的对象类型refs一样,你可以将函数类型的refs一路传递下去。

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  render() {
    return (
      <CustomTextInput
        inputRef={el => this.inputElement = el}
      />
    );
  }
}
复制代码

在上面的例子中,父组件<Parent>将函数类型的refs以一个叫inputRef的prop传递给<CustomTextInput>组件。然后<CustomTextInput>组件用真正的ref属性传递给<input>组件。从结果来看,<Parent>的实例属性inputElement引用的正是我们想要访问的,<CustomTextInput>组件里面的<input> DOM元素。

Legacy API: String Refs

如果你之前有使用过React,那么你应该知道ref属性的值可以是一个字符串,比如:“textInput”。然后你可以通过this.refs.textInput来访问原生的DOM元素。我们强烈建议你不要再使用它了。因为它存在某些问题,同时它已经被遗弃了。在未来的某个版本中,很有可能会把它的实现从代码中移除掉。

注意,如果你现在正在使用this.refs.textInput来访问refs,那么我们推荐你使用callback pattern或者createRef API来代替。

使用callback refs的注意点

如果你把一个inline function直接赋值给ref属性的话,那么这个inline function将会被调用两次。第一次是以null来调用的。第二次才是以真实的DOM元素去调用。这是因为,每一次render方法被调用的时候,inline function都会创建一个新的函数实例给ref属性。所以,React需要先移除旧的ref callback,再来设置新的。为了避免这个问题,你可以通过把这个inline function绑定成class component的方法,使之成为引用,然后将这个引用赋值给ref属性。不过大多数情况下,inline function不会造成什么大问题的。


http://www.niftyadmin.cn/n/2746110.html

相关文章

yii2 restful 风格搭建(一)

2019独角兽企业重金招聘Python工程师标准>>> 最近在研究 yii2 如何搭建 restful api&#xff0c;将心得写下&#xff0c;欢迎一起讨论 使用yii2.0.13 advanced 版&#xff0c;将 frontend 整个作为 api 接口项目&#xff0c;除了接口的路由规则可以认证通过外&#…

JavaScript操作Xml

如果你做Web开发&#xff0c;那就难免要与JavaScript打交道。而JavaScript作为一种浏览器脚本&#xff0c;以其强大的功能及方便的操作&#xff0c;已经赢得了全部浏览器的支持。在前面我介绍了如何使用Sql操作Xml和使用C#操作Xml&#xff0c;这里我就简单的介绍一下使用JavaSc…

java 切面获取参数_Java互联网架构-Spring Aop底层源码分析

欢迎关注头条号&#xff1a;java小马哥周一至周日早九点半&#xff01;下午三点半&#xff01;精品技术文章准时送上&#xff01;&#xff01;&#xff01;精品学习资料获取通道&#xff0c;参见文末什么是AOPAOP(Aspect-OrientedProgramming&#xff0c;面向方面编程)&#xf…

利用Echarts画3D线框图一些细节

1.使用的类型 3D折线图&#xff0c;可以去官网上下载示例改一下就行 2..数据构成 数据实际上是三维点的集合&#xff0c;所以我的点是从后台接口传过来的&#xff0c;而且我的z和xy没有函数关系&#xff0c;所以不能像示例那样写function&#xff0c;但是看示例的代码&#…

量子物理历史趣述

量子力学是在普朗克为了克服经典理论解释黑体辐射规律的困难&#xff0c;引入能量子概念的基础上发展起来的&#xff0c;爱因斯坦提出光量子假说&#xff0c;作用能量子概念使量子理论得到进一步发展。波尔、德布罗意、薛定谔、波恩、狄拉克等人为解决量子力学遇到的困难&#…

CSS设置选中网页文字时的背景和颜色

在网页中&#xff0c;选中某段文字&#xff0c;默认的显示效果为&#xff1a; 可以看到&#xff0c;选中后文字颜色为白色&#xff0c;背景为蓝色。 现我们想设置&#xff0c;选中后文字为红色&#xff0c;背景为黄色。需要用到CSS伪类 ::selection。 IE9、Opera、Google、Chro…

怎么判断前轮左右的位置_如何判断汽车左右轮位置?

展开全部一、10点一刻左右轮定位法&#xff1a;要求&#xff1a;自然驾驶姿态&#xff1b;直行&#xff1b;手62616964757a686964616fe59b9ee7ad9431333363396365握方向为10点(左)15分(右)&#xff0c;姆指向前&#xff1b;判断位置&#xff1a;直行时车头前近处可视地面&#…

IHttpHandler接口 处理静态页面

这样设置后&#xff0c;在本地运行。就可以出来效果了。但在远程访问&#xff0c;还是不行。这跟IIS的机制有关。普通的html,图片等等文件&#xff0c;IIS会自行处理&#xff0c;而不会交给asp.net处理的。所以你要在IIS中&#xff0c;更改该站点的配置。将.html的后缀名添加进…