Table of contents
삼항 연산자
리액트 프로젝트에서 조건부 렌더링할 때 삼항 연산자를 사용할 수 있다.
fetchApi
는 특정 데이터를 요청하는 임의의 함수이다.<Person />
은 특정 데이터를 렌더링하는 컴포넌트이다.
function App() {
const queryKey = {
key: "fetchPeople",
};
const {
data: people,
isLoading,
error,
} = useSWR(queryKey, () => {
return fetchApi();
});
return (
<div>
<h1>People</h1>
{!people ? (
<p>Empty data</p>
) : (
people.map((person) => {
return <Person key={person.id} person={person} />;
})
)}
</div>
);
}
삼항 연산자를 JSX 문법 내부에 사용하기 위해선 중괄호, 물음표, 콜론 등을 작성해야 한다. 삼항 연산자가 여러 개 중첩되면 컴포넌트의 가독성이 떨어진다는 이슈가 있었다. 따라서 삼항 연산자를 대체할 수 있는 별도의 컴포넌트를 작성해 보기로 했다.
두 가지의 필요조건이 있었다.
데이터는 객체 형태로 전달되고, 객체 내 모든 요소가 유효한 값일 때만 보이게 하고 싶다. 즉 객체에 null 또는 undefined 프로퍼티가 없어야 한다.
데이터가 없어도 특정 인자가 true일 때 컴포넌트가 보여야 한다.
Stricted 타입은 객체의 모든 요소가 null 또는 undefined가 아님을 나타내기 위해 작성한 유틸리티 타입이다.
Guard
컴포넌트
Props에 children
항목을 함수 형태로 넘겨줄 수 있었고 컴포넌트에 children
항목을 넘겨줄 때는 속성을 직접적으로 명시하지 않고 children
위치에 컴포넌트를 어떻게 렌더링할지를 함수로 작성해주면 된다.
interface LayoutProps {
children: () => ReactNode;
}
const Layout = (props: LayoutProps) => {
return props.children();
};
const Section = () => {
return (
<Layout>
{() => {
return <p>Section</p>;
}}
</Layout>
);
};
GuardProps
타입은 객체 형태의 데이터가 주어질 때 그리고 주어지지 않을 때 두 가지로 나뉘어진다.
isOk
로 내부 컴포넌트를 렌더링할지 판단할 수 있고, 결정되는 과정은 다음과 같을 것이다.
data
가 주어지면 data에 undefined 또는 null 값의 속성이 있는지 검사하고 하나라도 있으면isOk
변수는 false를 나타낼 것이다.data
가 없으면when
인자로 판단한다.
isOK
가 참일 때는 Props로 전달된 내부 컴포넌트가 렌더링된다. 거짓일 때는 따로 fallback으로 지정한 컴포넌트가 없으면 아무것도 렌더링되지 않는다.
import { ReactNode, useMemo } from "react";
type Stricted<T extends Record<string, unknown>> = {
[key in keyof T]-?: NonNullable<T[key]>;
};
export type GuardProps<T extends Record<string, unknown>> =
| {
data?: undefined;
when: boolean;
children: () => ReactNode;
fallback?: ReactNode;
}
| {
data: T;
when?: true;
children: (data: Stricted<T>) => ReactNode;
fallback?: ReactNode;
};
export const Guard = <T extends Record<string, unknown>>(
props: GuardProps<T>
) => {
const { fallback, children } = props;
const isOk = useMemo(() => {
if (props.data === undefined) {
return props.when;
} else {
const values = Object.values(props.data);
return values.every((value) => value !== undefined && value !== null);
}
}, [props.data, props.when]);
const component = useMemo(() => {
if (!isOk && !fallback) {
return <></>;
}
if (!isOk && !!fallback) {
return <>{fallback}</>;
}
if (!props.data) {
return (children as () => ReactNode)();
} else {
return children(props.data as Stricted<T>);
}
}, [children, isOk, fallback, props.data]);
return component;
};
사용 예시는 다음과 같았다.
사용자가 입력한 title
이 10자 이상일 때와 아닐 때를 구분할 수 있다.
import { ChangeEvent, useEffect, useState } from "react";
import { Guard } from "~/components/Guard";
export default function Home() {
const [title, setTitle] = useState("");
const onChange = function (event: ChangeEvent<HTMLInputElement>) {
setTitle(event.target.value);
};
return (
<section>
<Guard
data={{ title: title.length < 10 ? null : title }}
fallback={<span>10자 이상 입력</span>}
>
{({ title }) => {
return <p>Input: {title}</p>;
}}
</Guard>
<input type="text" value={title} onChange={onChange} />
</section>
);
}