react中ref使用方法

10,812 阅读4分钟

前言

在典型的 React 数据流中,props 是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个 React 组件的实例,也可能是一个 DOM 元素。对于这两种情况,React 提供了使用ref来解决它。

何时使用 Refs

下面是几个适合使用 refs 的情况:

  • 管理焦点,文本选择或媒体播放。
  • 触发强制动画。
  • 集成第三方 DOM 库。

react中创建ref的几种方式

1、React.createRef()

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

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

  componentDidMount() {
    this.myRef.current.focus();
  }
  
  render() {
    return <input ref={this.myRef} />;
  }
}

React 会在组件挂载时给 current 属性传入 DOM 元素,并在组件卸载时传入 null 值。ref 会在 componentDidMount 或 componentDidUpdate 生命周期钩子触发前更新。 这就是为什么ref.current总能拿到最新值的原因

2、callback ref

下面例子使用回调函数获取

class MyComponent extends React.Component {
  componentDidMount() {
    this.myRef.focus();
  }

  render() {
    return <input ref={(ele) => {
      this.myRef = ele;
    }} />;
  }
}

3、string ref(废弃)

使用示例

class MyComponent extends React.Component {
  componentDidMount() {
    this.refs.myRef.focus();
  }

  render() {
    return <input ref="myRef" />;
  }
}

废弃的原因有以下几点:
1、string ref 不可组合
例:第三方库的父组件已经给子组件传递了 ref,那么我们就无法再在子组件上添加 ref

class MyComponent extends React.Component {
  componentDidMount() {
    // this.refs.child 无法获取到 
    console.log(this.refs);
  }

  render() {
    return ( <Parent> <Child ref="child" /> </Parent> );
  }
}

2、回调引用没有一个所有者,因此您可以随时编写它们
3、不适用于Flow之类的静态分析
4、string ref 强制React跟踪当前正在执行的组件

以上三种使用ref方式不能在函数组件上使用,因为函数组件没有实例

如果要在函数组件中使用 ref:

  • 可以将该组件转化为 class 组件
  • 可以使用 forwardRef(可与 useImperativeHandle 结合使用)
  • 在函数组件 使用useRef

4、useRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。并且 useRef 可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。

impor {useRef} from 'react'
export default function App() {
  const myRef = useRef(0)
  useEffect(() => {
    myRef.current?.blur();
  }, []);

  return <input ref={(ref) => {
      myRef.current = ref;
  }} />;
}

useRef在hooks中使用的场景

1、忽略首次执行,只在依赖更新时执行

下面例子中,给a变量赋初始值0时,useEffect监测到a的变化也会执行,因此可以定义一个变量用于条件判断是否需要执行里面的内容

import { useState, useEffect,useRef } from "react";

export default function App() {
  const ref = useRef(false)
  const [a, setA] = useState(0);
  
  useEffect(() => {
    if(!ref.current){
      ref.current = true
    }else{
       //do some
    }
  }, [a]);
  return ();
}

2、useMemoizedFn(ahooks库中的一个自定义hooks)

useMemoizedFn可以完全代替useCallback,并且不需要写依赖参数。同时可以保证函数地址永远不会变化,可用于性能优化,useCallback在第二个参数deps发生变化时会重新生成新的函数。

useMemoizedFn的原理是使用了二个useRef,第一个可以拿到最外层最新的值,返回的是第二个ref,返回第二个ref时会自动调用第一个ref,并改变this的指向

//源码
import { useMemo, useRef } from 'react';

type noop = (...args: any[]) => any;

function useMemoizedFn<T extends noop>(fn: T) {
  if (process.env.NODE_ENV === 'development') {
    if (typeof fn !== 'function') {
      console.error(`useMemoizedFn expected parameter is a function, got ${typeof fn}`);
    }
  }

  const fnRef = useRef<T>(fn);

  // why not write `fnRef.current = fn`?
  // https://github.com/alibaba/hooks/issues/728
  fnRef.current = useMemo(() => fn, [fn]);

  const memoizedFn = useRef<T>();
  if (!memoizedFn.current) {
    memoizedFn.current = function (...args) {
      // eslint-disable-next-line @typescript-eslint/no-invalid-this
      return fnRef.current.apply(this, args);
    } as T;
  }

  return memoizedFn.current;
}

export default useMemoizedFn;

3、在react native中使用useRef

import React, { Component,useRef } from 'react';
import { TextInput } from 'react-native';

const App = () => {
  const [value, onChangeText] = React.useState('');
  const myRef = useRef(null)
  
  handleChange = (text) =>{
     myRef.current.blur()
     onChangeText(text)
  }

  return (
    <TextInput
      ref = {(ref)=>myRef.current = ref}
      onChangeText={text => handleChange(text)}
      value={value}
    />
  );
}

export default App;

4、在react native中用于声明一个动画变量

例子中,使用useRef创建的变量,当点击触发动画事件时,不会重新刷新整个组件,只会更新子组件

import React, { useRef } from "react";
import { Animated, Text, View, StyleSheet, Button } from "react-native";

const App = () => {
  const fadeAnim = useRef(new Animated.Value(0)).current;

  const fadeIn = () => {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 5000
    }).start();
  };
  
  return (
    <View style={styles.container}>
      <Animated.View
        style={[
          styles.fadingContainer,
          {
            opacity: fadeAnim // Bind opacity to animated value
          }
        ]}
      >
        <Text style={styles.fadingText}>Fading View!</Text>
      </Animated.View>
      <View style={styles.buttonRow}>
        <Button title="Fade In" onPress={fadeIn} />
      </View>
    </View>
  );
}
export default App;