在上一篇文章《React 18中hook函数详解之useState和useEffect》介绍了react v16.8版本以后,引入的Hooks函数的一些概念,以及useState和useEffect的一些用法。本文将接着上一篇文章着重介绍Hook函数当中常用的另一个函数:useRef。
useRef
是一个 React Hook,它能帮助引用一个不需要渲染的值。
useRef
返回一个只有一个属性的对象:
current
:初始值为传递的initialValue
。之后可以将其设置为其他值。如果将 ref 对象作为一个 JSX 节点的ref
属性传递给 React,React 将为它设置current
属性。
在后续的渲染中,useRef
将返回同一个对象。
一、获取dom元素
最简单的用法,在函数组件中,可以基于useRef
获取DOM元素
function App() {const [num, setNum] = useState(0);const btnBox = useRef(null); useEffect(() => {console.log(btnBox.current);}, [num]);return (<div><span>{num}</span><button ref={btnBox} onClick={() => setNum(num + 1)}>按钮</button></div>);
}
在父子组件当中,可以使用useRef在父组件当中获取子组件的实例,进而调用子组件的方法。获取子组件的方法,就要用到另一个hook函数——useImperativeHandle。useImperativeHandle是获取函数子组件内部状态或者方法的hook。
定义一个父组件app.tsx:
import React, { useRef, useEffect } from 'react';
import ChildName from './components/Child';
const parentRef= () => {const domRef = useRef<any>(null);const childRef = useRef<any>(null);useEffect(() => {console.log('ref:deom-init', domRef, domRef.current);console.log('ref:child-init', childRef, childRef.current);});const showChild = () => {console.log('ref:child', childRef, childRef.current);if (childRef.current) {childRef.current.say();}};return (<><div style={{ margin: '100px', border: '2px dashed', padding: '20px' }}><h2>这是外层组件</h2><divonClick={() => {console.log('ref:deom', domRef, domRef.current);domRef.current.focus();domRef.current.value = 'hh';}}aria-hidden="true"><span>这是一个dom节点</span><input ref={domRef} /></div><br /><button onClick={showChild} style={{ marginTop: '20px' }} aria-hidden="true">调用子组件的函数</button><div style={{ border: '1px solid', padding: '10px' }}><ChildName ref={childRef} /></div></div></>);
};export default parentRef;
二、父组件知识点
- useRef是一个方法,且useRef返回一个可变的ref对象;
- initialValue被赋值给其返回值的.current对象;
- 可以保存任何类型的值:dom、对象等任何可变值;
- ref对象与自建一个{current:‘’}对象的区别是:useRef会在每次渲染时返回同一个ref对象,即返回的ref对象在组件的整个生命周期内保持不变。自建对象每次渲染时都建立一个新的。
- ref对象的值发生改变之后,不会触发组件重新渲染。有一个窍门,把它的改变动作放到useState()之前;
- 本质上,useRef就是一个其.current属性保存着一个可变值“盒子”。目前我用到的是pageRef和sortRef分别用来保存分页信息和排序信息;
定义一个子组件Child.tsx:
import React, { useImperativeHandle, forwardRef } from 'react';const ChildName = (_props: any, ref: React.Ref<unknown> | undefined) => {useImperativeHandle(ref, () => ({say: sayHello}));const sayHello = () => {alert('hello,我是子组件');};return <h3>子组件</h3>;
};export default forwardRef(ChildName);
三、子组件知识点
- useImperativeHandle(ref,createHandle,[deps])可以自定义暴露给父组件的实例值。如果不使用,父组件的ref(chidlRef)访问不到任何值(childRef.current==null);
- useImperativeHandle应该与forwradRef搭配使用;
- React.forwardRef会创建一个React组件,这个组件能够将其接受的ref属性转发到其组件树下的另一个组件中;
- React.forward接受渲染函数作为参数,React将使用prop和ref作为参数来调用此函数;