手心森林 發表於 2020-8-4 21:31:00

React中Refs的使用方法

<p>转载请注明出处原文链接地址 Vincent'Blog-React中Refs的使用方法</p>
<h2 id="什么是refs">什么是Refs</h2>
<blockquote>
<p>Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。<br>
Ref转发是一项将ref自动通过组件传递到子组件的技巧。 通常用来获取DOM节点或者React元素实例的工具。在React中Refs提供了一种方式,允许用户访问dom节点或者在render方法中创建的React元素。</p>
</blockquote>
<h3 id="refs转发">Refs转发</h3>
<blockquote>
<p>Ref 转发是一个可选特性,其允许某些组件接收 ref,并将其向下传递(换句话说,“转发”它)给子组件。</p>
</blockquote>
<h3 id="背景">背景</h3>
<p>在React单向数据流中,props是父子组件交互的唯一方式。要修改一个子组件,需要通过新的props来重新渲染。在有些情况下,需要在数据流之外强行修改子组件(组件或者Dom元素),那么可以通过Refs来进行修改子组件。</p>
<h2 id="组件类型">组件类型</h2>
<h3 id="受控组件">受控组件</h3>
<blockquote>
<p>在 HTML 中,表单元素(如<code>&lt;input&gt;</code>、 <code>&lt;textarea&gt;</code> 和 <code>&lt;select&gt;</code>)之类的表单元素通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。</p>
</blockquote>
<h3 id="非受控组件">非受控组件</h3>
<p>不被React组件控制的组件。在受控制组件中,表单数据由 React组件自行处理。其中表单数据由DOM本身处理。文件输入标签就是一个典型的不受控制组件,它的值只能由用户设置,通过DOM自身提供的一些特性来获取。</p>
<h3 id="受控组件与非受控组件的区别">受控组件与非受控组件的区别</h3>
<p>受控组件和不受控组件最大的区别就是前者自身维护的状态值变化,可以配合自身的change事件,很容易进行修改或者校验用户的输入。在React中 因为 Refs的出现使得 不受控制组件自身状态值的维护变得容易了许多。</p>
<h2 id="使用场景">使用场景</h2>
<ul>
<li>对Dom元素的焦点控制、内容选择、控制</li>
<li>对Dom元素的内容设置及媒体播放</li>
<li>对Dom元素的操作和对组件实例的操作</li>
<li>集成第三方 DOM 库</li>
</ul>
<p>避免使用 refs 来做任何可以通过声明式实现来完成的事情。</p>
<p>举个例子,避免在 Dialog 组件里暴露 open() 和 close() 方法,最好传递 isOpen 属性。</p>
<h3 id="访问-refs">访问 Refs</h3>
<p>当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问。</p>
<pre><code class="language-js">const node = this.myRef.current;
</code></pre>
<p>ref 的值根据节点的类型而有所不同:</p>
<ul>
<li>当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。</li>
<li>当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。</li>
<li>不能再函数组件上使用Ref属性,因为函数组件没有实例。</li>
</ul>
<p>例子</p>
<h3 id="创建-refs">创建 Refs</h3>
<blockquote>
<p>Refs 是使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。</p>
</blockquote>
<pre><code class="language-js">class MyComponent extends React.Component {
constructor(props) {
    super(props);
    this.myRef = React.createRef();
}
render() {
    return &lt;div /&gt;;
}
}
</code></pre>
<h4 id="dom为元素添加ref">DOM为元素添加ref</h4>
<p>用 ref 去存储 DOM 节点的引用:</p>
<pre><code class="language-js">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 我们想把 &lt;input&gt; ref 关联到
   // 构造器里创建的 `textInput` 上
   return (
   &lt;div&gt;
       &lt;input
         type="text"
         ref={this.textInput} /&gt;
       &lt;input
         type="button"
         value="Focus the text input"
         onClick={this.focusTextInput}
       /&gt;
   &lt;/div&gt;
   );
}
}
</code></pre>
<p>组件会在挂在是给current属性传入DOM元素,并在组件卸载时传入null。ref会在componentDidMount或者componentDidUpdate生命周期触发前更新。</p>
<h4 id="class组件添加ref">class组件添加ref</h4>
<p>包装上面的 CustomTextInput,来模拟它挂载之后立即被点击的操作,我们可以使用 ref 来获取这个自定义的 input 组件并手动调用它的 focusTextInput 方法:</p>
<pre><code class="language-js">class AutoFocusTextInput extends React.Component {
constructor(props) {
    super(props);
    this.textInput = React.createRef();
}

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

render() {
    return (
      &lt;CustomTextInput ref={this.textInput} /&gt;
    );
}
}
</code></pre>
<h4 id="函数组件添加refs">函数组件添加refs</h4>
<p>默认情况下,不能再函数组件上使用ref属性,因为函数组件没有实例。如果要在函数组件上使用ref,需要使用<code>useRef</code>和<code>useImperativeHandle</code>结合使用。</p>
<p>useRef 返回一个可变的 ref 对象,其 current 属性被初始化为传入的参数(initialValue)</p>
<blockquote>
<p>useRef 返回的 ref 对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的ref 对象都是同一个(使用 React.createRef ,每次重新渲染组件都会重新创建 ref)</p>
</blockquote>
<pre><code class="language-js">import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
function Parent() {
    let = useState(0);
    return (
      &lt;&gt;
            &lt;Child /&gt;
            &lt;button onClick={() =&gt; setNumber({ number: number + 1 })}&gt;+&lt;/button&gt;
      &lt;/&gt;
    )
}
let input;
function Child() {
    const inputRef = useRef();
    console.log('input===inputRef', input === inputRef);
    input = inputRef;
    function getFocus() {
      inputRef.current.focus();
    }
    return (
      &lt;&gt;
            &lt;input type="text" ref={inputRef} /&gt;
            &lt;button onClick={getFocus}&gt;获得焦点&lt;/button&gt;
      &lt;/&gt;
    )
}
</code></pre>
<p>forwardRef.</p>
<ul>
<li>因为函数组件没有实例,所以函数组件无法像类组件一样可以接收 ref 属性</li>
<li>forwardRef 可以在父组件中操作子组件的 ref 对象</li>
<li>forwardRef 可以将父组件中的 ref 对象转发到子组件中的 dom 元素上</li>
<li>子组件接受 props 和 ref 作为参数</li>
</ul>
<pre><code class="language-js">function Child(props,ref){
return (
    &lt;input type="text" ref={ref}/&gt;
)
}
Child = React.forwardRef(Child);
function Parent(){
let = useState(0);
// 在使用类组件的时候,创建 ref 返回一个对象,该对象的 current 属性值为空
// 只有当它被赋给某个元素的 ref 属性时,才会有值
// 所以父组件(类组件)创建一个 ref 对象,然后传递给子组件(类组件),子组件内部有元素使用了
// 那么父组件就可以操作子组件中的某个元素
// 但是函数组件无法接收 ref 属性 &lt;Child ref={xxx} /&gt; 这样是不行的
// 所以就需要用到 forwardRef 进行转发
const inputRef = useRef();//{current:''}
function getFocus(){
    inputRef.current.value = 'focus';
    inputRef.current.focus();
}
return (
      &lt;&gt;
      &lt;Child ref={inputRef}/&gt;
      &lt;button onClick={()=&gt;setNumber({number:number+1})}&gt;+&lt;/button&gt;
      &lt;button onClick={getFocus}&gt;获得焦点&lt;/button&gt;
      &lt;/&gt;
)
}
</code></pre>
<p>useImperativeHandle</p>
<ul>
<li>useImperativeHandle可以让你在使用 ref 时,自定义暴露给父组件的实例值</li>
<li>在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用</li>
<li>父组件可以使用操作子组件中的多个 ref</li>
</ul>
<pre><code class="language-jsx">import React,{useState,useEffect,createRef,useRef,forwardRef,useImperativeHandle} from 'react';

function Child(props,parentRef){
    // 子组件内部自己创建 ref
    let focusRef = useRef();
    let inputRef = useRef();
    useImperativeHandle(parentRef,()=&gt;(
      // 这个函数会返回一个对象
      // 该对象会作为父组件 current 属性的值
      // 通过这种方式,父组件可以使用操作子组件中的多个 ref
      return {
            focusRef,
            inputRef,
            name:'计数器',
            focus(){
                focusRef.current.focus();
            },
            changeText(text){
                inputRef.current.value = text;
            }
      }
    });
    return (
      &lt;&gt;
            &lt;input ref={focusRef}/&gt;
            &lt;input ref={inputRef}/&gt;
      &lt;/&gt;
    )

}
Child = forwardRef(Child);
function Parent(){
const parentRef = useRef();//{current:''}
function getFocus(){
    parentRef.current.focus();
    // 因为子组件中没有定义这个属性,实现了保护,所以这里的代码无效
    parentRef.current.addNumber(666);
    parentRef.current.changeText('&lt;script&gt;alert(1)&lt;/script&gt;');
    console.log(parentRef.current.name);
}
return (
      &lt;&gt;
      &lt;ForwardChild ref={parentRef}/&gt;
      &lt;button onClick={getFocus}&gt;获得焦点&lt;/button&gt;
      &lt;/&gt;
)
}
</code></pre>
<p>转载请注明出处原文链接地址 Vincent'Blog-React中Refs的使用方法</p><br><br>
来源:https://www.cnblogs.com/vincent-c/p/13436061.html
頁: [1]
查看完整版本: React中Refs的使用方法