Todo list 리액트 프로젝트의 기능과 CSS를 전부 구현한 이후에 styled-component를 이용하여 스타일링을 구현하기 위해서 모든 파일들을 확인하는 중에 굉장히 자주 눈에 띄는 패턴이 하나 있었다.
isDone ? '완료' : '취소'
위와 같이 삼항 연산자를 이용해서 조건식이 true냐 false냐에 따라서 특정 값을 배출해야 하는 경우가 굉장히 많아졌다.
문자열의 경우에는 그냥 그러려니 짧기도 하고 넘어갔는데 문제는 styled-component를 하면서 확실하게 느껴졌다.
import React from "react";
import styled from "styled-components";
import { REMOVE } from "./TodoItem";
export default function TodoControlBtn({ role, handleClickBtn, children }) {
return (
<StyledTodoControlBtn
role={role}
className='box'
type='button'
onClick={handleClickBtn}>
{children}
</StyledTodoControlBtn>
);
}
const StyledTodoControlBtn = styled.button`
width: 40%;
background-color: transparent;
outline: none;
text-align: center;
transition: all 0.2s ease;
border-style: solid;
padding: 0.5rem 0.5rem;
border-color: ${props => props.role === REMOVE ? 'lightcoral' : 'lightgreen'};
&:hover {
background-color: ${props => props.role === REMOVE ? 'lightcoral' : 'lightgreen'};
color: ${props => props.role === REMOVE ? 'white' : ''};
}
`;
위 코드는 Todo에 들어가있는 2가지 종류의 버튼이 있는데 하나는 해당 Todo를 없애는 버튼과 나머지 하나는 Todo의 isDone을 토글 시키는 버튼이다.
그 코드 중 현재 버튼의 역할이 주어지면 해당 역할을 기준으로 다른 CSS를 주기위해서 위 처럼 styled component에 작성을 했다.
우선, 아래처럼 작성하여 다른 class명을 줘서 해결할 수 있을 거라고 생각했는데 이거 말고도 삼항연산자가 가독성이 떨어질 수 도 있겠구나라는 생각을 하였다.
// prop을 다르게 주어서 className을 각각 다르게 부여하여 class를 이용하여 분리하는 방법
export default function TodoControlBtn({ role, handleClickBtn, children }) {
return (
<StyledTodoControlBtn
className={'box ' + role === REMOVE ? 'remove' : ''}
type='button'
onClick={handleClickBtn}>
{children}
</StyledTodoControlBtn>
);
}
const StyledTodoControlBtn = styled.button`
width: 40%;
background-color: transparent;
outline: none;
text-align: center;
transition: all 0.2s ease;
border-style: solid;
padding: 0.5rem 0.5rem;
border-color: 'lightgreen';
&:hover {
background-color:'lightgreen';
}
&.remove {
border-color: 'lightcoral';
}
&.remove:hover {
background-color: 'lightcoral';
color: 'white'
}
`;
어떻게 해야 가독성을 챙기면서 props 이외에도 해당 패턴에서 사용할 수 있을까 생각을 하였다.
일단 styled component에서 사용하기 보다 좀 더 범용적으로 사용할 수 있게 isDone에 적용시킬 수 있는 방법을 먼저 생각하였다.
처음 생각은 간단했다.
a,b를 인자로 받는 alter함수를 만들고 반환 값으로 conditionFunction을 인자로 받는 함수를 반환한다음에 나중에 실행할 때 해당 conditionFunction을 실행시키고 나서 true 혹은 false일 때 a 혹은 b를 반환하도록 구현하였다.
// lib/alter.js
export const alter = (a,b) => conditionFunction => conditionFunction() ? a : b;
// TodoItem.jsx
const getBtnContent = alter('취소','완료');
function TodoItem({isDone}) {
return (<...>
{getBtnContent(() => !!isDone)}
</...>)
}
처음 생각처럼 구현을 하고 난뒤에 뭔가 아쉬웠다. 가독성 측면에서 리팩토링을 하려고 했는데 막상 getBtnContent는 컴포넌트 내부에서만 확인을 하면 무엇을 반환하는 것인지 짐작조차 가지 않았다. 내가 원하는 것은 컴포넌트 내부만 봐도 무엇을 반환하는지를 알고 싶었다.
그래서 alter 함수의 순서를 뒤집었다.
// lib/alter.js
export const alter = (conditionFunctino) => (a,b) => conditionFunction() ? a : b;
// TodoItem.jsx
function TodoItem({isDone}) {
const getBtnContent = alter(() => !!isDone);
return (<...>
{getBtnContent('취소','완료')}
</...>)
}
여기까지 구현을 하고 난 뒤에 내가 원하는대로 함수명과 그 함수가 무엇을 반환하는지 느낌이 왔다.
그래서 곧장 다른 사람들도 나와 같은 생각을 가지고 있을지 내 코드를 전혀 보지 못한 사람에게 해당 코드 부분을 보고 어떤 값이 반환될지 예상이 되냐고 물어보니 결과는 전혀 모르겠다는 답이 돌아왔다.
내가 구현하는 과정에서 어떤 결과가 나올 것인지 계산이 돼있는 상태에서 보는 getBtnContent와 남이 바라보는 getBtnContent는 전혀 다른 함수였다. 그래서 함수명을 어떻게 할 지 고민을 하다가 마지막으로 아래처럼 변경을 하였다.
const ifIsDoneElse = alter(() => !!isDone);
...
{ifIsDoneElse('취소','완료')}
...
다행히 다른 사람들도 이 함수는 이제 어떤 것일지 유추가 된다라는 답변을 받았다. 여기까지는 styled를 위한 함수는 아니었다.
이 생각을 기반으로 styled component내에서 삼항 연산자를 함수로 대신하여 내가 좀 더 쉽게 작성하고 또한 가독성 또한 높아져야 하는 것을 목표로 해야 한다.
styled component는 내부에 함수를 넣으면 해당 함수에 props를 전달하여 실행을 시킨다. 이 점을 이용하여 styled를 위한 alterWithStyled 함수를 따로 만들었다. 이미 alter를 구현하면서 생각했던 과정을 다만 어떻게 styled component에서 받을 수 있을까 그 부분만을 생각하니 금방 작성이 됐다.
const alterWithStyled = (conditionFunction) => (a,b) => (props) => conditionFunction(props) ? a : b;
위 함수를 이용해서 styled copmonent 내에서는 아래처럼 작성을 했다.
const ifAElseB = alterWithStyled(({role}) => role === REMOVE);
const StyledTodoControlBtn = styled.button`
...
border-color: ${ifAElseB('lightcoral','lightgreen')};
&:hover {
background-color:${ifAElseB('lightcoral','lightgreen')};
color: ${ifAElseB('white','')};
}
`