ReScript in Korean

Refs와 DOM

원문

Refs는 컴포넌트 함수 make로 만들어진 DOM 노드나 리액트 요소에 접근하는 방법을 제공합니다.

일반적인 리액트 데이터 흐름상 오직 props를 통해서만 부모 컴포넌트와 자식 컴포넌트와 상호작을 할 수 있습니다. 자식 요소를 수정하기 위해서는 새로운 props 를 통해 다시 렌더링 시켜야 합니다. 그러나 일반적인 리액트 데이터 흐름 외부에서 자식 요소를 강제로 수정해야하는 몇 가지 경우가 있습니다. 자식 요소가 수정되려면 React.element이거나 Dom.element여야 합니다. 이 둘의 경우에 대해 리액트는 이스케이프 해치를 제공합니다.

React.ref의 정의는 다음과 같습니다.

type t<'value> = { mutable current: 'value }

Ref.ref와 뮤테이션을 가능하게 하는 랭귀지 기능인 빌트인 ref type 를 혼동하지 마세요.

언제 Refs를 사용하나요

refs가 필요한 몇가지 괜찮은 케이스가 있습니다.

  • 어떠한 재 렌더링도 발생시키지 않는 상태 관리
  • 포커스, 문자열 선택 또는 미디어 재생 관리
  • 명령형 애니메이션을 트리거 시킬 때
  • 써드 파티 DOM 라이브러리 통합

선언적으로 수행할 수 있는 모든 작업에는 refs 를 사용하지 마세요.

Refs 생성하기

리액트 ref는 React.ref('value) 타입을 통해 재현할 수 있습니다. value 타입의 변경 가능한 값을 관리하는 컨테이너인 React.useRef 훅을 사용하여 이런 종류의 참조를 만들 수 있습니다.

@react.component
let make = () => {
let clicks = React.useRef(0);
let onClick = (_) => {
clicks.current = clicks.current + 1;
};
<div onClick>
{Belt.Int.toString(clicks.current)->React.string}
</div>
}

위 예시에서 React.ref(int) 타입의 바인딩 된 clicks를 정의합니다. clicks.current 값을 변경해도 컴포넌트가 다시 렌더링되지 않습니다.

Refs 접근하기

ref가 엘리먼트 렌더링중 전해지면 현재 ref의 속성에 대한 참조에 접근할 수 있습니다.

let value = myRef.current

ref의 값은 노드 유형에 따라 다릅니다.

  • ref 속성이 HTML 엘리먼트에 사용되면, ReactDOM.Ref.domRef 통해 전달 된 ref는 기본 DOM 엘리먼트를 current 속성으로 받습니다. (React.ref<Js.Nullable.t<Dom.element>> 타입)
  • 인터롭의 경우 사용자 정의 클래스 컴포넌트(자바스크립트 클래스 기반)에서 ref 속성이 사용되면 ref 객체는 해당 컴포넌트의 마운트 된 인스턴스를 current 값으로 받습니다. (이 문서에서 설명하지는 않겠습니다)
  • 여러분은 ref 속성을 컴포넌트 함수에 사용할 수 없습니다 왜냐면 인스턴스가 없기 때문입니다. (리스크립트에서는 자바스크립트 클래스를 노출하지 않아요)

여기 몇가지 예제가 있습니다.

DOM 요소에 Ref 추가하기

이 코드는 React.ref를 사용해서, 버튼을 클릭했을 때 텍스트 필드에 포커스를 두기 위해 input에 대한 참조를 저장합니다.

/* CustomTextInput.res */
@bs.send external focus: Dom.element => unit = "focus"
@react.component
let make = () => {
let textInput = React.useRef(Js.Nullable.null)
let focusInput = () =>
switch textInput.current->Js.Nullable.toOption {
| Some(dom) => dom->focus
| None => ()
}
let onClick = _ => focusInput()
<div>
<input type_="text" ref={ReactDOM.Ref.domRef(textInput)} />
<input type_="button" value="Focus the text input" onClick />
</div>
}

몇가지 작업을 진행했습니다. 어떻게 된 일인지 알아볼까요?

  • textInputJs.Nullable.null ref로 초기화합니다.
  • textInput ref를 ReactDOM.Ref.domRef(textInput)를 통해 <input> 요소의 속성으로 지정합니다
  • focusInput 함수는 먼저 DOM 요소가 존재하는지 확인한 다음 focus 바인딩을 사용해 포커스를 설정해야합니다.

리액트는 컴포넌트가 마운트 될 때 current 필드에 DOM 요소를 할당합니다. 그리고 언마운트 될 때 null 값을 할당합니다.

Refs와 컴포넌트 함수

리액트에서 우리는 ref 속성을 컴포넌트 함수에 인자로 사용할 수 없습니다.

module MyComp = {
@react.component
let make = (~ref) => <input />
}
@react.component
let make = () => {
let textInput = React.useRef(Js.Nullable.null)
/* 이 코드는 컴파일이 되지 않습니다. */
<MyComp ref={ReactDOM.Ref.domRef(textInput)} />
}
/* Compiler Error: */
/* Ref cannot be passed as a normal prop. Please use `forwardRef` */
/* API instead */

위 예시는 컴파일 되지 않고 다음과 같은 에러를 뿜을 것입니다. "Ref cannot be passed as a normal prop. Please use forwardRef API instead.".

오류 메시지에서 알 수 있듯이, 다른 사람이 당신의 컴포넌트 함수에서 ref 에 접근할 수 있게 하려면 ref forwarding를 대신 사용할 수 있습니다. (아마도 useImperativeHandle 훅과 함께 사용해야 할 것입니다)

DOM Refs를 부모 컴포넌트로 노출하기

자주 있는 경우는 아니지만 우리는 부모 컴포넌트로부터 자식의 DOM 노드에 접근하고 싶을 때가 있습니다. 이건 일반적으로 추천되지 않는 방법입니다. 왜냐면 이것은 컴포넌트 캡슐화를 망쳐버리기 때문입니다. 하지만 포커스를 트리거하거나 자식 DOM 노드의 크기 또는 위치를 측정하는 데 유용 할 수 있습니다.

우리는 이런 케이스에 ref forwarding를 사용하길 추천합니다. Ref 포워딩은 컴포넌트가 자식 컴포넌트의 ref를 자신의 것으로 노출하도록 선택할 수 있습니다 자식 컴포넌트의 DOM 노드를 부모 컴포넌트로 노출하는 자세한 예시는 ref 포워딩 문서에서 찾을 수 있습니다.

콜백 Refs

리액트는 또한 “callback refs”(React.Ref.callbackDomRef)라고 불리우는 refs 지정하는 방법을 지원합니다. 이는 refs가 설정되고 설정되지 않을 때 더 세밀한 제어를 제공합니다.

React.useRef()을 통해 생성된 ref 값을 전달하는 대신, 콜백 함수를 전달할 수 있습니다. 이 콜백 함수는 대상 Dom.element를 인수로 받으며 다른 곳에서 저장하고 접근이 가능합니다.

참고 주로 우리는 React.Ref.domRef()를 사용해 ref 값을 내려줍니다만 콜백 refs에 대해서는 React.Ref.callbackDomRef()를 사용합니다.

아래 예제는 일반적인 패턴을 구현합니다. ref 콜백을 사용하여 인스턴트 속성에 DOM 노드에 대한 참조를 저장합니다.

/* CustomTextInput.re */
@bs.send external focus: Dom.element => unit = "focus"
@react.component
let make = () => {
let textInput = React.useRef(Js.Nullable.null)
let setTextInputRef = element => {
textInput.current = element;
}
let focusTextInput = _ => {
textInput.current
->Js.Nullable.toOption
->Belt.Option.forEach(input => input->focus)
}
<div>
<input type_="text" ref={ReactDOM.Ref.callbackDomRef(setTextInputRef)} />
<input
type_="button" value="Focus the text input" onClick={focusTextInput}
/>
</div>
}

리액트는 컴포넌트가 마운트 될 때 DOM 요소로 ref 콜백을 호출하고 마운트 해제 될 때 null을 호출합니다.

React.useRef()로 생성 된 객체 참조와 마찬가지로 컴포넌트 요소간에 콜백 참조를 전달할 수 있습니다.

/* Parent.res */
@bs.send external focus: Dom.element => unit = "focus"
module CustomTextInput = {
@react.component
let make = (~setInputRef) => {
<div>
<input type_="text" ref={ReactDOM.Ref.callbackDomRef(setInputRef)} />
</div>
}
}
@react.component
let make = () => {
let textInput = React.useRef(Js.Nullable.null)
let setInputRef = element => { textInput.current = element}
<CustomTextInput setInputRef/>
}

위 예시에서, Parent는 ref 콜백을 setInputRef props 으로 CustomTextInput에 전달하고 CustomTextInput은 특별한 ref 속성과 동일한 함수를 <input>에 전달합니다. 결과적으로 Parent의 textInput ref는 CustomTextInput<input요소에 해당하는 DOM 노드로 설정됩니다.