본문 바로가기
SPA/React

[React] StyledComponents와 Emotion을 비교해보기 (2) - css

by J4J 2022. 2. 26.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 StyledComponents와 Emotion을 비교해보는 시간에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

들어가기에 앞서 styled를 이용한 비교를 원하시는 분들은 다음 링크를 참고해주시길 바랍니다.

 

 

 

 

이번에는 Emotion을 사용할 때 styled가 아닌 css를 사용하여 구현하는 테스트 코드를 작성해보며 StyledComponents와 비교해보는 시간을 가져보겠습니다.

 

 

 

StyledComponents 개발 환경 세팅

 

[ 1. 패키지 설치 ]

 

$ npm install styled-components styled-reset
$ npm install -D @types/styled-components

 

 

 

[ 2. GlobalStyle 생성 ]

 

/src/style 경로에 globalStyle.tsx 파일을 생성하여 다음과 같이 작성해보겠습니다.

 

import { createGlobalStyle } from 'styled-components';
import reset from 'styled-reset';

const GlobalStyle = createGlobalStyle`
    ${reset}

    *, *::before, *::after {
        box-sizing: border-box;
    }

    body {
        font-family: 'noto sans';
    }
`;

export default GlobalStyle;

 

 

 

[ 3. theme 생성 ]

 

/src 경로에 theme.ts 파일을 생성한 뒤 다음과 같이 작성해보겠습니다.

 

import { DefaultTheme } from 'styled-components';

const theme: DefaultTheme = {
    saleColor: '#fa622f',
}

export default theme;

 

 

 

또한 만들어진 theme를 자동완성을 이용하기 위해 /src/@types 경로에 import-styledComponents.d.ts 파일을 생성한 뒤 다음과 같이 타입을 정의하도록 하겠습니다.

 

import { DefaultTheme } from 'styled-components';

declare module 'styled-components' {
    export interface DefaultTheme {
        saleColor: string;
    }
}

 

 

 

[ 4. 마켓컬리 카피 화면 생성 ]

 

StyledComponets를 이용하여 만들어 볼 간단한 화면은 마켓컬리 카피 화면입니다.

 

/src/pages 경로에 kurly.tsx 파일을 생성한 뒤 다음과 같이 작성해보도록 하겠습니다.

 

import * as React from 'react';
import styled from 'styled-components';
import arrowroot from '../assets/images/arrowroot.png';
import orange from '../assets/images/orange.png';
import purplebox from '../assets/images/purplebox.png';

const Kurly = (): JSX.Element => {
    return (
        <Wrapper>
            <Header>
                <Navbar.Container>
                    <Navbar.MenuList>
                        <Navbar.Menu>
                            <Navbar.MenuText>전체 카테고리</Navbar.MenuText>
                        </Navbar.Menu>

                        <Navbar.Menu>
                            <Navbar.MenuText>신상품</Navbar.MenuText>
                        </Navbar.Menu>

                        <Navbar.Menu>
                            <Navbar.MenuText>베스트</Navbar.MenuText>
                        </Navbar.Menu>

                        <Navbar.Menu>
                            <Navbar.MenuText>알뜰쇼핑</Navbar.MenuText>
                        </Navbar.Menu>

                        <Navbar.Menu>
                            <Navbar.MenuText>특가/혜택</Navbar.MenuText>
                        </Navbar.Menu>
                    </Navbar.MenuList>
                </Navbar.Container>

                <Banner.Container image={purplebox}>
                    <Banner.ArrowList>
                        <Banner.Icon className="fa-solid fa-arrow-left"></Banner.Icon>
                        <Banner.Icon className="fa-solid fa-arrow-right"></Banner.Icon>
                    </Banner.ArrowList>
                </Banner.Container>
            </Header>

            <Body>
                <Item.Container>
                    <Item.Header>
                        <Item.TitleText>이 상품은 어때요?</Item.TitleText>
                    </Item.Header>

                    <Item.Body>
                        <Item.ItemList>
                            <Item.Item>
                                <Item.ItemImageContainer>
                                    <Item.ItemImage src={orange} />
                                </Item.ItemImageContainer>
                                <Item.ItemText>[콜린스그린] 더 오렌지 1000mL</Item.ItemText>
                                <Item.ItemSaleText>20%</Item.ItemSaleText>
                                <Item.ItemPriceText>14,080원</Item.ItemPriceText>
                            </Item.Item>

                            <Item.Item>
                                <Item.ItemImageContainer>
                                    <Item.ItemImage src={arrowroot} />
                                </Item.ItemImageContainer>
                                <Item.ItemText>[한애가]] 담금초 칡 350mL</Item.ItemText>
                                <Item.ItemSaleText>30%</Item.ItemSaleText>
                                <Item.ItemPriceText>23,800원</Item.ItemPriceText>
                            </Item.Item>
                        </Item.ItemList>
                    </Item.Body>
                </Item.Container>
            </Body>
        </Wrapper>
    )
}

const Wrapper = styled.div``;

const Header = styled.header``

const Navbar = {
    Container: styled.div`
        max-width: 1024px;

        margin: 0 auto;

        height: 60px;
    `,

    MenuList: styled.ul`
        width: 100%;
        height: 100%;

        display: flex;
    `,

    Menu: styled.li`
        height: 100%;

        display: flex;
        align-items: center;

        margin: 0 16px;

        cursor: pointer;
    `,

    MenuText: styled.h3`
        font-weight: 700;
        font-size: 18px;
    `,
}

const Banner = {
    Container: styled.div<{ image: string }>`
        position: relative;

        background-image: ${(props) => `url(${props.image})`};
        background-repeat: no-repeat;
        background-position: center;

        height: 360px;
    `,

    ArrowList: styled.div`
        position: absolute;
        top: 0;

        width: 100%;
        height: 100%;

        display: flex;
        align-items: center;
        justify-content: space-between;
    `,

    Icon: styled.i`
        font: normal normal normal 14px/1 FontAwesome;
        font-size: 36px;

        margin: 0 18px;

        opacity: 0.4;
    `
}

const Body = styled.div``;

const Item = {
    Container: styled.div`
        max-width: 1024px;

        margin: 64px auto;
    `,

    Header: styled.header`
        text-align: center;

        padding: 20px 0;
    `,

    Body: styled.div`
        margin-top: 22px;
    `,

    ItemList: styled.div`
        display: flex;
    `,

    Item: styled.div`
        margin: 0 12px;
    `,

    ItemImageContainer: styled.div`
        width: 240px;

        cursor: pointer;

        overflow: hidden;
    `,

    ItemImage: styled.img`
        width: 100%;

        &:hover {
            transform: scale(1.06);

            transition: .3s ease-out;
        }
    `,

    TitleText: styled.h2`
        font-weight: 700;
        font-size: 32px;
    `,

    ItemText: styled.h4`
        margin: 14px 0;
    `,

    ItemSaleText: styled.span`
        font-weight: 700;
        color: ${(props) => props.theme.saleColor};

        margin-right: 18px;
    `,

    ItemPriceText: styled.span`
        font-weight: 700;
    `,
};


export default Kurly;

 

 

 

[ 5. App.tsx 파일 수정 ]

 

지금까지 작성한 코드들이 모두 적용될 수 있도록 App.tsx 파일을 다음과 같이 수정해보겠습니다.

 

import * as React from 'react';
import { ThemeProvider } from 'styled-components';
import Kurly from './pages/kurly';
import GlobalStyle from './style/globalStyle';
import theme from './theme';

const App = (): JSX.Element => {
    return (
        <ThemeProvider theme={theme}>
            <GlobalStyle />
            <Kurly />
        </ThemeProvider>
    )
}

export default App;

 

 

 

[ 6. 실행 화면 ]

 

다음과 같이 실행되는 것을 확인할 수 있습니다.

 

실행 화면

 

 

 

Emotion 개발 환경 세팅

 

이번엔 Emotion을 이용하여 StyledComponents와 동일한 작업을 해보도록 하겠습니다.

 

[ 1. 패키지 설치 ]

 

$ npm install @emotion/react @emotion/core @emotion/styled emotion-reset

 

 

 

[ 2. GlobalStyle 생성 ]

 

/src/style 경로에 globalStyle.tsx 파일을 생성한 뒤 다음과 같이 작성하겠습니다.

 

import * as React from 'react';
import { css, Global } from "@emotion/react";
import reset from 'emotion-reset';

const GlobalStyle = () => {
    return (
        <Global styles={css`
            ${reset}

            *, *::before, *::after {
                box-sizing: border-box;
            }

            body {
                font-family: 'noto sans';
            }
        `}/>
    )
}

export default GlobalStyle;

 

 

 

[ 3. theme 생성 ]

 

/src 경로에 theme.ts 파일을 생성한 뒤 다음과 같이 작성하겠습니다.

 

import { Theme } from "@emotion/react";

const theme: Theme = {
    saleColor: '#fa622f',
}

export default theme;

 

 

 

또한 theme 타입을 정의해주기 위해 /src/@types 경로에 import-emotion.d.ts 파일을 생성한 뒤 다음과 같이 작성해보겠습니다.

 

import '@emotion/react';

declare module '@emotion/react' {
    export interface Theme {
      saleColor: string;
    }
}

 

 

 

[ 4. tsconfig.json 파일 수정 ]

 

css를 이용하여 Emotion 코드를 작성하기 위해서는 TypeScript 설정을 수정해줘야 합니다.

 

tsconfig.json에서 다음 해당하는 부분만 수정해주면 됩니다.

 

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@emotion/react",
  },
}

 

 

 

[ 5. 마켓컬리 카피 화면 생성 ]

 

Emotion에서도 마켓컬리 카피 화면이 동일하게 나오도록 해보겠습니다.

 

/src/pages 경로에 kurly.tsx 파일을 생성한 뒤 다음과 같이 작성하겠습니다.

 

import { css, useTheme } from '@emotion/react';
import { Theme } from '@emotion/react';
import arrowroot from '../assets/images/arrowroot.png';
import orange from '../assets/images/orange.png';
import purplebox from '../assets/images/purplebox.png';

const Kurly = (): JSX.Element => {

    const theme = useTheme();

    return (
        <div css={Wrapper}>
            <header css={Header}>
                <div css={Navbar.Container}>
                    <div css={Navbar.MenuList}>
                        <div css={Navbar.Menu}>
                            <h3 css={Navbar.MenuText}>전체 카테고리</h3>
                        </div>

                        <div css={Navbar.Menu}>
                            <h3 css={Navbar.MenuText}>신상품</h3>
                        </div>

                        <div css={Navbar.Menu}>
                            <h3 css={Navbar.MenuText}>베스트</h3>
                        </div>

                        <div css={Navbar.Menu}>
                            <h3 css={Navbar.MenuText}>알뜰쇼핑</h3>
                        </div>

                        <div css={Navbar.Menu}>
                            <h3 css={Navbar.MenuText}>특가/혜택</h3>
                        </div>
                    </div>
                </div>

                <div css={Banner.Container(purplebox)}>
                    <div css={Banner.ArrowList}>
                        <i css={Banner.Icon} className="fa-solid fa-arrow-left"></i>
                        <i css={Banner.Icon} className="fa-solid fa-arrow-right"></i>
                    </div>
                </div>
            </header>

            <div css={Body}>
                <div css={Item.Container}>
                    <header css={Item.Header}>
                        <h2 css={Item.TitleText}>이 상품은 어때요?</h2>
                    </header>

                    <div css={Item.Body}>
                        <div css={Item.ItemList}>
                            <div css={Item.Item}>
                                <div css={Item.ItemImageContainer}>
                                    <img css={Item.ItemImage} src={orange} />
                                </div>
                                <h4 css={Item.ItemText}>[콜린스그린] 더 오렌지 1000mL</h4>
                                <span css={Item.ItemSaleText(theme)}>20%</span>
                                <span css={Item.ItemPriceText}>14,080원</span>
                            </div>

                            <div css={Item.Item}>
                                <div css={Item.ItemImageContainer}>
                                    <img css={Item.ItemImage} src={arrowroot} />
                                </div>
                                <h4 css={Item.ItemText}>[한애가]] 담금초 칡 350mL</h4>
                                <span css={Item.ItemSaleText(theme)}>30%</span>
                                <span css={Item.ItemPriceText}>23,800원</span>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    )
}

const Wrapper = css``;

const Header = css``;

const Navbar = {
    Container: css`
        max-width: 1024px;

        margin: 0 auto;

        height: 60px;
    `,

    MenuList: css`
        width: 100%;
        height: 100%;

        display: flex;
    `,

    Menu: css`
        height: 100%;

        display: flex;
        align-items: center;

        margin: 0 16px;

        cursor: pointer;
    `,

    MenuText: css`
        font-weight: 700;
        font-size: 18px;
    `,
}

const Banner = {
    Container: (image: string) => css`
        position: relative;

        background-image: url(${image});
        background-repeat: no-repeat;
        background-position: center;

        height: 360px;
    `,

    ArrowList: css`
        position: absolute;
        top: 0;

        width: 100%;
        height: 100%;

        display: flex;
        align-items: center;
        justify-content: space-between;
    `,

    Icon: css`
        font: normal normal normal 14px/1 FontAwesome;
        font-size: 36px;

        margin: 0 18px;

        opacity: 0.4;
    `
}

const Body = css``;

const Item = {
    Container: css`
        max-width: 1024px;

        margin: 64px auto;
    `,

    Header: css`
        text-align: center;

        padding: 20px 0;
    `,

    Body: css`
        margin-top: 22px;
    `,

    ItemList: css`
        display: flex;
    `,

    Item: css`
        margin: 0 12px;
    `,

    ItemImageContainer: css`
        width: 240px;

        cursor: pointer;

        overflow: hidden;
    `,

    ItemImage: css`
        width: 100%;

        &:hover {
            transform: scale(1.06);

            transition: .3s ease-out;
        }
    `,

    TitleText: css`
        font-weight: 700;
        font-size: 32px;
    `,

    ItemText: css`
        margin: 14px 0;
    `,

    ItemSaleText: (theme: Theme) => css`
        font-weight: 700;
        color: ${theme.saleColor};

        margin-right: 18px;
    `,

    ItemPriceText: css`
        font-weight: 700;
    `,
};


export default Kurly;

 

 

 

[ 6. App.tsx 파일 수정 ]

 

위에 작성한 코드들이 적용될 수 있도록 다음과 같이 App.tsx 파일을 수정하겠습니다.

 

import { ThemeProvider } from '@emotion/react';
import Kurly from './pages/kurly';
import GlobalStyle from './style/globalStyle';
import theme from './theme';

const App = (): JSX.Element => {
    return (
        <ThemeProvider theme={theme}>
            <GlobalStyle />
            <Kurly />
        </ThemeProvider>
    )
}

export default App;

 

 

 

[ 7. 실행 화면 ]

 

코드를 실행하면 다음과 같이 StyledComponets와 동일한 화면을 확인할 수 있습니다.

 

실행 화면

 

 

 

환경 세팅 비교

 

StyledComponets와 Emotion을 이용해 환경 세팅을 해보면 차이가 크게 없다는 것을 느낄 수 있습니다.

 

그나마 말할 수 있었던 차이점은 StyledComponets는 styled를 이용해 하나의 HTML 태그를 선택하여 컴포넌트화 시키는 것이고 Emotion은 css를 이용해 스타일만 지정한 뒤 스타일을 표현해줄 때 HTML 태그를 선택해서 사용해줄 수 있다는 것입니다.

 

어떤 목적을 가지고 사용하냐에 따라 더 편리함을 느낄 수 있을 것이라고 생각되지만 큰 체감은 없을 것이라고 판단되어 환경 세팅만 봤을 땐 개인의 성향에 따라 선택해도 될 것 같다고 느껴집니다.

 

 

 

번들 파일 비교

 

먼저 StyledComponets를 이용해 만들어진 번들 파일은 다음과 같이 1,105KB 입니다.

 

번들 크기

 

 

 

그와 반대로 Emotion을 이용해 만들어진 번들 파일은 다음과 같이 1,205KB 입니다.

 

번들 크기

 

 

 

기본적인 세팅만 했을 때는 Emotion이 더 큰 사이즈의 번들 파일을 만드는 것을 확인할 수 있습니다.

 

 

 

코드를 더 작성했을 때 증가되는 크기를 비교해보기 위해 동일하게 실행되는 코드들을 추가적으로 작성한 뒤 빌드를 해봤습니다.

 

StyledComponents는 다음과 같이 1,118KB로 변경되었습니다.

 

번들 크기

 

 

 

Emotion은 다음과 같이 1,219KB로 변경되었습니다.

 

번들 크기

 

 

 

StyledComponets는 1118 - 1105 = 13만큼 사이즈가 증가했고 Emotion은 1219 - 1205 = 14만큼 사이즈가 증가했습니다.

 

위에 링크를 걸어둔 styled를 사용할 때도 비슷하게 Emotion이 더 큰 사이즈의 번들 파일이 생성되는 것을 확인할 수 있었는데 css를 사용할 때도 동일하게 Emotion이 더 큰 사이즈의 번들 파일을 생성하는 것을 확인할 수 있었습니다.

 

Emotion이 StyledComponetns보다 더 작은 사이즈의 번들 파일을 만든다는 얘기를 들었었는데... 모든 케이스에 대해 Emotion이 더 큰 사이즈를 만드니 당황스러운 상황인 것 같습니다...

 

 

 

 

 

 

 

이상으로 StyledComponents와 Emotion을 비교해보는 시간에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글