본문 바로가기
SPA/React

[React] StyledComponents와 Emotion을 비교해보기 (1) - styled

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

안녕하세요. J4J입니다.

 

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

 

 

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

 

 

 

 

최근 스타일을 꾸미기 위해 CSS-in-JS를 많이 사용하고는 합니다.

 

그중 가장 대표적으로 자리매김을 하고 있던 것이 StyledComponents입니다.

 

또한 최근에 새롭게 등장하여 점점 사용성이 높아지고 있는 Emotion이 존재합니다.

 

 

 

이들 모두를 한번에 사용하는 것은 혼란만 높이기 때문에 하나를 선택하는 것이 좋다고 생각하고 그렇기 때문에 동일한 상황을 만들어보며 두 기술을 비교해보겠습니다.

 

 

 

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. 마켓컬리 카피 화면 생성 ]

 

테스트 화면으로 마켓 컬리 카피 화면을 생성하려고 합니다.

 

/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;

 

 

728x90

 

 

[ 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. 마켓컬리 카피 화면 생성 ]

 

StyledComponents와 동일하게 마켓 컬리 카피 페이지를 만들어보겠습니다.

 

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

 

import * as React from 'react';
import styled from '@emotion/styled';
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 { ThemeProvider } from '@emotion/react';
import * as React from '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;

 

 

 

[ 6. 실행 화면 ]

 

코드를 작성한 뒤 실행해보면 다음과 같이 StyledComponents와 동일한 화면을 확인할 수 있습니다.

 

실행 결과

 

 

 

환경 세팅 비교

 

위에서 StyledComponents와 Emotion을 모두 테스트해본 결과 별 차이가 없다고 느껴졌습니다.

 

둘 다 styled를 이용해서 코드를 작성할 수 있었기 때문에 코드 작성하는 것에 있어서도 차이가 없었고 그나마 있던 차이를 하나 뽑으라고 하면 Emotion이 조금 더 패키지를 많이 설치해야 되는 정도라고 생각합니다.

 

환경 세팅으로만 봤을 때는 둘 중 어떤 것을 선택하더라도 차이가 없을 것이라고 생각되었고 개인의 성향에 따라 선택해도 될 것 같다는 생각이 들었습니다.

 

 

 

번들 파일 비교

 

Emotion을 사용하는 이유가 번들링 이후에 만들어지는 파일의 크기가 작아진다는 말을 들었던 적이 있어서 번들 파일을 비교해보려고 합니다.

 

먼저 StyledComponets의 크기는 다음과 같이 1,105KB가 나옵니다.

 

번들 크기

 

 

 

그리고 Emotion의 크기는 다음과 같이 1,176KB가 나옵니다.

 

번들 크기

 

 

 

저의 예상과 달리 Emotion이 더 큰 사이즈의 번들 파일이 생성된 것을 확인할 수 있었습니다.

 

 

 

추가적으로 Emotion이 설치하는 패키지의 갯수가 더 많기 때문에 현재는 크기가 더 크게 나온 것이라고 생각되어 코드들을 추가적으로 동일하게 작성해봤습니다.

 

그런 뒤 다시 빌드를 해봤을 때 StyledComponents의 크기는 다음과 같이 1,118KB가 나왔습니다.

 

번들 크기

 

 

 

Emotion의 크기는 다음과 같이 1,190KB가 나왔습니다.

 

번들 크기

 

 

 

StyledComponents는 1118 - 1105 = 13만큼 사이즈가 증가했고 Emotion은 1190 - 1176 = 14만큼 사이즈가 증가했습니다.

 

결론적으로 Emotion이 동일한 코드로 번들 크기가 더 증가하는 것을 확인할 수 있었습니다.

 

제가 알고 있던 것과 다른 결과가 나와서 좀 당황스럽네요...

 

 

 

 

 

 

 

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

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글