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의 권장 패턴을 따르니 향후 마이그레이션 부담도 줄어들 것 같다.