React 컴포넌트에서 ref 콜백으로 DOM 제어하기
문제 상황
검색 결과 리스트에서 키보드 방향키로 포커스를 이동시켜야 했다. 기존에는 ref="item0" 같은 문자열 ref를 사용했는데, React 16에서 deprecated 경고가 떴다.
ref 콜백 패턴
Ref 콜백은 컴포넌트가 마운트/언마운트될 때 호출되는 함수다. DOM 엘리먼트를 인자로 받아서 직접 저장할 수 있다.
class SearchResults extends React.Component {
constructor(props) {
super(props);
this.itemRefs = [];
this.state = { focusedIndex: 0 };
}
handleKeyDown = (e) => {
const { focusedIndex } = this.state;
const { results } = this.props;
if (e.key === 'ArrowDown') {
const nextIndex = Math.min(focusedIndex + 1, results.length - 1);
this.setState({ focusedIndex: nextIndex });
this.itemRefs[nextIndex].focus();
} else if (e.key === 'ArrowUp') {
const prevIndex = Math.max(focusedIndex - 1, 0);
this.setState({ focusedIndex: prevIndex });
this.itemRefs[prevIndex].focus();
}
}
render() {
return (
<ul onKeyDown={this.handleKeyDown}>
{this.props.results.map((item, index) => (
<li
key={item.id}
ref={(el) => this.itemRefs[index] = el}
tabIndex={-1}
>
{item.name}
</li>
))}
</ul>
);
}
}
주의사항
- ref 콜백은 마운트 시 DOM 엘리먼트와 함께 호출되고, 언마운트 시 null과 함께 호출된다
- 인라인 함수로 ref 콜백을 정의하면 업데이트마다 두 번 호출될 수 있다 (null, 그 다음 엘리먼트)
- 대부분의 경우 문제없지만, 클래스 메서드로 정의하면 이를 피할 수 있다
결과
문자열 ref 경고가 사라졌고, 배열로 여러 ref를 관리하면서 동적 리스트의 포커스 제어가 깔끔하게 해결됐다. React 16의 권장 패턴을 따르니 향후 마이그레이션 부담도 줄어들 것 같다.