렌더링에 필요하지 않은 값을 참조할 수 있는 React Hook
ref는 애니메이션에서 특히 많이 사용하니 알아두자!
const ref = useRef(initialValue);
기본적인 사용 형식은 위와 같다.
import { useRef } from 'react';
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
alert(inputRef.current.value);
}
return (
<>
<input ref={inputRef} />
<button onClick={handleClick}>
Focus the input
</button>
</>
);
}
이런 식으로, 렌더링에 사용되지 않지만 form 제출이나 값을 계산하는 등의 작업에 사용할 수 있다.
주의할 점
렌더링 중에는
ref.current를 쓰거나 읽지 마세요.React는 컴포넌트의 본문이 순수 함수처럼 동작하기를 기대합니다.
입력값들
(props, state, context)이 동일하면 완전히 동일한 JSX를 반환해야 합니다.
다른 순서나 다른 인수를 사용하여 호출해도 다른 호출의 결과에 영향을 미치지 않아야 합니다.
렌더링 중에ref를 읽거나 쓰면 이러한 기대가 깨집니다.function MyComponent() { // ... // 🚩 Don't write a ref during rendering myRef.current = 123; // ... // 🚩 Don't read a ref during rendering return <h1>{myOtherRef.current}</h1>; }대신 이벤트 핸들러나 Effect에서 ref를 읽거나 쓸 수 있습니다.
커스텀 컴포넌트의 ref를 얻기 위한 장치
Framer Motion같은 라이브러리를 사용할 때 유용하니 알아두자!
const inputRef = useRef(null);
return <MyInput ref={inputRef} />;
위와 같이 커스텀 컴포넌트의 ref를 얻으려고 하면, 기본적으로 컴포넌트는 내부의 DOM 노드에 대한 ref를 외부로 노출하지 않기 때문에, 오류가 발생한다.
오류를 해결하려면, 아래와 같이 커스텀 컴포넌트를 수정해야 한다.
// MyInput.js
import { forwardRef } from 'react';
const MyInput = forwardRef(({ value, onChange }, ref) => {
return (
<>
<label>입력</label>
<input
value={value}
onChange={onChange}
ref={ref}
/>
</>
);
});
export default MyInput;
이렇게 ref를 직접 지정해주고, 컴포넌트의 props 뒤에 오는 매개변수로 전달해주어야 한다.
이런 형식이 불편하다면 아래와 같이 export에만 붙여서 사용할 수도 있다.
import { forwardRef } from 'react';
const MyInput = ({ value, onChange }, ref) => {
return (
<>
<label>입력</label>
<input
value={value}
onChange={onChange}
ref={ref}
/>
</>
);
};
export default forwardRef(MyInput);
ref로 노출되는 핸들을 사용자가 직접 정의할 수 있게 해주는 React Hook
무슨 말인지 조금 어려울 수 있다.
일단 사용법부터 보자.
useImperativeHandle(ref, createHandle, dependencies?)
기본적인 사용 형식은 위와 같다.
// MyInput.js
import { forwardRef, useRef, useImperativeHandle } from 'react';
const MyInput = forwardRef(function MyInput({value, onChange}, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => {
return {
// DOM노드를 직접 노출하는 대신, 메서드를 통해 핸들링
focus() {
inputRef.current.focus();
},
alertRef() {
alert(inputRef.current.value);
},
};
}, []);
return (
<>
<label>입력</label>
<input
value={value}
onChange={onChange}
ref={inputRef}
/>
</>
);
});
이렇게 useImperativeHandle을 사용하면, 더이상 DOM노드를 컴포넌트 외부로 노출하지 않게 된다.
// Form.js
import { useRef } from 'react';
import MyInput from './MyInput.js';
export default function Form() {
const ref = useRef(null);
function handleClick() {
ref.current.focus();
// 이 작업은 DOM 노드가 노출되지 않으므로 작동하지 않습니다.
// ref.current.style.opacity = 0.5;
}
return (
<form>
<MyInput placeholder="Enter your name" ref={ref} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}
사용할 때에는 위와 같이 useImperativeHandle에서 정의한 메서드들만 사용가능하다.
쉽게말해, ref를 통해 컴포넌트(DOM노드)를 직접 조작하는 것이 위험할 수 있으니, 의도된 동작만을 수행하도록 컴포넌트를 조작하는 몇 개의 메서드만을 제한적으로 사용할 수 있게 하는 것이다.