陆家嘴李现 發表於 2019-6-4 14:49:00

在 React 组件中使用 Refs 指南

<blockquote>
<p>原文:Fullstack React's Guide to using Refs in React Components<br>作者:Yomi Eluwande<br>译者:博轩</p>
<p>译文:https://segmentfault.com/a/1190000019277029</p>
</blockquote>
<p>使用&nbsp;<code>React</code>&nbsp;时,我们的默认思维方式应该是&nbsp;<strong>不会强制修改&nbsp;<code>DOM</code></strong>&nbsp;,而是通过传入&nbsp;<code>props</code>&nbsp;重新渲染组件。但是,有些情况却无法避免修改&nbsp;<code>DOM</code>&nbsp;。</p>
<p><code>React</code>&nbsp;中的&nbsp;<code>Refs</code>&nbsp;提供了一种访问&nbsp;<code>render()</code>&nbsp;方法中创建的&nbsp;<code>React</code>&nbsp;元素(或&nbsp;<code>DOM</code>&nbsp;节点)的方法。</p>
<p>当父组件需要与子组件交互时,我们通常使用&nbsp;props&nbsp;来传递相关信息。 但是,<strong>在某些情况下,我们可能需要修改子项,而不用新的<code>props</code>&nbsp;重新呈现 (re-rendering) 它。</strong>&nbsp;这时候就需要&nbsp;<code>refs</code>&nbsp;出场了。</p>
<h1 id="articleHeader0">我什么时候应该使用 Refs ?</h1>
<p>我们建议在以下情况下使用&nbsp;<code>refs</code>:</p>
<ul>
<li>与第三方&nbsp;<code>DOM</code>&nbsp;库集成</li>
<li>触发命令式动画</li>
<li>管理焦点,文本选择或媒体播放</li>
</ul>
<blockquote>译注:第三点是否也可以理解为使用&nbsp;<code>event</code>&nbsp;对象呢?在 React 中就是合成事件(SyntheticEvent)。<br>官方文档中提到:避免使用&nbsp;<code>refs</code>&nbsp;来做任何可以通过声明式实现来完成的事情。</blockquote>
<p>所以一旦我们确定我们真的应该使用&nbsp;<code>refs</code>,我们需要如何使用它们呢?</p>
<h1 id="articleHeader1">在 React 中使用 Refs</h1>
<p>您可以通过多种方式使用&nbsp;<code>refs</code>&nbsp;:</p>
<ul>
<li>React.createRef()</li>
<li>回调引用 (Callback refs)</li>
<li>String refs(已过时)</li>
<li>转发&nbsp;<code>refs</code>&nbsp;(Forwarding refs)</li>

</ul>
<p>接下来,让我们看看每一种实现方式:</p>
<h3 id="articleHeader2">React.createRef()</h3>
<p>可以使用该&nbsp;<code>React.createRef()</code>&nbsp;函数创建&nbsp;<code>Refs</code>&nbsp;,并通过该&nbsp;<code>ref</code>&nbsp;属性附加到&nbsp;<code>React</code>&nbsp;组件中的&nbsp;<code>HTML</code>&nbsp;元素。</p>
<p>通常在组件的构造函数内创建&nbsp;<code>ref</code>&nbsp;,使其在整个组件中可用。例如:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class MyComponent extends React.Component {
constructor(props) {
    super(props);
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.firstRef =<span style="color: rgba(0, 0, 0, 1)"> React.createRef();
}
render() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> &lt;div ref={<span style="color: rgba(0, 0, 255, 1)">this</span>.firstRef} /&gt;;
}}</pre>
</div>
<p>如上所示:</p>
<ul class="list-paddingleft-2">
<li>
<p>一个 ref 实例在构造函数中创建,并赋值给 this.firstRef</p>
</li>
<li>
<p>在 render() 方法内部,将构造函数中创建的 ref 传递给 div</p>
</li>
</ul>
<p>接下来,让我们看一个在 React 组件中使用 refs 的示例。</p>
<h4>使用 Refs 聚焦输入</h4>
<p>这是另一个例子:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class CustomTextInput extends React.Component {
constructor(props) {
    super(props);
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> create a ref to store the textInput DOM element</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span>.textInput =<span style="color: rgba(0, 0, 0, 1)"> React.createRef();
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.focusTextInput = <span style="color: rgba(0, 0, 255, 1)">this</span>.focusTextInput.bind(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
}

focusTextInput() {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Explicitly focus the text input using the raw DOM API</span>
    <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Note: we're accessing "current" to get the DOM node</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.textInput.current.focus();
}

render() {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> tell React that we want to associate the &lt;input&gt; ref</span>
    <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> with the `textInput` that we created in the constructor</span>
    <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;div&gt;
      &lt;input type="text" ref={<span style="color: rgba(0, 0, 255, 1)">this</span>.textInput}/&gt;

      &lt;<span style="color: rgba(0, 0, 0, 1)">input
          type</span>="button"<span style="color: rgba(0, 0, 0, 1)">
          value</span>="Focus the text input"<span style="color: rgba(0, 0, 0, 1)">
          onClick</span>={<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.focusTextInput}
      </span>/&gt;
      &lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">    );
}
}</span></pre>
</div>
<p>在上面的代码块中,我们构建了一个按钮,当单击它时,该页面会自动聚焦在输入框上。</p>
<p>首先,我们在构造方法中创建一个 ref 实例,并将其赋值给 this.textInput,然后通过 ref 属性将其分配给 input 元素。</p>
<div class="cnblogs_code">
<pre>&lt;input type="text" ref={<span style="color: rgba(0, 0, 255, 1)">this</span>.textInput} /&gt;</pre>
</div>
<p>注意,当 ref 属性被一个 HTML 元素使用时(比如当前示例中的 input 元素),在 constructor 中使用 React.createRef() 创建的 ref 会接收来自底层 DOM 元素的 current 值。</p>
<blockquote>
<p>译注:这里的 current 应该是 合成事件(SyntheticEvent)</p>
</blockquote>
<p>这意味着访问 DOM 值,我们需要写这样的东西:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">this</span>.textInput.current;</pre>
</div>
<p>第二个元素是一个按钮,点击它之后会自动聚焦到第一个输入框上面。我们为 onClick 属性设置了 this.focusTextInput 函数。</p>
<div class="cnblogs_code">
<pre>&lt;<span style="color: rgba(0, 0, 0, 1)">input
type</span>="button"<span style="color: rgba(0, 0, 0, 1)">
value</span>="Focus the text input"<span style="color: rgba(0, 0, 0, 1)">
onClick</span>={<span style="color: rgba(0, 0, 255, 1)">this</span>.focusTextInput}/&gt;;</pre>
</div>
<p>函数 focusTextInput() 使用了 JavaScript 构建 DOM 的标准函数。 .focus() 方法会将光标聚焦于文本输入框上。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">focusTextInput() {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.textInput.current.focus();}</pre>
</div>
<p>最后,focusTextInput 函数绑定在这样的 constructor 方法中的:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">this</span>.focusTextInput = <span style="color: rgba(0, 0, 255, 1)">this</span>.focusTextInput.bind(<span style="color: rgba(0, 0, 255, 1)">this</span>);</pre>
</div>
<h4>从 ref 中获取值</h4>
<p>在这个例子中,我们将看到如何为 input 输入框设置 ref 属性,并通过 ref 来获取值。示例如下:</p>
<p>在这个例子中,我们创建了一个 input 输入框来输入值。然后,当单击提交按钮时,我们将读取此值,并在控制台打印。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class CustomTextInput extends React.Component {
constructor(props) {
    super(props);
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> create a ref to store the textInput DOM element</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span>.textInput =<span style="color: rgba(0, 0, 0, 1)"> React.createRef();
}

handleSubmit </span>= e =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    e.preventDefault();

    console.log(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.textInput.current.value);
};

render() {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> tell React that we want to associate the &lt;input&gt; ref</span>
    <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> with the `textInput` that we created in the constructor</span>
    <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;div&gt;
      &lt;form onSubmit={e =&gt; <span style="color: rgba(0, 0, 255, 1)">this</span>.handleSubmit(e)}&gt;
          &lt;input type="text" ref={<span style="color: rgba(0, 0, 255, 1)">this</span>.textInput}/&gt;
          &lt;button&gt;Submit&lt;/button&gt;
      &lt;/form&gt;
      &lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">    );
}
}</span></pre>
</div>
<p>同样,我们使用该 React.createRef() 函数创建一个 ref 实例,然后将它分配给实例变量 this.textInput。</p>
<p>在 render 函数中,我们希望读取 form 下输入框的值。我们如何读取这个值? 通过为 input 指定一个 ref ,然后读取 ref 的值。</p>
<div class="cnblogs_code">
<pre>&lt;input type="text" ref={<span style="color: rgba(0, 0, 255, 1)">this</span>.textInput} /&gt;</pre>
</div>
<p>点击提交按钮,上面示例中 form 元素会通过 onSubmit 方法,调用 this.handleSubmit 函数 ,并在控制台打印输入框中的信息。</p>
<div class="cnblogs_code">
<pre>handleSubmit = e =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
e.preventDefault();
console.log(</span><span style="color: rgba(0, 0, 255, 1)">this</span>.textInput);};</pre>
</div>
<blockquote>
<p>上面,参数 e 包含事件对象。我们使用e.preventDefault() 来告诉浏览器我们正在处理被点击的提交按钮,我们不希望这个事件“冒泡”(意思就是说,阻止浏览器的默认行为)。<br>译注:这里可以看一下 React 对于事件的处理:在 React 中另一个不同点是你不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault</p>

</blockquote>
<p>在上面示例中,我们打印了 this.textInput ,在控制台可以看到一个 ref 对象。</p>
<div class="cnblogs_code">
<pre>&gt; Object {current: HTMLInputElement}</pre>
</div>
<p>请注意,它有一个 current属性,即 HTMLInputElement 。这是 input DOM 元素本身,而不是实际值。 我们必须使用 this.textInput.current.value 来获取 input 标签的实际值:</p>
<div class="cnblogs_code">
<pre>handleSubmit = e =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
e.preventDefault();
console.log(</span><span style="color: rgba(0, 0, 255, 1)">this</span>.textInput.current.value);};</pre>
</div>
<p>使用 refs 是一种从表单中直接提取值的方式:只需要给 input 标签设置 ref ,并在你需要的时候将值提取出来。</p>
<h4>Refs 回调</h4>
<p>Refs 回调 是在 React 中使用 ref 的另一种方式。要以这种方式使用 ref,我们需要为 ref 属性设置回调函数。当我们设置 ref 时,React 会调用这个函数,并将 element 作为第一个参数传递给它。</p>
<p>这是另一个例子的代码。像上面的示例一样,此代码获取 input 标签的文本值,但在这里我们使用回调引用:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class CustomTextInput extends React.Component {
constructor(props) {
    super(props);
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.textInput = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;

    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.setTextInputRef = element =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.textInput =<span style="color: rgba(0, 0, 0, 1)"> element;
    };
}

handleSubmit </span>= e =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    e.preventDefault();
    console.log(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.textInput.value);
};

render() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;div&gt;
      &lt;form onSubmit={e =&gt; <span style="color: rgba(0, 0, 255, 1)">this</span>.handleSubmit(e)}&gt;
          &lt;input type="text" ref={<span style="color: rgba(0, 0, 255, 1)">this</span>.setTextInputRef}/&gt;
          &lt;button&gt;Submit&lt;/button&gt;
      &lt;/form&gt;
      &lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">    );
}
}</span></pre>
</div>
<p>上面的示例中,我们将 input 标签的 ref 设置为 this.setTextInputRef。</p>
<p>当组件安装时,React 会将 DOM 元素传递给 ref 的回调;当组件卸载时,则会传递 null。(ref 回调会在 componentDidMount 和 componentDidUpdate 生命周期之前调用。)</p>
<h4>String Ref(已过时)</h4>
<p>还有另一种设置 refs 的方法,但它被认为是过时的,可能很快就会被弃用。但是你可能会在其他人的代码中看到它,所以这里说一下。</p>
<p>使用 string refs,你将会看到这样的 input 标签:</p>
<div class="cnblogs_code">
<pre>&lt;input type="text" ref="textInput" /&gt;</pre>
</div>
<p>然后,我们可以在组建上得到这样的值:this.refs.textInput.value - 但是,再次声明,这不应该在新代码中使用,因为这个 API 将被弃用。</p>
<h4>转发 Refs (Forwarding Refs)</h4>
<p>Ref forwarding 是一种将 ref 通过组件传递给其子节点的技术。它对于可复用组件库和高阶组件(HOC)等情况非常有用。</p>
<p>您可以使用 React.forwardRef 函数将 ref 转发到组件。我们来看下面的例子:</p>
<div class="cnblogs_code">
<pre>const TextInput = React.forwardRef((props, ref) =&gt;<span style="color: rgba(0, 0, 0, 1)"> (
</span>&lt;input type="text" placeholder="Hello World" ref={ref}/&gt;));
const inputRef =<span style="color: rgba(0, 0, 0, 1)"> React.createRef();

class CustomTextInput extends React.Component {
handleSubmit </span>= e =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    e.preventDefault();
    console.log(inputRef.current.value);
};

render() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;div&gt;
      &lt;form onSubmit={e =&gt; <span style="color: rgba(0, 0, 255, 1)">this</span>.handleSubmit(e)}&gt;
          &lt;TextInput ref={inputRef}/&gt;
          &lt;button&gt;Submit&lt;/button&gt;
      &lt;/form&gt;
      &lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">    );
}
}</span></pre>
</div>
<p>Ref forwarding 允许组件接收一个 ref ,并将它向下传递(换句话说,“转发”它)给子组件。</p>
<p>在上面的示例中,我们使用 input 标签创建了一个名为 TextInput 的组件。那么,我们如何将 ref 传递或转发到 input 标签呢?</p>
<p>首先,我们使用下面的代码创建一个 ref :</p>
<div class="cnblogs_code">
<pre>const inputRef = React.createRef();</pre>
</div>
<p>然后,我们将 ref 通过为组件 &lt;TextInput ref={inputRef}&gt; 指定一个同名的 JSX 的属性,将 ref 向下传递。然后 React 将会把 ref 作为第二个参数转发给 forwardRef 函数。</p>
<p>接下来,我们将此 ref 参数转发给 &lt;input ref={ref}&gt;。现在可以在外层组件通过 inputRef.current 访问DOM节点的值了。</p>
<h4>转发 refs 和高阶组件</h4>
<p>最后,让我们看一下使用 refs 的另一个例子,但这次是使用高阶组件(HOC)。</p>
<p>在上面的示例应用程序中,会将所有 input 标签中输入的值在控制台打印。这里已经为 input 标签设置了 ref 属性,接下来,让我们看一下需要如何在高阶组件中传递 / 转发 ref 。</p>
<div class="cnblogs_code">
<pre>const Input = InputComponent =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
const forwardRef </span>= (props, ref) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    const onType </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> console.log(ref.current.value);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> &lt;InputComponent forwardedRef={ref} onChange={onType} {...props} /&gt;;
<span style="color: rgba(0, 0, 0, 1)">};
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> React.forwardRef(forwardRef);
};</span></pre>
</div>
<p>这里有一个名为 Input 的高阶组件 ,它接受 InputComponent 作为参数。当用户输入的时候,他还会将 ref 的值在控制台打印。</p>
<p>在 Input 高阶组件内,forwardRef 函数会返回 InputComponent。forwardRef 函数中所包含的 ref 参数,是由 React.forwardRef 函数创建的。 高阶组件最终会将包装好的组件作为值返回。</p>
<p>接下来,我们创建一个组件,将 input 作为子组件包含进来。</p>
<div class="cnblogs_code">
<pre>const TextInput = ({ forwardedRef, children, ...rest }) =&gt;<span style="color: rgba(0, 0, 0, 1)"> (
</span>&lt;div&gt;
    &lt;input ref={forwardedRef} {...rest} /&gt;
<span style="color: rgba(0, 0, 0, 1)">    {children}
</span>&lt;/div&gt;);</pre>
</div>
<p>上面的组件会将 forwardedRef 分配给 ref 属性, 当渲染子组件的时候,input 输入框就会接收到这个 ref 。…rest 是 props 的解构(也就是说,我们会将 rest 数组中的所有参数作为 props 传递给 input 组件)。那么我们该如何使用 TextInput 组件呢?像这样:</p>
<div class="cnblogs_code">
<pre>const InputField =<span style="color: rgba(0, 0, 0, 1)"> Input(TextInput);

class CustomTextInput extends Component {
render() {
    const inputRef </span>=<span style="color: rgba(0, 0, 0, 1)"> React.createRef();

    </span><span style="color: rgba(0, 0, 255, 1)">return</span> &lt;InputField ref={inputRef}/&gt;;
<span style="color: rgba(0, 0, 0, 1)">}
}</span></pre>
</div>
<p>最后,将 TextInput 传入 Input 高阶组件,会返回一个 InputField component。</p>
<p>创建一个 ref ,并作为参数传递给 InputField 组件。</p>
<h4>结论</h4>
<p>与通过 props 和 state 不同,Refs 是一种将数据传递给特定子实例的好方法。</p>
<p>你必须要小心,因为 refs 操纵实际的 DOM,而不是虚拟的 DOM,这与 React 思维方式相矛盾。因此,虽然 refs 不应该是通过应用程序流动数据的默认方法,但是当您需要时,它们是可以从 DOM 元素读取数据的好方法。</p>
<p>&nbsp;</p>

</div>
<div id="MySignature" role="contentinfo">
    让每一天过的有意义!<br><br>
来源:https://www.cnblogs.com/BillyYoung/p/10973540.html
頁: [1]
查看完整版本: 在 React 组件中使用 Refs 指南