React 기능 구성 요소 내에서 클래스 사용

이 기사는 힘을 사용하여 사각형 모양을 둥근 구멍으로 부수는 것에 관한 것입니다 :)
React는 기능적 접근 방식을 사용하도록 권장하지만 고집이 세서 대신 클래스를 사용하고 싶다면 어떻게 해야 할까요? 글쎄, 당신이 충분히 완고하다면 할 수 있습니다.

카운터를 작성한다고 가정하고 클래스를 생각해 봅시다.

export class Counter {
  private _value: number;

  constructor(initialValue: number) {
    this._value = initialValue;
  }

  public get value(): number {
    return this._value;
  }

  public increment(): void {
    this.add(1);
  }

  public decrement(): void {
    this.add(-1);
  }

  public add(n: number): void {
    this._value += n;
    console.log(`value changed, new value is: ${this._value}`);
  }
}


그런 다음 UI 라이브러리를 선택하고 사용하기로 결정합니다.

import { Counter } from "./counter/Counter.class";

export function App(): JSX.Element {
  const c = new Counter(100);
  const c2 = new Counter(-200);
  return (
    <div className="App">
      <section>
        <button onClick={() => c.decrement()}>decrement</button>
        {c.value}
        <button onClick={() => c.increment()}>increment</button>
      </section>
      <section>
        <button onClick={() => c2.decrement()}>decrement</button>
        {c2.value}
        <button onClick={() => c2.increment()}>increment</button>
      </section>
    </div>
  );
}


몇 개의 버튼을 누르고 React가 UI를 업데이트하지 않는다는 것을 알았지만 콘솔에서는 값이 업데이트되고 있음이 분명합니다. 이제 클래스를 사용자 지정 후크로 전환할 수 있지만 재미가 없습니다.

대신 업데이트가 발생하지 않는 이유에 대해 생각해 보겠습니다. 대답은 간단합니다. 소품이 변경되지 않았고 구성 요소 상태가 변경되지 않았으며 구성 요소를 업데이트할 필요가 없습니다. 꽤 합리적입니다. 그래서 우리는 무엇을 할 수 있습니까? 기본적으로 구성 요소를 강제로 다시 렌더링하려면Counter 클래스 메서드가 필요합니다. 즉, 몇 가지 후크를 사용해야 합니다.
React는 메서드에 대한 데코레이터를 제공하므로 인스턴스 메서드가 실행될 때 구성 요소 다시 렌더링을 트리거하는 사용자 지정 데코레이터를 사용할 수 있습니다.

import { useState } from "react";

export function useReactChangeDetection(
  target: unknown,
  propertyKey: string,
  descriptor: PropertyDescriptor
): void {
  const [, setState] = useState<string | undefined>();
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: unknown[]) {
    const result = originalMethod.apply(this, args);
    setState((prev) => (prev === undefined ? "" : undefined));
    return result;
  };
}


흥미로운 점은 React 기능 구성 요소 외부에 후크를 사용하거나 다른 후크를 사용할 수 없으므로 데코레이터를 Typescript 클래스에 직접 적용할 수 없으므로 다른 것을 생각해야 합니다.

우리의 목표는 후크 데코레이터를 React 클래스에 적용하는 것이므로 우리가 할 수 있는 것은 Counter 클래스를 확장하고 데코레이터를 지정된 메서드 이름에 적용하는 사용자 정의 후크를 작성하는 것입니다. 물론 이를 위해서는 메소드 이름을 추출할 수 있는 제네릭을 작성해야 합니다.

export type ClassMethod<T> = {
    [P in keyof T]: T[P] extends (...args: any[]) => any ? P : never;
}[keyof T];


이제 Counter 수퍼클래스의 확장된 클래스를 생성하는 후크를 만들 수 있습니다.

import { useMemo } from "react";

import { ClassMethod } from "../ClassMethod.model";
import { Counter } from "./Counter.class";
import { useReactChangeDetection } from "./useChangeDetection.hook";

export const useCounterClass = (
  method: ClassMethod<Counter>,
  value: number
) => {
  class UseCounterClass extends Counter {
    @useReactChangeDetection
    public override [method](n: number): void {
      super[method](n);
    }
  }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  return useMemo(() => new UseCounterClass(value), []);
};


우리가 슈퍼 메서드를 오버라이드하고 Counter 후크로 장식하는 방법에 주목하십시오. 이제 후크 내부에서 사용되므로 완벽하게 괜찮습니다. Counter를 새 후크로 교체하면 인스턴스화할 때 구성 요소 업데이트를 트리거할 클래스 메서드를 선택할 수도 있습니다.

import { useCounterClass } from "./counter";

export function App(): JSX.Element {
  const c = useCounterClass("add", 100);
  const c2 = useCounterClass("decrement", -200);
  return (
    <div className="App">
      <section>
        <button onClick={() => c.decrement()}>decrement</button>
        {c.value}
        <button onClick={() => c.increment()}>increment</button>
      </section>
      <section>
        <button onClick={() => c2.decrement()}>decrement</button>
        {c2.value}
        <button onClick={() => c2.increment()}>increment</button>
      </section>
    </div>
  );
}


거기에서 모든 상태는 클래스 인스턴스 내부에 있으며 useReactChangeDetection 업데이트를 존중해야 합니다. 터무니없지 않습니까? :디

좋은 웹페이지 즐겨찾기