学习React-15-useImperativeHandle
目录
学习React-15-useImperativeHandle
useImperativeHandle
useImperativeHandle
是 React 的一个 Hook,用于自定义向父组件暴露的 ref 句柄。通常与forwardRef
结合使用,允许子组件控制父组件通过 ref 访问的属性和方法。
基本语法
useImperativeHandle(ref, createHandle, dependencies?)
- ref:通过
forwardRef
传递的 ref 对象。 - createHandle:一个函数,返回需要暴露给父组件的对象。
- dependencies(可选):依赖项数组,类似
useEffect
的依赖项,用于控制何时重新计算createHandle
。
使用场景
- 限制暴露的属性和方法:默认情况下,通过 ref 可以访问子组件的全部实例。使用
useImperativeHandle
可以只暴露特定的方法或属性。 - 封装第三方库:在包装第三方库时,可能需要隐藏底层实现,只暴露部分接口。
- 优化性能:避免父组件直接操作子组件的 DOM 或实例,减少不必要的渲染或副作用。
小栗子
import React, { useState, useRef, forwardRef, useImperativeHandle } from 'react'
interface ChildRef {
name: string,
count: number,
add: () => void,
minus: () => void
}
// React 18版本的用法
// const Child = React.forwardRef<ChildRef>((props, ref) => {
// 相对于React版本,19版本将ref放在了props中
const Child = ({ref}: {ref:React.Ref<ChildRef>}) => {
const [count, setCount] = useState(0)
const add = () => {
setCount(count + 1)
}
const minus = () => {
setCount(count - 1)
}
useImperativeHandle(ref, () => {
return {
name: 'child',
count,
add,
minus
}
})
return (
<div>
<div>这是子组件</div>
<div>count: {count}</div>
<button onClick={add}>+</button>
<button onClick={minus}>-</button>
</div>
)
})
export const App = () => {
const childRef = useRef<ChildRef>(null)
return (
<div>
<h1>我是父组件</h1>
<button onClick={() => childRef.current?.add()}>操作子组件 +</button>
<button onClick={() => childRef.current?.minus()}>操作子组件 -</button>
<br />
<button onClick={() => console.log(childRef.current)}>获取子组件</button>
<Child ref={childRef} />
</div>
)
}
export default App
实际案例
需求: 利用父子组件实现表单的验证
父组件
import React, { useState, useRef, forwardRef, useImperativeHandle, useEffect } from 'react'
import TextFrom from './textFrom'
import './index.css'
interface FromProps {
handleSubmit: (e: React.FormEvent) => void
handleReset: () => void
checkForm: () => void
fromData: {
text: string,
password: string,
number: string
}
}
export const App = () => {
const chlidFormRef = useRef<FromProps>(null)
const [formData, setFormData] = useState({ text: '', password: '' });
const getFormData = () => {
if (chlidFormRef.current) {
// 触发表单提交
// 这里我们直接获取表单数据并更新状态
setFormData({
text: chlidFormRef.current.fromData.text,
password: chlidFormRef.current.fromData.password
});
}
};
return (
<div>
<div className='login_main'>
<div className="login-left">
<h1>登陆人</h1>
<div>用户: {formData.text}</div>
<div>密码: {formData.password}</div>
</div>
<div className="login-right">
<TextFrom ref={chlidFormRef} />
<div className="form-buttons">
<button type="button" className="submit-button" onClick={chlidFormRef.current?.handleSubmit}>
登录
</button>
<button type="button" onClick={chlidFormRef.current?.handleReset} className="reset-button">
重置
</button>
<button className="submit-button" onClick={() => chlidFormRef.current?.checkForm()}>
检查表单
</button>
<button className="submit-button" onClick={getFormData}>
获取数据
</button>
</div>
</div>
</div>
</div>
)
}
export default App
子组件
import React, { useState, useImperativeHandle, useEffect } from 'react'
interface FromProps {
handleSubmit: (e: React.FormEvent) => void
handleReset: () => void
checkForm: () => void
fromData: {
text: string,
password: string,
number: string
}
}
const From = ({ ref }: { ref: React.Ref<FromProps> }) => {
const [formData, setFromData] = useState({
text: '',
password: '',
number: ''
})
const [isSubmit, setIsSubmit] = useState(false)
type Action = 'text' | 'password' | 'number'
// 更新数据
const HandleState = (action: Action, value: string) => {
switch (action) {
case 'text':
setFromData((preState) => ({ ...preState, text: value }))
break;
case 'password':
setFromData((preState) => ({ ...preState, password: value }))
break;
case 'number':
setFromData((preState) => ({ ...preState, number: value }))
break;
}
}
useEffect(() => {
console.log(formData);
}, [formData])
const checkForm = () => {
if (formData.text == '') {
alert('名称不能为空');
return
}
if (formData.password == '') {
alert('密码不能为空');
return
}
if (formData.number == '') {
alert('验证码不能为空');
return
}
setIsSubmit(true)
}
const handleSubmit = (e: React.FormEvent) => {
// 阻止浏览器默认的提交行为
e.preventDefault();
if (!isSubmit) {
alert('请先检查表单');
return
}
console.log(formData);
// 登录成功后可以在这里添加回调或其他逻辑
}
// 重置输入
const handleReset = () => {
setFromData({
text: '',
password: '',
number: ''
})
}
// 暴露给父组件
useImperativeHandle(ref, () => {
return {
handleSubmit,
checkForm,
handleReset,
fromData: formData
}
})
return (
<div className="form-container">
<h2 className="form-title">用户登录</h2>
<form onSubmit={handleSubmit} className="login-form">
<div className="form-group">
<label htmlFor="text">用户名</label>
<input
type="text"
id="text"
value={formData.text}
onChange={(e) => HandleState('text', e.target.value)}
className="form-input"
placeholder="请输入用户名"
/>
</div>
<div className="form-group">
<label htmlFor="password">密码</label>
<input
type="password"
id="password"
value={formData.password}
onChange={(e) => HandleState('password', e.target.value)}
className="form-input"
placeholder="请输入密码"
/>
</div>
<div className="form-group">
<label htmlFor="number">验证码</label>
<input
type="text"
id="number"
value={formData.number}
onChange={(e) => HandleState('number', e.target.value)}
className="form-input"
placeholder="请输入验证码"
/>
</div>
</form>
</div>
)
}
export default From
注意事项
- 避免滥用:React 推荐以 props 为主,ref 为辅。过度使用
useImperativeHandle
可能导致代码难以维护。 - 依赖项优化:如果
createHandle
依赖外部变量,需在依赖项数组中声明,避免过期闭包问题。 - 与
forwardRef
结合:必须使用forwardRef
包装子组件,否则无法传递 ref。
对比其他方案
- 直接传递 ref:父组件可以访问子组件的全部实例,但可能破坏封装性。
- 回调 ref:通过回调函数传递 ref,灵活性高,但逻辑可能分散。
- useImperativeHandle:提供更精细的控制,适合需要隐藏实现细节的场景。