목표

  • 목록 페이지 만들기
  • axios.get()으로 목록 페이지 서버 데이터 받기
  • dayjs 패키지를 이용해 날짜 포맷하기

 


결과 화면

 


1. axios 패키기 설치하기

axios 패키기를 설치한다.

이미 설치되어 있다면 설치하지 않아도 된다.

yarn add axios
또는
npm install axios

 

2. axios.get()으로 서버 데이터 받기

https://jae-study.tistory.com/80

Nest.js와 Psql을 사용해서 백앤드와 데이터베이스를 만들었다.

 

직접 백앤드와 데이터베이스를 만들어도 되고,

JSONPlaceholder 사이트를 통해 가짜 데이터를 받아와도 된다.

 

Postman을 통해서 GET으로 받아온 데이터를 확인할 수 있다.

 

 

  • src/pages/board/list/index.tsx
  1. useEffect()를 안에 axios를 작성한다.
  2. 목록 페이지는 서버에서 데이터를 받아 화면에 보여주면 되기 때문에 get() 메서드를 사용한다.
  3. [setBoardList] state 변수에 axios로 받아온 데이터를 저장한다.
const BoardList = () => {
  const [boardList, setBoardList] = useState<BoardType[]>([])

  useEffect(() => {
    axios.get('http://localhost:3001/board')
      .then((response) => {
        setBoardList(response.data)
      })

      .catch(function(error) {
        console.log(error)
      })
  }, [])

  return (
    <div className="board-list">
      <table>
	...
      </table>
    </div>
  )
}

export default BoardList

 

3. 서버 데이터를 화면에 출력하기

  • src/pages/board/list/index.tsx

[setBoardList] state 변수에 저장한 데이터를 map() 메서드를 사용해 화면에 출력한다.

const BoardList = () => {
  const [boardList, setBoardList] = useState<BoardType[]>([])

  useEffect(() => {
    ...
  }, [])

  return (
    <div className="board-list">
        ...
        <tbody>
          {
            boardList.map((board, index) => {
              return (
                <tr key={index}>
                  <td>{index + 1}</td>
                  <td className="title">
                    <Link to={`/board/${board.id}`}>{board.title}</Link>
                  </td>
                  <td>{board.created_at}</td>
                </tr>
              )
            })
          }
        </tbody>
      </table>
    </div>
  )
}

 

4. dayjs 패키지를 사용해 날짜 포맷하기

dayjs 패키지를 설치한다.

yarn add dayjs
또는 
npm install dayjs

 

  • src/pages/board/list/index.tsx

dayjs().format()에 원하는 날짜 형식을 작성한다.

const BoardList = () => {
  const [boardList, setBoardList] = useState<BoardType[]>([])

  useEffect(() => {
    ...
  }, [])

  return (
    <div className="board-list">
        ...
        <tbody>
          {
            boardList.map((board, index) => {
              return (
                <tr key={index}>
                  <td>{index + 1}</td>
                  <td className="title">
                    <Link to={`/board/${board.id}`}>{board.title}</Link>
                  </td>
                  
                  // 수정한 부분
                  <td>{dayjs(board.created_at).format('YYYY.MM.DD')}</td>
                </tr>
              )
            })
          }
        </tbody>
      </table>
    </div>
  )
}

 


전체 코드

  • src/pages/board/list/index.tsx
import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import axios from 'axios'
import dayjs from 'dayjs'
import { BoardType } from '../../../interface/BoardType'
import Title from '../../../components/text/Title'
import './index.scss'

const BoardList = () => {
  const [boardList, setBoardList] = useState<BoardType[]>([])

  const boardLength = boardList.length

  useEffect(() => {
    axios.get('http://localhost:3001/board')
      .then((response) => {
        setBoardList(response.data)
      })

      .catch(function(error) {
        console.log(error)
      })
  }, [])

  return (
    <div className="board-list">
      <Title children="Board list"/>

      <h4>Total post : {boardLength}</h4>

      <table>
        <colgroup>
          <col width="15%"/>
          <col width="65%"/>
          <col width="20%"/>
        </colgroup>

        <thead>
          <tr>
            <th>No</th>
            <th>Title</th>
            <th>Date</th>
          </tr>
        </thead>

        <tbody>
          {
            boardList.map((board, index) => {
              return (
                <tr key={index}>
                  <td>{index + 1}</td>
                  <td className="title"><Link to={`/board/${board.id}`}>{board.title}</Link></td>
                  <td>{dayjs(board.created_at).format('YYYY.MM.DD')}</td>
                </tr>
              )
            })
          }
        </tbody>
      </table>
    </div>
  )
}

export default BoardList

 

#2.1
axios.get()을 통해서 서버 데이터를 받아오고,
map() 메서드를 통해 화면에 출력한 뒤,
dayjs 패키지를 사용해서 날짜를 포맷했다.

목표

  • 게시판 목록 페이지 만들기
  • 게시판 상세 페이지 만들기
  • 게시판 작성 페이지 만들기
  • 게시판 수정 및 삭제 페이지 만들기

Typescript로 React를 설치하고, 게시판에 필요한 페이지들을 만든다.

 


결과 화면

 


1. 타입스크립트로 리액트 프로젝트 설치하기

https://jae-study.tistory.com/70

리액트 설치에 대한 자세한 설명은 위의 링크를 참고한다.

 

터미널에서 아래 명령어를 실행해 타입스크립트로 리액트 프로젝트를 설치한다.

yarn create react-app 폴더명 --template typescript
또는
npx create-react-app 폴더명 --template typescript

 

 

2. 게시판 폴더 구조

  • 목록 : src/pages/board/list/index.tsx
  • 상세 : src/pages/board/detail/index.tsx
  • 생성 : src/pages/board/create/index.tsx
  • 수정 : src/pages/board/modify/index.tsx

게시판 목록, 상세, 생성, 수정, 총 4가지 페이지를 만든다.

 

 

3. 마크업, css/scss 적용하기

https://jae-study.tistory.com/72

리액트에 scss를 적용하고 싶으면 위의 링크를 참고한다.

 

페이지 별로 마크업을 하고,

css 또는 scss 파일을 작성한 뒤, 각 index.tsx 파일에 import 해준다.

 

  • src/pages/board/list/index.tsx

해당 마크업은 예시일 뿐, 원하는 디자인으로 목록, 상세, 생성, 수정 페이지를 만든다.

import { Link } from 'react-router-dom'
import Button from '../../../components/form/Button'
import Title from '../../../components/text/Title'
import './index.scss'

const BoardList = () => {
  return (
    <div className="board-list">
      <Title children="Board list"/>

      <h4>Total post : 10개</h4>

      <table>
        <colgroup>
          <col width="15%"/>
          <col width="65%"/>
          <col width="20%"/>
        </colgroup>

        <thead>
          <tr>
            <th>No</th>
            <th>Title</th>
            <th>Date</th>
          </tr>
        </thead>

        <tbody>
          <tr>
            <td>1</td>
            <td>
              <Link to={`/board/1`}>공지사항 입니다.</Link>
            </td>
            <td>2023.06.30</td>
          </tr>
        </tbody>
      </table>

      <Link to="/board/create">
        <Button children="Write" variant="primary"/>
      </Link>
    </div>
  )
}

export default BoardList

 

  • src/pages/board/list/index.scss
@import '../../../assets/scss/base/variable';

.board-list {
  h4 {
    margin-bottom: 20px;
    font-weight: 700;
  }

  table {
    background-color: $bg-light;
    border-radius: 5px;

    thead {
      th {
        padding: 20px 0;
        font-size: 20px;
        font-weight: 700;
      }
    }

    tbody {
      border-top: 1px solid $bg-point;

      td {
        padding: 25px 0;
        text-align: center;
        vertical-align: middle;

        &.title {
          text-align: left;

          a {
            padding-bottom: 2px;

            &:hover {
              border-bottom: 1px solid $bg-point;
            }
          }
        }
      }
    }
  }
}

 

4. 라우터 연결하기

https://jae-study.tistory.com/73

react-router-dom에 대한 자세한 설명은 위의 링크를 참고한다.

 

react-router-dom 패키지를 설치한다.

yarn add react-router-dom
또는
npm install react-router-dom

 

  • src/index.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

const root = ReactDOM.createRoot (
  document.getElementById('root') as HTMLElement
)

root.render(
  <React.StrictMode>
    <App/>
  </React.StrictMode>
)

 

  • src/App.tsx

BrowserRouter, Routes, Route 패키지를 import 하고,

<Route> 컴포넌트의 path를 설정한다.

게시판 상세와 수정 페이지는 게시글 별로 페이지가 있기 때문에 :id 값을 받아야 한다.

import React from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import './assets/scss/common.scss'

import BoardList from './pages/board/list'
import BoardDetail from './pages/board/detail'
import BoardCreate from './pages/board/create'
import BoardModify from './pages/board/modify'

const App = () => {
  return (
    <>
      {
        <BrowserRouter>
          <Routes>
            <Route path="/board" element={<BoardList/>}/>
            <Route path="/board/:id" element={<BoardDetail/>}/>
            <Route path="/board/create" element={<BoardCreate/>}/>
            <Route path="/board/modify/:id" element={<BoardModify/>}/>
          </Routes>
        </BrowserRouter>
      }
    </>
  )
}

export default App

 

#1
타입스크립트로 리액트를 설치하고,
게시판 목록, 상세, 생성, 수정 페이지를 만들고,
라우터 연결까지 완료했다.

결과 화면

 


1. styled-components, @types/styled-components 설치하기

styled-components 패키지와 @types/styled-components 패키지를 설치한다.

 

자바스크립트일 경우, styled-components 패키지만 설치해도 되지만,

타입스크립트에서 사용하기 위해서는 @types/styled-components 패키지도 설치해야 한다.

yarn add styled-components
또는
npm install styled-components
yarn add @types/styled-components
또는
npm install @types/styled-components

 

2. Button 컴포넌트 만들기

2-1. StyledButton 만들기

  • src/components/Button.tsx

styled-components를 import 한다.

StyledButton 컴포넌트를 만든 후, `` 안에 css를 작성한다.

StyledButton 컴포넌트를 적용할 Button 컴포넌트를 만든다. 

import styled from 'styled-components'

const StyledButton = styled.button`
  // 필요한 css를 작성한다.
  
  width: 100%;
  height: 50px;
  ...
`

const Button = () => {
  return (
    <StyledButton>버튼</StyledButton>
  )
}

export default Button

 

2-2. StyledButton에 props 적용하기

props로 값을 받기 위해서는 ${(props) => props.~} 형태로 받아야 한다.

 

backgroundColor 변수를 만들고, 필요한 값을 작성한다. (background-color가 필요했을 뿐, 다른 값들도 가능하다.)

props로 넘길 값(backgroundColor)의 타입을 정의하기 위해 interface를 만든다.

StyledButton 컴포넌트에 타입(ButtonProps)과 props로 넘길 값을 정의한다.

Button 컴포넌트의 파라미터에 variant 값을 넘긴다.

import styled from 'styled-components'

const backgroundColor = {
  primary: '#677987',
  secondary: '#B4B6AC'
}

interface ButtonProps {
  readonly variant: 'primary' | 'secondary'
}

const StyledButton = styled.button<ButtonProps>`
  width: 100%;
  height: 50px;
  background-color: ${(props) => backgroundColor[props.variant]};
  text-align: center;
`

const Button = ( { variant }: ButtonProps) => {
  return (
    <StyledButton variant={variant}>버튼</StyledButton>
  )
}

export default Button

 

2-3. 부모 컴포넌트에 값 전달하기

Button 컴포넌트의 텍스트도 props로 전달하기 위해서 children을 사용한다.

 

interface에 children의 타입을 정의한다.

Button 컴포넌트의 파라미터에 childred 값을 넘긴다.

import styled from 'styled-components'

const backgroundColor = {
  primary: '#677987',
  secondary: '#B4B6AC'
}

interface ButtonProps {
  children: string
  readonly variant: 'primary' | 'secondary'
}

const StyledButton = styled.button<ButtonProps>`
  width: 100%;
  height: 50px;
  background-color: ${(props) => backgroundColor[props.variant]};
  text-align: center;
`

const Button = ({ children, variant }: ButtonProps) => {
  return (
    <StyledButton variant={variant}>{children}</StyledButton>
  )
}

export default Button

 


최종 코드

  • src/components/Button.tsx
import styled from 'styled-components'

const backgroundColor = {
  primary: '#677987',
  secondary: '#B4B6AC'
}

const textColor = {
  primary: '#FCFCFC',
  secondary: '#FCFCFC'
}

interface ButtonProps {
  children: string
  readonly variant: 'primary' | 'secondary'
}

const StyledButton = styled.button<ButtonProps>`
  width: 100%;
  height: 50px;
  border-radius: 5px;
  background-color: ${(props) => backgroundColor[props.variant]};
  color: ${(props) => textColor[props.variant]};
  font-size: 16px;
  text-align: center;
  transition: all 0.3s;
  
  &:hover {
    opacity: 0.8;
  }
`

const Button = ({ children, variant }: ButtonProps) => {
  return (
    <StyledButton variant={variant}>{children}</StyledButton>
  )
}

export default Button

 

  • src/App.tsx

Button 컴포넌트를 import 한다.

props로 넘길 값들(children, variant)을 작성한다.

 

Title 컴포넌트도 같은 방식으로 작성됐다.

import Title from '../../components/base/Title'
import Button from '../../components/form/Button'
import './index.scss'

const App = () => {
  return (
    <>
      <div className="board-create">
        <Title children="Write content"/>
        
        <Button children="Confirm" variant="primary"/>
        <Button children="Cancel" variant="secondary"/>
      </div>
    </>
  )
}

export default App

결과화면

 


1. react-router-dom 설치하기

react-router-dom 패키지를 설치한다.

설치가 완료되면 packae.json 파일에서 확인할 수 있다.

yarn add react-router-dom
또는
npm install react-router-dom

 

2. BrowerRouter 컴포넌트를 최상위 태그에 감싸기

  • src/index.tsx

App 컴포넌트 위에 BrowerRouter 컴포넌트를 감싼다.

import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App'

const root = ReactDOM.createRoot (
  document.getElementById('root') as HTMLElement
)

root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App/>
    </BrowserRouter>
  </React.StrictMode>
)

 

3. path를 설정하기

  • src/App.tsx

path와 그에 해당하는 element 컴포넌트를 작성한다.

import React from 'react'
import { Routes, Route } from 'react-router-dom'
import Layout from './components/layouts/Layout'
import MainPage from './pages/main'
import BoardPage from './pages/board'
import './assets/scss/common.scss'

const App = () => {
  return (
    <>
      {
        <Layout>
          <Routes>
            <Route path="/" element={<MainPage/>}/>
            <Route path="/board" element={<BoardPage/>}/>
          </Routes>
        </Layout>
      }
    </>
  )
}

export default App

결과 화면

 


1. node-sass 설치하기

node-sass 패키지를 설치한다.

설치가 완료되면 packae.json 파일에서 확인할 수 있다.

yarn add node-sass
또는
npm install node-sass

 

2. SCSS 작성하기

  • src/assets/scss/components/header.scss
header {
  width: 100%;

  .inner {
    display: flex;
    justify-content: space-between;
    align-items: center;
    height: 80px;

    h1 {
      a {
        font-family: 'Cherry Bomb One', cursive;
      }
    }

    nav {
      ul {
        display: flex;
        justify-content: space-between;
        align-items: center;

        li {
          padding-left: 20px;
        }
      }
    }
  }
}

 

  • src/components/layouts/Heaser.tsx

작성한 scss 파일을 상단에 import 시킨다.

import '../../assets/scss/components/header.scss'

const Header = () => {
  return (
    <>
      <header>
        <div className="inner">
          <h1><a href="/">BOB</a></h1>

          <nav>
            <ul>
              <li><a href="/">menu01</a></li>
              <li><a href="/">menu02</a></li>
              <li><a href="/">menu03</a></li>
            </ul>
          </nav>
        </div>
      </header>
    </>
  )
}

export default Header

 

  • src/assets/scss/common.scss

reset.scss, font.scss 등 공통적으로 필요한 scss 파일을 페이지마다 import 하면 번거롭고, 코드도 길어지기 때문에 common.scss를 만들어 한 개의 scss 파일에 작성되도록 한다.

// base
@import './base/reset';
@import './base/font';

// layout
@import './layout/layout';

 

  • src/App.tsx

최종적으로 common.scss를 App 컴포넌트에 import 한다.

import React from 'react'
import Layout from './components/layouts/Layout'
import './assets/scss/common.scss'

const App = () => {
  return (
    <Layout>
      <h1>내용을 작성해 주세요.</h1>
    </Layout>
  )
}

export default App

결과 화면

 


1. 레이아웃 컴포넌트 만들기

  • src/components/layouts/Header.tsx
const Header = () => {
  return (
    <>
      <header>헤더 컴포넌트입니다.</header>
    </>
  )
}

export default Header

 

  • src/components/layouts/Footer.tsx
const Footer = () => {
  return (
    <>
      <footer>푸터 컴포넌트입니다.</footer>
    </>
  )
}

export default Footer

 

  • src/components/layouts/Layout.tsx

헤더와 푸터는 페이지마다 공통으로 보여지는 영역이기 때문에 import 해서 사용하면 되지만,

메인의 컨텐츠 영역은 페이지마다 내용이 바뀌기 때문에 props로 사용한다.

import Header from './Header'
import Footer from './Footer'

const Layout = (props: {children: React.ReactNode}) => {
  return (
    <>
      <Header/>

      <main>{props.children}</main>

      <Footer/>
    </>
  )
}

export default Layout

 

2. index.tsx, App.tsx 수정하기

  • src/index.tsx

불필요한 소스를 삭제한다.

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
)

root.render(
  <React.StrictMode>
    <App/>
  </React.StrictMode>
)

 

  • src/App.tsx

레이아웃 컴포넌트를 import 한다.

<h1>의 내용이 <Layout> 컴포넌트의 <main> 태그 안에 들어가게 된다.

<Layout> 컴포넌트로 감싸 여러 페이지들을 만들면 된다.

import React from 'react'
import Layout from './components/layouts/Layout'
import './App.css'

const App = () => {
  return (
    <Layout>
      <h1>내용을 작성해 주세요.</h1>
    </Layout>
  )
}

export default App

1. Node.js 설치하기

https://nodejs.org/ko

Node.js는 Chrome V8 JavaScript 엔진으로 빌드된 JavaScript 런타임이다.

 

위의 정의는 노드를 통해서 자바스크립트 애플리케이션을 실행할 수 있다는 의미이다.

타입스크립트는 자바스크립트에 타입을 추가한 언어이기 때문에 타입스크립트를 사용하기 위해서는 Node.js를 먼저 설치해야 한다.

 

1-1. Node.js 버전 확인하기

node --version
또는
node -v

 

 

2. typescript, ts-node 글로벌 설치하기

typescript와 ts-node를 글로벌로 설치하면 리액트 프로젝트가 아니더라도 타입스크립트를 사용할 수 있기 때문에 따로 설치하는 것이 좋다.

또한 글로벌이기 때문에 1번만 설치하면 추가 설치 없이 바로 사용할 수 있다.

yarn add -g typescript ts-node
또는
npm install -g typescript ts-node

 

2-1. typescript, ts-node 버전 확인하기

tsc --version 또는 tsc -v

ts-node --version 또는 ts-node -v

 

 

3. 프로젝트 만들기

yarn create react-app 폴더명 --template typescript
또는
npx create-react-app 폴더명 --template typescript

 

tsx 파일이 생성됐는지 확인한다.

 

 

4. 불필요한 파일 삭제하기

src 폴더에서 App.css, App.tsx, index.css, index.tsx 파일만 남기고 전부 삭제한다.

 

5. 로컬호스트 연결 확인하기

http://localhost:3000/

터미널에서 yarn start 명령어를 실행해 로컬호스트에 접속이 잘 되는지 확인한다.

아래와 같은 화면이 나온다면 정상적으로 세팅이 완료된 것이다.

 

버전에 맞춰서 강의와 다른 코드가 있습니다.

강의와 별개로 추가한 내용입니다.

 

결과 화면

 


redux-persist 패키지를 사용해서 데이터를 로컬 스토리지에 저장했다.

yarn add redux-persist 또는 npm install redux-persist

 

파일 구조

  • src/index.js

1. persistStore, PersistGate를 import 시킨다.

2. persistStore(store)를 만든다.

3. PersistGate 태그로 App 컴포넌트를 감싼다.

import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import { persistStore } from 'redux-persist';
import { PersistGate } from 'redux-persist/integration/react';
import store from './store/store'
import App from './App'

const root = ReactDOM.createRoot(document.getElementById('root'))
const persistor = persistStore(store)

root.render(
  <Provider store={store}>
    <PersistGate loading={null} persistor={persistor}>
      <App/>
    </PersistGate>
  </Provider>
)

 

  • src/store/rootReducer.js

1. store 디렉터리에 rootReducer.js 파일을 만든다.

2. combineReducers()에 기존에 만든 reducer를 작성한다. (toDos 이외에 리듀서가 여러 개라면 쉼표로 추가해 작성한다.)

import { combineReducers } from 'redux'
import toDos from './toDoReducer'

const rootReducer = combineReducers({
  toDos
})

export default rootReducer

 

  • src/store/store.js

1. store와 reducer를 나눠서 작성한다. (store 관련 코드만 남긴다.)

2. persistReducer를 import 시키고, persistReducer()에 rootReducer를 파라미터로 가지고 온다.

3. persistConfig 변수를 만들고, configureStore()에 리듀서를 작성한다.

import { configureStore } from '@reduxjs/toolkit'
import { persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import rootReducer from './rootReducer'

const persistConfig = {
  key: 'root',
  storage
}

const reducer = persistReducer(persistConfig, rootReducer)

const store = configureStore({
  reducer
})

export default store

 

  • src/store/toDoReducer.js

1. store와 reducer를 나눠서 작성한다. (reducer관련 코드만 남긴다. 파일명이 헷갈릴 수 있으니 리네이밍한다.)

import { createSlice } from '@reduxjs/toolkit'

const todoSlice = createSlice({
  name: 'reducerToDo',

  initialState: {
    toDoList: []
  },

  reducers: {
    addToDo: (state, action) => {
      state.toDoList.unshift({ id: Date.now(), text: action.payload })
    },
    deleteToDo: (state, action) => {
      state.toDoList = state.toDoList.filter(list => list.id !== action.payload)
    }
  }
})

export const { addToDo, deleteToDo } = todoSlice.actions
export default todoSlice.reducer

 

  • src/routes/Home.js

1. useSelector()로 state를 가지고 올 때, 로컬 스토리지에 있는 데이터(state.toDos.toDoList)를 가지고 온다.

import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { addToDo } from '../store/toDoReducer'
import ToDo from '../components/ToDo'
import './home.scss'

function Home() {
  // ** Hooks
  const dispatch = useDispatch()

  // ** States
  const [text, setText] = useState('')

  // ** Redux States
  const toDos = useSelector(state => state.toDos.toDoList)

  function onChange(e) {
    setText(e.target.value)
  }

  function onSubmit(e) {
    e.preventDefault()
    dispatch(addToDo(text))
    setText('')
  }

  return (
    <>
      <div className="layout home">
        <h1>To Do List</h1>

        <form onSubmit={onSubmit}>
          <input type="text" value={text} onChange={onChange} placeholder="What is your to do?"/>
          <button>Add</button>
        </form>

        <ul>
          {
            toDos.map((toDo, index) => {
              return (
                <ToDo id={toDo.id} text={toDo.text} key={index}/>
              )
            })
          }
        </ul>
      </div>
    </>
  )
}

export default Home

 

 

자세한 소스코드는 아래 깃 레포지토리를 참고한다.

https://github.com/heejae0811/react-todo

버전에 맞춰서 강의와 다른 코드가 있습니다.

 

결과 화면

 


Redux Toolkit 기본 개념

  • configureStore()

기존 createStore 역할을 대신해 사용한다.

configureStore()는 리듀서 조각들을 자동으로 합쳐주고, 기본으로 제공되는 기능뿐만 아리나 Redux DevTools 확장을 사용할 수 있게 해준다.

 

  • createSlice()

기존 reducer에서 switch문 역할을 대신해 사용한다.

초기 state와 리듀서 함수를 작성할 수 있고, array.push와 같은 state를 직접 수정할 수 있다.

state는 mutate 하면 안되는 것이 원칙인데, 리덕스 툴깃이 자동으로 새로운 state로 반환시켜준다.

 


 

redux toolkit을 사용하기 위해 터미널에서 yarn add @reduxjs/toolkit 또는 npm install @reduxjs/toolkit 명령어르 실행한다.

 

파일 구조

  • src/store/store.js

store를 만든다.

import { configureStore } from '@reduxjs/toolkit'
import { persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import rootReducer from './rootReducer'

const persistConfig = {
  key: 'root',
  storage
}

const reducer = persistReducer(persistConfig, rootReducer)

const store = configureStore({
  reducer
})

export default store

 

  • src/store/rootReducer.js

rootRedecer를 만든다.

toDos 이외의 reducer를 만들게 되면 rootReducer에 추가한다.

import { combineReducers } from 'redux'
import toDos from './toDoReducer'

const rootReducer = combineReducers({
  toDos
})

export default rootReducer

 

  • src/store/toDoReducer.js

reducers 안에 액션을 만든다.

initialState의 toDoList에 toDo를 추가하고, 삭제하는 기능을 만들기 위해 addToDo와 deleteToDo를 만든다.

addToDo 액션에서 unshift()를 통해 배열에 새로운 toDo를 추가하고,

deleteToDo 액션에서 filter()를 통해 id값이 일치하지 않는 나머지 값들만 return해 toDo를 삭제한다.

import { createSlice } from '@reduxjs/toolkit'

const todoSlice = createSlice({
  name: 'reducerToDo',

  initialState: {
    toDoList: []
  },

  reducers: {
    addToDo: (state, action) => {
      state.toDoList.unshift({ id: Date.now(), text: action.payload })
    },
    deleteToDo: (state, action) => {
      state.toDoList = state.toDoList.filter(list => list.id !== action.payload)
    }
  }
})

export const { addToDo, deleteToDo } = todoSlice.actions
export default todoSlice.reducer

 

  • src/routes/Home.js

useSelector()로 스토어에 있는 todoList state를 가지고 온다.

dispatch를 통해 reducers에 만들어 놓은 액션을 가지고 온다.

import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { addToDo } from '../store/store'
import ToDo from '../components/ToDo'
import './home.scss'

function Home() {
  // ** Hooks
  const dispatch = useDispatch()

  // ** States
  const [text, setText] = useState('')

  // ** Redux States
  const toDos = useSelector(state => state.todoList)

  function onChange(e) {
    setText(e.target.value)
  }

  function onSubmit(e) {
    e.preventDefault()
    dispatch(addToDo(text))
    setText('')
  }

  return (
    <>
      <div className="layout home">
        <h1>To Do List</h1>

        <form onSubmit={onSubmit}>
          <input type="text" value={text} onChange={onChange} placeholder="What is your to do?"/>
          <button>Add</button>
        </form>

        <ul>
          {
            toDos.map((toDo, index) => {
              return (
                <ToDo id={toDo.id} text={toDo.text} key={index}/>
              )
            })
          }
        </ul>
      </div>
    </>
  )
}

export default Home

버전에 맞춰서 강의와 다른 코드가 있습니다.

강의와 별개로 추가한 내용입니다.

 

결과 화면

 


scss를 사용하기 위해 터미널에서 yarn add node-sass 또는 npm install node-sass 명령어를 실행한다.

 

파일 구조

  • src/scss/layout/_layout.scss
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700&display=swap');

body {
    background-color: burlywood;
    font-family: 'Noto Sans', 'Noto Sans KR', sans-serif;

    .layout {
        position: relative;
        top: 0;
        left: 0;
        bottom: 0;
        right: 0;
        max-width: 500px;
        width: 90%;
        margin: 50px auto;
        padding: 30px;
        background-color: bisque;

        button {
            min-height: 30px;
            padding: 0 10px;
            background-color: #fff;
        }
    }
}

 

  • src/routes/home.scss
.home {
    h1 {
        margin-bottom: 30px;
        text-align: center;
    }

    form {
        display: flex;
        justify-content: space-between;
        gap: 10px;
        margin-bottom: 10px;

        input {
            width: 100%;
            height: 30px;
            padding: 10px;
        }
    }
}

 

  • src/components/todo.scss
ul {
    li {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 10px;

        &:last-child {
            margin-bottom: 0;
        }
    }
}

 

  • src/routes/detail.scss
.detail {
    h1 {
        margin-bottom: 30px;
        text-align: center;
    }

    .date {
        margin-bottom: 10px;
        text-align: right;
    }
}

+ Recent posts