PropsWithRef를 사용하기 전에 ref를 prop으로 넘기기 위해 사용하는 forwardRef()
에 대한 이해가 선행되어야 할 것 같다.
forwardRef()란?
코드 예제forwardRef() 없이 ref prop을 넘기면?
function MyInput({ ref }) {
// 자식 컴포넌트는 받아온 ref prop을 내부 input 태그의 ref prop으로 넘겨줌
return <input type="text" ref={ref} />;
}
export default function InputBox() {
// useRef로 생성한 ref 객체
const inputRef = useRef(null);
function handleFocus() {
inputRef.current.focus();
}
return (
<>
// 생성한 ref 객체를 자식 컴포넌트에 prop으로 넘겨줌
<MyInput ref={inputRef} />
<button onClick={handleFocus}>제출하기</button>
</>
);
}
이러한 에러가 발생한다.
에러 메세지에서 제안하는 해결방식은 두 가지이다.
(1) prop의 이름을 ref가 아닌 다른 것으로 바꾸기
수정 코드import React, { useRef } from "react";
function MyInput({ refprop }) {
return <input type="text" ref={refprop} />;
}
export default function InputBox() {
const inputRef = useRef(null);
function handleFocus() {
inputRef.current.focus();
}
return (
<>
<MyInput refprop={inputRef} />
<button onClick={handleFocus}>제출하기</button>
</>
);
}
예시 코드처럼, prop의 이름을 refprop으로 수정하면 해당 에러는 발생하지 않는다.
🤔 근데 이렇게 써도 될까?
물론 'refprop' 정도면 직관적인 편에 속하긴 하지만,
협업 등의 측면에서 봤을 때
HTML element에서 사용하는 prop과 이름을 동일하게 ref로 사용하는 것이 좋아보인다.
(2) forwardRef() 사용하기
forwardRef()
함수 사용이 필요하다!코드 예제forwardRef()는 어떻게 사용할까?
type InputProps = {
value: string;
placeholder: string;
};
const MyInput1 = forwardRef<HTMLInputElement, InputProps>(({ value }, ref) => {
return <input type="text" ref={ref} value={value} />;
});
export function InputBox1() {
const inputRef = useRef(null);
function handleFocus() {
if (inputRef.current) {
inputRef.current.focus();
}
}
return (
<>
<MyInput1 ref={inputRef} value="hi:)" placeholder="write here" />
<button onClick={handleFocus}>제출하기</button>
</>
);
}
위처럼 코드를 수정해주면, 변수명을 ref로 지정해도 에러가 발생하지 않는 것을 확인할 수 있다.
forwardRef()
만 사용해줘도 화면에 에러 없이 렌더링되긴 하지만, 🤔 일반 React.FC에서 Ref를 prop으로 사용하려면 어떻게 해야 할까?
이때 사용할 수 있는 것이 바로 PropsWithRef이다.
/** Ensures that the props do not include string ref, which cannot be forwarded */
// ref 속성이 문자열로 전달되는 것을 방지하고, 다른 타입의 ref만 허용하도록 props를 보장한다.
type PropsWithRef<P> =
// Just "P extends { ref?: infer R }" looks sufficient, but R will infer as {} if P is {}.
"ref" extends keyof P
? P extends { ref?: infer R }
? string extends R
? PropsWithoutRef<P> & { ref?: Exclude<R, string> }
: P
: P
: P;
"ref" extends keyof P ?
{ ref: ... }
형태인지 검사한다.P extends { ref?: infer R } ?
{ ref?: infer R }
형태일 때, ref의 타입을 추론하는 조건식{ }
일 경우 (어떠한 속성도 가지지 않을 경우) TS는 P 를 { ref?: infer R }
로 해석함{ }
로 추론됨{ }
로 추론되는 것string extends R ?
PropsWithoutRef<P>
와 { ref?: Exclude<R, string> }
을 합쳐서 반환한다.{ }
를 상속하게 되므로 빈 객체의 경우 위의 조건식은 참이 된다.{ }
로 추론되는 것을 막을 수 있다.type InputProps2 = {
...
ref: React.Ref<HTMLInputElement>;
};
const MyInput2: FC<PropsWithRef<InputProps2>> = ({...}) => {
return (
<input ref={ref} ... />
);
};
PropsWithRef<P>
의 P 부분은 위에서 정의상 InputProps2가 될 것이라고 예상할 수 있다.React.Ref<HTMLInputElement>
라는 value가 존재하므로 ref는 빈 값이 아님을 확인한다.React.Ref<HTMLInputElement>
값을 할당한다.