有時候會希望可以不透過state、props傳遞的方式呼叫child component內部的function,這種方式稱為imperative approach。
以下示範如何用 ref
、forwardRef
和 useImperativeHandle
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}
>
</>
);
});
重點
- Imperative approach 要用
useImperativeHandle
hook; useImperativeHandle
的第一個引數是定義在parent component的ref (e.g., emailRef、passwordRed),第二個引數則要傳入一個function並return一個特別的object,這個object可以放一些要被parent component使用的東西,如範例中的focus函式可以透過activate
名稱呼叫;- 外部ref並非props的一部分,要額外跟props一起傳入,如範例中
(props, ref)
的ref
; - 此外,要真正能使用外部ref,必須將child component用
forwardRef
包起來,forwardRef
會綁定外部ref並回傳一個component。
References
React - The Complete Guide (incl Hooks, React Router, Redux)