xtring.dev

[Styling] 💅Styled Components 이해하고 사용하기! (with React) 본문

Front-End/Styling

[Styling] 💅Styled Components 이해하고 사용하기! (with React)

xtring 2021. 3. 9. 11:44

 

styled components

 

  Styled Components

 

1. 스타일? Styled Components를 왜 배우는가!

  SASS 코드를 설치 없이 사용하고 CSS 파일 없이 CSS 코드를 짜고 게다가 이 코드를 React Native 앱으로 공유할수 있습니다. 가장 큰 장점은 기존의 className을 사용하지 않는 것과 컴포넌트에 스타일을 적용하여 스타일 코드를 몰아 넣을 수 있으며 공통 코드를 줄이고 React의 컴포넌트 형태로 사용할 수 있다는 점입니다!

 

2. 셋업

npx create-react-app <project-name>을 통해 환경을 설치합니다. 그리고 /src 디렉토리에 App.js와 index.js을 제외한 모든 파일을 제거합니다.

가장 먼저 버튼을 만드는데 hover, active state, 배경색을 바꿔보겠습니다.(js와 css를 사용합니다.)

App.css 파일을 생성합니다.(/src)

 

 

App.js에 App.css를 연결하고 .button의 스타일을 적용합니다.

 

[App.js]

import './App.css';

import React, { Fragment } from 'react';

function App() {
    return (
        <Fragment>
            <button className="button button--success">Hello</button>
            <button className="button button--danger">Hello</button>
        </Fragment>
    );
}

export default App;

 

 

[App.css]

.button {
    border-radius: 50px;
    padding: 5px;
    min-width: 120px;
    color: white;
    font-weight: 600;
    -webkit-appearance: none;
    cursor: pointer;
}

.button:active,
.button:focus {
    outline: none;
}

.button--success {
    background-color: #2ecc71;
}

.button--danger {
    background-color: #e74c3c;
}

 

  위 방식과 같이 style을 적용할 때 className을 사용하는 것은 때때 불편합니다. className을 사용하는 방식은 global하기 때문에 모든 naming이 겹치게 되면 스타일이 중복됩니다.

 

  이를 방지하기 위해 모듈을 사용하기도 하는데 모듈을 사용하기 위해서는 eject를 해야하며 그러면 설정이 필요합니다.(이는 매번 고통스럽습니다...) 그리고 우리는 React에서 아래와 같은 방식으로 Button 컴포넌트를 작성했었죠.(이는 복잡하고 못 생겼습니다!) 과거에 우리가 사용하는 방식이며 지금부터 이를 수정해 보겠습니다.

 

import './App.css';

import React, { Fragment } from 'react';

function App() {
    return (
        <Fragment>
            <Button danger />
        </Fragment>
    );
}

const Button = ({ danger }) => (
    <button
        className={danger ? "button button--danger" : "button button--success"}
    >
        Hello
    </button>
)

export default App;

 

3. Hello World with Styled Components

  먼저 yarn add styled-components를 설치합니다. 그리고 App.js의 import './App.css'를 제거하고 import styled from 'styled-components';를 추가합니다.

 

import React, {Fragment} from 'react';
import styled from 'styled-components';

...

 

이제 CSS 파일을 만들지 않고 styled를 통해 스타일을 적용할 수 있습니다!

 

import React from 'react';
import styled from 'styled-components';

function App() {
    return (
        <Container>
            <Button />
            <Button danger />
        </Container>
    );
}

const Container = styled.div`
    width: 100%;
    height: 100vh;
    background-color: #bdc3c7;
`;

const Button = styled.button`
    border-radius: 50px;
    padding: 5px;
    min-width: 120px;
    color: white;
    font-weight: 600;
    -webkit-appearance: none;
    cursor: pointer;
    &:active,
    &:foucus {
        outline: none;
    }
`;

export default App;

  위 코드가 바로 styled components를 이용한 Button 컴포넌트입니다! 이 처럼 어떤 SASS 작업 없이 스타일을 적용할 수 있습니다. 그렇다면 이번에는 prop를 적용한 컴포넌트를 만들어 볼까요?

 

 

  Button 컴포넌트에 아래 코드를 추가합니다!

 

const Button = styled.button`
    ...
    background-color: ${(props) => (props.danger ? '#e74c3c' : '2ecc71')};
}
`;

 

  만약 기존의 CSS 적용 방식을 사용했다면 CSS 파일과 클래스명을 지정하는 작업이 필요했을 것입니다. 하지만 styled-components을 통해 이를 해결할 수 있죠.

 

4. injectGlobal and Extend

  styled-components의 injectGlobal을 통해 html과 body의 margin, padding을 0을 만들어 봅니다.

injectGlobal을 사용하려고 찾아보니 v4부터 [Deprecated] 된 것을 찾아 볼 수 있었습니다. 따라서 createGlobalStyle을 사용하여 이를 적용해야합니다.
참고 : https://styled-components.com/docs/api

 

styled-components: API Reference

API Reference of styled-components

styled-components.com

 

 

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

import React from 'react';

const GlobalStyle = createGlobalStyle`
  body{
    padding: 0;
    margin: 0;
  }
`;

function App() {
    return (
        <Container>
            <GlobalStyle />
            <Button success>Hello</Button>
            <Button danger>Hello</Button>
        </Container>
    );
}

...

 

  다음은 버튼을 앵커, 링크로 사용하고 싶을 때는 어떻게 사용하면 되는지 배워 봅시다.

 

  styled-components를 재활용하여 이를 사용합니다. 이를 위해서는 extensions라는 것을 사용합니다. 말 그대로 버튼을 연장한다는 말인데요. styled()를 사용하여 컴포넌트를 확장하고 기존의 컴포넌트를 물려받을 수 있게 됩니다.

 

강의에서는 withComponents()와 extend를 사용한다고 했지만 [Deprecated] 된 것을 찾아 볼 수 있었습니다. 따라서 styled()을 사용하여 이를 적용해야합니다.
참고 : https://styled-components.com/docs/api

 

 

const Anchor = Button.withComponent("a").extend`
    text-decoration: none;
`;

 

  위 코드는 동작하지 않으며 아래 코드를 사용해야 합니다.

 

...

<Anchor as="a" href="https://www.google.com">
    Go to google
</Anchor>

...

const Anchor = styled(Button)`
    text-decoration: none;
`;

 

  styled-components의 버전이 올라가면서 deprecated된 부분들을 찾아 볼 수 있었습니다.

 

5. Animations

  애니메이션을 사용해 봅니다. 애니메이션을 사용하기 위해서는 styled-components의 keyframes을 사용합니다.

import styled, { createGlobalStyle, keyframes } from 'styled-components';

... 

const rotation = keyframes`
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(360deg);
    }
`;

 

  keyframes 메소드를 통해 애니메이션 동작을 생성합니다. 그리고 Button 컴포넌트 안에서 아래의 형태로 적용하면 Button이 danger props를 받을 때 애니메이션이 실행됩니다.

 

import styled, { createGlobalStyle, css, keyframes } from 'styled-components';

...

const Button = styled.button`
    border-radius: 50px;
    padding: 5px;
    min-width: 120px;
    color: white;
    font-weight: 600;
    -webkit-appearance: none;
    cursor: pointer;
    &:active,
    &:foucus {
        outline: none;
    }
    background-color: ${(props) => (props.danger ? '#e74c3c' : '#2ecc71')};
    ${(props) => {
        if (props.danger) {
            return css`  // 함수의 return으로 css를 전달하고 css 메소드를 사용합니다!
                animation: ${rotation} 2s linear infinite;
            `;
        }
    }}
`;

 

  다만 주의할 점은 (애니메이션 뿐만 아니라 css가 모두 해당되는 점인데) 함수의 return 값으로 css를 전달하고자 할 때 return 값에 css를 적용해줘야 한다는 것입니다. 또한 이런 방식의 CSS 전달 방식에서는 props를 통해 스타일을 직접 변경할 수 있습니다.

 

<Button danger rotationTime={5}> // rotationTime props를 넘겨준다.
    Hello
</Button>

...

const Button = styled.button`
        ...
    ${(props) => {
        if (props.danger) {
            return css`  
                animation: ${rotation} ${rotationTime}s linear infinite; // props를 통해 rotation time 값을 전달 받는다.
            `;
        }
    }}
`;

 

  props 전달에 의한 스타일 변경 방식은 CSS의 className의 개수를 현저히 줄일 수 있는 방법입니다.

 

6. Extra Attributes and Mixins

  지금까지 만들었던 Button 컴포넌트를 제거하고 새로운 Input 컴포넌트를 만들어보겠습니다.

 

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

import React from 'react';

const GlobalStyle = createGlobalStyle`
  body{
    padding: 0;
    margin: 0;
  }
`;

const Input = styled.input``;

function App() {
    return (
        <Container>
            <GlobalStyle />
            <Input placeholder="Hello" />
        </Container>
    );
}

const Container = styled.div`
    width: 100%;
    height: 100vh;
    background-color: pink;
`;

export default App;

 

  때때로 우리는 Input 컴포넌트에 attributes를 바꾸고 싶을 때가 있습니다. 이때 우리는 attrs() 메소드를 사용합니다.

 

...

const Input = styled.input.attrs({
    required: true,
})`
    border-radius: 5px;
`;

...

 

  styled-components에 attrs({ /* attributes */ })를 사용하게 되면 선언되어지는 컴포넌트 안에 각가의 다른 attribute를 지정할 수 있습니다.

 

  스타일을 그룹화하고 여러 장소에서 사용하기 위해서는 mixin을 사용합니다. minxin은 CSS 그룹입니다. 이를 위해서는 2가지 방법이 있는데 하나는 다른 컴포넌트를 확장(extend)하거나 mixin를 사용하는 방법입니다.

 

  먼저 styled-components에 css을 불러옵니다.

 

import styled, { createGlobalStyle, css } from 'styled-components';

 

  그리고 CSS block(awesomeCard)를 생성합니다.

 

const awesomeCard = css`
    box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
    background-color: white;
    border-radius: 10px;
    padding: 20px;
`;

 

  mixin을 통해 카드 스타일을 적용할 수 있으며 Input 컴포넌트에 적용하고 싶다면 아래와 같이 적용합니다.

 

...

const Input = styled.input.attrs({
    required: true,
})`
        border: none;
    border-radius: 5px;
      ${awesomeCard}; // CSS block인 awesomeCard를 적용
`;

...

 

여기서 잠시!! CSS는 동일한 property 값이 지정되어 있다면 뒤에 있는 값이 스타일에 적용됩니다. 예를 들어 아래와 같이 먼저 awesomeCard에 background-color가 지정되어 있었지만 그 뒤에 새로운 background-color가 지정된다면 뒤의 스타일 적용됩니다. 실제로 확인해보세요!

 

...

const Input = styled.input.attrs({
    required: true,
})`
        border: none;
    border-radius: 5px;
      ${awesomeCard};
        background-color: pink; // add line
`;

...

 

  이 처럼 Header, Footer, Card combination을 만들고 싶을때 minxin 기법을 손쉽게 재활용할 수 있습니다.(${awesome}, 미리 지정한 CSS block을 여러 컴포넌트에 적용하여 사용)

 

7. Theming

  디자이너와 협업하게 되면 색상, 색상 팔레트로 작업하는 것이 중요합니다.(acceent color, success color, danger color, neutral color, main color, ... 등이 있을 수 있죠.) 그리고 만약 이러한 지정된 color 들이 바뀌게 되면 일일이 지정되어 있는 곳 들을 찾아 변경해야할 수도 있습니다. 그래서 우리는 Theming을 해야합니다.

 

 

  먼저 theme.js 파일을 생성합니다. 그리고 객체 형태로 mainColor, dangerColor, successColor을 지정합니다.

 

const theme = {
    mainColor: '#3498db',
    dangerColor: '#e74c3c',
    successColor: '#2ecc71',
};

export default theme;

 

  theme에는 색상 뿐만 아닌 card의 padding, width 등의 자주 중복되면 앱의 전체적인 theme을 유지할 수 있는 스타일을 지정할 수 있습니다. 이 처럼 테마를 통해 모든 엘리먼트에 동일한 스타일을 적용할 수 있습니다.

 

  이를 적용하기 위해서는 ThemeProvider를 사용합니다. 그리고 이를 통해 porps 형태로 theme에 지정된 color 값들을 가져올 수 있습니다.

 

import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';

import React from 'react';
import theme from './theme';

const GlobalStyle = createGlobalStyle`
  body{
    padding: 0;
    margin: 0;
  }
`;

const Container = styled.div`
    width: 100%;
    height: 100vh;
    background-color: pink;
`;

const Card = styled.div`
    background-color: white;
`;

const Button = styled.button`
    border-radius: 30px;
    padding: 25px 15px;
    background-color: ${(props) => props.theme.successColor}; // ThemeProvider 아래 level의 컴포넌트에서는 지정받은 theme를 props 형태로 받아올 수 있습니다.
`;

function App() {
    return (
        <ThemeProvider theme={theme}>  {/* ThemeProvider의 theme prop에 불러온 theme를 넣어줍니다. */}
            <Container>
                <GlobalStyle />
                <Form />
            </Container>
        </ThemeProvider>
    );
}

const Form = () => (
    <Card>
        <Button>Hello</Button>
    </Card>
);

export default App;

 

  이렇게 prop를 통해 theme 값들을 내려받을 수 있는 형태는 어떤 속성에 의한 지정된 theme를 가지게 해줄 수 있습니다.(예를 들어 Light mode와 Dark mode, 색맹을 위한 사람들을 위한 theme 등...)

 

8. Nesting

  말 그대로 그물(Nest)을 치는 형태의 기술이며 Nesting이라고 합니다. Container 안의 Card를 참조하고 싶다면 아래와 같이 적용합니다. 이는 SASS와 같은 형태로 작성하며 비슷하게 동작합니다.

 

...

const Card = styled.div`
    background-color: red;
`;

const Container = styled.div`
    width: 100%;
    height: 100vh;
    background-color: pink;
    ${Card} {
        background-color: blue;
    }
`;

...

function App() {
    return (
        <ThemeProvider theme={theme}>
            <Container>
                <GlobalStyle />
                <Form />
            </Container>
        </ThemeProvider>
    );
}

export default App;

 

  또한 child 지정을 통한 사용도 가능합니다. 아래는 :last-child를 지정했습니다.

 

...

const Card = styled.div`
    background-color: red;
`;

const Container = styled.div`
    width: 100%;
    height: 100vh;
    background-color: pink;
    ${Card}:last-child {
        background-color: blue;
    }
`;

...

function App() {
    return (
        <ThemeProvider theme={theme}>
            <Container>
                <GlobalStyle />
                <Form />
                <Form />
                <Form />
                <Form />
                <Form />
            </Container>
        </ThemeProvider>
    );
}

export default App;

 

  이 경우 맨 마지막 Form의 경우에만 background-color가 blue로 지정되는 것을 확인할 수 있습니다.

 

9. CSS on React Native

  React Native에 styled componets 를 적용해봅니다!

 

 

  styled-components를 사용하면 웹/앱 각각에서 사용하던 컴포넌트를 그대로 옮겨서 사용 할 수 있습니다. React Native 프로젝트를 생성하고

 

yarn add styled-components

 

를 설치해 줍니다.

 

 

  React Native에서 사용하는 방식은 기존의 React 프로젝트에서 사용했던 방식과 같이 사용이 가능합니다.

 

import styled from 'styled-components';

...

const Container = styled.View`
        flex: 1;
        justify-content: center;
        align-items: center;
`;

...

function App() {
        return (
                <Container>
                        ...
                </Container>
        )
}

...

 

  이전에 제 경우엔 StyleSheet을 사용해 왔었는데 이번에 배운 styled-components를 통해 새로운 스타일 정의 방식을 사용할 수 있겠네요. styled-components을 사용함으로서 naming에서 자유로워지고 기존의 CSS 방식의 property을 사용할 수 있으며 코드 재 활용성을 높일 수 있는 장점을 살릴 수 있겠습니다!! 또 styled-components-breakpoint을 사용하여 tablet, desktop에서 각각의 스타일을 지정하는 방식도 있습니다. 이를 통해 Responsive Design을 구현할 때도 사용할 수 있습니다.

 

https://www.npmjs.com/package/styled-components-breakpoint

 

styled-components-breakpoint

Utility functions for creating breakpoints in `styled-components` 💅.

www.npmjs.com

 

 

 

 

 

 

Ref. 📗

https://styled-components.com/docs/api

반응형
Comments