React中Hooks的useRef 的高级用法

React中的Refs为我们提供了一种在组件的整个生命周期中存储可变值的方法,并且通常用于与DOM交互而无需重新渲染组件。换句话说,我们不需要依赖状态管理来使用Refs更新元素。这在某些特定的使用案例中非常有用,但在代替状态管理或生命周期方法集成时,也被视为一种反模式。

hooks已经集成到react的框架中,使用生命周期的类组件,现在可以替换成为函数组件和hooks。

该useRefhook已实现为在函数组件中使用React ref的解决方案。在本文中,我们将与其他可以一起工作的钩子一起探索这个钩子。更具体地说,我们将:

  • 介绍如何将useRefhook与函数组件结合使用,并介绍与useEffect和共同使用的hook的一些用例useLayoutEffect
  • 如何useRef与并发React一起正确使用
  • 探索useRef演示组件的用例

这里我们主要关注function组件,尽管这绝不意味着它们应该在类组件上使用,这也带来了它们的好处。

将项目移至函数组件是否值得?

函数组件提供速度和样板优化的地方,类组件提供了久经考验的组件生命周期方法和众所周知的结构,我们都已经习惯了(使用this类属性等)。

为了促进代码的一致性,开发人员通常选择对项目使用纯类组件方法或纯函数组件方法。我个人选择创建函数组件作为我的默认选择,并退回到有意义的类组件,例如当我想显式定义生命周期方法,许多类属性等时。

以我的经验,Typescript似乎比功能性组件更多地补充了类组件,能够将类型以及属性和方法插入类型本身。类具有更详细的结构。

在任何情况下,React Ref都可以在类和函数组件中使用。让我们探讨一下如何使用带有useRef钩子的引用。

使用 useRef Hook

函数组件Ref的实现已通过名为的钩子实现useRef。让我们看看它是如何集成的,然后探究它的特性以及何时使用它。

Ref可以在组件内定义:

import React, { useRef } from 'react';
...
const refContainer = useRef(initialValue);

这个钩子有一个非常简单的API-可以说比它的类更简单。选择该refContainer名称(来自官方文档)以反映该变量实际上充当基础引用的容器。

为了与Refs的基类实现一致,被引用的对象本身存储在current此容器变量的属性中。有关此current属性的两个关键事实:

  • 该属性是可变的
  • 它可以在组件生命周期中随时更改

函数组件仍然具有类组件的生命周期,尽管没有生命周期方法。组件生命周期的考虑将变得越来越重要。

另外,initialValue我们上面传入的参数可用于使用current默认值进行初始化。该值通常充当占位符,直到我们实际引用DOM中的元素或为其分配任意值为止。

事件尽管Ref通常用于引用DOM元素,但它们也可以存储原始类型和对象。我们将介绍这两种情况的示例。

传入的初始值也是完全合法的null:

//初始化一个空引用
const myRef = useRef(null);

无论current是什么,我们都可以在组件生命周期的任何时间log该属性以查看其值:

//亲自查看Ref实际引用的是什么
console.log(refContainer.current);

通过ref属性完成对DOM元素的引用。我们return通过ref属性在语句内的JSX级别上执行此操作。下面使用button元素完全做到了这一点:

// referencing a `button` element
...
render() {
  return(
    
  );
}

记住,我们引用的是DOM HTML元素,而不是React组件。

如果引用的是按钮,则将refContainer.current指向该

; ); }; export default Counter;

在上面的示例中,refCount.current从值开始0,并在组件更新的提交阶段递增。

请记住,如果我们要在主功能块中进行此增量,则更新将在返回render函数之前在render阶段进行,使增量遭受不可预测的重复。

现在,让我们看一下另一个引用DOM元素的组件,并通过其ref将事件侦听器附加到该组件。事件侦听器再次在useEffecthook中定义。此外,useEffect当组件被卸载时,act的返回函数可作为整齐的手段触发,在这里我们可以从ref中删除事件监听器:

import React, { useEffect, useRef } from 'react';

function App () {
  const refInput = useRef();

  useEffect(() => {
    const { current } = refInput;

    const handleFocus = () => {
      console.log('input is focussed');
    }
    const handleBlur = () => {
      console.log('input is blurred');
    }

    current.addEventListener('focus', handleFocus);
    current.addEventListener('blur', handleBlur);

    return () => {
      current.removeEventListener('focus', handleFocus);
      current.removeEventListener('blur', handleBlur);
    }
  });

  return (
    

); } export default App;

现在,如果您单击文本输入,然后单击相应的console.log输出,则相应的输出将通知您该文本输入正在聚焦和模糊。

让我们进一步了解这个概念。下一个示例通过refInputRef操作按钮元素的类。我们还引入了样式化组件来定义一个active类,该类会更改文本输入边框和文本颜色:

import React, { useEffect, useRef } from 'react';
import styled from 'styled-components';

const Wrapper = styled.div`
  input {
    color: #666;
    border: 1px solid #ccc;
    outline: none;
    &.active {
      color: #000;
      border-color: #000;
    }
  }
`;

function App () {
  const refInput = useRef();

  useEffect(() => {
    const { current } = refInput;

    const handleFocus = () => {
      current.classList.add('active');
    }
    const handleBlur = () => {
      current.classList.remove('active');
    }

    current.addEventListener('focus', handleFocus);
    current.addEventListener('blur', handleBlur);

    return () => {
      current.removeEventListener('focus', handleFocus);
      current.removeEventListener('blur', handleBlur);
    }
  });

  return (
    
      
    
  );
}

export default App;

现在,我们无需依赖状态更新就可以进行一些CSS操作。

演示的最后阶段是实现我们之前讨论的内容—实现一个Submit按钮,如果文本输入的值为空,则禁用它。为此,我useRef为提交按钮本身引入了一个额外的钩子。为了切换disabled属性,RefsetAttribute和removeAttributeJavascript API已被使用refSubmit。

完整的解决方案如下:

import React, { useEffect, useRef } from 'react';
import styled from 'styled-components';

const Wrapper = styled.div`
  .text {
    color: #666;
    border: 1px solid #ccc;
    outline: none;
    &.active {
      color: #000;
      border-color: #000;
    }
  }
`;

function App () {
  const refInput = useRef();
  const refSubmit = useRef();

  useEffect(() => {

    const { current } = refInput;

    const handleFocus = () => {
      current.classList.add('active');
    }

    const handleBlur = () => {
      current.classList.remove('active');

      current.value !== ''
        ? refSubmit.current.removeAttribute('disabled')
        : refSubmit.current.setAttribute('disabled', true);
    }

    current.addEventListener('focus', handleFocus);
    current.addEventListener('blur', handleBlur);

    return () => {
      current.removeEventListener('focus', handleFocus);
      current.removeEventListener('blur', handleBlur);
    }
  });

  return (
    
      

); } export default App;

disabled一旦我们在文本输入之外单击(或点击),或者当它变得模糊时,提交按钮的属性就会更新,这是确定表单是否有效的自然时间。

one more thing

本文的最后一部分将介绍值得注意的使用技巧useRef。

当useEffect引起问题时,请使用useLayoutEffect

在本文开头,我们提到了该useLayoutEffect钩子,该钩子也常与一起使用useRefuseLayoutEffectcomponentDidMountcomponentDidUpdate类组件生命周期方法在同一阶段触发,因此您可能倾向于使用它代替useEffect

但是,官方文档建议开发人员应useEffect主要尝试使用,useLayoutEffect如果出现问题请退后。使用会牺牲一些速度useLayoutEffect,因为只有在所有DOM突变/更新都发生后才被同步调用-除了这个细节,它与相同useEffect

转发useRef的

就像在类组件中初始化的转发Refs一样useRef,只要遵循相同的约定,也可以转发通过钩子初始化的Refs 。不要将ref作为“ ref”属性传递-这是React中保留的属性名称,会导致错误。相反,一个名为的道具forwardRef

...
// defining `refInput` within `App`, forwarding it to `MyInput`
function App () {
  const refInput = useRef();
  return  {
    console.log(props.forwardRef.current);
  });
  const { forwardRef } = props;
  return (
    );
}

用useRef切换焦点

除了监听事件,我们还可以触发事件。如果我们不展示此演示,则演示将不完整。在下面的组件中,单击一个按钮将再次关注另一个文本输入,再次使用useRef:

// focussing an element with a button press
function TextInput () {
const refInput = useRef();
  function handleFocus () {
    refInput.current.focus();
  }
  return (
    <>
      
      
    
  );
}

以编程方式聚焦的元素可以改善用户体验,例如在第一次加载表单并自动聚焦第一输入时。

总结

本文是的useRef总结,并介绍了如何在考虑组件生命周期的情况下正确实现引用。对于这里讨论的用例,使用refs可能是一种便捷的方法:

  • 使用焦点,模糊,禁用和其他与表单管理相关的属性来微管理输入
  • 要从元素中添加或删除类,可能控制过渡或关键帧动画
  • 官方文档中推荐的ref的另一个用例是与其他HTML5库(例如媒体播放器)进行交互。这样的库将无法通过React状态访问,而Refs为我们提供了一个后备功能,可以在与组件的生命周期一致的同时直接与其他元素进行交互

参考

React: Using Refs with the useRef Hook

你可能感兴趣的:(React中Hooks的useRef 的高级用法)