프론트엔드 데브코스 5기 TIL 71 - eventSource 다루기, isValidElement와 cloneElement의 타입

김영현·2024년 2월 25일
0

TIL

목록 보기
82/129
post-thumbnail

Eventsource

전에도 설명했지만 SSE연결을 사용할때 쓰는 인터페이스다.
이 녀석은 연결할 때 헤더에 인증정보를 실어서 보내는 기능이 없어서 EventSourcePollyfill이라는 라이브러리를 받아서 사용했다.
그렇기에 공식 API와 차이가 있을 수 있으니 주의.

에러 핸들링

연결 실패시 에러메시지를 보내준다. 덕분에 try-catch문으로 감쌀 필요는 없었다.
대신 이벤트 핸들러방식이기에 ErrorBoundary는 사용하지 못한다.
따라서 자체적으로 에러를 핸들링 해야했다.

const SSEErrorBoundary = ({ fallback, children }: SSEErrorBoundary) => {
  const { getItem } = storageFactory(localStorage);
  const { data, isError, error } = useSSE({
    url: ``,
    options: {
      headers: {
        Authorization: `Bearer ${getItem('MyToken', null)}`,
      },
      heartbeatTimeout: SSE_TIME_OUT_LIMIT,
      withCredentials: true,
    },
  });

  let newFallback = null;
  if (isValidElement<{ error: SSEErrorType }>(fallback)) {
    newFallback = cloneElement(fallback, {
      error,
    });
  } else {
    newFallback = fallback;
  }

  return isError ? newFallback : children(data);
};

굉장히 조잡한 코드일 수 있지만...나는 이렇게 처리했다.

  1. fallback으로 에러시 나타내줄 ReactElement, null을 받아온다.
  2. fallback이 유효한 ReactElement라면...useSSE에서 반환한 error를 주입한다.
    이때 반환하는 error의 타입은 놀랍게도 제각각이다. 서버가 꺼져있을 땐 error.errorError타입이 담겨옴. url, token등을 잘못 실어서 연결한 거라면 status = 400으로 온다.
export interface SSEErrorEvent extends Event {
      status?: number;
      error?: Error;
    }

export type SSEErrorType = SSEErrorEvent | null;
  1. 자식 컴포넌트는 function as child component기법으로 값을 전달해준다.

쓰다보니 깨달았는데...이렇게 props를 주입하면 실제 자식 컴포넌트를 불러올때 props 에러가 난다.

const SSEFallBack = ({ error }: { error?: SSEErrorType }) => {

그래서 옵셔널로 해두었지만, SSEErrorBoundary구현체를 모르는 이상 알아차리기가 쉽지 않다.
따라서 fallback으로 들오는 값도 function as child component기법으로 처리하면 될 것 같다.

//타입 살짝 변경
  fallback: (
    error: SSEErrorType
  ) => ReactElement<
    unknown,
    string | FunctionComponent | typeof Component
  > | null;

...
return isError ? fallback(error) : children(data);

이러면 명시적으로 어떤 인자를 받아오는지 구현체를 보지 않고도 볼 수 있다!


isValidElement, cloneElement

리액트에서 제공해주는 내장 메서드.
isValidElement유효한 리액트 엘리먼트인지 판단해준다.
출처는 리액트 공식문서

import { isValidElement, createElement } from 'react';

// ✅ JSX tags are React elements
console.log(isValidElement(<p />)); // true
console.log(isValidElement(<MyComponent />)); // true

// ✅ Values returned by createElement are React elements
console.log(isValidElement(createElement('p'))); // true
console.log(isValidElement(createElement(MyComponent))); // true

// ❌ These are *not* React elements
console.log(isValidElement(null)); // false
console.log(isValidElement(25)); // false
console.log(isValidElement('Hello')); // false
console.log(isValidElement({ age: 42 })); // false
console.log(isValidElement([<div />, <div />])); // false
console.log(isValidElement(MyComponent)); // false

즉, 기본 JSX 요소인지 판단해준다는 뜻이다.
이 메서드는 보통 cloneElement와 같이 쓰인다.

cloneElement리액트 엘리먼트를 복제하여 새로운 리액트 엘리먼트를 만들어줌.

const clonedElement = cloneElement(element, props, ...children)

이때 첫번째 인자가 유효한 ReactElement. 즉, JSX여야한다.
그렇기에 isValidElement로 한번 걸러준다.

또한 병합될때 약간씩 데이터가 변경된다.

말만 들어보면 참 쉽다. 그냥 isValidElement로 유효성 검사를 하고 cloneElement로 새로운 자식을 생성하면 된다. 그러나 타입이 들어가면 헷갈린다.

타입

isValidElement는 제네릭을 받는다.

isValidElement<P>(object: {} | null | undefined): object is ReactElement<P>;

즉 리턴하는 JSX의 제네릭에 P를 전달한다.

interface ReactElement<
        P = any,
        T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>,
    > {
        type: T;
        props: P;
        key: string | null;
    }

그 P는 props의 타입이다.

내가 원했던 코드는 이랬다.

 let newFallback = null;
  if (isValidElement(fallback)) {
    newFallback = cloneElement(fallback, {
      error,
    });
  } else {
    newFallback = fallback;
  }

props로 받아오는 fallback이라는 값이 유효한 ReactElement라면, error라는 props를 주입해서 새로 반환한다.

하지만 fallback이라는 값은 어떠한 props를 들고있을지 타입스크립트 컴파일러가 모른다.

이를 해결하기 위하여 fallback이 유효한 엘리먼트라면, error라는 props를 갖고 있다고 미리 명시해 둔다.

if (isValidElement<{ error: SSEErrorType }>(fallback)) {
...
}

따라서 위처럼 props의 타입을 제네릭으로 전달하여 컴파일러에게 명시

참고로 cloneElement도 제네릭을 받는다.

 function cloneElement<P>(
        element: ReactElement<P>,
        props?: Partial<P> & Attributes,
        ...children: ReactNode[]
    ): ReactElement<P>;

새로 만들어질 ReactElement의 props타입을 지정해준다.
이미 isValideElement에서 error props가 있다고 미리 명시해두었으니 여기는 전달해줄 필요가 없다.

이상!

profile
모르는 것을 모른다고 하기

0개의 댓글