ReScript in Korean

Ref 포워딩하기

원문

Ref 포워딩은 컴포넌트를 통해 다식 중 하나에 React.ref를 자동으로 전달하는 테크닉입니다. 이는 일반적인 애플리케이션의 대부분의 컴포넌트에서 필요하지 않습니다. 그러나 일부 컴포넌트, 특히 재사용 가능한 컴포넌트 라이브러리에는 유용할 수 있습니다. 가장 일반적인 시나리오는 아래에 설명되어있습니다.

왜 Ref를 포워딩해야 하나요?

네이티브 버튼 DOM 요소를 렌더링하는 FancyButton을 한 번 봅시다.

/* FancyButton.res */
@react.component
let make = (~children) => {
<button className="FancyButton">
children
</button>
}

리엑트 컴포넌트는 렌더링 된 출력을 포함하여 구현 세부 정보를 숨깁니다. FancyButton을 사용하는 다른 컴포넌트는 보통 내부 버튼() DOM 요소에 대한 참조를 가져올 필요가 없습니다. 이는 컴포넌트가 서로의 DOM 구조에 강하게 결합되는 것을 방지하기 때문에 좋고 권장됩니다.

이러한 캡슐화는 FeedStory 또는 Comment와 같은 애플리케이션 레벨 컴포넌트에 잘 어울리지만 FancyButton 또는 MyTextInpput처럼 재사용성이 높은 단말(leaf) 컴포넌트에는 불편할 수 있습니다. 이러한 컴포넌트는 일반 DOM button과 input과 유사한 방식으로 애플리케이션 전체에 사용되는 경향이 있으며, 포커스, 셀렉트 또는 애니메이션을 관리하기 위해서는 해당 DOM 노드에 불가피하기 액세스해야합니다.

컴포넌트를 통해 참조를 포워딩하는 방법에는 두가지 전략이 있습니다. 리스크립트와 리액트에서는 ref를 prop으로 넘기기를 강력하게 추천합니다만 React.forwardRef라는 전용 API도 있습니다.

우리는 이 두가지 방법에 대해 설명합니다.

Props를 통한 Ref 포워드

React.ref는 다른 prop처럼 전달할 수 있습니다. 컴포넌트는 참조를 올바른 DOM 요소로 포워딩합니다.

여기서는 다른 새로 배울 것이 없어요! 이미 봤거든요!

아래 예시에서 FancyInput input 요소로 전달 될 inputRef prop을 정의합니다.

// App.res
module FancyInput = {
@react.component
let make = (~children, ~inputRef: ReactDOM.domRef) =>
<div> <input type_="text" ref=inputRef /> children </div>
}
@send external focus: Dom.element => unit = "focus"
@react.component
let make = () => {
let input = React.useRef(Js.Nullable.null)
let focusInput = () =>
input.current
->Js.Nullable.toOption
->Belt.Option.forEach(input => input->focus)
let onClick = _ => focusInput()
<div>
<FancyInput inputRef={ReactDOM.Ref.domRef(input)}>
<button onClick> {React.string("Click to focus")} </button>
</FancyInput>
</div>
}

우리는 inputRef를 나타내기 위해 ReactDOM.domRef 타입을 사용합니다. DOM ref 속성 (<input ref={ReactDOM.Ref.domRef(myRef)} />)과 똑같은 방식으로 ref를 포워딩합니다.

(비추) React.forwardRef

참고 이 방법은 어느 시점에서 사라질 가능성이 높으며 이전에 나온 ref prop 전달에 비해 확실한 이점을 제공하지 않으므로 권장하지 않습니다.

React.forwardRef 는 커스텀 컴포넌트 내부에서 "ref prop을 에뮬레이션"하는 방법을 제공합니다. 내부적으로 컴포넌트는 전달 된 ref값을 대상 DOM 요소로 대신 전달합니다.

다음은 이전 예제에 React.forwardRef 접근 방식을 사용한 모습입니다.

/* App.res */
module FancyInput = {
@react.component
let make = React.forwardRef((~className=?, ~children, ref_) =>
<div>
<input
type_="text"
?className
ref=?{Js.Nullable.toOption(ref_)->Belt.Option.map(
ReactDOMRe.Ref.domRef,
)}
/>
children
</div>
)
}
@send external focus: Dom.element => unit = "focus"
@react.component
let make = () => {
let input = React.useRef(Js.Nullable.null)
let focusInput = () =>
input.current
->Js.Nullable.toOption
->Belt.Option.forEach(input => input->focus)
let onClick = _ => focusInput()
<div>
<FancyInput className="fancy" ref=input>
<button onClick> {React.string("Click to focus")} </button>
</FancyInput>
</div>
}

참고 @react.component 데코레이터는 기존 make 함수와 동일한 방식으로 React.forwardRef함수 내에서 레이블이 지정된 인수(labeled arguments) props 를 변환합니다.

이렇게 하면 FancyInput을 사용하는 컴포넌트가 기본 input DOM 노드에 대한 참조를 가져와 필요한 경우 액세스 할 수 있습니다. 마치 DOM input을 직접 사용한 것처럼요.

컴포넌트 라이브러리 관리자를 위한 참고사항

컴포넌트 라이브러리에서 ref 포워딩을 사용하기 시작하면 주요 변경사항(Breaking Change)으로 취급하고 라이브러리의 메이저 버전을 새로 릴리즈 해야합니다 이는 라이브러리에 눈에 띄가 다른 동작(Ex. ref가 할당되는 항목 및 Export 되는 경우)이 있을 가능성이 높고 이로 인해 이전 동작에 의존하는 앱 및 기타 라이브러리가 손상될 수 있기 때문입니다.