Frontend Study - 2/HTML & CSS

CSS : Styled-Components 활용법 + typescript

갓데미 2022. 7. 31. 15:42

기존 css의 경우 scss와 같은 전처리기가 있었음에도 동적으로 사용하기에는 불편함이 있었습니다. 인라인 스타일을 사용하거나, 클래스명을 이용해야 했습니다. 인라인 스타일의 경우 적용 순위가 높아지고, 클래스명의 경우 작명과 같은 불편함이 있었습니다.

 

이러한 점을 CSS in JS를 통해 해결할 수 있었습니다. CSS in JS는 css의 변수와 함수를 js파일 안에서 사용할 수 있게 합니다. Styled-Components 현재 가장 많이 쓰이고 있는 CSS in JS 중 하나입니다.

 

 

1. 설치 와 실행

$ npm install styled-components
import React from 'react';
import styled from 'styled-components';

기존 css의 경우 .css 파일을 별도 생성 뒤 js 파일에서 불러와서 사용하였지만

styled components의 경우 js 파일 안에서 불러온 뒤, 선언해서 사용합니다. 

 

 

+ 타입스크립트와 같이 사용

npm i -D @types/styled-components
yarn add -D @types/styled-components

 

 

 

2. 기본 사용

const [컴포넌트명] = styled.[html태그]`
  [부여하고자 하는 css속성]
`;
import styled from 'styled-components';          // 1

const App = () => {
  return <Title>styled-components!!</Title>;    // 2
};

const Title = styled.h1`       ⌉
  font-size: 32px;             |
  text-align: center;          |  // 3
  color: purple;               |
`;                             ⌋

1. 설치 된 styled-components를 사용할 js파일에서 import 해옵니다.

2. UI가 그려지는 return 문 안에서 스타일을 적용할 컴포넌트를 만들어 줍니다. 

3. 만들었던 컴포넌트에 대한 스타일을 밖에서 컴포넌트와 함께 선언해 줍니다.

+ 태그가 가지고있는 속성값들은 그대로 사용할 수 있습니다. ex) <Input type="password" ... />

 

 

3. 스타일드 컴포넌트의 활용

 

1) props를 사용한 동적 사용

 

자식 컴포넌트에 props를 전달해 주듯이, 컴포넌트 태그 안에서 전달해 준 요소들을 props로 받아서 동적으로 활용할 수 있습니다.

// App.js

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

const App = () => {
  const [changeColor, setChangeColor] = useState(false);

  const handleChanageColor = () => {
    setChangeColor(!changeColor);
  };

  return (
    <>
      <Button onClick={handleChanageColor} primary={changeColor}>
        Button
      </Button>
    </>
  );
}

const Button = styled.button`
  padding: 20px;
  margin: 10px;
  border: none;
  background-color: ${(props) => (props.primary ? "purple" : "pink")}; // 1
`;                         

export default App;
```

 

 

+타입스크립트와 같이 활용하려면 Props에 타입을 지정해 주어야 한다. 

 

 

//text.ts


//이렇게 짜면 오류
<Text done={done}>{text}</Text> // done이라는 props를 전달 받은 것.

export const Text = styled.div`
  flex: 1;
  font-size: 21px;
  color: #495057;
  ${({ done }) =>
    done &&
    css`
      color: #ced4da;
    `}
`;


//이렇게 하면 해결

export const Text = styled.div<{ done: boolean }>` // done 값을 받을 때 타입 지정.
  flex: 1;
  font-size: 21px;
  color: #495057;
  ${({ done }) =>
    done &&
    css`
      color: #ced4da;
    `}
`;

 

 

1. 단일 props 사용시

styled-components 1 props 타입지정

 

// const Container = styled.div< {프롭스명 : 타입지정} >`

const Container = styled.div< { age : number } >`

  color: ${(props) => (props.age > 20 ? 'red' : 'gray')};

`;

 

2. 다수 props 사용시 : interface 작성

// Container styled-components에 적용할 interfacer를 작성

interface Container extends 상속타입 {
  isActive: boolean;
  age: number;
  프롭스명: 타입지정;
}

// styled-components에 interface 타입 지정하기

const Container = styled.div<Container>`
  color: ${(props) => (props.age > 20 ? 'red' : 'gray')};
  background-color: ${(props) => (props.isActive ? 'red' : 'gray')};
`;

 

 

3. 상속 컴포넌트에 타입지정

// 상속컴포넌트의 타입 상속받기

interface Container {
  isActive: boolean;
  age: number;
  프롭스명: 타입지정;
}


// 상속받은 컴포넌트에 타입 추가하기

const Container = styled(상속받을 컴포넌트명)<Container>` //상속받을 컴포넌트명 기입
  color: ${(props) => (props.age > 20 ? 'red' : 'gray')};
  background-color: ${(props) => (props.isActive ? 'red' : 'gray')};
`;

 

 

2) 복제 기능

이미 선언된 스타일드 컴포넌트를 활용해서 새로운 컴포넌트를 만들 수 있습니다.

const [새로운 컴포넌트명] = styled(기존 컴포넌트명)`
  [부여하고자 하는 css속성]
`;
const App = () => {
  return (
    <>
      <Button>Button</Button>
      <NewButton>New Button</NewButton>
    </>
  );
};

const Button = styled.button`      
  margin: 20px;
  padding: 20px;
  border: none;
  background-color: yellow;
  font-size: 20px;
`;

const NewButton = styled(Button)`  
  color: white;
  background-color: purple;
`;

단순 태그 뿐만 아니라, Link, 아이콘 라이브러리 등 외부 라이브러리의 컴포넌트도 확장해서 사용할 수 있습니다.

 

import { Link } from 'react-router-dom';
;import styled from 'styled-components';

const App = () => {
  return <CartLink to="/cart">장바구니</CartLink>;
};

const CartLink = styled(Link)`
  color: red;
`;

export default App;

 

 

3) 네스팅

스타일드 컴포넌트를 사용하다 보면 네스팅을 사용해야 하는 때도 있습니다.

react-slick과 같은 외부 라이브러리를 사용하여 이미 정해진 스타일을 변경해야 할 때는 네스팅을 이용해서 스타일링을 해야 합니다.

import React from 'react';
import styled from 'styled-components';
import Slider from 'react-slick';
import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';

const Main = () => {
  return (
    <Carousel>
      // slick 코드
    </Carousel>
  );
};

const Carousel = styled(Slider)`
    .slick-slide {
      // slick 커스텀 스타일
    }
`;

export default Main;

 

 

4. 스타일드 컴포넌트의 공통스타일

기존 css에서의 common.css, reset.css 와 같이 공통으로 적용해주어야 하는 사항에 대한 스타일드 컴포넌트의 적용방법입니다.

 

1) common

공통으로 적용해 주고 싶은 스타일을 관리할 파일을 만듭니다.  (GlobalStyle.js)

createGlobaclStyle함수를 가져와서 전역에 적용해 주고 싶은 스타일을 넣을 컴포넌트를 선언해 주고,

상위컴포넌트에서 Import 해온 뒤 호출해줍니다.

// src/styles/GlobalStyle.js

import React from 'react'
import { createGlobalStyle } from 'styled-components'

const GlobalStyle = createGlobalStyle`
  * {
    box-sizing: border-box;
    font-family: 'Noto Sans KR', sans-serif;
  }
`

export default GlobalStyle;
// index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import { ThemeProvider } from 'styled-components';
import Router from './Router';
import GlobalStyle from './styles/GlobalStyle';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <ThemeProvider>
    <GlobalStyle />
    <Router />
  </ThemeProvider>
);

2) reset의 경우에 컴포넌트 라이브러리를 설치한 뒤, createGlobalStyle 안에서 템플릿리터럴로 선언해 줍니다.

npm install styled-reset

yarn add styled-reset
import React from 'react';
import { createGlobalStyle } from 'styled-components';
import reset from 'styled-reset';

const GlobalStyle = createGlobalStyle`
  ${reset}

  * {
    box-sizing: border-box;
    font-family: 'Do Hyeon', sans-serif;
  }
`;

export default GlobalStyle;

 

3) theme

 

사용할 스타일에 대한 파일 만들기. theme.js

color, fontSize등 공통된 테마는 theme.js에서 선언하여 각 컴포넌트에서 props로 받아 스타일 적용할 수 있습니다.  

// theme.js

const theme = {
  black: '#000000',
  white: '#FFFFFF',
  lightGrey: '#B0B0B0',
  middleGrey: '#717171',
  deepGrey: '#222222',
  hoverGrey: '#DBDBDB',
};

export default theme;

스타일드 컴포넌트에서는 ThemeProvider를 통해 전역으로 테마, JS변수 등을 공유하여 사용합니다.

ThemeProvider는 위의 예시와 같이 프로젝트의 엔트리 포인트인 index.js의 최초로 렌더링 되는 컴포넌트를 감싸주어서 모든 컴포넌트의 prop에 theme을 부여하여 적용할 수 있습니다.

// index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import { ThemeProvider } from 'styled-components';
import GlobalStyle from './styles/GlobalStyle';
import theme from './styles/theme';
import Router from './Router';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <ThemeProvider theme={theme}>
    <GlobalStyle />
    <Router />
  </ThemeProvider>
);
// App.js

import { Link } from 'react-router-dom';
import styled from 'styled-components';

const App = () => {
  return <Container>title</Container>;
};

const Container = styled.div`
  background-color: ${props => props.theme.lightGrey}
`;

 

4) mixin

자주 사용하는 css 스타일에 대해서는 variables.js 파일을 별도로 생성하여 사용하는 게 좋습니다. variables.js 파일은 theme과 variables를 Theme Provider에 같이 prop으로 합쳐서 전역에서 사용하거나, 사용하고자 하는 파일에서만 import해서 mixin을 사용할 수 있습니다. 아래와 같은 varibales.js 파일을 생성했을 때 어떤 방식으로 적용할 수 있는지 예시와 함께 확인해 보도록 하겠습니다.

 

// variables.js

import { css } from 'styled-components';

const variables = {
  flex: (direction = 'row', justify = 'center', align = 'center') =>`
    display: flex;
    flex-direction: ${direction};
    justify-content: ${justify};
    align-items: ${align};
  `,

  absoluteCenter: css`
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  `,
};

export default variables;
// index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import { ThemeProvider } from 'styled-components';
import GlobalStyle from './styles/GlobalStyle';
import theme from './styles/theme';
import variables from './styles/variables'
import Router from './Router';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <ThemeProvider theme={{ style: theme, variables }}>
    <GlobalStyle />
    <Router />
  </ThemeProvider>
);
// App.js

const App = () => {
  return (
    <Container>
      <Button>첫번째 버튼</Button>
      <Button>두번째 버튼</Button>
    </Container>
  );
};

const Container = styled.div`
  ${(props) => props.theme.variables.flexSet()} 
`;  // a

const Button = styled.button`
  background-color: ${(props) => props.theme.style.lightGrey};
`;

export default App;

variables.js 파일에서 기본으로 설정한 값을 사용할 때는 flexSet()을 써주고, 다른 인자를 넘겨줄때는 ${(props) => props.theme.variables.flexSet('','space-between',center')} 처럼 적용하고 싶은 인자를 넘겨줄 수 있습니다.

 

각 컴포넌트에서 불러오기

// App.js

import styled from 'styled-components';
import variables from './styles/variables';

const App = () => {
  return (
    <Wrapper>
      <Button primary="pink">Pink Button</Button>
      <Button primary="Yellow">Yellow Button</Button>
    </Wrapper>
  );
};

const Wrapper = styled.div`
  ${variables.flex()}
`;

const Button = styled.button`
  margin: 20px;
  padding: 20px;
  border: none;
  background-color: ${(props) => props.primary};
  font-size: 20px;
`;

export default App;

 

추가설명 Mixin.

 

import { css } from 'styled-components´;

export const Size = ({ height, maxHeight, maxWidth, width }) => css`
  block-size: ${height};
  inline-size: ${width};
  max-block-size: ${maxHeight};
  max-inline-size: ${maxWidth};

  @supports not (block-size: ${height}) {
    height: ${height};
    maxHeight: ${maxHeight};
  }

  @supports not (inline-size: ${width}) {
    max-width. ${maxWidth};
    width: ${width};
  }
`;
import { Size } from 'Mixins';

const SomeComponent = styled.div`
  ${Size({ height: '15rem', maxWidth: '50rem', width: '100%' });
`;