WEB🔨/React

리액트를 다루는 기술 요약#5

최문경 블로그 2021. 6. 24. 15:20

해당 시리즈는 제목 그대로 김민준님의 '리액트를 다루는 기술'을 요약한 글입니다.

 

목차

9장 컴포넌트 스타일링

10장 일정관리 웹 애플리케이션 만들기

 

 

9장 컴포넌트 스타일링

리액트에서 컴포넌트를 스타일링할 때는 다양한 방법을 사용할 수 있다.

이 장에서 알아볼 스타일링 방식들

  • 일반 CSS
  • Sass
  • CSS Module
  • styled-components

 

 

가장 흔한 방식, 일반 CSS

중요한 것은 CSS 클래스를 중복되지 않게 만드는 것.

중복되는 것을 방지하기 위해서 규칙을 사용하거나 CSS Selector를 활용하면 됨.

규칙 사용: 컴포넌트 이름-클래스 형태로 짓거나 BEM 네이밍 등등..

CSS Selector: ex) .App .logo

 

 

Sass 사용하기

Sass(Syntactically Awesome Style Sheets)는 CSS 전처리기로 재활용성과 가독성을 높여주기 때문에 유지 보수를 쉽게 할 수 있도록 도와준다.

두 가지 확장자 .scss와 .sass를 지원합니다. Sass가 처음 나왔을 때는 .sass 확장자만 지원되었으나 나중에 개발자들의 요청에 의해 .scss 확장자도 지원.

 

// .sass 확장자
$font-stack: Helvetica, sans-serif
$primary-color: #333

body
    font: 100% $font-stack
    color: $primary-color
// .scss 확장자
$font-stack: Helvetica, sans-serif;
$primary-color: #333;

body {
    font: 100% $font-stack;
    color: $primary-color;
}

사용하려면 node-sass라는 라이브러리를 설치해야함.

 

 

import React from 'react';
import './SassComponent.scss';

const SassComponent = () => {
  return (
    <div className="SassComponent">
      <div className="box red" />
      <div className="box orange" />
      <div className="box yellow" />
      <div className="box green" />
      <div className="box blue" />
      <div className="box indigo" />
      <div className="box violet" />
    </div>
  );
};

export default SassComponent;

 

 

Sass의 장점 중 하나는 라이브러리를 쉽게 불러와서 사용할 수 있다는 점입니다. yarn을 통해 설치한 라이브러리를 사용하는 가장 기본적인 방법은 무엇일까요? 다음과 같이 상대 경로를 사용하여 node_modules까지 들어가서 불러오는 방법입니다.

 

@import '../../../node_modules/library/styles';

 

하지만 이보다 더 쉬운 방법이 있는데, 바로 물결 문자(~)를 사용하는 방법입니다.

 

@import '~library/styles';

 

물결 문자를 사용하면 자동으로 node_modules에서 라이브러리 디렉터리를 탐지하여 스타일을 불러올 수 있습니다.

// utils.scss

@import '~include-media/dist/include-media';
@import '~open-color/open-color';

// 변수 사용하기
$red: #fa5252;
$orange: #fd7e14;
$yellow: #fcc419;
$green: #40c057;
$blue: #339af0;
$indigo: #5c7cfa;
$violet: #7950f2;

// 믹스인 만들기 (재사용되는 스타일 블록을 함수처럼 사용 할 수 있음)
@mixin square($size) {
  $calculated: 32px * $size;
  width: $calculated;
  height: $calculated;
}

위와 같이 여러 파일에서 공통적으로 사용되는 변수나 믹스인을 하나의 파일에 모아두고 필요한 파일에서 불러와 사용할 수 있습니다.

 

@import './styles/utils';

 

하지만 프로젝트에 디렉터리를 많이 만들어서 구조가 깊어졌다면 한참 거슬러 올라가야하고, 파일마다 import를 시키는 것이 귀찮을 수도 있습니다.

 

이것은 웹팩에서 Sass를 처리하는 sass-loader의 설정을 커스터마이징하여 해결할 수 있습니다.

 

create-react-app으로 만든 프로젝트는 프로젝트 구조의 복잡도를 낮추기 위해 세부 설정이 모두 숨겨져 있습니다. 따라서 이를 커스터마이징하려면 yarn eject 명령어를 통해 밖으로 꺼내야 합니다.

 

그리고 create-react-app에서는 기본적으로 Git 설정이 되어 있는데 yarn eject는 아직 Git에 커밋되지 않은 변화가 있다면 진행되지 않기때문에 먼저 커밋을 해야합니다.

 

yarn eject 명령어를 실행하면 config라는 디렉터리가 생성됩니다. 그 안에 들어있는 webpack.config.js를 열어보세요.

그리고 "sassRegex"라는 키워드를 찾아보세요.

 

{
  test: sassRegex,
  exclude: sassModuleRegex,
  use: getStyleLoaders(
    {
      importLoaders: 3,
      sourceMap: isEnvProduction
        ? shouldUseSourceMap
        : isEnvDevelopment,
    },
    'sass-loader'
  ),
  sideEffects: true,
},
{
  test: sassRegex,
  exclude: sassModuleRegex,
  use: getStyleLoaders({
    importLoaders: 3,
    sourceMap: isEnvProduction
      ? shouldUseSourceMap
      : isEnvDevelopment,
  }).concat({
    loader: require.resolve('sass-loader'),
    options: {
      sassOptions{
        includePaths: [paths.appSrc + '/styles'],
      },
      sourceMap: isEnvProduction && shouldUseSourceMap,
    },
  }),
  sideEffects: true,
},

 

여기서 'sass-loader' 부분을 지우고, 뒷부분에 concat을 통해 커스터마이징 sass-loader 설정을 넣어 주세요. 해당 설정은 앞부분에 상대 경로를 입력하지 않아도 styles 디렉터리 기준 절대 경로를 사용하여 불러오는 설정입니다.

 

만약 import를 적어주지 않아도 자동으로 import를 하도록 하려면 아래와 같이 sass-loader의 옵션에 있는 data 필드를 다음과 같이 설정하세요.

{
  test: sassRegex,
  exclude: sassModuleRegex,
  use: getStyleLoaders({
    importLoaders: 3,
    sourceMap: isEnvProduction
      ? shouldUseSourceMap
      : isEnvDevelopment,
  }).concat({
    loader: require.resolve('sass-loader'),
    options: {
      sassOptions {
        includePaths: [paths.appSrc + '/styles'],
      },
      sourceMap: isEnvProduction && shouldUseSourceMap,
      prependData: `@import 'utils';`,
    },
  }),
  sideEffects: true,
},

이렇게 작성하고 서버를 재시작하면 모든 scss 파일에서 utils.scss를 자동으로 불러올 것입니다.

 

 

혹시나 위와 같은 에러가 발생했다면 prepenData를 맨 밑의 줄의 값 중 하나인 additionalData 로 변경해주면 됩니다.

 

 

// SassComponent.scss

.SassComponent {
  display: flex;
  background: $oc-gray-2;
  @include media('<768px') {
    background: $oc-gray-9;
  }
  .box {
    background: red; // 일반 CSS 에선 .SassComponent .box 와 마찬가지
    cursor: pointer;
    transition: all 0.3s ease-in;
    &.red {
      // .red 클래스가 .box 와 함께 사용 됐을 때
      background: $red;
      @include square(1);
    }
    &.orange {
      background: $orange;
      @include square(2);
    }
    &.yellow {
      background: $yellow;
      @include square(3);
    }
    &.green {
      background: $green;
      @include square(4);
    }
    &.blue {
      background: $blue;
      @include square(5);
    }
    &.indigo {
      background: $indigo;
      @include square(6);
    }
    &.violet {
      background: $violet;
      @include square(7);
    }
    &:hover {
      // .box 에 마우스 올렸을 때
      background: black;
    }
  }
}

 

 

CSS Module

CSS를 불러와서 사용할 때 클래스 이름을 고유한 값, 즉 [파일 이름]_[클래스 이름]_[해시값] 형태로 자동으로 만들어서 컴포넌트 스타일 클래스 이름이 중첩되는 현상을 방지해주는 기술.

.module.css  확장자로 파일을 저장하기만 하면 CSS Module이 적용됨.

 

import React from 'react';
import styles from './CSSModule.module.css';

const CSSModule = () => {
  return (
    <div className={styles.wrapper}>
      안녕하세요, 저는 <span className="something">CSS Module!</span>
    </div>
  );
};
.wrapper {
  background: black;
  padding: 1rem;
  color: white;
  font-size: 2rem;
}
.inverted {
  color: black;
  background: white;
  border: 1px solid black;
}

/* 글로벌 CSS 를 작성하고 싶다면 */
:global .something {
  font-weight: 800;
  color: aqua;
}

 

 

CSS Module을 사용한 클래스 이름을 두 개 이상 적용할 때

import React from 'react';
import styles from './CSSModule.module.css';

const CSSModule = () => {
  return (
    <div className={`${styles.wrapper} ${styles.inverted}`}>
      안녕하세요, 저는 <span className="something">CSS Module!</span>
    </div>
  );
};

 

 

classnames 라이브러리를 사용하면 클래스를 조건부로 설정할 수도 있고 CSS Module과 함께 사용하면 CSS Module 사용이 더 편리해짐.

// classnames 간략 사용법

import classNames from 'classnames';

classNames('one', 'two'); // = 'one two'
classNames('one', { two: true }); // = 'one two'
classNames('one', { two: false }); // = 'one'
classNames('one', ['two', 'three']); // = 'one two three'

const myClass = 'hello';
classNames('one', myClass, { myCondition: true }); // = 'one hello myCondition'


// 예시 코드

const MyComponent = ({ highlighted, theme }) => (
	<div className={classNames('MyComponent', { highlighted }, theme)}Hello</div>
);

 

 

다음 코드는 아까 만든 CSSModule 컴포넌트에 classnames의 bind 함수를 적용한 예.

import React from 'react';
import styles from './CSSModule.module.css';
import classNames from 'classnames/bind';

const cx = classNames.bind(styles); // 미리 styles 에서 클래스를 받아오도록 설정하고

const CSSModule = () => {
  return (
    <div className={cx('wrapper', 'inverted')}>
      안녕하세요, 저는 <span className="something">CSS Module!</span>
    </div>
  );
};

export default CSSModule;

 


Sass를 사용할 때도 .module.scss 확장자를 사용해주면 CSS Module로 사용 가능

/* 자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용가능*/

.wrapper {
  background: black;
  padding: 1rem;
  color: white;
  font-size: 2rem;
  &.inverted {
    // inverted 가 .wrapper 와 함께 사용 됐을 때만 적용
    color: black;
    background: white;
    border: 1px solid black;
  }
}

/* 글로벌 CSS 를 작성하고 싶다면 */
:global {
  // :global {} 로 감싸기
  .something {
    font-weight: 800;
    color: aqua;
  }
  // 여기에 다른 클래스를 만들 수도 있겠죠?
}

 

 

styled-components

개발자들이 가장 선호하는 CSS-in-JS 라이브러리

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

const Box = styled.div`
  /* props 로 넣어준 값을 직접 전달해줄 수 있습니다. */
  background: ${props => props.color || 'blue'};
  padding: 1rem;
  display: flex;
  width: 1024px;
  margin: 0 auto;
  ${media.desktop`width: 768px;`}
  ${media.tablet`width: 100%;`};
`;

const Button = styled.button`
  background: white;
  color: black;
  border-radius: 4px;
  padding: 0.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  font-size: 1rem;
  font-weight: 600;

  /* & 문자를 사용하여 Sass 처럼 자기 자신 선택 가능 */
  &:hover {
    background: rgba(65, 31, 31, 0.9);
  }

  /* 다음 코드는 inverted 값이 true 일 때 특정 스타일을 부여해줍니다. */
  ${props =>
    props.inverted &&
    css`
      background: none;
      border: 2px solid white;
      color: white;
      &:hover {
        background: white;
        color: black;
      }
    `};
  & + button {
    margin-left: 1rem;
  }
`;

const StyledComponent = () => (
  <Box color="black">
    <Button>안녕하세요</Button>
    <Button inverted={true}>테두리만</Button>
  </Box>
);

export default StyledComponent;

위와 같이 styled-components와 일반 classNames를 사용하는 CSS/Sass를 비교했을 때, 가장 큰 장점은 props 값으로 전달해 주는 값을 쉽게 스타일에 적용할 수 있다는 것!

 

 

반응형 디자인

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

const sizes = {
  desktop: 1024,
  tablet: 768
};

// 위에있는 size 객체에 따라 자동으로 media 쿼리 함수를 만들어줍니다.
// 참고: https://www.styled-components.com/docs/advanced#media-templates
const media = Object.keys(sizes).reduce((acc, label) => {
  acc[label] = (...args) => css`
    @media (max-width: ${sizes[label] / 16}em) {
      ${css(...args)};
    }
  `;

  return acc;
}, {});

const Box = styled.div`
  /* props 로 넣어준 값을 직접 전달해줄 수 있습니다. */
  background: ${props => props.color || 'blue'};
  padding: 1rem;
  display: flex;
  width: 1024px;
  margin: 0 auto;
  ${media.desktop`width: 768px;`}
  ${media.tablet`width: 100%;`};
`;

... 생략

 

 

10장 일정 관리 웹 애플리케이션 만들기

프로젝트 준비하기

yarn add node-sass classnames react-icons open-color

 

 

UI 구성하기

1. TodoTemplate: 앱을 가운데 정렬, 앱 타이틀을 보여주고 children으로 내부 JSX를 props로 받아 와서 렌더링

2. TodoInsert: 새로운 항목을 입력하고 추가.

3. TodoListItem: 각 할 일 항목에 대한 정보

4. TodoList: todos 배열을 props로 받아 온 후, map을 사용해 여러 개의 TodoLIstItem 컴포넌트로 변환하여 보여줌

 

 

TodoTemplate

import React from 'react';
import '../styles/TodoTemplate.scss';

const TodoTemplate = ({ children }) => {
  return (
    <div className="TodoTemplate">
      <div className="app-title">To Do App</div>
      <div className="content">{children}</div>
    </div>
  );
};

export default TodoTemplate;
.TodoTemplate {
  width: 512px;
  margin: 0 auto;
  margin-top: 6rem;
  border-radius: 4px;
  overflow: hidden;

  .app-title {
    background: $oc-red-4;
    color: #fff;
    height: 4rem;
    font-size: 1.5rem;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .content {
    background: #fff;
  }
}

 

 

TodoInsert

import React from 'react';
import { MdAdd } from 'react-icons/md';
import '../styles/TodoInsert.scss';

const TodoInsert = () => {
  return (
    <form className="TodoInsert">
      <input type="text" placeholder="할 일을 입력하세요." />
      <button type="submit">
        <MdAdd />
      </button>
    </form>
  );
};

export default TodoInsert;
.TodoInsert {
  display: flex;
  background-color: $oc-gray-2;

  input {
    background: none;
    outline: none;
    border: none;
    padding: 20px;
    font-size: 18px;
    color: $oc-gray-7;

    &::placeholder {
      color: $oc-gray-7;
    }

    flex: 1;
  }

  button {
    background: none;
    outline: none;
    border: none;
    background-color: $oc-gray-5;
    color: #fff;
    padding: 0 1rem;
    font-size: 1.5rem;
    display: flex;
    align-items: center;
    cursor: pointer;
    transition: 0.1s all ease-in;
    &:hover {
      background-color: $oc-gray-6;
    }
  }
}

 

 

App

import React from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';

function App() {
  return (
    <TodoTemplate>
      <TodoInsert />
    </TodoTemplate>
  );
}

export default App;

 

 

현재 상황

 

 

TodoListItem

import React from 'react';
import {
  MdCheckBoxOutlineBlank,
  MdCheckBox,
  MdRemoveCircleOutline,
} from 'react-icons/md';
import '../styles/TodoListItem.scss';

const TodoListItem = () => {
  return (
    <div className="TodoListItem">
      <div className="checkbox">
        <MdCheckBoxOutlineBlank />
        <div className="text">할 일</div>
      </div>
      <div className="remove">
        <MdRemoveCircleOutline />
      </div>
    </div>
  );
};

export default TodoListItem;
.TodoListItem {
  padding: 1rem;
  display: flex;
  align-items: center;
  &:nth-child(even) {
    background-color: $oc-gray-1;
  }
  .checkbox {
    cursor: pointer;
    flex: 1;
    display: flex;
    align-items: center;

    svg {
      font-size: 1.5rem;
    }

    .text {
      flex: 1;
      margin-left: 0.5rem;
    }

    &.checked {
      svg {
        color: $oc-gray-5;
      }

      .text {
        color: $oc-gray-5;
        text-decoration: line-through;
      }
    }
  }

  .remove {
    display: flex;
    align-items: center;
    font-size: 1.5rem;
    color: $oc-red-3;
    cursor: pointer;
    &:hover {
      color: $oc-red-6;
    }
  }

  & + & {
    border-top: 1px solid $oc-gray-3;
  }
}

 

 

TodoList

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

const TodoList = () => {
  return (
    <div className="TodoList">
      <TodoListItem />
      <TodoListItem />
      <TodoListItem />
    </div>
  );
};

export default TodoList;

 

 

현재 상황

 

 

기능 구현하기

App에서 todos 상태 사용하기

import React, { useState } from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';

function App() {
  const [todos, setTodos] = useState([
    {
      id: 1,
      text: '프로젝트 준비하기',
      checked: true,
    },
    {
      id: 2,
      text: 'UI 구성하기',
      checked: true,
    },
    {
      id: 3,
      text: '기능 구현하기',
      checked: false,
    },
  ]);

  return (
    <TodoTemplate>
      <TodoInsert />
      <TodoList todos={todos} />
    </TodoTemplate>
  );
}

export default App;

 

 

TodoList

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

const TodoList = ({ todos }) => {
  return (
    <div className="TodoList">
      {todos.map((todo) => (
        <TodoListItem todo={todo} key={todo.id} />
      ))}
    </div>
  );
};

export default TodoList;

 

 

TodoListItem

import React from 'react';
import {
  MdCheckBoxOutlineBlank,
  MdCheckBox,
  MdRemoveCircleOutline,
} from 'react-icons/md';
import cn from 'classnames';
import '../styles/TodoListItem.scss';

const TodoListItem = ({ todo }) => {
  const { text, checked } = todo;
  return (
    <div className="TodoListItem">
      <div className={cn('checkbox', { checked })}>
        {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
        <div className="text">{text}</div>
      </div>
      <div className="remove">
        <MdRemoveCircleOutline />
      </div>
    </div>
  );
};

export default TodoListItem;

 

 

현재 상황

 

 

항목 추가 기능 구현하기

App

import React, { useCallback, useRef, useState } from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';

function App() {
  const [todos, setTodos] = useState([
    {
      id: 1,
      text: '프로젝트 준비하기',
      checked: true,
    },
    {
      id: 2,
      text: 'UI 구성하기',
      checked: true,
    },
    {
      id: 3,
      text: '기능 구현하기',
      checked: false,
    },
  ]);

  const nextId = useRef(4);

  const onInsert = useCallback(
    (text) => {
      const todo = {
        id: nextId.current,
        text,
        checked: false,
      };
      setTodos(todos.concat(todo));
      nextId.current += 1;
    },
    [todos],
  );

  return (
    <TodoTemplate>
      <TodoInsert onInsert={onInsert} />
      <TodoList todos={todos} />
    </TodoTemplate>
  );
}

export default App;

id 값은 렌더링되는 정보가 아니기 때문에 useRef를 사용하여 관리! (화면에 보이지도 않고 값이 바뀐다고 해서 컴포넌트가 리렌더링될 필요도 없음)

 

props로 전달해야 할 함수를 만들 때는 useCallback을 사용하여 함수를 감싸는 것을 습관화하세요.

 

 

TodoInsert에서 onSubmit 이벤트 설정

import React, { useCallback, useState } from 'react';
import { MdAdd } from 'react-icons/md';
import '../styles/TodoInsert.scss';

const TodoInsert = ({ onInsert }) => {
  const [value, setValue] = useState('');

  const onChange = useCallback((e) => {
    setValue(e.target.value);
  }, []);

  const onSubmit = useCallback(
    (e) => {
      onInsert(value);
      setValue('');

      e.preventDefault();
    },
    [onInsert, value],
  );

  return (
    <form className="TodoInsert" onSubmit={onSubmit}>
      <input
        type="text"
        placeholder="할 일을 입력하세요."
        value={value}
        onChange={onChange}
      />
      <button type="submit">
        <MdAdd />
      </button>
    </form>
  );
};

export default TodoInsert;

 

 

현재 상황

 

 

지우기 기능 구현하기

App.js에 onRemove 함수 만들고 TodoList에 props로 전달해주기

import React, { useCallback, useRef, useState } from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';

function App() {
  
  ( ... )
  
  const onRemove = useCallback(
    (id) => {
      setTodos(todos.filter((todo) => todo.id !== id));
    },
    [todos],
  );

  return (
    <TodoTemplate>
      <TodoInsert onInsert={onInsert} />
      <TodoList todos={todos} onRemove={onRemove} />
    </TodoTemplate>
  );
}

export default App;

 

 

TodoList에서 TodoListItem으로 전달하고 삭제 함수 호출하기

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

const TodoList = ({ todos, onRemove }) => {
  return (
    <div className="TodoList">
      {todos.map((todo) => (
        <TodoListItem todo={todo} key={todo.id} onRemove={onRemove} />
      ))}
    </div>
  );
};

export default TodoList;
import React from 'react';
import {
  MdCheckBoxOutlineBlank,
  MdCheckBox,
  MdRemoveCircleOutline,
} from 'react-icons/md';
import cn from 'classnames';
import '../styles/TodoListItem.scss';

const TodoListItem = ({ todo, onRemove }) => {
  const { id, text, checked } = todo;
  return (
    <div className="TodoListItem">
      <div className={cn('checkbox', { checked })}>
        {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
        <div className="text">{text}</div>
      </div>
      <div className="remove" onClick={() => onRemove(id)}>
        <MdRemoveCircleOutline />
      </div>
    </div>
  );
};

export default TodoListItem;

 

 

현재 상황

 

 

수정 기능 구현하기

App.js에 onToggle 함수 만들고 TodoList에 props로 전달해주기

import React, { useCallback, useRef, useState } from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';

function App() {
  
  ( ... )

  const onToggle = useCallback((id) => {
    setTodos(
      todos.map((todo) =>
        todo.id === id ? { ...todo, checked: !todo.checked } : todo,
      ),
    );
  }, 
  [todos],
);

  return (
    <TodoTemplate>
      <TodoInsert onInsert={onInsert} />
      <TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
    </TodoTemplate>
  );
}

export default App;

 

 

TodoList에서 TodoListItem으로 onToggle 전달해주기

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

const TodoList = ({ todos, onRemove, onToggle }) => {
  return (
    <div className="TodoList">
      {todos.map((todo) => (
        <TodoListItem
          todo={todo}
          key={todo.id}
          onRemove={onRemove}
          onToggle={onToggle}
        />
      ))}
    </div>
  );
};

export default TodoList;

 

 

TodoListItem에서 onToggle 호출하기

import React from 'react';
import {
  MdCheckBoxOutlineBlank,
  MdCheckBox,
  MdRemoveCircleOutline,
} from 'react-icons/md';
import cn from 'classnames';
import '../styles/TodoListItem.scss';

const TodoListItem = ({ todo, onRemove, onToggle }) => {
  const { id, text, checked } = todo;
  return (
    <div className="TodoListItem">
      <div className={cn('checkbox', { checked })} onClick={() => onToggle(id)}>
        {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
        <div className="text">{text}</div>
      </div>
      <div className="remove" onClick={() => onRemove(id)}>
        <MdRemoveCircleOutline />
      </div>
    </div>
  );
};

export default TodoListItem;

 

 

현재 상황

 

 

정리

이번에 만든 프로젝트는 소규모이기 때문에 따로 컴포넌트 리렌더링 최적화 작업을 하지 않아도 정상적으로 작동합니다. 하지만 일정 항목이 몇 만 개씩 생긴다면 새로운 항목을 추가하거나 삭제 및 토글할 때 지연이 발생할 수 있습니다. 이에 관한 내용은 이어지는 11장에서 다루겠습니다.