[译]react refs and dom(v16.3.1)

作者: mytac | 来源:发表于2018-04-10 21:11 被阅读48次

    refs提供了可以在render方法中访问dom节点和创建的react元素的方法。

    为了良好的阅读体验,请查看github原文

    在典型的react数据流中,props是父子组件通信的唯一方式,要修改子组件,需要新props重新渲染。然而有一些情况需要在数据流外修改子组件。

    何时使用Refs

    1. 管理focus,文本选择和媒体播放
    2. 触发命令式动画
    3. 集成第三方dom库

    避免过度使用

    你的第一反应时用refs在app中“搞点事情”,如果是这样,请多加时间考虑组件的层次中应该需要哪些state。通常情况下,用到state的地方应在更高层次(意思是在更外面的父组件中)。

    创建Refs

    使用React.createRef()创建Refs,并通过ref属性关联到react组件中。当组件被构造时,Refs通常被分配给一个实例属性,以便它们可以在整个组件中被引用。

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

    访问refs

    当ref被传递给render中的元素时,对ref的当前属性可以访问该节点的引用。

    const node = this.myRef.current;
    

    refs的值根据节点类型而有所不同:

    1. 当ref属性应用在html元素上时,在构造函数中使用React.createRef()创建的ref接收底层DOM元素作为其当前属性。
    2. 当ref属性应用在自定义的类组件上时,ref对象接受挂载组件作为当前值。
    3. 不能在function构造的组件中使用ref属性,因为他们没有实例

    下面的例子中演示了不同的例子:

    1.在dom元素上添加ref

    此代码使用ref来存储对DOM节点的引用:

    class CustomTextInput extends React.Component {
      constructor(props) {
        super(props);
        // 创建一个ref来存储textInput DOM元素
        this.textInput = React.createRef();
        this.focusTextInput = this.focusTextInput.bind(this);
      }
    
      focusTextInput() {
        // 使用原始DOM API显式地关注文本输入
        // 注意:我们访问“current”来获取DOM节点
        this.textInput.current.focus();
      }
    
      render() {
        // 告诉React,我们想要将我们在构造函数中创建的<input> ref与“textInput”关联
        return (
          <div>
            <input
              type="text"
              ref={this.textInput} />
    
            <input
              type="button"
              value="Focus the text input"
              onClick={this.focusTextInput}
            />
          </div>
        );
      }
    }
    

    React将在组件装载时为当前属性分配DOM元素,并在卸载时将其分配回null。ref更新发生在componentDidMountcomponentDidUpdate生命周期挂钩之前。

    2.添加refs到类组件上

    如果我们想要封装上面的CustomTextInput以模拟在组件挂载后立即点击它,我们可以使用ref来访问自定义的input并手动调用其focusTextInput方法。

    // 注意他只在类组件上工作
    class AutoFocusTextInput extends React.Component {
      constructor(props) {
        super(props);
        this.textInput = React.createRef();
      }
    
      componentDidMount() {
        this.textInput.current.focusTextInput();
      }
    
      render() {
        return (
          <CustomTextInput ref={this.textInput} />
        );
      }
    }
    

    3.refs和function组件

    不能在function组件上使用refs,因为他们没有实例

    function MyFunctionalComponent() {
      return <input />;
    }
    
    class Parent extends React.Component {
      constructor(props) {
        super(props);
        this.textInput = React.createRef();
      }
      render() {
        // This will *not* work!
        return (
          <MyFunctionalComponent ref={this.textInput} />
        );
      }
    }
    

    如果你需要,可以把它转换成类组件。

    但是,只要引用DOM元素或类组件,就可以在functional组件中使用ref属性:

    function CustomTextInput(props) {
      // textInput 必须在这里声明,目的是让ref引用他
      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节点的大小或位置有用。

    虽然你能把refs添加到子组件上,但这这并不是一个理想的结果,因为只会得到一个组件实例而不是dom节点。另外,它也不会对functional component起到作用。

    一些情况下我们推荐暴露一个特别的prop给子组件。这个prop可以命名为ref(e.g inputRef)。子组件可以把这个ref转发到dom节点作为ref属性。这让父节点通过中间的组件将其ref传递给子节点节点。

    这适用于类,也适用于functional组件。

    function CustomTextInput(props) {
      return (
        <div>
          <input ref={props.inputRef} />
        </div>
      );
    }
    
    class Parent extends React.Component {
      constructor(props) {
        super(props);
        this.inputElement = React.createRef();
      }
      render() {
        return (
          <CustomTextInput inputRef={this.inputElement} />
        );
      }
    }
    

    在上面的例子中,父类通过它的类属性this.inputElement作为一个prop传递到CustomTextInput子组件中,CustomTextInput将相同的ref传递给<input>。因此,父组件中的this.inputElement.current会被设置为对应于CustomTextInput中的dom节点。

    注意上例中的inputRef并没有什么特别的意义,他只是一个普通的prop而已。然而,在<input>中使用ref属性是非常重要的,他会告诉如何将ref添加到<input>的dom节点。

    即使CustomTextInput是一个functional组件,他也可以工作。与ref只能为dom元素和class组件的特殊属性这一点不同,prop没有限制。

    这种模式的另一个好处是它可以深入地处理多个组件。比如,父组件中不需要dom节点,但在父组件的父组件(以下称为祖父组建)中需要访问他。然后,我们可以让祖父组件为父组件提供inputRefprop,并使父组件传递给CustomTextInput

    function CustomTextInput(props) {
      return (
        <div>
          <input ref={props.inputRef} />
        </div>
      );
    }
    
    function Parent(props) {
      return (
        <div>
          My input: <CustomTextInput inputRef={props.inputRef} />
        </div>
      );
    }
    
    class Grandparent extends React.Component {
      constructor(props) {
        super(props);
        this.inputElement = React.createRef();
      }
      render() {
        return (
          <Parent inputRef={this.inputElement} />
        );
      }
    }
    

    这里,祖父组件首先指定ref为this.inputElement,他作为一个正常的prop传递给inputRef,之后父组件又把他作为prop传递给CustomTextInput。最终,CustomTextInput读取inputRef prop 并将传递的ref作为ref属性附加到<input>。因此,祖父组件中的this.inputElement.current将被设置为与CustomTextInput<input>元素对应的DOM节点。

    如果可能,我们建议不要暴露dom节点,但他可能是一个有用的应急出口??(这句不太明白,原文:When possible, we advise against exposing DOM nodes, but it can be a useful escape hatch. )请注意,这种方法需要您向子组件添加一些代码。如果你完全不能控制子组件的实现,你最终的选择是findDOMNode(),但并不推荐这个方法。

    refs回调

    react也会支持另一种方式来设置refs,这称为callback refs,它可以在设置refs和取消设置时能更好的控制细节。

    您不必传递由createRef()创建的ref属性,而是传递一个函数。该函数接收React组件实例或HTML DOM元素作为其参数,可以在其他地方存储和访问它。下面的示例实现了一个通用模式:使用ref回调来存储对实例属性中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将会在组件挂载时调用带有dom元素的回调,当他卸载时他为空。ref回调在componentDidMountcomponentDidUpdate之前被调用。

    可以在组件之间传递回调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}
          />
        );
      }
    }
    

    在上面的例子中,父组件通过传递他的refs作为inputRef prop给CustomTextInput,CustomTextInput传递相同的函数给<input>,因此这个父组件this.inputElement将会为对应CustomTextInput中元素的节点中。

    回调refs的警告

    如果ref回调被定义为一个内联函数,那么它将在更新期间被调用两次,首先是null,然后是DOM元素。这是因为函数的一个新实例是在每次渲染时创建的,因此react需要清除旧的ref,并设置新的ref。您可以通过将ref回调定义为类上的绑定方法来避免这种情况,但是请注意,在大多数情况下它都不重要。

    参考文档

    1. Refs and the DOM

    相关文章

      网友评论

        本文标题:[译]react refs and dom(v16.3.1)

        本文链接:https://www.haomeiwen.com/subject/ftfohftx.html