Refs and the DOM
文章目录

tags: ref, refs, dom, state, props

Refs and the DOM

In the typical React dataflow, props are the only way that parent components interact with their children. To modify a child, you re-render it with new props. However, there are a few cases where you need to imperatively modify a child outside of the typical dataflow. The child to be modified could be an instance of a React component, or it could be a DOM element. For both of these cases, React provides an escape hatch.

  • ref 的目的是在正常生命周期之外,获取到对应的 DOM, 并且对这些 DOM 做额外的操作
  • 一般情况下想要刷新 DOM 必须通过新的 prop/state 刷新
  • 因此一般不推荐使用 ref

When to Use Refs

There are a few good use cases for refs:

  • Managing focus, text selection, or media playback.
  • Triggering imperative animations.
  • Integrating with third-party DOM libraries.

Avoid using refs for anything that can be done declaratively.

For example, instead of exposing open() and close()methods on a Dialog component, pass an isOpen prop to it.

可以使用 Ref 的情况:

  1. 管理 focus, 文本选择或者媒体管理
  2. 命令性的动画
  3. 三方库集成

使用ref之前切记要确定一下对应的属性是不是可以使用 prop 代替, 开发时应该尽可能遵从 React 推荐的信息流

Adding a Ref to a DOM Element

React supports a special attribute that you can attach to any component. The ref attribute takes a callback function, and the callback will be executed immediately after the component is mounted or unmounted.

关于ref有这些特点:

  1. 可以给任何 DOM 提供这个 attr
  2. 提供给 ref 的 value 应该是个 callback
  3. 这个 callback 可以在 mounted/unmounted 的时候调用
    • 注意会在mounted/unmounted的时候执行一次, 而不是每次渲染的时候执行
  4. 当提供这个参数给 HTML elem 的时候, 这个ref指向的 callback 会获取当前 DOM elem 作为参数.

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // Explicitly focus the text input using the raw DOM API
    // 3. 因此这儿可以通过 this.textInput 调用 focus()方法
    this.textInput.focus();
  }

  render() {
  
    // Use the `ref` callback to store a reference to the text input DOM
    // element in an instance field (for example, this.textInput).
    // 1. Component 载入的时候就会执行 ref 里面的 callback.
    // 2. 然后这个 Elem 会当做参数传入, 因此就可以通过 this.textInput 引用这个 DOM elem
    
    
    return (
      <div>
        <input
          type="text"
          ref={(input) => { this.textInput = input; }} />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

Adding a Ref to a Class Component

类似ref在 HTML DOM 中的使用, 将ref赋值给自定义 Component 的时候, ref对应的 callback 就会接受自身这个 instance 作为参数

注意: 必须要这个自定义 Component 是个完整的 class, 不可以是函数

When the ref attribute is used on a custom component declared as a class, the ref callback receives the mounted instance of the component as its argument. For example, if we wanted to wrap the CustomTextInput above to simulate it being clicked immediately after mounting:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AutoFocusTextInput extends React.Component {
  componentDidMount() {
    // 2. 这儿就可以对这个 instance 进行引用
    this.textInput.focusTextInput();
  }

  render() {
    return (
      <CustomTextInput
      // 1. 对于自定义的 Component, ref 对应的 callback 会将自身这个 instance 当做参数传进去
        ref={(input) => { this.textInput = input; }} />
    );
  }
}

Exposing DOM Refs to Parent Components

一般数据流不应该在 Parent Class 里面控制 Child Class, 但是偶尔就有这样傻逼的需求.

这时候用ref最适合了, 但是依然不推荐这么使用.

更好的解决办法是你和需求分析的人打一架然后让他根据代码改需求

当然上文提到了解决办法是让 child class 暴露一个 prop, 但是这个办法无法对function Component使用.

另外下面这个办法对function Component也有用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function CustomTextInput(props) {
  // 2. 在 child component 里面将这个 callback 赋值给 ref
  // 2+. 当然这儿只能用 ref 这个 attr
  // 3. 然后这个 child component 在 mount/unmount 的时候就会执行这个 callback
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  render() {
  // `Parent` passes its ref callback as an `inputRef`prop to the `CustomTextInput`
  // 1. 这儿提供一个 callback 作为 props.inputReg
    return (
      <CustomTextInput
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

最终在parent component中就可以通过inputElement这个变量调用到对应输入框元素.

而且这个可以逐级传递, 比如下面这个隔代传递的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 {
  render() {
    return (
      <Parent
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

看起来暴露 DOM 有些别扭, 但是很多时候需求就是比较特别. 因此上面是推荐用法.

旧版本 React 里面曾经有过使用this.refs.textInput的用法, 其中 ref 会指向一段字符串, 新版本 React 不推荐如此使用

注意事项

如果ref是个 inline function, updates 的时候会被调用两次. 第一次是null然后第二次是 DOM Elem.

这个是因为 update 的时候 React 需要清理旧的并且建立新的 component

Solutions

为了防止这个问题, 可以将 ref callback 和对应 class 进行绑定.

即使不绑定也不会引发太多问题