react16——ref & dom

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cjg214/article/details/83240480

基本使用

1.Refs 提供了一种方式,用于访问在 render 方法中创建的 DOM 节点或 React 元素。

2.使用refs的情况:

(1)处理焦点、文本选择、媒体控制。
(2)自定义动画
(3)通过第三方DOM库,获取DOM节点

3.如果可以,优先通过声明状态实现,进而避免使用refs。

例如,不要在 Dialog 组件上直接暴露 open() 和 close() 方法,最好传递 isOpen 属性。

4.不要过度使用refs,如果想使用refs来更新组件,推荐做法是“状态提升”。(后续完善)

创建 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 中的 current 属性对节点的引用进行访问。

const node = this.myRef.current;

小结:

  • 不能在函数式组件(无状态函数)上使用ref属性,因为他们没有实例。
function MyFunctionalComponent() {
  return <input />;
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }
  render() {
    // 这将 *不会* 工作!
    return (
      <MyFunctionalComponent ref={this.textInput} />
    );
  }
}

解决:将它转换为类组件。

备注:你可以在函数式组件内部使用 ref,只要它指向一个 DOM 元素或者 class 组件:

function CustomTextInput(props) {
  // 这里必须声明 textInput,这样 ref 回调才可以引用它
  let textInput = null;

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

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

      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );  
}
  • React 会在组件加载时将 DOM 元素传入 current 属性,在卸载时则会改回 null。
  • ref的值取决于节点的类型:

(1)html元素 --》 将底层dom元素作为它的current属性

(2)自定义类组件 --》该组件已挂载的实例

  • ref 的更新会发生在componentDidMount 或 componentDidUpdate 生命周期钩子之前。

对父节点暴露子节点

方法如下:

(1)从父节点访问子节点的DOM节点

点评:因为它会破坏组件的封装,但偶尔也可用于触发焦点或测量子 DOM 节点的大小或位置。(后续完善)

(2)在子组件上添加Ref.

见:

父组件:

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

  componentDidMount() {
    this.textInput.current.focusTextInput();
  }

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

子组件:

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // 创建 ref 存储 textInput DOM 元素
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // 直接使用原生 API 使 text 输入框获得焦点
    // 注意:通过 "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>
    );
  }
}

点评:不够理想,只能获取组件实例而不是DOM节点。并且还在函数式组件(无状态函数)上无效。

(3)v16.3+,采用ref转发。 Ref 转发使组件可以像暴露自己的 ref 一样暴露子组件的 ref。(后续完善)

function logProps(Component) {
  // 子组件
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      const {forwardedRef, ...rest} = this.props;

      // Assign the custom prop "forwardedRef" as a ref
      return <Component ref={forwardedRef} {...rest} />;
    }
  }

  // Note the second param "ref" provided by React.forwardRef.
  // We can pass it along to LogProps as a regular prop, e.g. "forwardedRef"
  // And it can then be attached to the Component.
  //父组件
  function forwardRef(props, ref) {
    return <LogProps {...props} forwardedRef={ref} />;
  }

  // These next lines are not necessary,
  // But they do give the component a better display name in DevTools,
  // e.g. "ForwardRef(logProps(MyComponent))"
  const name = Component.displayName || Component.name;
  forwardRef.displayName = `logProps(${name})`;

  return React.forwardRef(forwardRef);
}

(4)v16.2-,将ref作为特殊名字的prop属性,直接传递。

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

class Parent extends React.Component {
  constructor(props) {
    super(props);
    // 1.React.createRef() 创建 refs,再将它赋值给实例的一个属性,这样你可以在组件中任意一处使用它们.
    this.inputElement = React.createRef();
  }
  render() {
    return (
        //2. 自定义ref,作为属性传递出去
      <CustomTextInput inputRef={this.inputElement} />
    );
  }
}

(5)暴露DOM节点(不推荐)

findDOMNode()

语法:ReactDOM.findDOMNode(component)

使用:当你需要从DOM中读取值时,比如表单的值,或者计算DOM元素的尺寸,这个函数会非常有用。

大多数情况下,你可以添加一个指向DOM节点的引用,从而完全避免使用findDOMNode 这个函数.

当 render 返回 null 或者 false 时, findDOMNode 也返回 null.

备注:

findDOMNode 是用于操作底层DOM节点的备用方案。
findDOMNode 只对挂载过的组件有效。
findDOMNode 不能用于函数式的组件中。

回调ref

使用:不同于传递 createRef() 创建的 ref 属性,你会传递一个函数。这个函数接受 React 组件的实例或 HTML DOM 元素作为参数,以存储它们并使它们能被其他地方访问。

特点:更加细致地控制何时 ref 被设置和解除。

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

    this.textInput = null;
    // 2.将在组件挂载时将 DOM 元素传入ref 回调函数并调用,当卸载时传入 null 并调用它。
    this.setTextInputRef = element => {
      this.textInput = element;
    };

    this.focusTextInput = () => {
      // 3.直接使用原生 API 使 text 输入框获得焦点
      if (this.textInput) this.textInput.focus();
    };
  }

  componentDidMount() {
    // 渲染后文本框自动获得焦点
    this.focusTextInput();
  }

  render() {
    // 1.使用 `ref` 的回调将 text 输入框的 DOM 节点存储到 React
    // 实例上(比如 this.textInput)
    return (
      <div>
        <input
          type="text"
          ref={this.setTextInputRef}
        />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}


备注:

(1)旧版本的this.refs.textInput存在问题,未来可能被移除,建议回调函数代替。

(2)通过将 ref 的回调函数定义成类的绑定函数.

(3)注意
如果 ref 回调以内联函数的方式定义,在更新期间它会被调用两次,第一次参数是 null ,之后参数是 DOM 元素。这是因为在每次渲染中都会创建一个新的函数实例。因此,React 需要清理旧的 ref 并且设置新的。

通过将 ref 的回调函数定义成类的绑定函数的方式可以避免上述问题,但是大多数情况下无关紧要。

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

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

  componentDidMount() {
    console.log(this.ref);
  }

  change(_ref) {
    this.ref = _ref;
  }

  render() {
    return (
      {/* <p ref={(_ref) => this.change(_ref) }>111</p>  改良    */}
      {/* <p ref={(_ref) => this.ref = _ref}>111</p>    内联函数 */}
    );
  }
}

ReactDOM.render(
  <FileInput />,
  document.getElementById('root')
);

猜你喜欢

转载自blog.csdn.net/cjg214/article/details/83240480