React: forwardRef

有時候會希望可以不透過state、props傳遞的方式呼叫child component內部的function,這種方式稱為imperative approach。

以下示範如何用 refforwardRefuseImperativeHandle hook 達成:

// Form.js (parent component)
import {useState, useRef} from 'react'

export default function Form(){
    const [emailIsValid, setEmailIsValid] = useState(false);
    const [passwordIsValid, setPasswordIsValid] = useState(false);

    const emailRef = useRef();
    const passwordRef = useRef();

    // * Call focus function (alias: activate) inside <Input/>
    useEffect(()=>{
        if(!emailIsValid){
            emailRef.current.activate();   // *
        } else{
            passwordRef.current.activate();   // *
        }
    }, []);

    return (
        <form>
            <Input
                ref={emailRef}
                id="email"
                name="email"
                type="text"
            >
            <Input
                ref={passwordRef}
                id="password"
                name="password"
                type="password"
            >
        </form>
    );
}


<Input> 實際上是一個用來包裝 <label><input> 標籤的component:

// Input.js (child component)
import { useRef, useImperativeHandle } from 'react'

// ref is the ref defined outside and "actually" used via fowardRef
export default function React.forwardRef(Input(props, ref){

    const inputRef = useRef();

    const focus = () => {
        inputRef.current.focus();
    }


    // Manipulate something inside imperatively
    // (NOT through regular state or props management)
    useImperativeHandle(ref, () => {
        return {
            // A translation object that let something that can be used outside
            activate: focus
        }
    });


    return (
        <>
            <label>{props.name}</label>
            <input
                ref={inputRef}   // Ref defined inside
                id={props.id}
                name={props.name}
                type={props.type}
            >
        </>
    );
});


重點

  1. Imperative approach 要用 useImperativeHandle hook;
  2. useImperativeHandle 的第一個引數是定義在parent component的ref (e.g., emailRef、passwordRed),第二個引數則要傳入一個function並return一個特別的object,這個object可以放一些要被parent component使用的東西,如範例中的focus函式可以透過 activate 名稱呼叫;
  3. 外部ref並非props的一部分,要額外跟props一起傳入,如範例中(props, ref)ref
  4. 此外,要真正能使用外部ref,必須將child component用 forwardRef 包起來,forwardRef 會綁定外部ref並回傳一個component。


References React - The Complete Guide (incl Hooks, React Router, Redux)