目录

学习React-15-useImperativeHandle

学习React-15-useImperativeHandle

useImperativeHandle

useImperativeHandle 是 React 的一个 Hook,用于自定义向父组件暴露的 ref 句柄。通常与 forwardRef 结合使用,允许子组件控制父组件通过 ref 访问的属性和方法。

基本语法

useImperativeHandle(ref, createHandle, dependencies?)
  • ref:通过 forwardRef 传递的 ref 对象。
  • createHandle:一个函数,返回需要暴露给父组件的对象。
  • dependencies(可选):依赖项数组,类似 useEffect 的依赖项,用于控制何时重新计算 createHandle

使用场景

  1. 限制暴露的属性和方法:默认情况下,通过 ref 可以访问子组件的全部实例。使用 useImperativeHandle 可以只暴露特定的方法或属性。
  2. 封装第三方库:在包装第三方库时,可能需要隐藏底层实现,只暴露部分接口。
  3. 优化性能:避免父组件直接操作子组件的 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

注意事项

  1. 避免滥用:React 推荐以 props 为主,ref 为辅。过度使用 useImperativeHandle 可能导致代码难以维护。
  2. 依赖项优化:如果 createHandle 依赖外部变量,需在依赖项数组中声明,避免过期闭包问题。
  3. forwardRef 结合:必须使用 forwardRef 包装子组件,否则无法传递 ref。

对比其他方案

  • 直接传递 ref:父组件可以访问子组件的全部实例,但可能破坏封装性。
  • 回调 ref:通过回调函数传递 ref,灵活性高,但逻辑可能分散。
  • useImperativeHandle:提供更精细的控制,适合需要隐藏实现细节的场景。