在react中,我们可以使用ref来获取组件示例或者DOM元素。
一般使用方法:
const ref = useRef(initialValue);
<input ref={ref} />
注意:
- useRef返回一个可变的 ref 对象,该对象只有个 current 属性,初始值为传入的参数( initialValue );
- 返回的 ref 对象在组件的整个生命周期内保持不变,除非手动修改;
- 当更新 current 值时并不会 re-render ,这是与 useState 不同的地方;
- useRef也可以用来区分初始渲染还是更新(通过current有没值);
拓展1:createRef
import React from 'react';
import './App.css';
class App extends React.Component {
componentDidMount() {
this.refIns = React.createRef();
}
render() {
return (
<div>
<div id="refIns" ref={this.refIns}>hello</div>
</div>
);
}
}
与useRef类似,createRef也是用来创建mutable object,该object也只包含一个current属性,同样的,当current的值发生改变时,页面不会重新渲染。但是,createRef与useRef也存在些许不同:
- useRef是use hooks的一种,一般用于function组件,而createRef一般用于class组件;
- 由useRef创建的ref对象在组件的整个生命周期内都不会改变,但是由createRef创建的ref对象,组件每更新一次,ref对象就会被重新创建。
事实上,useRef之所以出现,就是因为在函数式组件中使用createRef创建ref时存在弊端(组件每次更新,ref对象就会被重新创建)。
拓展2:ref对象可以用于保存数据
实际上,不管是useRef还是createRef,他们创建的ref对象不仅是DOM示例,还可以用于存储数据的。
function App() {
const valRef = React.useRef(80);
return <div>
value: {valRef.current}
<button onClick={() => valRef.current += 1}>+</button>
</div>
}
你会发现,不管你怎么点击按钮,value的值都会是80,并不会发生变化,这是因为ref对象的改变,并不会触发页面的更新。
function App() {
const valRef = React.useRef(80);
const [, setChange] = React.useState();
return <div style={{padding: "100px 200px"}}>
value: {valRef.current}
<button onClick={() => {
valRef.current += 1;
setChange({});
}}>+
</button>
</div>
}
当然,如果你在function组件中使用createRef实现以上功能,你就能够发现问题所在。
function App() {
const valRef = React.createRef();
const [, setChange] = React.useState();
return <div style={{padding: "100px 200px"}}>
value: {valRef.current}
<button onClick={() => {
valRef.current = 80;
setChange({});
}}>+
</button>
</div>
}
不管你怎么点击按钮,value始终没有值,因为每次触发组件渲染,ref对象都会被重新创建。
拓展3:获取子组件的属性或方法
例如:在父组件中调用子组件中的一个方法
一般做法,父组件通过给子组件设置ref,然后通过ref.current来调用子组件的方法
import React, { MutableRefObject, useState, useEffect, useRef, useCallback} from 'react'
interface IProps {
//prettier-ignore
label: string,
cRef: MutableRefObject<any>
}
const ChildInput: React.FC<IProps> = (props) => {
const { label, cRef } = props
const [value, setValue] = useState('')
const handleChange = (e: any) => {
const value = e.target.value
setValue(value)
}
const getValue = useCallback(() => {
return value
}, [value])
useEffect(() => {
if (cRef && cRef.current) {
cRef.current.getValue = getValue()
}
}, [getValue])
return (
<div>
<span>{label}:</span>
<input type="text" value={value} onChange={handleChange} />
</div>
)
}
const ParentCom: React.FC = (props: any) => {
const childRef: MutableRefObject<any> = useRef({})
const handleFocus = () => {
const node = childRef.current
alert(node.getValue())
}
return (
<div>
<ChildInput label={'名称'} cRef={childRef} />
<button onClick={handleFocus}>focus</button>
</div>
)
}
export default ParentCom
但是,这种做法其实是可以的,但是看起来并不优雅,需要自定义一个属性传入子组件。
第二种做法,通过useImperativeHandle,配合forwardRef。
useImperativeHandle:在函数式组件中,用于定义暴露给父组件的ref方法,用来限制子组件对外暴露的信息,只有useImperativeHandle第二个参数定义的属性跟方法才可以在父组件获取到。(解决直接使用userRef暴露子组件所有属性和方法的弊端)
forwardRef:将父类的ref作为参数,传入函数式组件中(直接为子组件创建ref属性,而不是将ref属性传给子组件,子组件再为某个DOM元素赋值)。
```JavaScript
import React, { MutableRefObject, useState, useImperativeHandle, useRef, forwardRef, useCallback } from 'react'
interface IProps {
label: string
}
let ChildInput = forwardRef((props: IProps, ref: any) => {
const { label } = props
const [value, setValue] = useState('')
// 作用: 减少父组件获取的DOM元素属性,只暴露给父组件需要用到的DOM方法
// 参数1: 父组件传递的ref属性
// 参数2: 返回一个对象,父组件通过ref.current调用对象中方法
useImperativeHandle(ref, () => ({
getValue
}))
const handleChange = (e: any) => {
const value = e.target.value
setValue(value)
}
const getValue = useCallback(() => {
return value
}, [value])
return (
<div>
<span>{label}:</span>
<input type="text" value={value} onChange={handleChange} />
</div>
)
})
const ParentCom: React.FC = (props: any) => {
const childRef: MutableRefObject<any> = useRef({})
const handleFocus = () => {
const node = childRef.current
alert(node.getValue())
}
return (
<div>
<ChildInput label={'名称'} ref={childRef} />
<button onClick={handleFocus}>focus</button>
</div>
)
}
export default ParentCom