前言
在典型的 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;