styled-components셋팅 및 설정

jung moon chai·2022년 6월 27일
3
post-thumbnail
post-custom-banner

Nextjs를 활용한 블로그 형식의 포트폴리오 제작을 진행하며 개인적인 관점에서 느낀점, 기술 기록용으로 작성될 예정입니다:). 흥미 위주로 서술한 만큼 오류가 있을 수 있습니다. 가벼운 마음으로 읽고 지적 해 주실 부분이 있으시다면 언제나 환영입니다! :)

이번엔 styled-componets를 세팅작업을 진행해보겠습니다.

👏styled-components


styled-components란 무엇인가?

styled-compoents는 이름처럼 그대로 스타일 컴포넌트다.
일단 컴포넌트와 styled-components의 형태를 한번 비교해보자.

const App = () => {
  return (
    <Layout>
      <p>본문내용</p>
    </Layout>
  );
};
const Header = () => {
  return <div>헤더</div>
};
const Layout = ({children}) => {
  return (
    <>
      <Header />
      {children}
    </>
  );
};


App 컴포넌트안에서 사용된 Layout컴포넌트 안에 담긴 내용들을 children 이라는 props로 받아 출력 해주고있다.
styled-components도 비슷한 모양새를 가지고 있다.

const Styles = styled.div`
  color:red;
`;

const App = () => {
  return (
    <Styles>
      <p>본문내용</p>
    </Styles>
  );
};

이전까지는 css, scss 파일을 밖에 두고, 태그나 id, class이름으로 가져와 스타일을 씌웠었다.
하지만 점점 구성요소들을 쪼개고 재활용하는 방식이 선호되며 자연히 css도 쪼개져야 된다고 생각했던 것 같다. 구성요소들을 이른바 컴포넌트라 하며 css나 scss파일을 생성 할 필요없이 해당 컴포넌트 내부에서만 쓰이게 되는 스타일을 만들수 있게 나온것이 styled-components 라고 생각되며, 이러한 기법을 CSS-in-JS라고 한다.


styled-components의 장단점

장점

  • 유지보수시 스타일시트 파일을 작업할 필요 없이 컴포넌트 내부에서 유지보수가 가능하다.
  • js파일 내부에서 작성되기 때문에 javascript 환경을 최대한 활용가능하다.
  • props를 받아 스타일링 컨트롤이 가능하다.

일단 개인적으로 가장 큰 장점으로 꼽자면 props를 받아 스타일링을 컨트롤 할 수 있다는것.
단순 조건부 뿐만 아니라 내부에서 간단한 함수를 작성 할 수도 있다.

단점

  • 컴포넌트가 렌더링되며 형태가 잡히기 때문에 원형의 모습이 잠깐 노출되는 현상이 발생한다.

별다른 조치를 하지않으면 html 원형이 잠깐 먼저 보여지게 되고 그 후에 스타일링을 입히게 된다.
하지만 걱정 할 필요없다 babel이 해결해 줄 것이다.


styled-component 설치 및 적용

이제 프로젝트에 styled-components를 설치해보자.

$ npm i styled-components

설치가 끝나고나면 typescript이기 때문에 추가 패키지를 dev옵션에 추가 설치 해준다.

$ npm i @types/styled-components -D
# 혹은
$ npm i --save-dev npm @types/styled-components

여담이지만 가끔 styled-component 를 설치하다 실패 했다는 내용이 심심찮게 보인다. 실제로 구글에 styled-components를 검색하면 스택오버플로우에 styled-component설치에 실패했는데 원인을 모르겠다는 질문도 종종 보인다. styled-component가 아닌 styled-components다.

이제 전역 스타일을 작성한다.
프로젝트 root에 src폴더를 생성하고 그 안에 styles 폴더를 생성한다.

그리고 src/styles 폴더안에 globalStyle.tsx 파일을 생성하고 초기화 css코드를 작성하자.

import { createGlobalStyle } from "styled-components";

const GlobalStyle = createGlobalStyle`
  /* font cdn */
  @import url('https://fonts.googleapis.com/css?family=Mukta:400,500,600,700,800&display=swap');
  @import url('https://fonts.googleapis.com/css?family=Noto+Sans+TC:100,300,400,500,700,900&display=swap');
  @import url('https://fonts.googleapis.com/css?family=Noto+Sans+KR:100,300,400,500,700,900&display=swap&subset=korean');
  @import url("https://fonts.googleapis.com/css?family=Pacifico");
  @import url("//fonts.googleapis.com/earlyaccess/jejumyeongjo.css");
  @font-face {
    font-family: 'SEBANG_Gothic_Bold';
    src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2104@1.0/SEBANG_Gothic_Bold.woff') format('woff');
    font-weight: normal;
    font-style: normal;
  }
  /* 리셋 css */
  * {
    margin:0;padding:0;border:0;
  }
  html, 
  body {
    width:100%; 
    font-size: calc( 12px + 0.4vw ); 
    font-family:'Noto Sans KR', sans-serif;
    text-align: center;
    font-weight: 100;
    text-align: center;
  }
  html, 
  h1, 
  h2, 
  h3, 
  h4, 
  h5, 
  h6, 
  form, 
  fieldset, 
  img {
    margin:0;
    padding:0;
    border:0;
  }
  h1, 
  h2, 
  h3, 
  h4, 
  h5, 
  h6 {
    font-family:'Noto Sans KR', sans-serif;
    font-size:1rem;
    font-weight: 100;
  }
  article, 
  aside, 
  details, 
  figcaption, 
  figure, 
  footer, 
  header, 
  hgroup, 
  menu, 
  nav, 
  section {
    display:block;
  }

  ul, 
  dl,
  dt,
  dd {
    margin:0;
    padding:0;
    list-style:none;
  }
  legend {
    position:absolute;
    margin:0;
    padding:0;
    font-size:0;
    line-height:0;
    text-indent:-9999em;
    overflow:hidden;
  }
  label, 
  input, 
  button, 
  select, 
  img {
    vertical-align:middle;
    font-size:1em;
  }
  input, 
  button {
    margin:0;
    padding:0;
    font-family:'Noto Sans KR', sans-serif;
    font-size:1em;
  }
  input[type="submit"] {
    cursor:pointer
  }
  button {
    cursor:pointer
  }
  textarea, 
  select {
    font-family:'Noto Sans KR', sans-serif;
    font-size:1em;
  }
  select {
    margin:0;
  }
  p {
    margin:0;
    padding:0;
    word-break:break-all;
  }
  hr {
    display:none;
  }
  pre {
    overflow-x:scroll;
    font-size:1.1em;
  }
  a {
    color:#000;
    text-decoration:none;
  }
  a:hover {
    color:#000;
    text-decoration:none;
  }
  *, :after, :before {
      -webkit-box-sizing: border-box;
      -moz-box-sizing: border-box;
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }
`;

export default GlobalStyle;

그리고 pages 폴더에 _app.js 파일을 생성한다.
_app.js는 모든 라우트들의 공통으로 적용할 속성을 관리 하는 파일로 이곳에서 전역 스타일링을 추가해주자.

import { AppProps } from "next/app";
import { NextPage } from "next";
import Head from "next/head";
import GlobalStyle from "../src/styles/globalStyle";

const Reactproject: NextPage<AppProps> = ({
  Component,
  pageProps,
}: AppProps) => {
  return (
    <>
      <Head>
        <meta charSet="utf-8" />
        <title>DVISIGN :: 웹 퍼블리셔 정문채 포트폴리오</title>
      </Head>
      <Component {...pageProps} />
      <GlobalStyle />
    </>
  );
};

export default Reactproject;

작성하자마자 또 에러와 경고를 뱉는다..아 괜히했나;;

무슨 이유 때문인지 검색해도 결과가 잘 나오짆않았다.
그래서 일단 node_modules에서 import해오는 부분을 먼저 확인해 보았다.
next/app을 가져오는데 에러가 나서 node_modules에 next가 설치된 곳으로 가보았다.

그렇다 대충 감이 온다. next/app은 실제로는 존재하지 않는다. 정확히 꼽자면 next/app.js에서 가져오는거다. 그래서 import { AppProps } from "next/app.js"; 로 고쳐보았다.

생각이 맞았다. AppProps를 못 불러오던 에러가 사라졌다. 하지만 근본적인 해결책이 되진않는다.
수 많은 패키지들 사용방식은 같지만 실제 폴더트리들이 다르기때문에 어디엔 정상적으로 불러오고 어디엔 지금처럼 못 불러올수도 있다. 그래서 구글링을 해보니 eslint설정에 settings에서 import 해오는 경로옵션에 .js파일을 추가하고 node_modules의 eslint검사 제외를 시켰다.

자세한 내용이곳을 방문해보자.

그리고 Prop spreading is forbidden 경고는 props확산 금지옵션에 걸려있었다.
구글링을 해봐도 전체적인 에러의 이유 보다는 굳이 props를 스프레딩으로 넘겨주지 못할 이유가 없다며 해당옵션을 off하는 방식을 제공하고 있었고, eslint-plugin-react에서도 props가 너무 많을 경우엔 해제하는 방식을 알려주고 있었다.(참조)
그래서 나도 해당옵션을 끄기로했다. 끄는 방식또한 여러가지인데 해당 라인에 주석으로 알리는 방식도 있고, .eslintrc.js의 옵션설정하는 방식이 있었다.
라인에 주석하는 방식은 해당 에러(혹은 다른 검사제외를 필요로 하는 곳)라인 위에 /* eslint-disable react/jsx-props-no-spreading */주석을 추가해주거나,
.eslintrc.js의 rules 옵션에서 react/jsx-props-no-spreading기능을 오프하였다.

Missing file extension "tsx" for "../src/styles/globalStyle" 경고는 import해오는 파일의 확장자를 검사하는 경고였다. 확장자 또한 js, jsx, ts, tsx를 제외하고는 모두 확장자를 붙일 예정이라 해당 옵션에 js, jsx, ts, tsx에는 확장자를 제거 하도록 옵션을 설정하였다.자세한내용이곳을 방문하여 확인하자.

.eslintrc.js파일을 수정하였다.

module.exports = {
  env: {
    browser: true,
    es6: true,
    node: true,
    commonjs: true,
  },
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
    "airbnb",
    "plugin:prettier/recommended",
  ],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: "latest",
    sourceType: "module",
  },
  plugins: ["react", "react-hooks", "@typescript-eslint", "prettier"],
  rules: {
    "react/react-in-jsx-scope": 0,
    "react/prefer-stateless-function": 0,
    "react/jsx-filename-extension": 0,
    "react/jsx-one-expression-per-line": 0,
    "no-nested-ternary": 0,
    "react/function-component-definition": [
      2,
      {
        namedComponents: "arrow-function",
        unnamedComponents: "arrow-function",
      },
    ],
    "no-console": process.env.NODE_ENV === "production" ? "error" : "off",
    // props 확산 방지기능 제외
    "react/jsx-props-no-spreading": "off",
    // import 확장자 제거
    "import/extensions": [
      "error",
      "ignorePackages",
      {
        js: "never",
        jsx: "never",
        ts: "never",
        tsx: "never",
      },
    ],
  },
  settings: {
    "import/resolver": {
      node: {
        // .js 추가
        extensions: [".js", ".ts", ".tsx", ".native.js"],
      },
    },
  },
  // 검사무시 추가
  ignorePatterns: ["node_modules/*"],
};

그리고 css syntax 경고는 위에 작성했던 globalStyle.tsx의 코드에서 폰트를 import해오는 부분에서 문제가 발견되었다. 그래서 일단 폰트를 import해오는 소스코드를 제거하고 기본폰트만 Head안에서 link로 불러오도록 수정하고 그 이외의 폰트들은 사용할 컴포넌트에서 자체적으로 Head에 커스텀으로 추가하도록 하겠다.

// globalStyle.tsx
import { createGlobalStyle } from "styled-components";

const GlobalStyle = createGlobalStyle`
  /* font cdn */
  @font-face {
    font-family: 'SEBANG_Gothic_Bold';
    src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2104@1.0/SEBANG_Gothic_Bold.woff') format('woff');
    font-weight: normal;
    font-style: normal;
  }
  /* 리셋 css */
  * {
    margin:0;padding:0;border:0;
  }
  html, 
  body {
    width:100%; 
    font-size: calc( 12px + 0.4vw ); 
    font-family:'Noto Sans KR', sans-serif;
    text-align: center;
    font-weight: 100;
    text-align: center;
  }
  html, 
  h1, 
  h2, 
  h3, 
  h4, 
  h5, 
  h6, 
  form, 
  fieldset, 
  img {
    margin:0;
    padding:0;
    border:0;
  }
  h1, 
  h2, 
  h3, 
  h4, 
  h5, 
  h6 {
    font-family:'Noto Sans KR', sans-serif;
    font-size:1rem;
    font-weight: 100;
  }
  article, 
  aside, 
  details, 
  figcaption, 
  figure, 
  footer, 
  header, 
  hgroup, 
  menu, 
  nav, 
  section {
    display:block;
  }

  ul, 
  dl,
  dt,
  dd {
    margin:0;
    padding:0;
    list-style:none;
  }
  legend {
    position:absolute;
    margin:0;
    padding:0;
    font-size:0;
    line-height:0;
    text-indent:-9999em;
    overflow:hidden;
  }
  label, 
  input, 
  button, 
  select, 
  img {
    vertical-align:middle;
    font-size:1em;
  }
  input, 
  button {
    margin:0;
    padding:0;
    font-family:'Noto Sans KR', sans-serif;
    font-size:1em;
  }
  input[type="submit"] {
    cursor:pointer
  }
  button {
    cursor:pointer
  }
  textarea, 
  select {
    font-family:'Noto Sans KR', sans-serif;
    font-size:1em;
  }
  select {
    margin:0;
  }
  p {
    margin:0;
    padding:0;
    word-break:break-all;
  }
  hr {
    display:none;
  }
  pre {
    overflow-x:scroll;
    font-size:1.1em;
  }
  a {
    color:#000;
    text-decoration:none;
  }
  a:hover {
    color:#000;
    text-decoration:none;
  }
  *, :after, :before {
      -webkit-box-sizing: border-box;
      -moz-box-sizing: border-box;
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }
`;

export default GlobalStyle;

// _app.tsx
import { AppProps } from "next/app";
import { NextPage } from "next";
import Head from "next/head";
import GlobalStyle from "../src/styles/globalStyle";

const Reactproject: NextPage<AppProps> = ({
  Component,
  pageProps,
}: AppProps) => {
  return (
    <>
      <Head>
        <meta charSet="utf-8" />
        <title>DVISIGN :: 웹 퍼블리셔 정문채 포트폴리오</title>
        <link href="https://fonts.googleapis.com/css?family=Noto+Sans+TC:100,300,400,500,700,900&display=swap" />
        <link href="https://fonts.googleapis.com/css?family=Noto+Sans+KR:100,300,400,500,700,900&display=swap&subset=korean" />
      </Head>
      <Component {...pageProps} />
      <GlobalStyle />
    </>
  );
};

export default Reactproject;

드디어 모든 경고를 clear했다. 그럼 이제 간단하게 index.tsx에서 글자색을 바꾸어 styled컴포넌트를 제대로 사용가능한지, 그리고 글로벌 스타일이 제대로 먹혔는지 확인해보자.

// index.tsx
import type { NextPage } from "next";
import styled from "styled-components";

const TestStyle = styled.div`
  color: red;
`;

const Index: NextPage = () => {
  return <TestStyle>hello next</TestStyle>;
};
export default Index;


글자 색도 잘 변했고 글로벌스타일도 잘 먹혔고, 경고도 알림이 없었다.


👏마무리

오늘은 스타일드 컴포넌트를 적용하면서 추가적으로 eslint에 대한 설정을 수정했습니다. 어째 스타일드 컴포넌트를 붙이는것보다 eslint설정에 공을 더 들인 시간같았네요. 그럼 다음시간에는 추가적으로 내가 필요한 설정들과 리덕스 툴킷을 연결해 보겠습니다.:)

profile
고급개발자되기
post-custom-banner

0개의 댓글