Replies: 9 comments 12 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.
-
저는 약간 좀 더 논쟁적일(?) 수 있는 절차지향적이고 mutable 한 스타일 사용중입니다. #4 (reply in thread) 에서 @hoseungme 님이 짚어주신 것과 같이 React 에서는 컴포넌트를 JSX에서 사용한것과, 컴포넌트 함수가 호출돼서 생성되고 및 렌더링 되는 동작은 별개더라구요. 그래서 위에서 나온 예시들 몇가지를 제 스타일로 작성하면 #4 (comment) 이 예시코드는 다음과 같이, function Component() {
if (someCondition) {
return <A />
}
}; #4 (comment) 이 예시코드는 다음과 같이 작성하겠습니다. function OrderStatus() {
const { status, payment, shipping } = useOrder();
let waitingMessage;
if (status === 'PENDING') {
if (payment.status === 'WAITING') {
waitingMessage = <PaymentWaitingMessage deadline={payment.deadline} />;
}
}
let deliveryInfo;
if (status === 'PROCESSING') {
if (shipping.status === 'PREPARING') {
deliveryInfo = <PreparingShipment estimatedDate={shipping.estimatedDate} />;
} else if (shipping.status === 'IN_DELIVERY') {
deliveryInfo = <InDeliveryInfo trackingNumber={shipping.trackingNumber} />;
} else {
deliveryInfo = <DeliveredInfo deliveryDate={shipping.deliveryDate} />;
}
}
return (
<div>
{waitingMessage}
{deliveryInfo}
</div>
);
}; 마찬가지로 조건문 뿐 아니라, map, filter, reduce 함수들도 별로 좋아하진 않아서 평범한 반복문 사용합니다. function EvenOdd(props: { lines: string[] }) {
const children = [];
for (let i = 0; i < props.lines.length; i++) {
if (i % 2 === 0) {
const even = <Even value={line} />;
children.push(even);
} else {
const odd = <Odd value={line} />;
children.push(odd);
}
}
return (
<div>{children}</div>
);
}; |
Beta Was this translation helpful? Give feedback.
-
다들 좋은 의견 많이 남겨주셔서 감사합니다. 개인적인 추가적인 의견을 남겨봅니다. 하지만 동일하게 예를 들어, AB테스트를 수행하는 <ABTest
condition={condition}
truthyComponent={<></>}
falsyComponent={<></>}
/> 해당 컴포넌트는 사실 이를 사용할 때 느낀 장점을 말씀드리자면, 실무에서는 서비스 내 여러 영역에서 AB테스트를 수행하는 케이스가 꽤 있을 것이라 생각합니다. 이때, 단순 조건식 혹은 논리 연산자로만 이런 케이스를 처리 했을 때 프로젝트 규모가 크다면 AB테스트 영역을 추적하는데 |
Beta Was this translation helpful? Give feedback.
-
먼저 좋은 주제 남겨주셔서 감사합니다. 저는 조건이 단순할때는 논리연산자나 삼항연산자를 사용하는 편입니다. 논리연산자나 삼항연산자가 그 자체로 나쁘다고 생각하진 않습니다. 다만 앞선 분들이 말씀하신 것처럼 조건이 복잡해져서 분기처리가 다양해질 때가 문제인듯 합니다. 확장성 예측가능성 타입추론등이 모두 어려워지기 때문입니다. 그럴떄 저는 early return과 추상화된 컴포넌트를 이용하여 처리하는 편입니다. 이를 통해 결합도를 낮추고 코드 가독성을 높힐 수 있다고 생각합니다. 컴포넌트는 jsx 내에서 statement를 사용할 수 없기에 사용하는 컴포넌트 같은데, 앞선 예제들이 모두 컴포넌트 내에서 return 문을 하나만 사용함으로써 예측 가능성과 가독성이 오히려 떨어지는 것 같습니다. 이와 관련하여 제가 번역한 블로그 포스트 하나와 ui 컴포넌트 추상화를 위한 글 하나를 첨부하겠습니다. https://wonderwalls.tistory.com/entry/컴포넌트-합성은-훌룡합니다-그런데 |
Beta Was this translation helpful? Give feedback.
-
여러 관점의 글이 있어서 재미있네요. 저도 복잡한 조건부 렌더링을 할 때 나름의 방식을 사용하고 있는데요. 저는 각 상태들의 가능한 조합을 하나의 변수 만들어서, ts-pattern의 match 문을 사용해서 표현하려고 합니다. 다른 분들은 어떻게 생각하시는지 궁금합니다. const Component() {
const [isLoading, setIsLoading] = useState(false)
const [hasError, setHasError] = useState(false)
const [data, setData] = useState(null)
useEffect(() => {
setIsLoading(true)
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
setData(data)
})
.catch(() => {
setHasError(true)
})
.finally(() => {
setIsLoading(false)
})
}, [])
const viewState = useMemo(() => {
if (isLoading) {
return 'loading'
}
if (hasError) {
return 'error'
}
return 'success'
}, [isLoading, hasError, data])
return (
<>
{match(viewState)
.with('loading', () => <LoadingView />)
.with('error', () => <ErrorView />)
.with('success', () => <SuccessView data={data} />)
.otherwise(() => null)}
</>
)
}
} |
Beta Was this translation helpful? Give feedback.
-
안녕하세요! 리액트로 코드를 작성하다 보면 조건부 렌더링을 할 일이 정말 많죠.
저는 보통 AND 논리 연산자(&&)나 삼항 연산자(?:)를 이용해서 조건부 렌더링을 처리하곤 하는데요.
최근 회사 코드를 살펴보다가, 아래처럼 선언적인 방식으로 조건부 렌더링을 구현한 걸 봤어요.
이런 방식이 익숙하지 않아서, 기존 방식(논리 연산자나 삼항 연산자)과 비교했을 때 어떤 장점과 단점이 있는지 궁금하더라고요.
혹시 이런 선언적인 방식을 사용하시는 분들이 많으신지, 의견 나눠주시면 감사하겠습니다.
333 votes ·
Beta Was this translation helpful? Give feedback.
All reactions