전에도 설명했지만 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);
};
굉장히 조잡한 코드일 수 있지만...나는 이렇게 처리했다.
fallback
으로 에러시 나타내줄 ReactElement, null을 받아온다.fallback
이 유효한 ReactElement라면...useSSE
에서 반환한 error
를 주입한다.error
의 타입은 놀랍게도 제각각이다. 서버가 꺼져있을 땐 error.error
에 Error
타입이 담겨옴. url, token
등을 잘못 실어서 연결한 거라면 status = 400
으로 온다.export interface SSEErrorEvent extends Event {
status?: number;
error?: Error;
}
export type SSEErrorType = SSEErrorEvent | null;
쓰다보니 깨달았는데...이렇게 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
는 유효한 리액트 엘리먼트인지 판단해준다.
출처는 리액트 공식문서
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가 있다고 미리 명시해두었으니 여기는 전달해줄 필요가 없다.
이상!