[React] SOLID 원칙 기반 클린 코딩 3 - LSP

olwooz·2023년 1월 3일
0

React

목록 보기
5/8

LSP - Liskov Substitution Principle

한국어로는 리스코프 치환 원칙이라고 부른다.
상위(부모) 타입 객체를 하위(자식) 타입 객체로 치환해도 정상 작동해야 한다는 원칙이다.

Custom Input 컴포넌트를 예시로 들겠다.

import cx from "clsx";

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

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

  return (
    <div>
      <div>
        <svg
          aria-hidden="true"
          fill="none"
          stroke="currentColor"
          viewBox="0 0 24 24"
          xmlns="http://www.w3.org/2000/svg"
        >
          <path
            strokeLinecap="round"
            strokeLinejoin="round"
            strokeWidth="2"
            d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
          ></path>
        </svg>
      </div>
      <input
        type="search"
        id="default-search"
        className={cx(isLarge && "text-3xl")}
        placeholder="Search for the right one..."
        required
        value={value}
        onChange={onChange}
        {...restProps}
      />
    </div>
  );
}

SearchInput 컴포넌트는 input태그의 하위 타입 객체라고 볼 수 있다.
React에서 이런 식으로 HTML element를 상속해서 사용할 때는 LSP를 준수하기 위해 두 가지 사항을 지켜야 한다.

  1. ISearchInputProps와 같이 상위 element의 type을 extend한 인터페이스를 정의한다.
  2. restProps와 같이 우리가 직접 사용하는 props를 제외한 나머지 props들 또한 전달해준다.

LSP를 준수하게 되면 코드에서 상위 타입 객체와 하위 타입 객체를 서로 자유자재로 대체할 수 있기 때문에 이점이 있다.
하지만 React에선 종종 부모 타입 객체가 가지지 않은 새로운 기능 등을 부여하기 위해 자식 객체를 만들고, 이 과정에서 부모 객체의 인터페이스를 사용하지 못하게 될 수도 있다.
그렇기 때문에 LSP는 항상 반드시 지킬 수는 없고, 그래야만 하는 것도 아니다.
다만 LSP를 불필요하게 위반하는 상황은 피해야 한다.

LSP를 불필요하게 위반하는 상황은 크게 두 가지가 있다.

  1. 위의 예제의 ...restProps에 해당하는 부분을 이유 없이 생략해 props를 모두 상속받지 않는 상황
  2. 특정 prop에 alias를 사용하는 상황 (아래 예시)
type Props = HTMLAttributes<HTMLInputElement> & {
  onUpdate: (value: string) => void
}

const CustomInput = ({ onUpdate, ...props }: Props) => {
  const onChange = (event) => {
    /// ... some logic
    onUpdate(event.target.value)
  }

  return <input {...props} onChange={onChange} />
}

위 코드에서는 CustomInput 내부의 onChange라는 로컬 함수로 인해 props로 onChange 대신 onUpdate라는 alias를 사용했다. 이런 상황을 방지하기 위해 아래와 같은 naming convention을 통해 LSP를 준수하도록 만든다.

type Props = HTMLAttributes<HTMLInputElement>

const CustomInput = ({ onChange, ...props }: Props) => {
  const handleChange = (event) => {
    /// ... some logic
    onChange(event)
  }

  return <input {...props} onChange={handleChange} />
}

세 줄 요약

  1. LSP는 상위 타입 객체를 하위 타입 객체로 치환해도 정상 작동해야 한다는 원칙이다.
  2. 상위 타입 객체를 extend하는 인터페이스를 정의하고 props를 모두 상속받는다.
  3. 항상 반드시 지켜야만 하는 것은 아니지만 불필요하게 위반하지 않아야 한다.

참고 자료

0개의 댓글