React中ref是一个对象,它有一个current属性,可以对这个属性进行操作,用于获取DOM元素和保存变化的值。什么是保存变化的值?就是在组件中,你想保存与组件渲染无关的值,就是JSX中用不到的或不显示到页面,让用户看到的值,比如setTimeout的返回的ID,就可以把这个值放到ref中。为什么要放到ref中,因为更改ref的值,不会引起组件的重新渲染,因为值与渲染无关,它也不应该引起组件渲染。怎么获取ref对象呢?useRef()函数和createRef()函数,调用函数,就返回ref对象。在组件的整个生命周期中,ref对象一直存在。组件创建,更准确地说法是,组件挂载,ref对象创建,组件销毁,ref对象销毁。
useRef是一个React Hooks,在函数组件中使用,它还可以接受一个参数,用于初始化useRef返回的对象的current属性。
const ref = useRef(initialValue);
使用useRef获取DOM元素,就是把ref对象赋给react element的ref属性, 每一个react element都有一个ref属性。组件挂载后,ref对象的current属性,就自动指向DOM元素
import React, { useRef } from "react"; const CustomTextInput = () => { const textInput = useRef(); focusTextInput = () => textInput.current.focus(); return ( <> <input type="text" ref={textInput} /> <button onClick={focusTextInput}>Focus the text input</button> </> ); }
组件挂载完成,textInput.current指向input输入框,就可以直接调用输入框的focus方法。
使用useRef保存变量的值,直接把变量或值,赋值给ref对象的current属性就可以了。
import React, { useRef, useEffect } from "react"; const Timer = () => { const intervalRef = useRef(); useEffect(() => { const id = setInterval(() => { console.log("A second has passed"); }, 1000); intervalRef.current = id; return () => clearInterval(intervalRef.current); }); const handleCancel = () => clearInterval(intervalRef.current); return ( <> //... </> ); }
这里要注意的是更新ref对象的值,是一个side effect,因为这个值不参与渲染,更新值是React渲染之外,要做的事情,所以要放到useEffect或useLayoutEffect中,放到事件处理函数中也可以。如果以下有代码
import React, { useRef } from "react"; const RenderCounter = () => { const counter = useRef(0); counter.current = counter.current + 1; return ( <h1>{`The component has been re-rendered ${counter} times`}</h1> ); };
最好改成
import React, { useRef } from "react"; const RenderCounter = () => { const counter = useRef(0); useEffect(() => { counter.current = counter.current + 1; }); return ( <h1>{`The component has been re-rendered ${counter} times`}</h1> ); };
函数组件中也可以使用createRef, 但当使用createRef时,每一次组件渲染时都会创建全新的ref对象,而不是每一次渲染都共用一个ref对象,性能会有问题,再说useRef就是代替createRef的,所以在函数组件中就没有必要使用createRef了。
其实,使用useRef,也可以获取到子组件,直接调用子组件中的方法,不过就是用点麻烦,要结合forwardRef 和useImperativeHandle。使用create-react-app 创建React项目,在src中创建一个Counter组件
import React from 'react'; import { useState } from "react" const Counter = () => { const [count, setCount] = useState(0); const clickHandler = () => { setCount(c => c + 1); } return ( <p>count is {count} </p> ) } export default Counter;
然后在App.js中引入
import React from 'react'; import Counter from "./Counter"; function App() { return ( <React.Fragment> <Counter></Counter> <button>Add</button> </React.Fragment> ); } export default App;
此时,如果想点击父组件App中的button来增加子组件的count,怎么办?首先,子组件Counter,要把clickHandler方法暴露出来。做法,1,export的不是组件了,而是forwardRef(组件); 2,组件要接受参数ref,const Counter = (props, ref); 3, 在组件内部,使用useImperativeHandle,它的第一个对数是ref,第二个参数是回调函数,返回一个对象,对象中的属性和方法,就可以在父组件中使用ref获取到。
import React from 'react'; import Counter from "./Counter"; function App() { return ( <React.Fragment> <Counter></Counter> <button>Add</button> </React.Fragment> ); } export default App;
其次,在父组件App中使用ref,引用子组件,并在button的click回调函数中使用ref
function App() { const counteRef = useRef(); const handleClick = () => { counteRef.current.click(); } return ( <React.Fragment> <Counter ref={counteRef}></Counter> <button onClick={handleClick}>Add</button> </React.Fragment> ); }
如果项目中使用了Redux和React-Redux,子组件export的是connect()(组件),是一个高阶组件,那父组件中怎么引用子组件呢?如果是react-redux 6.0以前的版本,connect函数第三个参数设置为{ withRef: true },父组件getWrappedInstance()就可以获取到包裹的子组件
connect(null, null, null, { withRef: true })(组件);
如果是react-redux 6.0 以后的版本,使用 forwardRef: true , 代替{ withRef: true },父组件中的ref可以直接获取到包包裹的子组件
connect(null, null, null, { forwardRef: true })(组件);
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。