美文网首页思科DevNet
Refs和Refs 转发

Refs和Refs 转发

作者: Kevin丶CK | 来源:发表于2019-04-24 18:10 被阅读0次

1、定义

在React 数据流中,props是父组件与子组件交互的唯一方式。需要修改子组件,要使用新的props来重新渲染。
但有些情况下,需要我们强制修改子组件(被修改的子组件可能是一个 React 组件的实例,也可能是一个 DOM 元素),这时就可以使用Refs。
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。

2、使用场景

下面是几个适合使用 refs 的情况:
1.管理焦点,文本选择或媒体播放。
2.触发强制动画。
3.集成第三方 DOM 库。
4.避免使用 refs 来做任何可以通过声明式实现来完成的事情。

3、Refs API使用

3.1 创建Refs

Refs 使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。(在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们)

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

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

console.log(this.myRef.current);//打印出设置了refs 的节点元素

ref 的值根据节点的类型而有所不同:
一、当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。

 //使用React.createRef()创建refs
      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.value = "";
          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>
          );
        }
      }

二、当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。

 //为 class 组件添加 Ref
      class AutoFocusTextInput extends React.Component {
        constructor(props) {
          super(props);
          this.textInput = React.createRef();
        }

        componentDidMount() {
          //调用class组件的方法
          this.textInput.current.focusTextInput();
        }

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

你不能在函数组件上使用 ref 属性,因为他们没有实例,可但以在函数组件内部使用 ref 属性。

 //使用React.createRef()创建refs
      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.value = "";
          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>
          );
        }
      }

4、回调 Refs

React 也支持另一种设置 refs 的方式,称为“回调 refs”。它能助你更精细地控制何时 refs 被设置和解除。
不同于传递 createRef() 创建的 ref 属性,你会传递一个函数。这个函数中接受 React 组件实例或 HTML DOM 元素作为参数,以使它们能在其他地方被存储和访问。

    //回调Refs
      class MyComponent extends React.Component {
        constructor(props) {
          super(props);
          this.myRef = null;
        }
        componentDidMount() {
          this.myRef.innerHTML = "我是回调Refs";
          console.log(this.myRef);
        }
        render() {
          return (
            <div
              ref={element => {
                this.myRef = element;
              }}
            />
          );
        }
      }

可以在组件间传递回调形式的 refs,就像你可以传递通过 React.createRef() 创建的对象 refs 一样。

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

class Parent extends React.Component {
  render() {
    return (
      <MyComponent
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

5、String 类型的 Refs(老版本React中的API,已经过时,不建议使用)

//过时的Refs
      class MyComponent extends React.Component {
        constructor(props) {
          super(props);
        }
        componentDidMount() {
          this.refs.myRef.innerHTML = "我是过时的String 类型的Refs";//通过 this.refs.XXXX来访问 DOM 节点
          console.log(this.refs.myRef);
        }
        render() {
          return <div ref="myRef" />;
        }
      }

6、将 DOM Refs 暴露给父组件

在极少数情况下,你可能希望在父组件中引用子节点的 DOM 节点。通常不建议这样做,因为它会打破组件的封装。在新版本中可以使用Refs 转发,老版本可以将refs作为一个参数,放在props中直接传递(如下例)

 function MyComponent(props) {//作为props的一员直接传递
        return (
          <div>
            <input
              ref={props.inputRef}
              onChange={null}
            />
          </div>
        );
      }
6.1 Refs 转发

Ref 转发是一项将 refs自动地通过组件传递到其一子组件的技巧。对于可重用的组件库是很有用的。
我们需要使用API React.forwardRef 来获取传递给它的 ref,然后转发到它渲染的 DOM 。

   const MyButton = React.forwardRef((props, ref) => (
        <button ref={ref} >
          {props.children}
        </button>
      ));

这样,使用 MyButton 的组件可以获取底层 DOM 节点 button 的 ref ,并在必要时访问,就像其直接使用 DOM button 一样。
注意:
第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。常规函数和 class 组件不接收 ref 参数,且 props 中也不存在 ref。
Ref 转发不仅限于 DOM 组件,你也可以转发 refs 到 class 组件实例中。

   class App extends React.Component {
        constructor(props) {
          super(props);
          // 你可以直接获取 DOM button 的 ref:
          this.ref = React.createRef();
        }

        componentDidMount() {
          setTimeout(() => {
            this.ref.current.innerText = "Refs 转发";
          }, 2000);
          console.log(this.ref.current);

        }
        render() {
          return (
            <div>
              <MyButton ref={this.ref}>Click me!</MyButton>
            </div>
          );
        }
      }
  ReactDOM.render(<App />, document.getElementById("root"));

如图,在父组件打印出了refs内容,并且2秒改了了button上的内容。

6.2 在高阶组件中转发 refs

利用高阶组件,就可以转发 refs 到 class 组件实例中。什么是高阶组件呢?
高阶组件是一个函数,它接受一个组件并返回一个新组件。

     class TextInput extends React.Component {
        constructor(props) {
          super(props);
          this.textInput = React.createRef();
        }
        setInputValue(value){
          this.textInput.current.value=value;
        }
        render() {
          return (<div><input ref={this.textInput} /></div>);
        }
      }

      class App extends React.Component {
        constructor(props) {
          super(props);
          // 你可以直接获取 DOM button 的 ref:
          this.ref = React.createRef();
          this.HOCref = React.createRef();
        }

        componentDidMount() {
          setTimeout(() => {
            this.ref.current.innerText = "Refs 转发";
          }, 2000);
          console.log(this.ref.current);
          console.log(this.HOCref.current);
          this.HOCref.current.setInputValue('我是父组件设置的值');
        }
        render() {
          return (
            <div>
              <MyButton ref={this.ref}>Click me!</MyButton>
              <NewFancyButton ref={this.HOCref}>高阶函数</NewFancyButton>
            </div>
          );
        }
      }
      const NewFancyButton = HOCFunc(TextInput);
     function HOCFunc(Component) {
        class HOCComponent extends React.Component {
          componentDidUpdate(prevProps) {
            console.log("old props:", prevProps);
            console.log("new props:", this.props);
          }

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

            // 将自定义的 prop 属性 “forwardedRef” 定义为 ref
            return <Component ref={forwardedRef} {...rest} />;
          }
        }

        // 注意 React.forwardRef 回调的第二个参数 “ref”。
        // 我们可以将其作为常规 prop 属性传递给 HOCComponent,例如 “forwardedRef”
        // 然后它就可以被挂载到被 HOCComponent 包裹的子组件上。
        return React.forwardRef((props, ref) => {
          return <HOCComponent {...props} forwardedRef={ref} />;
        });
      }

上面的示例有一点需要注意:refs将不会透传下去。这是因为ref不是prop属性。就像key一样,其被React进行了特殊处理。如果你对HOC添加ref,该ref将引用最外层的容器组件,而不是被包裹的组件。
幸运的是,我们可以使用React.forwardRefAPI明确地将refs转发到内部的TextInput组件。React.forwardRef接受一个渲染函数,其接收props和ref参数并返回一个React节点。



上图中打印出了转发后得到refs的Class组件TextInput,并且在父组件中操作了子组件中的方法。

相关文章

网友评论

    本文标题:Refs和Refs 转发

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