디자이너님과 함께 메인 페이지 작업을 시작했다.
결론부터 말하자면 디자인은 총 5번이 바뀌었다. 어흑...
첫 디자인 변경당시, 하드코딩 된 값들을 제거하는 작업을 하며, 디자이너님과 소통하는 과정에서 아래의 사실을 깨닫게 되었다.
- CSS에 대한 값은 정해져있다. 요구사항(figma)과 일치하는 CSS 값으로 코드를 작성할 것.
- 다만, 그 요구사항은 상황에 따라 변할 수 있다.
요컨대, 정확한 수치가 주어져있지만, 그 수치가 언제든 바뀔 수 있다는 뜻이다.
모순과 같은 이 상황을 어떻게 해결할 것인가?
가장 먼저 생각난 해결책은 소프트 코딩이다.
자주 변할 수 있는 부분(넓이, 높이, 색상 등)을 Constant 파일에 선언해두고 import해 전역적으로 관리하는 방법이다.
/* 아래와 같이 NAV 컴포넌트의 속성을 선언해둔다면, 해당 값이 바뀔 때,
영향을 받는 컴포넌트들을 소프트코딩한다면 변화에 쉽게 대처가 가능하다! :) */
export const NAV = Object.freeze({
WIDTH: 60,
BORDER_WIDTH: 1,
BACKGROUND: '#D9D9D9',
GAP: 10,
});
import styled from 'styled-components';
import { SIDE_NAV, TOP_NAV } from 'constants/';
import { DEVICES } from 'styles';
import { useLocation } from 'react-router-dom';
import { useMediaQuery } from 'hooks';
import { navChecker } from 'libs';
import { useSelector } from 'react-redux';
import { RootState } from 'redux/store';
export function ContentWrapper({ children }: { children: React.ReactNode }) {
const isNotSmallDevice = useMediaQuery(DEVICES.MOBILES);
const { pathname } = useLocation();
const hasNav = navChecker(pathname);
const isLoading = useSelector((state: RootState) => state.authLoadingSlicer);
return (
<Wrapper
isNotSmall={isNotSmallDevice}
hasNav={hasNav}
isLoading={isLoading}
>
<GridBox>{children}</GridBox>
</Wrapper>
);
}
interface WrapperProps {
isNotSmall: boolean;
hasNav: boolean;
isLoading: boolean;
}
const Wrapper = styled.div<WrapperProps>`
position: fixed;
top: ${(props) => (props.hasNav ? TOP_NAV.HEIGHT : 0)}px;
left: ${(props) =>
props.isNotSmall && props.hasNav && !props.isLoading
? `${SIDE_NAV.WIDTH}px`
: '0px'};
width: ${(props) =>
props.isNotSmall && props.hasNav && !props.isLoading
? `calc(100% - ${SIDE_NAV.WIDTH}px - ${TOP_NAV.PADDING * 2}px)`
: `calc(100% - ${TOP_NAV.PADDING * 2}px)`};
height: ${(props) =>
props.hasNav
? `calc(100% - ${TOP_NAV.HEIGHT}px - ${TOP_NAV.PADDING * 2}px)`
: `calc(100% - ${TOP_NAV.PADDING * 2}px)`};
padding: ${TOP_NAV.PADDING}px;
`;
const GridBox = styled.div`
display: grid;
height: 100%;
border-radius: 10px;
`;
단순히 px의 변경뿐 아니라 해당 페이지에 Navbar가 있는지, 로딩중인지, 디바이스 크기가 적당한지 등을 판별하고 모든 경우의 수에 맞는 유동적인 CSS를 제공할 수 있다.
코드를 작성하고 "이 정도면 괜찮게 해결했을지도..?"라는 생각이 들 때, 한 가지 문제점을 발견했다.
나의 코드는 Theme Toggle이 들어갈 때 굉장히 취약하다는 것. 😨 헉...
결국 GlobalStyle에 모든 값들을 선언해야하나?라는 생각이 들었다.
하지만, 해당 방법은 컴포넌트가 늘어날수록 themeToggle의 코드 길이가 굉장히 길어지기 때문에 명확한 해결책이라 볼 수 없다.
곰곰히 생각해보았다.
디자이너님이 정해주는 값은 크게 두 가지로 나뉜다.
1번. 즉, 자주 바뀌는 값은 소프트 코딩의 대상이다.
1번은 또 다시 아래처럼 나뉜다.
- color, backgroundColor 등
- width, height, border-radius 등
styled-components의 GlobalStyle에서 변수로 선언해 전역적으로 관리
하기로 결정했다.
상위 ThemeProvider에서 현재 theme의 state와 색상에 따라 값을 변경해주어야 하기 때문이다.
이전과 동일하게 constant로 관리하기로 했다.
theme state에 의해 변하지 않는다는 점에서 어느 곳에 관리해도 상관없지만, GlobalStyle의 theme에서 관리할 경우 객체의 관심사가 일치하지 않는다고 판단했고, lightTheme과 darkTheme에 중복하여 선언을 해야하기 때문에 힙 메모리가 낭비된다고 생각했기 때문이다.
결과는 아래와 같다.
import styled from 'styled-components';
import { NAV } from 'constants/layout';
function Nav() {
return (
<Wrapper>
// 컴포넌트
</Wrapper>
);
}
export default Nav;
const Wrapper = styled.div`
position: fixed;
left: 0;
top: 0;
background: ${({ theme }) => theme.navBackground};
box-sizing: border-box;
border-right: black ${NAV.BORDER_WIDTH}px solid;
width: ${NAV.WIDTH}px;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 23.5px;
gap: ${NAV.GAP}px;
`;
export const NAV = Object.freeze({
WIDTH: 88 as const,
BORDER_WIDTH: 1 as const,
GAP: 24,
});
//background가 빠짐!
export const darkTheme: DefaultTheme = {
background: '#2b2b2b',
transparentBackground: 'rgba(43, 43, 43, 0.65)',
color: '#F5F6F7',
transparentColor: 'rgba(245, 246, 247, 0.65)',
pointColor: '#FF681B',
transitionOption: 'ease-in-out 0.15s',
/* nav */
navBackground: '#D9D9D9',
navLinkBackground: '#666666',
};
export const lightTheme: DefaultTheme = {
background: '#F5F6F7',
transparentBackground: 'rgba(245, 246, 247, 0.65)',
color: '#2b2b2b',
transparentColor: 'rgba(43, 43, 43, 0.65)',
pointColor: '#FF681B',
transitionOption: 'ease-in-out 0.15s',
/* nav */
navBackground: '#666666',
navLinkBackground: '#D9D9D9',
};
삼항연산자로 각 컴포넌트에서 해결할까 생각도 해보았지만, 규모가 커질 경우 재사용성이 제한된다고 판단해, 해당 방법을 채택하였다 :)