리액트 SOLID

silverj-kim·2022년 12월 4일
0
post-thumbnail

SOLID principles

객체 지향 프로그래밍 (OOP) 에서 자주 나오는 설계 원칙 5개 S.O.L.I.D

S Single Responsibility

단일 책임 원칙
"every class should have only one responsibility"
클래스는 단일 책임을 가져야 한다. 한 클래스가 많은 책임이 있는 경우 책임 중 하나를 변경하면 사용자도 모르게 사이드이펙이 나올 수 있는 가능성이 많아진다.

export function Good() {
  const { products } = useProducts();

  //data filter는 hook에서
  const { filterRate, handleRating } = useRateFilter();

  return (
    <div className="flex flex-col h-full">
      //필터 View 로직은 Filter 컴포넌트에서
      <Filter
        filterRate={filterRate as number}
        handleRating={handleRating}
      />
      <div className="h-full flex flex-wrap justify-center">
        {filterProducts(products, filterRate).map((product: any) => (
          //Product View 로직은 Product 컴포넌트에서
          <Product product={product} />
        ))}
      </div>
    </div>
  );
}

O Open Closed

개방-폐쇄 원칙
"software entities should be open for extension but closed for modification"
클래스는 확장에는 열려있어야 하지만 수정에는 닫혀있어야 한다.
클래스의 현재 동작을 변경하면 해당 클래스를 사용하는 모든 시스템에 영향을 미친다. 이미 존재하는 기능을 변경하지 않고 추가하는 것이 중요하다.


interface IButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  text: string;
  // role?: "back" | "forward" | "main" | "not-found"; //bad
  icon?: React.ReactNode; //good
}

export function Button(props: IButtonProps) {
  const { text, role, icon } = props;

  return (
    <button
      className="flex items-center font-bold outline-none pt-4 pb-4 pl-8 pr-8 rounded-xl bg-gray-200 text-black"
      {...props}
    >
      {text}
      <div className="m-2">
        {/* bad
          {role === "forward" && <HiOutlineArrowNarrowRight />}
          {role === "back" && <HiOutlineArrowNarrowLeft />}
        */}
        {/* good */}
        {icon}
      </div>
    </button>
  );

L Liskov Substitution

리스코프 치환 원칙
"subtype object shoul d be substitutable for subtype objects"
S가 T의 자식 타입인 경우, T 타입 개체는 속성변경 없이 S 개체로 대체 될 수 있다.
하위 클래스가 상위 클래스와 동일한 작업을 수행할 수 없는 경우 버그가 발생할 가능성이 높다.
하위 클래스는 상위 클래스와 동일한 결과를 제공하거나 동일한 타입의 결과를 제공할 수 있어야 한다.


interface ISearchInputProps
  extends React.InputHTMLAttributes<HTMLInputElement> {
  isLarge?: boolean;
}

export function SearchInput(props: ISearchInputProps) {
  const { value, onChange, isLarge, ...restProps } = props;

  return (
    <div className="flex w-10/12">
      ...
        <input
          type="search"
          id="default-search"
          className={cx(
            "block p-4 pl-10 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none",
            isLarge && "text-3xl"
          )}
          placeholder="Search for the right one..."
          required
          value={value}
          onChange={onChange}
          {...restProps}
        />
      </div>
    </div>
  );
}

InputElement 를 상속받아 만든 컴포넌트인 SearchInput은 isLarge를 제외한 super type component(Input)의 속성을 그대로 전달해야한다.

I Interface Segregation

인터페이스 분리 원칙
"client should not depend upon interfaces that they don't use"
클라이언트가 사용하지 않는 인터페이스에 의존해선 안된다.
클래스는 역할을 수행하는 데 필요한 작업만 수행하도록 해야한다.


interface IThumbnailProps {
  // product: IProduct;
  imageUrl: string;
}

export function Thumbnail(props: IThumbnailProps) {
  // const { product } = props;
  const { imageUrl } = props;

  return (
    <img
      className="p-8 rounded-t-lg h-48"
      src={imageUrl}
      alt="product image"
    />
  );
}

D Dependency Inversion

의존성 역전 원칙
"one entity should depend upon abstractions not concretions"
추상화는 구체화에 의존해서는 안되며 세부 사항은 추상화에 의해 달라진다.
high level 모듈은 low level 모듈에 의존해서는 안된다. 둘 다 추상화에 의존해야 한다.

High-level Module : 도구로 액션을 수행하는 클래스
Low-level Module: 작업을 실행하는 데 필요한 도구
Abstraction: 두 클래스를 연결하는 인터페이스
Concretion: 도구 작동 방식

DIP 에 따르면 클래스는 작업을 실행하는 데 사용하는 도구와 융합되어선 안된다. 오히려 도구가 클래스에 연결할 수 있도록 Interface에 융합되어야 한다.
클래스와 인터페이스 모두 도구가 작동하는 방식을 몰라야 한다. 그러나 도구는 인터페이스의 사양을 충족해야 한다. 하위 클래스에 대한 상위 클래스의 종속성을 줄이는 것을 목표로 작성해야 한다.


export function Form(props: IFormProps) {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const { onSubmit } = props;

  // const handleSubmit = async (e: React.FormEvent) => {
  //   e.preventDefault();

  //   await axios.post("https://localhost:3000/login", {
  //     email,
  //     password,
  //   });
  // };

  const handleSubmit2 = (e: React.FormEvent) => {
    onSubmit(email, password);
  };

  return (
    <div>
    ...
    </div>
  );

High-level Module : Form 작성하는 View 로직이 있는 컴포넌트
Low-level Module: Submit 시 로그인 요청을 하는 비즈니스 로직이 있는 컴포넌트
Abstraction: IFormProps (prop: onSubmit)
Concretion: 제출 버튼 클릭 이벤트 헨들러

참고 영상: https://www.youtube.com/watch?v=MSq_DCRxOxw
참고 블로그: https://medium.com/backticks-tildes/the-s-o-l-i-d-principles-in-pictures-b34ce2f1e898

profile
Front-end developer

0개의 댓글