재미있는 안티 패턴인 React에서 자식 상태 조작하기

11965 단어 reactwebdevjavascript
Photo by Thomas Tastet (Unsplash)

참고: 이러한 코드 조각을 희소하게 사용하는 것이 좋습니다. 결국, 그것은 안티 패턴입니다. 그리고 초보자라면 먼저 상태를 들어 올려야 할 것입니다read this on how to change the value of child components...

자식 구성 요소의 상태를 수정하는 방법은 무엇입니까?



React를 사용하는 법을 배우면서 이것이 불가능하거나 적어도 바람직하지 않다는 말을 들었습니다. 이 문제를 해결하려면 상태를 높이고 컨텍스트, 구성 또는 기타 패턴을 사용해야 합니다.

그것이 올바른 방법이지만 때로는 아이들이 손을 뻗는 대신 구성 요소에 "손을 뻗고"싶을 수도 있습니다...

그리고 저에게는 React에서 실제로 가능한 것에 대해 이 기술을 찾는 것이 "아하 순간"이었습니다.

심판 입력



실제 DOM과 상호 작용할 때 refs를 사용하여 이를 수행합니다. 자신의 "상태"를 유지하는 다른 객체에 대한 참조:

function App() {
  const ref = useRef();

  useEffect(() => {
    ref.current.innerText =
      "I am manupulating the refs 'state'";
  }, []);

  return <div className="App" ref={ref} />;
}


이 기술을 사용하여 구성 요소에 대한 참조를 첨부할 수 있습니다. 그리고 우리가 "다른 것"과 상호작용하는 동안 우리가 아이들의 상태를 조작하는 것처럼 느껴집니다...

오래된 충실한 카운터 예



간단한 상태를 가진 자체 포함된 Counter 구성 요소가 있다고 가정해 보겠습니다.

function Counter() {
  const [value, setValue] = useState(0);

  function changeValue(factor) {
    return function () {
      setValue(value + factor);
    };
  }

  return (
    <div className="counter-container">
      <button onClick={changeValue(-1)}>-</button>
      <div className="counter-value">{value}</div>
      <button onClick={changeValue(1)}>+</button>
    </div>
  );
}


이제 사양이 변경되었으며 상위 구성 요소에서 번호를 사용자 지정 변경해야 합니다.

물론 올바른 방법은 값과 변경 핸들러를 상위 구성 요소로 올리는 것입니다. 그런 다음 상위 구성 요소가 상태를 유지하도록 하여 카운터 구성 요소를 업데이트할 수 있습니다.

그러나 우리는 이것을 하지 맙시다. 우리는 이상하게 가고있다

forwardRef 및 useImperativeHandle을 구출



문제를 해결하기 위해 React 라이브러리의 두 가지 유틸리티를 사용하고 있습니다. 우선 forwardRef

이 함수는 구성 요소를 래핑하고 참조를 다른 자식 구성 요소에 연결할 수 있습니다. 이것은 일반적으로 컴포넌트 라이브러리에서 ref를 DOM 요소에 첨부하는 데 필요합니다(위의 예와 같이). forwardRef로 래핑할 때 구성 요소는 두 개의 인수를 받습니다. 첫 번째 일반적인 props 객체와 두 번째(선택 사항) ref, 구성 요소를 인스턴스화하는 부모의 실제 ref 객체입니다.

const Counter = forwardRef(function (props, ref) {
...
})


다음으로 useImperativeHandle 후크

이 후크는 (문서에 명시된 대로) "customizes the instance value that is exposed to parent components when using ref" . (또한 이것은 좋은 습관이 아니라는 것을 경고합니다... 그러나 두 번째 부분은 무시합시다 😊)

즉, ref를 가져와 속성이나 기능을 첨부할 수 있습니다. 따라서 ref를 인스턴스화하는 상위 구성 요소에 사용할 수 있습니다.

구성 요소에 추가하는 것은 다음 코드입니다.

useImperativeHandle(ref, () => ({
    /** In the imperative handler the change  will 
        immediatly be executed.
    */
    changeValue: (factor) => changeValue(factor)(),
    setValue
  }));


이제 카운터 구성 요소의 전체 코드는 다음과 같습니다.

const Counter = forwardRef(function (_, ref) {
  const [value, setValue] = useState(0);
  function changeValue(factor) {
    return function () {
      setValue(value + factor);
    };
  }

  useImperativeHandle(ref, () => ({
    /** In the imperative handler, the change  will 
        immediately be executed.
    */
    changeValue: (factor) => changeValue(factor)(),
    setValue
  }));

  return (
    <div className="counter-container">
      <button onClick={changeValue(-1)}>-</button>
      <div className="counter-value">{value}</div>
      <button onClick={changeValue(1)}>+</button>
    </div>
  );
});


이제 카운터 구성 요소를 사용할 때마다 const ref = useRef() 메서드를 사용하여 참조를 만들고 카운터 구성 요소<Counter ref={ref} />에 전달할 수 있습니다. ref에 대한 액세스 권한이 있는 곳에서 다음과 같이 setValue 및 changeValue 함수를 실행할 수 있습니다.

<button 
  className="wide" 
  onClick={() => ref.current.setValue(2)}
>
  Set counter to 2
</button>


The full code and example can be found here


요약



명시된 바와 같이 이것은 원래 문제를 해결하는 가장 좋은 방법이나 가장 올바른 방법이 아닐 수 있습니다. 하지만 React로 가능성을 탐색하는 재미있는 방법입니다. 나는 이것을 내부 구성 요소 라이브러리와 함께 사용하여 구성 요소의 논리가 독립적이어야 하는 내부 상태의 일부에 액세스하거나 조작하는 데만 사용했습니다. 하지만 그런 다음 문제가 발생하고 그 작은 상태 또는 핸들러에 도달해야 할 수도 있습니다.

좋은 웹페이지 즐겨찾기