Replies: 5 comments 7 replies
-
저는 사용성 관점에서 생각해봤어요. 반대로 사용성이 높은 코드에서는 선언적으로 상태를 넘겨주는 방식이 더 효과적일 것 같아요. |
Beta Was this translation helpful? Give feedback.
-
저의 경우는 조건을 인지하는 방식에 따라 세가지로 나뉘는 것 같아요!
function Example(){
return isLoading && <Loading/>
}
functino Example(){
switch(state){
case "READY":
return <Ready/>;
case "PROGRESS":
return <Progress/>;
case "DONE":
return <Done/>;
}
function Example(){
<AccessManager country={["KR", "US", "JP"]} version="2.6.10" fallback={<Fallback/>}>
<Content/>
</AccessManager>
} 위의 사항들을 고려해봤을 때 위의 If 컴포넌트의 예시는 1번에 해당되는 것으로 보여서 저라면 삼항연산이나 논리연산을 사용할 것 같습니다! |
Beta Was this translation helpful? Give feedback.
-
저는 AND 연산자 Short-circuiting먼저, 예시JSX를 쓰니까 명확히 드러나지 않는 부분이 있는데요. 구체적인 JavaScript 코드로 살펴볼게요. 다음과 같이 <If condition={value != null}>
<span>값: {value}</span>
</If> JSX를 원래 JS 문법인 React.createElement(If, { condition: value != null }, [
// 이 라인은 value 값에 관계없이 항상 평가됨
React.createElement('span', {}, `값: ${value}`)
]); 반대로, 다음과 같이 삼항 연산자를 사용하는 상황을 생각해 볼게요. value != null ? (
<span>값: {value}</span>
) : null 다음 원본 코드를 보면, 완전히 value != null ? (
// value == null 이면 이 함수 호출이 완전히 무시됨
React.createElement('span', {}, `값: ${value}`)
) : null 그래서 더 언어 레벨에서 안전하게 코드를 다룰 수 있어요. 이렇게 TypeScript에서 타입 안전성을 보장하기 위해서 AND 연산자나 삼항 연산자를 쓰는 것이 더 좋은 방향이라고 생각해요. |
Beta Was this translation helpful? Give feedback.
-
저는 선언적인 컴포넌트 사용의 장점을 조금 생각해 봤어요. 인지적 관점에서 비교적 우수하다.예를 들어, 아래와 같은 상태를 처리하는 컴포넌트를 구현한다고 가정해 볼게요.
// 기존 방식 (&&, 삼항 연산자 사용)
const OrderStatus = () => {
const { status, payment, shipping } = useOrder();
return (
<div>
{status === 'PENDING' && payment.status === 'WAITING' && (
<PaymentWaitingMessage deadline={payment.deadline} />
)}
{status === 'PROCESSING' && (
shipping.status === 'PREPARING' ? (
<PreparingShipment estimatedDate={shipping.estimatedDate} />
) : shipping.status === 'IN_DELIVERY' ? (
<InDeliveryInfo trackingNumber={shipping.trackingNumber} />
) : (
<DeliveredInfo deliveryDate={shipping.deliveryDate} />
)
)}
</div>
);
}; 기존 방식으로 작성된 코드는 가독성이 좋다고 생각되지는 않아요. 이유는 이렇습니다.
이제 선언적인 컴포넌트 // 선언적 컴포넌트 사용
const OrderStatus = () => {
const { status, payment, shipping } = useOrder()
return (
<div>
<If condition={status === 'PENDING' && payment.status === 'WAITING'}>
<PaymentWaitingMessage deadline={payment.deadline} />
</If>
<If condition={status === 'PROCESSING'}>
<SwitchCase
value={shipping.status}
caseBy={{
PREPARING: (
<PreparingShipment estimatedDate={shipping.estimatedDate} />
),
IN_DELIVERY: (
<InDeliveryInfo trackingNumber={shipping.trackingNumber} />
),
DELIVERED: <DeliveredInfo deliveryDate={shipping.deliveryDate} />,
}}
/>
</If>
</div>
)
} 주관적일 수 있겠지만, 저는 이 방식이 훨씬 잘 읽힌다고 생각해요.
위의 장점들 덕분에 기능 명세 없이 코드만 봐도 렌더링 조건을 알 수 있을 정도로 명확하게 표현할 수 있어요. 얼마 전에 어느 아티클에서 읽은 내용인데요, 원본 글을 찾지 못해 기억나는 대로 재구성하자면 이런 내용이었습니다.
별도로 관리되는 문서는 최신을 보장할 수 없으며, 구현된 코드와 일치한다고 할 수 없어요. 사실은...저도 평소에 |
Beta Was this translation helpful? Give feedback.
-
저는 리액트에서 조건부 렌더링을 어떤식으로 작성하냐를 넘어서, "선언적이다", 그리고 "가독성이 좋다"라는 이유로 컴포넌트의 형태로 만들어 사용하는 것 자체에 대해 이야기하고 싶은데요. 지금까지 많은 프론트엔드 코드 리뷰와 논의를 지켜보면서, 어느 시점부터 공감이 잘 안가기 시작했던 부분이 바로 '특정 패턴을 일단 컴포넌트로 만든 후 그것을 선언적이라고, 또는 가독성이 좋다고 평가하는 것'이었습니다. 아주 적합한 예시로, 위에서도 바로 언급될 만큼 많은 분들이 익숙하신 switch case 컴포넌트가 있습니다. <Switch
value={value}
case={{
a: <A />,
b: <B />,
c: <C />,
}}
/> 자, 아주 선언적이고 가독성 좋은 예쁜 코드가 작성되었네요! 어라, 그런데 역으로 생각해봅시다. 그럼 아래 코드는 선언적이지 않은가요? 또는 가독성이 좋지 않은가요? switch (value) {
case "a":
return <A />
case "b":
return <B />
case "c":
return <C />
} 두 코드 모두 "value가 잠시 리액트를 떠나서 생각해봅시다. 주어진 숫자 중에서 가장 큰 값을 뽑는 로직을 작성했을 때, 두 코드 중 무엇이 선언적이라고 느껴지시나요? const arr = [1, 2, 3];
let max = 0;
for (let i = 0; i < arr.length; i++) {
if (max < arr[i]) {
max = arr[i];
}
} const max = Math.max(1, 2, 3); 저는 후자가 더 선언적이라고 생각합니다. 그 이유는, 전자는 결과를 어떻게 구하는지가 자세히 드러나지만, 후자는 원하는 결과가 무엇인지, 결과를 위해 무엇이 필요한지만 명확히 드러내고, 과정은 숨기기 때문이죠. 즉, '컴포넌트 사용'과 '선언적인 코드'는 서로 관계가 없다고 생각합니다. 반복되는 UI 렌더링 로직을 잘 추상화하여 선언적인 형태의 컴포넌트로 만들어서 사용할 때, 선언적인 코드가 되는 것이죠. 따라서 조건부 렌더링을 컴포넌트로 구현하는게 선언적인 측면, 가독성 측면에서 아무런 차이가 없으니, 다른 측면에서 어떤 이점이 있는지 살펴봐야 결정할 수 있을 것 같습니다. 첫 번째로 리렌더링 성능입니다. 우리는 부모 컴포넌트 전체가 불필요하게 리렌더링되는 것을 막기 위해, 자식 컴포넌트를 만들고 상태를 그 안으로 옮깁니다. 아래와 같이 말이죠. function Parent() {
return (
<ChildA />
<ChildB />
);
}
function ChildA() {
const [state, setState] = useState(); // state가 변경되면 ChildA 컴포넌트만 리렌더링됨
// ...
} 그런데 두 번째로 타입 안전성입니다. 아래와 같은 discriminated union이 있다고 해봅시다. type Query =
{ status: "fethcing" } |
{ status: "success", message: string } |
{ status: "failed", reason: string }; 이때 JavaScript의 switch-case로 작성하면, TypeScript 컴파일러가 query.status 값에 따라 query.message, query.reason 프로퍼티의 존재 여부를 추론해줍니다. switch (query.status) {
case "fetching":
return <Loader />;
case "success":
return <SuccessPage message={query.message} />;
case "failed":
return <ErrorPage reason={query.reason} />;
} 그런데 이를 즉, 오히려 컴포넌트로 조건부 렌더링을 했을 때 사용성을 넘어서 제품의 안정성까지 큰 손해를 보는 상황이 됩니다. 따라서 조건 분기를 컴포넌트로 작성하는 것은 선언적인 측면, 가독성 측면, 성능 측면, 안정성 측면 모두 아무런 이점이 없기 때문에, 그냥 JavaScript 기본 문법으로 분기하는게 더 옳다고 생각합니다. |
Beta Was this translation helpful? Give feedback.
-
안녕하세요! 리액트로 코드를 작성하다 보면 조건부 렌더링을 할 일이 정말 많죠.
저는 보통 AND 논리 연산자(&&)나 삼항 연산자(?:)를 이용해서 조건부 렌더링을 처리하곤 하는데요.
최근 회사 코드를 살펴보다가, 아래처럼 선언적인 방식으로 조건부 렌더링을 구현한 걸 봤어요.
이런 방식이 익숙하지 않아서, 기존 방식(논리 연산자나 삼항 연산자)과 비교했을 때 어떤 장점과 단점이 있는지 궁금하더라고요.
혹시 이런 선언적인 방식을 사용하시는 분들이 많으신지, 의견 나눠주시면 감사하겠습니다.
135 votes ·
Beta Was this translation helpful? Give feedback.
All reactions