React 난 이렇게 배웠다[총집합]

진건희·2024년 3월 13일
12
post-thumbnail
post-custom-banner

내가 이걸 쓰는 이유

남들에게 이 글이 도움이 되었으면 좋겠고
내가 잘 배웠는지 확인용으로도 사용할 예정이다.
그리고 개인적으로 React를 공부할 때
강의, 문서, 책 등 다 보면서 해도 힘들었던 기억이 있어
이런식으로 정리 해보려 한다
(전문용어를 많이 안 쓰고 저티어 감성으로 많이 적을 예정이다.)

React

한국에서 프론트 개발자 하려면
무조건 배워야 하는 라이브러리...
취업하여 사용하는 라이브러리 중 가장 많은 비율을 자랑함

간단 요약

프론트 개발자 하려면 배워야 됨 -♪

난 어떻게 배웠나?

생활코딩! 리액트 프로그래밍 책 으로 배웠다.
강의보다 이해하기 쉽고, 템포조절도 강의보다 훨씬 쉽다.

개발환경(React 세팅)

일단 React를 사용할 수 있는 환경을 만들어보자!

  1. Visual Studio code를 설치한다(여기에서 코드를 작성할 예정)
  2. 원하는 곳에 빈 폴더를 만들어준다.
  3. 빈 폴더를 Shift + 우클릭을 눌러 여기에 Powershell 창 열기 선택
  4. npx create-react-app 제목(Enter 하고 기다리기)
  5. npm start(실행시켜 본 뒤 잘 되는지 확인)
  6. Visual Studio code에서 폴더 열기

세부 과정


일단 빈 폴더를 만든다


그 다음 shift + 우클릭으로 powershell창을 연다


그럼 얘가 뜬다


npx create-react-app 제목 을 작성 후 Enter


뭔가 이러면서 빈 폴더에 이상한 파일들이 생길 거다 끝날 때까지 냅둬라.


이런식으로 뜨면 끝났다.


cd 아까 적은 제목 을 작성하여
디렉토리에 들어간다.
(디렉토리는 방이라고 생각해라 폴더가 집이고 디렉토리가 방임 나중에 따로 공부하면 내 설명이 뭔가 다르다라는 걸 깨닫게 될거다.)

그 다음 npm start를 적고 Enter로 실행한다


만약 이런식으로 뜨면 분명 내꺼 보기 전에 뭔가 다른데서 찾아봐서
실행했던적이 있었던거다

대충 설명하면 이미 서버 켜져 있는데 다른 포트(번호)로 킬까요?
라고 묻는 거다

y를 적는다


이렇게 넌 리액트를 켜보았다(로고가 삥글삥글 돌거임)

그럼 이제 창을 닫는다 (일단 닫아 그냥) (powershell도 닫아)


폴더 있던 곳으로 가보면 이렇게 꽉 차있을 거다.
(신경 안 써도 됨 건들지 마)


vs code에 들어가서 폴더 열기 선택


우리가 여태껏 말한 폴더를 선택후 폴더 선택 누른다.


이런식으로 뜰텐데 왼쪽에 잘 보면
title(아까 create-react-app 제목 했을때 제목을 말하는거임)이라고 있다 연다


열고 그 다음 src 열면 이런식이 된다

이제 개발 세팅이 끝났다.

일단 그럼 위에 Terminal로 터미널을 연다

그럼 터미널의 오른쪽 위에 보면 bash라고 되있다.
bash 오른쪽 아래쪽을 향하는 화살표를 눌러서 목록에서 powershell로 바꾼다.


(이 사진에 노란색을 누르면 powershell을 고를 수 있음)
그 다음 cd title(제목)
그리고 npm start로 로컬호스트를 연다(아까 열었던 거)

일단 App.js에 간다


그러면 이 함수(function 제목{내용} <- 이거 함수임)
return ()에 있는 내용을 지운다.


그러면 return에 아무 내용이 없어 에러가 난다
(일단 뭔가 있어야 함)
그러니 일단 hi라도 적자


(이걸 한 이유는 처음부터 시작하기 위해서 기본 기반은 내가 짠걸로 진행)

이제 react를 배울 수 있다.

컴포넌트(component)

React를 시작하면
html, css, js랑 달라지거나 새로 생기는 것이 많다.
얘도 대표적인 놈 중 하나다(많이 쓸 놈이라 제대로 배워야 함)

개념

컴포넌트의 개념을 알려주겠다
(위에서 말했듯이 초보를 위해 저티어 감수성으로 알려줄거다.)

html, css, js에서
쓰는 기본적으로 정의 해주는 코드
기본 코드가 있다
(ex : h1, p 이런거)

컴포넌트는 사용자가 사용하기 위해 직접 만든
사용자 정의 태그이다.
↳ 기본적으로 지원하지 않는 기능을 jsx(React에서 쓰는 js라고 이해하셈 js의 형임 - 성능이 유지보수가 더 잘됨)을 사용하면 구현 할 수 있지만
그 긴 코드를 계속 복붙하며 쓸 수 없으니 태그로 만든거다.

개념 간단요약

사용자가 자주 쓰려고 직접 만든 태그

실전

그럼 이번에는 컴포넌트를 만들어보자


일단 따라 써라

다 썼다면 차근차근 설명해보자
자 일단 return안에 코드들을 적은 건 좋았다.
그런데 전부 div로 묶었냐

원래 react에서는 return 안에 여러 가지에 코드가 있으면 안된다
그래서 최고위 부모 역할로 div를 두고
코드들을 자식으로 삼는다

그리고 header, nav, article
이 3가지는 원래 없던 태그다
이게 컴포넌트는 아니다
원래 없는 거 + 컴포넌트도 아니라서
신경 안써도 된다

이제 결과를 보자


이런식으로 되는데
총 header, nav, article이라는 3개의 레이아웃으로 나눈 것이다
Web : header
1,2,3 : nav

Welcome

hello, web : article
이다

그럼 이제 컴포넌트 차례다.

따라 써라.
설명하자면
3가지를 컴포넌트로 만들기 위해
함수 3개를 만든다.(이름을 무조건 대문자로 시작해야 됨, 그래야 인식함 걍 약속임)

위에 말한 부모, 자식 관계도 잘 지키고 옮긴다
함수의 이름이 Header, Nav, Article이다.

그러면 App에 있는 return에 부모 div를 제외하고 다 지운다.

그리고 위에서 만든 함수를 태그로 사용한다.

이게 컴포넌트다.

결과도

전과 똑같다.
App에 있는 코드는 줄었지만 결과는 같아(얼마나 좋냐)

그리고 우리가 사용하는 React의 jsx문법의 편한 점이 있다

이런식으로 작성해도 잘 작동한다

컴포넌트가 좋은 이유는

이런식으로 Header 컴포넌트를 3개 작성하면

이런식으로 편하게 Web도 3개가 된다

요약

  1. 함수를 만든다
  2. 함수에 문법 잘 지켜서 코드를 적는다
  3. 함수의 이름을 App에 <함수 이름 />
    이런식으로 쓴다
  4. 그럼 이게 컴포넌트

속성(props)

개념

props의 개념을 설명하겠다.

대충 html에서 썻던
태그 a를 예시로 들겠다.

<a>hello</a>

이런식으로 html에서 지원하는 태그는

<a title="props"></a>

src, width, height, title 등
속성들을 사용할 수 있다.

하지만 우리가 만든 컴포넌트는
사용자 정의 태그(우리가 만든 놈)
라서 속성(React에서는 속성을 prop이라고 함)을 못씀!
그래서
html과는 다른 방법을 쓴다.

요약

컴포넌트를 위한 속성

실전

일단 Header에 title을 추가한다.

title은 React이다.
원래 html이였으면 React가 나왔겠지만

여긴 React다.
F12로 개발자 모드로 봐도 Web으로 되어있다.

그럼 이제 우리가 원하는 React로 변하게 해보자.

일단 props를 변수로 넣는다.

여기서 아까 왜 Web으로 나왔는지 설명하자면

  1. App()에서는 Header 컴포넌트를 실행시켰다.
  2. Header 컴포넌트의 본체 Header 함수의 return값은 web으로 되어있다.
  3. 즉 속성(title)이 React여도 Header 컴포넌트의 반환값은 Web이다.

이런 이유이기 때문에 Web이 결과로 나온 것이다.
하지만


이런식으로 반환값을 web이라고 고정하지 않고
변수로 두면 title이 변할 때마다 반환값도 달라진다.
({}는 js를 React에서 변수 용도 쓰려면 {}로 감싸주어야 한다.)
(변수 이름.적용할 속성 = props.title)
변수 이름으로 props를 한 이유는 대부분의 사람들이 걍 이렇게 한단다.


이렇게 하면 화면, 개발자 모드 둘 다 값이 React로 바뀌었다.

이제 props를 써보았으니 다음을 위한
준비를 해두자.

일단 쉽게

이렇게 해줘서 Article도 유지보수가 편하게 props를 쓴다(결과는 원래랑 같음)

그럼 Nav를 건들자.


일단 변수 topics를 만든다.
id : 순서
title : 화면에 표시할 리스트
body : 이벤트에서 써먹을 예정


이렇게 topics라는 속성을 만들고
값을 위에서 작성한 topcis를 쓸거다
중괄호로 감싸야 데이터가 나온다
문자열로 하면 그냥 1 = 1이 되버리는 거랑 똑같다
↳당연한 말이 되버림

그리고

props를 쓰지 않은 이 딱딱한 놈들을 부드럽게 만들어야 한다.


Nav안에 lis라는 변수에 li를 다 넣어준다
(쉼표 안하면 여러개로 인식해서 에러 걸림)

하지만 아직 우리가 한 건 컴포넌트 살짝 고치기 정도밖에 안된다.
↳아직은 쓸모없음

이 코드에 대해 설명해주겠다.
지금 집중 잘해야 한다.

Nav에서 원래 있던 const lis를 건든다.

원래는 일반적인 딱딱한 값이였다 하면.
지금은 for문을 이용하여 값을 유동적(부드럽게) 만들었다.

for(let i = 0; i<props.topics.length; i++)

↳ 개수 관련 코드(위에 있던 lis는 값이 3가지로 정해져 있는 반면에
현재는 length 속성을 이용하여 개수를 특정함(양이 안 정해져 있음))

let t = props.topics[i];

↳ 위에서 말한 개수 코드에서 몇번 째 순번이지 받는 변수

lis.push(<li key={t.id}><a href={'/read/'+t.id}>{t.title}</a></li>) 

const lis = []

이렇게 lis가 비어있는데
위 코드에서 key로 순번을 정한다(ex : topics의 id)
그리고 내용으로 원래는

<a href = '/read/1'>html</a>

이런식이였다면

<a href= {'/read/'+t.id}>{t.title}</a>

이제는 t.id로 read 뒤에 직접 정었던 순번이 자동으로 적히게 되었다.
그리고 html 부분도 App에 있는 topics에 포함된 title이 나오도록 바뀌었다.

body에 대한 것은 나중에
이벤트에 들어가서 나온다.

오늘 이걸 보고 실천한 사람들은
과거의 살짝 편해진 컴포넌트가 아닌
props와 함께 이용하여 쓰기 완전히 편해진 컴포넌트를
사용할 수 있게 되었다.

수고했다

요약

Html에서 원래 있던 태그들은 속성(ex : width, height, src)들을 사용한다.
하지만 우리가 만든 컴포넌트를 위해 만든 속성이 props이다.

예시를 이용한 간단 설명

  1. App()에 있는 컴포넌트 분신에 title="web"을 추가
  2. 하지만 컴포넌트 본체의 return 내용은 React이기 때문에 화면에 React가 표시한다.
  3. 컴포넌트 본체인 Header()에 가서 () <= 안에 props(속성)을 넣음
  4. 그리고 return 내용인
<h1>Web</h1>

<h1>{props.title}</h1>

로 바꿈
5. 그러면 아래에 화면에 나타나는 App에 있는 title이 화면에 뜸
6. props.title = 속성 이름.적용할 속성 이런 느낌이고
{}는 jsx는 원래 {}에 표시해야 한다.
7. 그러면 title은 변수(변하는 수)이기 때문에 title의 내용이 바뀌면
적용값도 바뀐다.

이벤트(event)

우리는 컴포넌트에 속성까지 추가 했다.
그런데 뭐가 부족하지 않나?
그렇다 기능(이벤트)가 컴포넌트에 없다.
이번엔 기능을 한번 넣어보자.

개념

이벤트의 개념이라고 하면은
단순하다.
html에 onclick을 아는가?
누른다는 조건이다
예를 들어
onclick="alert" 라고 한다면
조건 = 기능
이러한 느낌이다
우린 React에서 이런 기능을 만들 것이다.

실전


일단 App에 있는 Header에 onChangeMode라는 속성(props)을 추가한다.
그리고 조건 = 기능 기억한가?
onChangeMode(속성)(조건) = function(함수)(기능)
인 것이다.

나는 Web이라는 글씨를 누르면 Header라고 적힌 경고창이 뜨게 할 것이다.

하지만 이것만으로는 화면에 변화가 없을 것이다.

왜냐하면 Header함수(본체)에 변화가 없기 때문이다.


본체에 변화가 생겼다.
어떤 변화인지 설명하자면
링크였던 Web(href[주소]에 url값을 /로 해놔서 작동은 안 함)을
누르면 원래 링크에 들어가지는 기능이 있었다.
그 기능이 있으면 경고창을 띄우기 힘들어 그 기능을 죽이는 변화다.

일단 클릭하였을 때라는 조건 onClick을 작성한다
그럼 조건이 완성되었을 때 기능

function(event){event.preventDefault()}

를 작성한다.
작동 방식은

  1. 파라미터로 event 넣기(소괄호에 들어가는 것이 파라미터)
  2. 변수이름.적용할 기능 느낌으로 event.preventDefault()를 사용한다.
  3. 그러면 위에 있는 코드의 기능인 링크로 인한 페이지 이동 막기가 작동된다.


(보기 편하게 중괄호 부분을 크기를 늘려놓음)
이제 원래 기능을 죽였으니
새로운 기능을 넣을 차례다.
사실 이미 기능 자체는 위에서 App에서 만들어 놨다.
우린 작동만 시키면 된다.
그래서 본체인 Header에 props.onChangeMode();로
작동 되게 만들어준다.


자 이제 web을 누르면 경고창에 Header라는 내용으로 뜬다.

그리고

Nav의 코드를 이렇게 바꾸어 Nav도 링크 이동 기능을 죽이고
onChangeMode(변수 이름.이름 중 어떤 놈.가지고 오고 싶은거)
해서 어떤 링크를 눌렀냐에 따라 다른 id가 적용되게 한다.

요약

원하는 컴포넌트에 이벤트 기능을 넣고 싶다면?

  1. App()에서 원하는 컴포넌트를 골라 속성(props)를 추가한다
  2. 그 props 내용으로 기능을 넣는다 function(){} ~~~
  3. 그 다음 컴포넌트 본체에 가서 props가 잘 작동되게 설정한다.

state

악마다.
React를 배우는 사람들에게 많은 힘듦을 주는 놈이다.

개념

React의 컴포넌트는 입력과 출력이 있다.
props가 입력이라면
state는 출력이다.

props를 통해 입력된 데이터를 컴포넌트 함수가 처리하여 return 값을 만들면 바로 적용이 된다.

책에는 이렇게 서술되어 있지만 난 저티어 감수성으로 알려주도록 하겠다.

위에서 props를 이용해 값을 바꾸면 바로 적응 되는 것을 보았을 것이다.
하지만 개발 중이여서 그렇지 실제 유저 입장에서는 안된다.

예를 들어
숫자가 0있다.
+,- 버튼을 이용하여 숫자를 나타내는 시스템이라 치자.

let count = 0;
<plus onClick="function(){count += 1};" />
<Minus onClick="function(){count -= 1};" />

라고 해도
버튼을 눌렀을 때 값은 변할 것이다.
props를 이용한다면 실시간으로 바뀐다.
그런데 변화를 확인하려면 렌더링(새로고침)을 해야한다.

하지만 state를 쓰면 값이 변화하자마자 렌더링 해준다.
↳새로고침을 하지 않아도 화면에 변화가 생김

개념 요약

props는 값이 실시간으로 변함
state는 props의 기능 + 화면 자동 새로고침을 이용한 실시간 화면 구성

실전

일단 우리는 내비게이션 상세보기 같이 뭘 누르냐에 따라 Article이 바뀌게 만들것이다.(화면도 자동으로 바뀌게 그래야 state지)


일단 무엇을 누르냐에 따라 뭔가 달라야 하기 때문에
mode라는 상수를 만들고
조건문을 이용해 다양한 결과가 나오게 하겠다.


그 다음 WELCOME모드 일 때는 title이 WELCOME body가 Hello, web
그 다음 READ모드 일 때는 title이 WELCOME body가 Hello, READ
두 가지로 나눈다
그리고 그 결과를 넣을 변수 content를 만든다.(null 아무것도 없는 상태)
그리고 Article 자리에 content가 나오게 설정한다.

그리고 Nav에 있는 3가지를 나누기 위해 topics에 있는 id를 이용해
onChangeMode로 html페이지 인지, css인지, js인지 나눈다.


WELCOME 모드일때 ↓


READ모드 일때 ↓

이렇게 모드에 따라 결과가 달라지게 되었다.

일단 WELCOME모드로 돌려놓고
이제는 이벤트가 발생했을 때 mode의 값이 변하게 하면 된다.

Header에서는 Web에서 WELCOME으로 바뀌게
NAV에서는 mode를 READ로 바뀌게 할 것이다.


App에 Header와 Nav에 onChangeMode로 mode가 바뀌게 한다.
이렇게 하면 Header(Web)을 누르면..?

에러가 뜨거나 아무 일도 벌어나지 않는다.

분명 우린 누르면 mode가 변하게 해놨는데 왜 이러는 것인가.

바로 렌더링이 안되서 그렇다.
쉽게 말하자면 mode가 WELCOME에서 READ도 바뀌어도
App함수 그 자체가 재실행 되지는 안되서 그렇다.

우리는 mode라는 변수의 값이 바뀌면 컴포넌트 함수가 재실행되게 해야 한다.
그래서 우리는 state를 사용해야 된다.


윗쪽에

import {useState} from 'react';

추가한다.
그래야 state를 사용할 수 있다.


일단 useState를 콘솔에서 봐보자.
이걸 실행시키면

이렇게 된다.
개발자 탭에서 보면 알 수 있다.
useState는 배열을 return(출력)한다.

0 : WELCOME은 현재 상태의 값을 읽을 때 쓴다.
1은 상태의 값을 변경할 때 쓴다.

코드를 state에 맞춰 바꾸어 보겠다.


코드를 보면서 다시 이해하자.

const [mode, setMode] = useState('WELCOME');

여기서 mode가 변수 역할,
setMode가 변수의 값을 변하게 하는 스위치 역할,
()에 있는 'WELCOME'은 초기값이다.

초기에는 일단 mode는 WELCOME이고
setMode('바꾸고 싶은 값')으로 바꾸고 싶은 값으로 바꾼다.

[mode, setMode] = [0, 1]

결과는

이렇게 Header인 web을 누르면 Hello, web이
Nav목록들 중 하나를 누르면 Hello, READ가 뜬다.

그럼 이제 응용을 해보자
목록들 하나한 결과가 다르게 해보자.


Nav쪽은 나중에 설명하고
App쪽을 먼저 설명하겠다.

Nav의 목록들에게 id가 있는데
그 id에 따라 값이 달라지도록

const [id,setId] = useStaet(null);

로 한다.
그리고 Nav를 눌렀을 때의 상태
READ로 가서
for문을 만드러 id마다 title과 body가 다르게 한다.
(for문 안에 if문 조건은 목록들 id와 useState의 변수 id가 같을 때)

그리고 id마다 다른 결과가 나오는 걸 적용하기 위해
Article의 title과 body를 사진처럼 한다.

그 다음 return에 있는 Nav에 onChangemode에 setId를 추가한다.

그 다음 Nav 본체 쪽으로 가면
a태그에 topics의 있는 id를 앞에 붙인다.
그리고 아래에 Number을 추가해 event.target.id에 숫자 속성을 추가한다.

그러면 결과가

html을 누르면 html is ...
css를 누르면 css is...
js를 누르면 javascript is ...

이 뜨게 된다.

요약

state는 값 뿐만 아니라 화면 구성까지도 실시간으로 바뀌게 하기 위해 사용한다.

사용 방법

import {useState} from 'react';

를 코드 맨 위에 추가
2.

const [변수 값,변수 변화용 도구] = useState(초기값)
사용 시 변수 변화용 도구(바꿀 값);
  1. 예시
const [id, setId] = useState(null); - 선언
setId(READ); - 사용

참고하면 좋은 state 공부 자료
↳글쓴이도 이거 보고 공부함

Create

대부분의 애플리케이션은 CRUD 기능이 있다.
CRUD가 무엇이냐

Create(생성)
Read(읽기)
Update(수정)
Delete(삭제)

앞서 위에서 Read기능을 구현했으니

이번엔 Create를 구현해보자

이번엔 글을 생성하는 방법, React에서의 form 다루는 법을 알아보자

우리는 이제 form에서 제목과 본문을 입력한 다음 Create버튼을 누르면
새로운 글이 생성되고, 새로 생긴 글의 상세 페이지 이동이 가능한
애플리케이션을 만들 것이다.

일단 생성을 시작하기 위한 버튼 역할을 맡을
링크를 만든다.
그리고

event.preventDefault();

로 클릭했을 때 Url바뀌는(페이지 이동) 기능을 죽인다.
그리고 누르면 mode가 CREATE로 바뀌게 한다.

결과는

Create라는 링크가 생겼다.
그 다음에는


CREATE 모드일 때 Create 컴포넌트가 생성되게 코드를 적는다.
그럼 이제 Create 컴포넌트를 만들자.


Create컴포넌트다.
최상위 태그는 그냥 article로 해두고
Create창이라는 것을 알리기 위해 h2로 Create를 적고
어떠한 정보를 서버에 보내기 위해 사용하는 태그인

<form>

안에
제목을 쓸 input
본문을 쓸 textarea를 넣고
세로로 정렬 되게 둘다 p태그로 묶는다
(type은 종류, name은 태그의 이름, placeholder는 아무것도 안 적었을 때 보이는 글자 이다.)

결과는

이제 Create링크를 누르면 이런식으로 글을 쓸 수는 있다.

그 다음으로는 제목과 본문의 값을 모두 입력 후 전송할 때 사용할 장치가 필요하다.
그 장치는 submit이다.


이번엔 input의 type을 submit으로 하고
value를 Create로 적어 글을 생성하는 기능이 있다고 표시하자.

결과는?

버튼이 생겼다.

하지만 아직 제목과 본문을 적은 후 버튼을 눌러도 화면에 추가 되지 않는다.


form은 submit하면
페이지(화면)이 리로드가 되게 한다.

리로드 되지 않게

이렇게 만든다

그 다음에는

title과 body의 적은 내용을 받아올 변수
title, body를 만든다
(event.target은 이 이벤트가 발생한 태그를 가리킴)

(event.target.title.value는
이벤트가 발생한 태그 중 title이란 이름을 가진 태그의 value를 가져온다
라는 뜻)

그리고 props.onCreat(title, body)로
App에서 prop인 onCreate가 작동되게 한다.

그렇면 다음엔 무엇을 해야하나
topics를 state(상태)로 업그레이드 해주어야 한다.

기존 topics를 지우고

이렇게 변경해준다.

그 다음

CREATE 조건문에 가서
newTopic이라는 변수를 만든다
(title : title, body : body)

(프로퍼티 : 파라미터)
(이름 : onCreate에서 적은 소괄호 안에 있던 title, body)

그리고 상세페이지에서 사용할 id를 위해 state를 하나 만든다.
지금 앞에서 만든 html, css, js로 인해 마지막 id가 3이기 때문에
생성 되는 건 4부터 이다. (이 이유로 useState(4)이다)

그 다음 setTopics를 이용하여 push 해본다.

결과는

버튼을 눌러도 아무 변화 없다

왜 그렇냐
책에서 보면 약간 어려운데
저티어 감수성으로 설명해보겠다.

ex)

const[value, setValue] = useState([1]);
value.push(2);
setValue(value);

이건 고유 데이터를 바꾼것이다.
그리고 setValue를 하면 고유 데이터를 입력한 것이 된다.
React는 setValue를 호출했는데 고유 데이터와 신입 데이터인지 구별한다.
그리고 만약 고유면 이미 있던 놈이라 렌더링 안해준다.

반면에

const[value, setValue] = useState(1);
newValue = [...value]
newValue.push(2);
setValue(newValue);

이건 원시 데이터 타입을 사용한다.
이렇게 한 후 setValue를 쓰면 새로운 값이 된다(신입)

고유 데이터는 여전히 1, 새로운 신입으로 2가 생기는 것이기 때문에
이건 렌더링 해준다(화면 새로고침)

따라서 이런식으로 topics 같은 배열들은
고유 데이터를 복제한 뒤에
복제 데이터를 변경 하고
변경한 데이터를 setValue 해야 한다.

붉은 고인물을 복제
붉은 복제 고인물을 변경
푸른 복제 고인물로 변경됨
푸른 복제 고인물을 setValue

이런 느낌?

그럼 코드는 어떻게 하냐


원래 setTopics(topics)였지만
위에서 말한 방법 처럼
newTopics라는 이름으로 topics를 복제 하고
복제해서 얻은 newTopics로 setTopics를 가동한다.

그 다음 상세페이지에 가는 기능을 추가한다

글이 추가 되면 setMode를 CREATE -> READ로 바뀌게 한 다음
추가한 글의 id인 nextId를 setId로 지정한다.
그리고 현재 남아있는 마지막 id보다 nextId가 1 더 높게 값을 변경하게 해준다.

결과는

제목과 본문을 적고 버튼을 누르면 추가가 된다
Create를 구현했다
수고했다

Update

이번에는 앞서 얘기한 CRUD 중 Update를 배울 것이다.
Update는 Create + Read 이다.
난이도가 있는 편

일단 우리는 Create로 글을 만드는 기능을 만들었다
그래서 이번엔 원하는 글을 수정하는 기능을 만들 것 이다.

일단 수정페이지로 이동하는 링크를 추가하자!

App()의 return이다
create와 update가 세로로 정렬 되도록
ul과 li를 이용해 리스트 형식으로 만들었다.

하지만 이 형식은 상세페이지에 들어가지 않아도 업데이트가 떠버린다
우리는 상세페이지 에서만 update가 되도록 해야 한다.
그러기 mode가 READ일 때만 뜨게 해보자



변수 contextControl를 만든다.
contextControl에 원래 있던 update링크를 넣는다
↳상세페이지도 여러 개 이기 때문에 update 뒤에 id를 붙여 나눈다.
(ex html의 update,css의 update는 따로)
그리고 App의 return에 contextControl을 작동시킨다.

결과는

메인 페이지에서는 안 보이던 update가 이제 상세페이지에 가면 보인다.

그 다음은

create와 같이 링크 이동 기능을 막고(리로드 방지)
setMode로 mode를 UPDATE로 바뀔 수 있게 한다.

그럼 이제 UPDATE 모드 조건문을 만든다.

Update는 Create와 거의 똑같다
복붙하자.


(function의 이름, h2태그의 내용, props.onCreate -> props.onUpdate, value의 내용 등이 바뀌었다)

Update컴포넌트를 사용하는 쪽에 onUpdate라는 props(속성)이 전달 되게 하자.
title과 body를 전달 받을 수 있도록


Update 컴포넌트에 props인 onUpdate를 추가

이제부터 update의 내용이다
수정이라 하면은 일단 기존 정보가 그대로 있어야 한다
↳이 말은?

Update 컴포넌트에서 title과 body의 값을 기본적으로 가지고 있어야 한다.
다행히 앞서 READ에서 title과 body 값을 알아냈었다.
복붙하자


title과 body의 선언, for문을 복붙하고
아래에 Update 컴포넌트에 title, body의 값을 우리가 만든 title, body로 받는다

그럼 이것에 맞춰서

Update 컴포넌트 본테에 있는 title을 담당하는 input과 body를 담당하는 textarea에 value로 props의 값으로 준다.

이렇게 되면 Update 컴포넌트로 title, body값이 전달되고
폼에 출력된다

지금 상황을 보자

이런식으로 기존 정보가 그대로 출력된다
그런데 아직 수정이 되지 않는다.

이유는

하지만 props는 렌더링을 다시 해주지는 않기 때문에 우리가 적은 내용이
실시간으로 변동이 되지 않는다,
그럼 이 말은 뭐다?
우리가 수정을 시도해도 코드가 실시간으로 변화하지 않아 수정이 안된다는 것

그래서 우리는 state를 쓰는 것이다


이렇게 state를 통해 props값을 가져온다.

그 다음

event.target.value를 이용하여 가장 최근의 변경된 값을 새로운 title 값으로 바꾼다

이렇게 하면 제목과 본문이 잘 바뀔 것이다.

이제 버튼을 누르면 제목과 본문이 바뀌게 해보자.

위에 for문은 글을 실시간으로 수정 가능한 기능
아래에 있는 for문은 수정한 내용을 저장하는 기능이다.
위에서 말한 우회해서 저장하기를 이용해

newTopics에 topics를 저장하고
for문으로 id로 표시해서 수정한 내용으로 덮어씌우고

newTopicp[i] = updatedTopic

어떤 상세페이지인지 구별하고 맞는 상세페이지의 id면 멈춘다.
그리고

setTopics(newTopics)

로 마무리 한 다음에

setMode('READ') 

로 상세페이지로 돌아간다
결과를 보면


이렇게 Update도 구현했다.
수고했다

Delete

이번에는 삭제 기능을 만들 것이다.
앞서 만든 Create, Update보다 훨씬 쉬우니 안심해라


Update와 마찬가지로 상세 페이지에 가야만 Delete가 있게
READ에 있는 contextControl에 Delete버튼을 추가했다.
일단 원래는

<li>~~~~</li>

이런식이였는데

<></>

이런식으로 return에 넣는 것처럼
큰 부모 태그를 만들고 그 안에 Update와 Delete버튼 모두 넣는다.

결과는

이렇게 버튼이 생겼다.

button은 기본적으로 동작이 없어 event.preventDefault() 함수는
없어도 된다.

우리의 삭제 대상은 topics 데이터이다.


일단 newTopics란 비어있는 배열을 만든다.
그리고 for문을 이용해 i가 0일 때부터
topics의 길이만큼 반복해 topics의 id와 현재 선택된 id가 같지 않다면 newTopics의 배열을 추가한다.
↳즉 id가 일치하지 않는 글만 새로운 배열에 넣는다.

newTopics는 기존에 있던 topics 배열이 아닌 빈 배열이다.
삭제하고자 하는 글을 제외한 나머지 글이 담기게 된다(newTopics가)
그리고 newTopics를 setTopics로 전달해 topics를 변경한다.
이렇게 하면 삭제 기능이 완성된다.
그리고

setTopics(newTopics) = 삭제

를 실행한 다음에 WELCOME 페이지로 이동할 수 있게

setMode('WELCOME');

를 적는다.

결과는


삭제와 WELCOME 페이지로 이동 또한 잘된다.
수고했다

이제 React의 기본은 끝냈다 앞으로는
React Router Dom을 배워야 한다..

React Router Dom

React Router Dom
리액트를 사용할 때 페이지를 이동할 때 필요한 라이브러리이다.

React 기초에서 페이지 이동 기능이 등장하지 않은 이유이다.
(링크를 누르면 화면만 바뀌게 하는 기능으로 진행했었음)

개념

원래 html에서는
원래는 A페이지를 보여주고 싶다면 A.html파일을 이용하고 B페이지를 보여주고 싶다면 B.html
이런식으로 진행되었다.

하지만 리액트에서는 웹 사이트의 전체 페이지를 하나의 페이지에 담아 동적으로 화면을 바꿔가며 표현한다.
↳ 이 방식을 SPA(Single Page Application) 이라고 부른다.

쉽게 그림으로 표현하자면

html


이런식으로 필요 상황 때마다
파일을 옮겨다니는 방식

React


React라는 큰 틀 안에 있는 A,B 페이지를 옮겨 다니는 방식이다.

요약

React에서는 html과 다르게 페이지 이동하려면
React Router Dom을 배워야 한다.

개발환경(React Router Dom)

방법은 2가지 이다

1) 새로 파기
React 수업 참고

2) React 수업에서 했던 폴더에서 이어하기

  1. 터미널 켜기(보기 -> 터미널 켜기 or ctrl+`)

  2. npx create-react-app react-router-dom-example(이름)


    ※ 특정 단어는 이름으로 사용할 수 없음
    다 될 때까지 기다리기..

  3. cd react-router-dom-example(아까 설정한 이름)(만든 곳에 들어가는거임)

  4. npm start로 시작

  5. 수업을 위해 미리 필요없는 걸 지우자
    index.css에 가서 코드 모두 제거
    (앞으로 수업은 새로 만든 react-router-dom-example에서 진행됨)

  6. index.js에서 필요 없는 내용 삭제 + App 컴포넌트 구현


    결과

  7. App컴포넌트에 사용할 3개의 컴포넌트 제작


    결과

  8. React Router DOM 설치(가장 중요)
    터미널 열고 cd react-router-dom-example(아까 정한 이름)
    그 다음에
    npm install react-router-dom를 적어 설치하기


    한 1초정도 걸림

개발환경 설정 완료

Router

일단 기본적인 것부터 배우자
3가지 정도가 있는데

  • BrowserRouter
  • Route
  • Routes

개념

BrowserRouter

BrowserRouter는 HTML5의 History API(pushState, replaceState, popstate event)를 사용하여 URL과 UI를 동기해주는 Router이다.
↳ BrowserRouter는 웹 페이지에서 발생하는 주소 변경에 대한 관리를 담당하는 도구

기능(저티어 해석)
주소가 변경될 때 발생하는 이벤트(popstate event)를 감지
사용자가 브라우저의 주소 표시줄을 직접 조작하지 않고도 웹 페이지의 URL을 변경

이는 페이지를 새로고침하지 않고도 주소를 변경할 수 있도록 해주고, 현재 주소에 관련된 정보를 props로 조회 및 사용이 가능하도록 한다.

역할
BrowserRouter는 리액트 라우터 돔을 적용하고 싶은 컴포넌트의 최상위 컴포넌트를 감싸주는 래퍼 컴포넌트

Route

Route는 path에 따라 해당 UI를 보여주는 라우팅 기능을 가진 컴포넌트
path=""부분에 URL 경로를 적고, 렌더링될 컴포넌트를 자식요소로 넣어주면 된다.

예시

<Route path="/이동할 주소">
	<출력할 컴포넌트 />
<Route/>

저티어 해석
Route는 url의 따라 해당 페이지를 보여주는 기능을 가진 컴포넌트,
path="적용할 주소"로 url 설정하며, 보여줄 컴포넌트를 Route 컴포넌트 안에 넣는다.

Routes

Route 컴포넌트들을 묶을 컴포넌트
Route 상위 컴포넌트

예시

<Routes>
  <Route path="/" ~~~>
  <Route path="/" ~~~>
  <Route path="/" ~~~>
<Routes>

실전

이제 실무를 해보자
일단 BrowserRouter를 import 해야한다.
그리고 현재 최상위 컴포넌트인 App컴포넌트를 감싼다.

하지만 아직 Route와 Routes를 사용하지 않아 에러가 뜰 것이다.
그래서 우리가 미리 만들어 놓은 Home, Topics, Contact 컴포넌트를
React 기초 때와는 다르게 진짜 url이 바뀌는 페이지 이동을 구현된
페이지들로 만들 것이다.

Home, Topics, Contact 3개의 컴포넌트들을 url을 각각 나누기 위해
Route 3개를 추가한다.
그리고 3개의 url이 아닌 이상한 url일 때 경우도 추가한다.

<Route path='/' element={<Home />} />
<Route path='/topics' element={<Topics />} />
<Route path='/contact' element={<Contact />} />
<Route path='/*' element={'Not Found'} />

여기서 path는 경로(주소창에 뜨는 url)
그리고 element는 props(속성)이다.
속성을 적용해 path에 따라

아무런 path가 없는
/면 Home
/topics면 Topics
/contact면 Contact
컴포넌트가 나오고

이외의 path는 Not Found가 표시된다.

그 다음 Route의 상위 컴포넌트 Routes로
Route들을 모아둔다.

<Routes>
        <Route path='/' element={<Home />} />
        <Route path='/topics' element={<Topics />} />
        <Route path='/contact' element={<Contact />} />
        <Route path='/*' element={'Not Found'} />
</Routes>

이 코드들은 각 페이지의 내용들이다.

<ul>
        <li><a href="/">Home</a></li>
        <li><a href="/topics">Topics</a></li>
        <li><a href="/contact">Contact</a></li>
</ul>

요약

각 페이지의 내용을 만들어 놓고
Route 컴포넌트
Route path="/url" element(속성)={url에 따라 가질 컴포넌트}
한 뒤에
Routes 컴포넌트Route 컴포넌트를 감싼다.
마지막으로 render에 있는 최상위 였던 컴포넌트 App을 현 최상위 컴포넌트 BrowserRouter 컴포넌트로 감싼다.

결과

결과를 보자


Topics 페이지로 이동하였을 때

우리가 설정한 url 이외의 주소록이 지정되었을 때

Link

개념

컴포넌트 중 하나이다.
단일 페이지 애플리케이션(SPA)을 만들 때 중요한 것은 페이지가 리로드 되지 않고 동적으로 가져오는 데이터는 코드를 작성하거나 Ajax 기술을 이용해
비동기적으로 데이터를 가져와 페이지를 만들어야 한다.

Link 컴포넌트는 페이지가 리로드 되지 않게 자동으로 구현하는 컴포넌트이다.

저티어 해석

SPA를 만들 때 중요한 점은 페이지를 새로고침하지 않고도 필요한 데이터를 동적으로 가져와서 페이지를 업데이트하는 것이다.

Link 컴포넌트는 페이지를 새로고침하지 않고도 페이지 간 이동을 자동으로 처리하는 컴포넌트다.
↳ 사용자가 다른 페이지로 이동할 때에도 전체 페이지를 새로 로드할 필요 없이 필요한 부분만 업데이트

※ 데이터를 동적으로 가져온다?
↳ 데이터를 동적으로 가져온다는 것은 웹 페이지가 처음 로드될 때 모든 내용을 한 번에 가져오는 것 X
↳ 사용자가 페이지를 사용하는 동안 필요한 데이터만 필요할 때 가져온다는 것을 의미

실전

일단 Link를 사용해보자
첫 번째로 쓸 준비를 해야한다.

index. js의 react-router-dom import에 Link를 추가한다.

a태그들을 Link로 바꿔주고 href를 to로 바꿔준다.

이러면 끝이다.

결과

아까 말했다시피 기능적으로 눈에 보이는 것은 달라진게 없다,
단지 렌더링(자동 새로고침)이 좀 더 빠르다는 장점이 있다.

일단 그래도 너무 쉽기도 하고 빨리 끝나서 유용한 것 몇 개를 더 알려주겠다.

HashRouter

개념

HashRouter는 BroswerRouter와 다르게 URL에 #이 들어간다.
#이 붙어있따면 뒤의 내용은 북마크라는 뜻.
웹 서버는 # 문자 뒷부분의 주소를 무시한다.
대신 js를 이용해 # 뒷부분을 보기 때문에 적절한 컴포넌트로 라우팅 가능하다.
하지만 #이 URL에 포함되어 보기 불편하다.

저티어 해석 ↓

HashRouter는 URL에 해시(#)를 사용하여 페이지 간의 전환을 관리하는 React Router의 컴포넌트
서버 구성이 필요 없으며, 브라우저 호환성이 좋다.
페이지를 새로고침하지 않고도 클라이언트 측에서 페이지를 관리할 수 있습니다.

↳ Broswer와 달리 서버에 페이지 요청을 하지 않고 js에서 알아서 해서 속도가 더 빠르고 편리함
↳ URL에 #이 포함되어 보기 불편함

실전

HashRouter를 사용해보자.

index.js에서
BroswerRouter 대신 HashRouter를 쓴다.(색이 저러는 이유는 아직 안 써서 그렇다)

아래쪽에 있는 createroot에 BroswerRouter를 HashRouter로 바꾼다.

결과

url을 보면 #이 추가된 것을 볼 수 있다.
하지만 눈으로는 이것 말고의 차이를 알 수 없다.
하지만 기능적으로는 더 좋아졌다.

이번에 배울 건 NavLink이다.

개념

간단히 말하면 Link의 상우 호환이다.
기능이 살짝 더 생긴 느낌이다.
NavLink를 쓰게 되면
Link와 다르게 style: active가 생긴다.
그러면 원래 Link와 다르게 css적인 기능을 추가 할 수 있따.

실전

import에 있는 Link를 NavLink로 바꾼다.

Link 컴포넌트도 NavLink로 바꾼다.

이렇게 하면 오른쪽 개발자모드를 보면 class="active"가 생겼다.

어떤 식으로 사용할 수 있을까?

index.css로 가서 잘 보이는 색으로 설정해보자

결과

자신이 있는 페이지 링크에 색이 적용되었다.
이유는 저 링크는 NavLink이고 NavLink에는 active라는 css속성이 있다.
근데 active의 코드가 배경색 : aqua이기 때문에
NavLink에게 aqua가 적용되는 거다.

그러므로 다른 곳으로 가면

이동한 곳의 색이 바뀐다.

끝이다 이번 Link 편은 그래도 꽤 쉬운 편이었다.

Styled component

이번에 배울 것은 Styled components이다.

일단 개발환경부터 다시 맞춰보자
개발환경 준비 방법

나는 일단 styled라는 이름으로 하나 새로 만들었다.

개념

일단 개념을 알아보자
styled components는 무엇인가?

React에 있는 css in js 라이브러리이다.
원래 React에서는 css를 js화 하여서 불편하였다.

ex) css : background - color이지만 js에서는 -를 못 쓰기 때문에
backgroundColor로 바뀌었다.

이러한 점을 극복한 것이 Styled Components이다.

실전

일단 처음에는 App.js를 비우고 div하나 넣어준다.

이렇게 되면 cd styled(폴더 이름) -> npm start로 실행시키면
아무것도 없을 것이다.

깔끔하다.

그럼 다음에는 이제 개발을 해보자.

일단 styled components를 사용하려면
설치를 해야한다.

폴더 디렉토리에 들어가 있는 상태로(폴더이름/styled 이런식)
npm install styled-components 작성 후 Enter

설치는 끝났다.

import styled from 'styled-components로 styled components를 import 한다

이제 정말 준비가 끝났다.
이제 styled components를 만들겠다.

return 안에 말고 위쪽에(function 안에는 들어있어야 함)
const example = styled.div``;를 적어준다

선언 할 때 변수 이름 대문자로 시작해야 됨
example X
Example O
안 그러면 react가 사용자가 만든 거라고 인식을 못함

const example = styled.div``;가 무슨 의미냐
대충
const example = example이라는 변수 생성
styled.div = styled component임 근데 형태가 div

※ 참고 styled라는 큰 영역 안에 div, button 같은 원래 있던 애들이 포함되어있는 거임

그래서 마저 해석하면

const example = styled.div``;
div 형태의 styled component를
example이란 이름의 변수에 넣음!

``의 의미는 뭐냐 바로 css 내용을 넣을 곳이다

마저 개발을 해보자.

이런식으로 css 내용을 적고 컴포넌트 처럼 사용한다

실제로 그냥 css 추가된 컴포넌트이다.

결과

이렇게 잘 된다.

응용

styled components는 이 방식 말고도 쓸 수 있는 방법들이 있다.

props 사용 styled components

첫번째는 props를 이용하는 방법이다.

직접 응용해보며 배워보자

props를 이용하여 배경색을 바꿔가며 쓴다.

내용을 보면 background-color가
width 속성이 200 안 넘으면 black
넘으면 aqua 색으로 된다.

그리고 return을 보면

<Example /> - 기본 width가 200(200 이상 즉 aqua)
<Example width="190 /> - width가 190 즉 200 이하 그러면 black

※ 참고 ${}로 하는 이유는
React에서 js변수나 문장을 쓸 땐 ${이 안에 있는 것은 js 판단}

결과

global styled

자 그럼 두번째 global styled component란 뭐냐

아까 styled를 깔면서 새로 생긴 게 있다.
createGlobalStyle이라는 것이다.

일단 완성된 코드를 먼저 보자

자 간단하게 설명해서 createGlobalStyle은 뭐가 다르냐
이 녀석은 일반 styled component와 역할이 다르다

styled component = css가 적용된 컴포넌트
GlobalStyle = css 파일 + 감싸고 있는 애들 css 적용

이런 느낌이다

global은 거의 css 파일처럼
원하는 만큼의 css 코드를 적을 수 있고

return에서
Glabal 컴포넌트로 다른 것들을 감싸면
감싸진 놈들이 css가 적용된다.

결과

다른 파일에서 styled를 끌어올 수 있는 방법

자 마지막으로 styled component를 다른 파일에서 끌어오는 방법이다.
일단 그럼 js파일을 하나 더 만들자

style.js 생성

그 다음으로 style.js를 간단히 작성한다


보내고 싶은 styled component 앞에 export(보내기)를 붙인다

그럼 이제 export(보내기)를 했으니 import(받기)를 할 차례다.
일단 App.js에서 받고 어떻게 적용시킬 지 선언한다.

import * as S "./style"

import * as S "./style"
import = 받는다

  • as = 사용하겠다
    S = 변수
    "./style" = 보내는 곳

즉 style에서 보내는 styled component를 S라는 변수로 받아서 사용하겠다.
라는 뜻이며

S.Example
↳ S라는 Example 컴포넌트를 S(style component인) 받는다
s.Example = 딴데서 받아온 styled component인 Example 컴포넌트 라는 뜻

Context api

참고

이 시리즈들은 생활코딩 React 책을 보고 참고하여 적지만
이번 Context api 편에서는 참고하지 않았습니다.

개념

Context api는 React ver 16 이후 부터 사용 가능한 내장 api이다.
컴포넌트에게 props를 사용하지 않고 필요한 데이터(state)를 쉽게 공유할 수 있게 해준다.

사용 이유

많은 컴포넌트가 있을 때 정해진 값을 계속 주어야 할 때
↳ 테마(다크, 라이트), 인증된 유저, 언어 설정 등

똑같은 값을 props를 이용해 컴포넌트에 주입할 떄 편하다.

createContext

새로운 context 생성
ex) 테마와 관련된 무언가를 하고 있을 때
dark와 light 테마처럼 여러 컴포넌트에서 공유할 데이터를 담을 수 있음.

provider

Provider는 context의 값을 제공하는 역할
ex) 테마와 관련된 무언가를 하고 있을 때
하위 컴포넌트에게 dark라는 props를 제공

consumer

Provider가 제공한 데이터를 사용하는 역할
ex) 테마와 관련된 무언가를 하고 있을 때
provider로 제공한 dark라는 것을 적용시킴

useContext

함수형 컴포넌트에서 context 값을 가져오는 역할
ex) 테마와 관련된 무언가를 하고 있을 때
원래는 consumer 컴포넌트로 감싸서 dark를 적용을 시키지만
useContext를 사용하면 dark를 함수에 받아온다.
↳ 더 간편하고 가독성이 좋아짐

실전

일단 기본적으로 세팅을 하자

터미널에서 npx create-react-app 이름
으로 새로 하나 만들고 기본 세팅한다.
↳ 모르겠다면 전 시리즈들을 확인

난 context라는 이름으로 하였다.

그리고 저번들과 다르게 내장 api이기 때문에
install할 필요가 없다

일단 우리는
아까 사용 예시에 있던 테마context api로 구현할 것이다.

일단 context api가 하위 컴포넌트를 한번에 상태 관리 할 수 있는 점을 보여주기 위해
Header와 footer 컴포넌트를 만들 것이다.

일단 context api를 사용한다는 것을 선언해야 한다.
↳ 선언한다 라는 말이 애매할 까봐 저티어 해석 => React한테 context api 사용 허락 받기

선언했다.
선언 방법 : import { createContext } from 'react'

일단 그럼 theme 즉 라이트, 다크 모드를 구현해야 한다.
나는 라이트를 red
다크를 black으로 설정하였다.

const themeContext = createContext(light)
변수 선언 = createContext(초기값)인데
쉽게 말하자면 themeContext는 Context api를 쓰며, 초기값은 light인 것이다.

일단 App에 Header와 Footer를 사용한다.

그리고 themeContext.provider는 무엇이냐
context apiproviderconsumer가 있다.

provider는 데이터를 제공하는 역할
props를 하위 컴포넌트에 제공한다.

consumer는 Provider가 제공한 데이터를 사용
Provider가 데이터를 업데이트할 때마다 해당 데이터를 사용하여 자동으로 업데이트

저티어 해석 ↓

Provider는 물을 제공하는 수도꼭지이고, Consumer는 그 물을 사용하는 사람.
Provider가 물을 흘려주면 Consumer는 그 물을 받아서 사용

Provider가 데이터를 공급하고, Consumer는 그 데이터를 사용한다고 생각하면 된다.

그래서 일단 themeContext.provider로 provider로 선언하고, value라는 props로 dark(다크 모드)로 설정한다

일단 Header와 Footer를 만들자

그리고 styles를 적용하고
themeContext.Consumer로 사용하는 것을 선언한다.

이런식으로 하면


코드가 이렇게 나오고

결과는?

Header와 Footer에 dark를 직접 넣지 않았지만 dark가 적용되었다.
context api가 잘 작동하는 것을 더 확인해보자

provider에 있는 props(value)를 dark에서 light로 바꿔보자
그러면 아까 styles에서 dark가 아니라면 색을 red로 지정했기 때문에
색이 바뀔 것이다.

이렇게

이런식으로 하면
하위 컴포넌트가 아무리 많아도
관리하기 편해진다.

응용

이번에는 useContext라는 함수까지 써 볼 예정이다.

일단 Header와 Footer의 내용을 바꾼다.

일단 const theme = useContext(themeContext);
useContext를 쓴다.

해석하자면
theme라는 변수를 만들고, useContext로 값을 가져온다.
무슨 값을? themeContext를 가져오는 것이다.

이제 딱히 없다.

위에서 만든 themeContext를

provider로 dark로 설정한다.

그리고 마지막으로 설정한 dark를 가져온다.

결과는?


light일 때


이렇게 useContext를 써도 잘 작동하는 것을 볼 수 있다.

useReducer 이해하기

개념

React의 내장 hook 중 하나이다.
useState 처럼 상태 관리 할 때 사용됨.
주로 useState 보다 복잡한 로직에 사용한다.

  • state
    ↳ 상태는 컴포넌트 내부에서 관리되는 데이터 useState와 똑같이 현재 상태를 말함(상태)
  • action
    ↳ 액션은 상태를 변경하는데 필요한 정보를 포함한 객체.(변경할 정보)
  • dispatch
    ↳ 디스패치는 액션을 작동하는 역할(상태 변경 스위치)
  • reducer
    ↳ 리듀서는 상태를 변경하는 함수(상태 변경 기능)
  • initnalState
    ↳ 초기 상태는 상태의 초기값을 나타냄(초기값)

즉 useReducer는
state로 상태를 만들고, initnalState로 초기값을 state에 넣어줌.
그 다음 action으로 필요 정보를 state에 넣고
dispatch로 action을 작동시킴, 그 다음 reducer로 state를 렌더링 시켜서
최신 것으로 바꿈

이런식의 작동이 이루어진다.

useState와 useReducer의 차이

복잡한 상태 로직 관리

useState: 단순한 상태 값만을 다루는데 유용합니다. 간단한 상태 관리에 적합하다.
useReducer: 복잡한 상태 로직이나 여러 상태 값 간의 의존성이 있는 경우에 유용하다.
↳ state 같은 변수가 많을 때 좋음

액션 처리

useState: 상태를 직접 업데이트하는 setState 함수를 사용합니다. 액션에 대한 처리를 직접 작성해야 한다.
useReducer: 액션 객체를 사용하여 상태를 변경하며, 이를 처리하는 리듀서 함수를 통해 상태를 업데이트함.
액션에 대한 처리 로직이 리듀서 함수 내에 캡슐화되어 있습니다.
↳ 좀 더 코드 보기 편함(가독성 ↑), 유지보수 간편

컴포넌트 내부 코드의 가독성

useState: 상태 업데이트 로직이 컴포넌트 내부에 직접 작성되므로, 간단한 상태 관리에 적합하지만, 로직이 복잡해질수록 가독성이 떨어짐.
useReducer: 상태 업데이트 로직이 별도의 리듀서 함수로 분리되어 있기 때문에, 컴포넌트 내부 코드가 간결해짐.
특히 상태 업데이트 로직이 복잡한 경우에 전보다 보기 편함.

성능

useStateuseReducer는 모두 상태 관리에 사용될 수 있지만, 성능 측면에서 큰 차이는 없다.
하지만 리듀서 함수를 사용하면 상태 업데이트 로직이 분리되므로, 특히 컴포넌트의 규모가 크거나 복잡한 경우에 유지보수성이 더 좋아짐.

실전

미리 예시 코드를 만들어 왔다
이번에는 useReducer에서 나오는 복잡한 로직 중 무엇을 쓸 지 고민하다.
생각한 Todolist를 가볍게 만들어 볼 것이다.
(고퀄 X)

전체 코드

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

const Box = styled.div`
  width: 50vw;
  height: 10vh;
  background-color: white;
  display: block;
  margin-left: auto;
  margin-right: auto;
  border: 1px solid black;
  border-radius: 20px;
`

const Titlebox = styled.div`
  width: 30vw;
  height: 5vh;
  background-color: white;
  margin-left: 1vw;
  margin-top: 1vh;
`

const Title = styled.span`
  text-align: center;
  vertical-align: middle;
  font-size: 27px;
  font-family: bold;
`

const Text = styled.span`
  text-align: center;
  vertical-align: middle;
  font-family: bold;
`

const Nulldiv = styled.div`
  height: 15px;
`

const Button = styled.div`
  display: block;
  margin-top: 2vh;
  margin-left: 90vw;
`

function reducer(state, action){
  switch(action.type) {
    case "add":
      return { count: state.count + 1 };
    case "delete":
      return { count: state.count - 1 };
    default:
      throw new Error("unsupported action type: ", action.type);
  }
}

function App(){
  return(
    <div>
      <Nulldiv />
      <Box>
        <Titlebox>
          <Title><b>Todo list 만들기</b></Title>
          <br />
          <Text>Reducer 하나 배우려고 Todolist 만듭니다</Text>
        </Titlebox>
      </Box>
      <Nulldiv />
    </div>
  );
}

function Todolist() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  const increaseCount = () => {
    dispatch({ type: "add" });
  };

  const decreaseCount = () => {
    dispatch({ type: "delete" });
  };

  const renderApps = () => {
    const apps = [];
    for (let i = 0; i < state.count; i++) {
      apps.push(<App key={i} />);
    }
    return apps;
  };

  return (
    <div>
    <Button>
      <button onClick={increaseCount}>증가</button>
      <button onClick={decreaseCount}>감소</button>
    </Button>
    {renderApps()}
    </div>
  );
}

export default Todolist;

일단 코드를 3가지로 나눈다.
※ styled components를 사용하였음(styled component를 따로)

style

const Box = styled.div`
  width: 50vw;
  height: 10vh;
  background-color: white;
  display: block;
  margin-left: auto;
  margin-right: auto;
  border: 1px solid black;
  border-radius: 20px;
`

const Titlebox = styled.div`
  width: 30vw;
  height: 5vh;
  background-color: white;
  margin-left: 1vw;
  margin-top: 1vh;
`

const Title = styled.span`
  text-align: center;
  vertical-align: middle;
  font-size: 27px;
  font-family: bold;
`

const Text = styled.span`
  text-align: center;
  vertical-align: middle;
  font-family: bold;
`

const Nulldiv = styled.div`
  height: 15px;
`

const Button = styled.div`
  display: block;
  margin-top: 2vh;
  margin-left: 90vw;
`

App

App은 Todolist 컴포넌트이다.
어떻게 생겼는지 만들어진 것

function App(){
  return(
    <div>
      <Nulldiv />
      <Box>
        <Titlebox>
          <Title><b>Todo list 만들기</b></Title>
          <br />
          <Text>Reducer 하나 배우려고 Todolist 만듭니다</Text>
        </Titlebox>
      </Box>
      <Nulldiv />
    </div>
  );
}

그냥 그대로 따라 치거나, 복붙 하면 된다.

reducer

자 reducer이다.

일단 useReducer를 사용하려면
import React, { useReducer } from "react";로 선언을 해줘야 한다.

그리고 다음으로
파라미터안에 state, action을 선언해준다.
※ 파라미터 = ()안에 있는 것들

action은 필요 정보를 넣는 곳이라 했는데
useReducer를 사용할 때 switch문을 사용하는데
case에 결과가 달라진다(switch는)

그런 것 처럼 switch는 파라미터가 필수 인데
어떤 case인지 묻는 것이다.
우리는 action.type(액션의 종류)로 해준다.

그리고 todolist를 추가, 제거 기능을 넣기 위해
adddelete case를 추가하고
마지막으로 둘 다 아닐 때는 오류가 나오게 return 해준다.

add는 state.count(지금 현 상황의 count를 의미) +1 시켜서
list가 하나 더 생긴 것으로 의미한다.

delete는 반대로 -1 시켜 하나 사라진 것을 의미한다.

function reducer(state, action){
  switch(action.type) {
    case "add":
      return { count: state.count + 1 };
    case "delete":
      return { count: state.count - 1 };
    default:
      throw new Error("unsupported action type: ", action.type);
  }
}

Todolist

이번에는 Todolist 쪽이다.
여기서는 const [state, dispatch] = useReducer(reducer, { count: 0 });
를 선언한다.

해석하자면
const [state, dispatch] = useReducer(reducer, { count: 0 });
const [state, dispatch] = state와 dispatch 선언

useReducer(reducer, { count: 0});
reducer 함수 소환(dispatch한 것을 새로고침으로 표시)
count: 0 = initnalState(초기값)을 0으로 잡는다
count라는 함수의 초기값을 0으로 잡음

일단 코드에서 increaseCount, decreaseCount 라는 변수를 선언하고
dispatch로 각각 add와 delete를 실행한다.

그리고 renderApps라는 변수는 for문을 이용해 state.count의 값만큼
Todolist = App 컴포넌트를 생성 및 제거한다.

그리고 return으로 버튼과 renderApps를 표시한다.

function Todolist() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  const increaseCount = () => {
    dispatch({ type: "add" });
  };

  const decreaseCount = () => {
    dispatch({ type: "delete" });
  };

  const renderApps = () => {
    const apps = [];
    for (let i = 0; i < state.count; i++) {
      apps.push(<App key={i} />);
    }
    return apps;
  };

  return (
    <div>
    <Button>
      <button onClick={increaseCount}>증가</button>
      <button onClick={decreaseCount}>감소</button>
    </Button>
    {renderApps()}
    </div>
  );
}

결과

초깁값이 0이라 list가 없다.

그렇다면 증가를 눌러보자

증가를 누르자 App 컴포넌트가 나온다.

증가를 누를 수록 state.count의 수가 커지고
그 수만큼 컴포넌트가 늘어난다

마찬가지로 감소를 누르면 누른 횟수만큼 state.count가 줄어
컴포넌트의 개수가 줄어든다.

Redux란?

리덕스는 JavaScript 앱의 상태 관리를 위한 라이브러리로, 애플리케이션의 상태를 중앙 집중적으로 관리하여 상태 관련 로직을 효율적으로 관리할 수 있게 해준다.
↳ useState와 같은 상태 관리를 하는 라이브러리

context api의 전역적으로 관리[앱 or 웹 전체 관리]
+
useState의 상태 관리

두 가지의 기능을 합친 존재 같은 느낌

useState와 Redux의 차이

범위

useState : 로컬 상태를 관리하며, 해당 컴포넌트 내에서만 유효하다.
Redux : 전역 상태를 관리하며, 애플리케이션 전체에서 유효하다.

useState : 해당 페이지에서 사용하는 '개별' 정보
Redux : 전체 앱에서 공유하는 '전체' 정보

관리 방식

useState : 컴포넌트 내에서 상태를 직접 변경하고 관리한다.
Redux : 액션과 리듀서를 통해 상태를 변경하고 관리한다.

useState : 각 페이지에서 직접 정보를 바꾸고 관리
Redux : 앱 전체에서 일관된 방식으로 정보를 바꾸고 관리

구현

useState : 리액트 내장 훅을 사용하여 상태를 관리한다.
Redux : Redux 라이브러리를 사용하여 전역 상태를 관리한다.

useState : 리액트에서 기본적으로 제공하는 훅을 사용하여 상태를 관리합니다. 코드 작성이 비교적 간단하고 직관적.
Redux : Redux 라이브러리를 설치하고 설정한 후, 액션, 리듀서, 스토어 등을 직접 구현하여 사용합니다. 초기 설정과 코드 구조가 필요하기 때문에 상대적으로 구현이 복잡할 수 있음.

복잡성

useState* : 간단한 상태 관리에 적합하며, 컴포넌트 간의 데이터 공유가 필요한 경우에 유용하다.
Redux : 대규모 애플리케이션 및 복잡한 상태 관리에 적합하며, 상태 변화를 예측 가능하고 추적 가능하게 한다.

useState : 한 페이지 안에서 정보를 관리하는 것
Redux : 앱 전체에서 정보를 관리하는 것

유지보수성

useState : 각 컴포넌트에서 상태를 독립적으로 관리하므로 유지 보수가 비교적 쉽습니다.
Redux : 전역 상태를 중앙 집중적으로 관리하므로 상태 변경 로직을 중앙화하여 유지 보수가 용이합니다.

useState : 각 페이지를 개별적으로 유지보수하는 것
Redux : 앱 or 웹 전체를 일관되게 유지보수하는 것

저티어 해석 ↓


범위

useState : 해당 페이지에서 사용하는 '개별' 정보
Redux : 전체 앱에서 공유하는 '전체' 정보

관리 방식

useState : 각 페이지에서 직접 정보를 바꾸고 관리
Redux : 앱 전체에서 일관된 방식으로 정보를 바꾸고 관리

구현

useState : 리액트에서 기본적으로 제공하는 훅을 사용하여 상태를 관리함.
코드 작성이 비교적 간단하고 직관적.
Redux : Redux 라이브러리를 설치하고 설정한 후, 액션, 리듀서, 스토어 등을 직접 구현하여 사용함.
초기 설정과 코드 구조가 필요하기 때문에 상대적으로 구현이 복잡할 수 있음.

복잡성

useState : 한 페이지 안에서 정보를 관리하는 것
Redux : 앱 or 웹 전체에서 정보를 관리하는 것

유지보수성

useState : 각 페이지를 개별적으로 유지보수하는 것
Redux : 앱 or 웹 전체를 일관되게 유지보수하는 것


핵심 요소

단일 상태 트리(Single State Tree) : 리덕스는 애플리케이션의 전체 상태를 하나의 객체로 관리한다.
이를 단일 상태 트리라고 부르며, 모든 컴포넌트에서 이 상태에 접근할 수 있다.

액션(Action) : 상태를 변경하는 데 필요한 정보를 나타내는 객체다.
액션은 반드시 type이라는 필드를 가져야 하며, 추가적인 데이터를 포함할 수 있다.

리듀서(Reducer) : 현재 상태와 액션을 받아서 새로운 상태를 반환하는 함수다.
리듀서는 순수 함수여야 하며, 이전 상태를 변경하지 않고 새로운 상태를 생성하여 반환한다.

스토어(Store) : 전체 애플리케이션의 상태를 담고 있는 객체다.
스토어에는 현재 상태와 리듀서가 포함되어 있으며, 상태 변경을 구독하고 액션을 디스패치할 수 있다.

저티어 해석 ↓

단일 상태 트리(Single State Tree) : 앱 안에 있는 모든 정보를 하나로 모아놓은 것.
그래서 어디서든 이 정보에 접근할 수 있음.

액션(Action) : 어떤 일이 일어났는지 알려주는 메시지 같은 것.
예를 들어, "할일 목록에 새로운 할일이 추가되었다"라는 메시지를 보낼 수 있음.

리듀서(Reducer) : 액션을 받아서 앱의 상태를 바꾸는 역할을 하는 함수.
앱이 할일 목록을 가지고 있는데, 새로운 할일이 추가되면 그 목록을 업데이트하는 역할.

스토어(Store) : 앱 안에 있는 모든 정보를 담고 있는 곳.
이런 정보를 통해 앱의 상태가 어떻게 변하는지 추적할 수 있다.


원칙

single source of truth

  • 동일한 데이터는 항상 같은 곳에서 가져온다.
    ↳ 즉, 스토어라는 딱 하나 뿐인 데이터 공간이 있다.

state is read-only

  • 리액트에서는 setState 메소드를 이용해야만 상태 변경이 가능하다.
    ↳ 리덕스에서도 액션이라는 객체를 통해서만 상태 변경이 가능함.

Changes are made with pure function

  • 변경은 순수함수로만 가능하다.
    ↳ 리듀서와 연관되는 개념
    ↓ store(스토어) - Action(액션) - Reducer(리듀서)

사용법

스토어 생성

createStore 함수를 사용하여 스토어를 생성

저티어 해석
스토어를 만든다.
이 스토어는 앱 안에 있는 모든 정보를 담아두는 곳이다.

import { createStore } from 'redux'; // createStore 함수를 사용하여 스토어를 생성
import rootReducer from './reducers'; // 여러 개의 리듀서를 합친 루트 리듀서

const store = createStore(rootReducer); // rootReducer를 사용하여 스토어를 생성

액션 정의

액션을 정의하여 상태 변경에 필요한 정보를 포함시킨다.

저티어 해석
액션을 정의한다.
이 액션은 어떤 일이 일어났는지 알려주는 메시지 같은 것이다.

// 액션을 정의하여 상태 변경에 필요한 정보를 포함시킨다.
const addTodo = (text) => ({
  type: 'ADD_TODO', // 액션의 타입을 정의
  payload: { text } // 추가할 할일의 텍스트 정보를 페이로드(payload)에 포함시킴.
});

리듀서 작성

각 액션에 따라 상태를 어떻게 변경할지 정의하는 리듀서 함수를 작성한다.

저티어 해석
리듀서를 만든다.
이 함수는 액션을 받아서 상태를 어떻게 바꿀지 정의함.

// 각 액션에 따라 상태를 어떻게 변경할지 정의하는 리듀서 함수를 작성한다.
const initialState = {
  todos: [] // 초기 상태로 빈 할일 목록을 설정.
};

// todoReducer 함수는 이전 상태와 액션을 받아서 새로운 상태를 반환.
const todoReducer = (state = initialState, action) => {
  switch(action.type) {
    case 'ADD_TODO': // ADD_TODO 액션이 전달되면
      return {
        ...state, // 기존 상태를 유지한 채
        todos: [...state.todos, { text: action.payload.text, completed: false }] // 새로운 할일을 추가함.
      };
    default:
      return state; // 그 외의 경우에는 기존 상태를 반환.
  }
};

액션 디스패치

dispatch 메서드를 사용하여 액션을 스토어에 전달하여 상태를 변경합니다.

저티어 해석
액션을 보낸다.
이렇게 하면 스토어에 있는 정보가 업데이트 됨.

// dispatch 메서드를 사용하여 액션을 스토어에 전달하여 상태를 변경함.
store.dispatch(addTodo('Buy groceries')); // 'Buy groceries'를 추가하는 액션을 디스패치함.

구독

subscribe 메서드를 사용하여 상태 변경을 감지하고 업데이트한다.

저티어 해석
상태를 감시한다.
상태가 바뀔 때마다 실행할 함수를 등록함.

// subscribe 메서드를 사용하여 상태 변경을 감지하고 업데이트.
store.subscribe(() => {
  console.log('Updated state:', store.getState()); // 상태가 업데이트될 때마다 새로운 상태를 출력함.
});

실습

실습용 파일 만들기
npx create-react-app redux
redux라는 이름의 React 폴더를 만듬

cd redux
redux 폴더로 들어감

npm i redux
redux 설치하기
↳ 리덕스는 라이브러리이기 때문에 추가 설치가 필요함

Store(스토어)

스토어를 일단 생성한다.
데이터 공간을 만드는 거다.

import { createStore } from 'redux';

// 초기 상태 정의
const initialState = {
  todos: []
};

// 리듀서 함수
const todoReducer = (state = initialState, action) => {
  switch(action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, { id: Date.now(), text: action.payload.text }]
      };
    case 'REMOVE_TODO':
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.payload.id)
      };
    default:
      return state;
  }
};

// 스토어 생성
const store = createStore(todoReducer);

redux에 있는 store를 사용한다고 선언하는 코드
import { createStore } from 'redux';

↓ 그 다음으로 스토어(store)를 구축하기 시작 ↓

초기 상태를 빈 상태로 정의하는 코드

const initialState = {
  todos: []
};

↓ 상태 관리를 하는 애플리케이션에서 사용되는 리듀서 함수를 정의 ↓

const todoReducer = (state = initialState, action) => {//코드 내용}
todoReducer 함수는 현재 상태와 액션을 받아들여서 새로운 상태를 반환하는데, 초기 상태가 정의되어 있지 않으면 initialState를 기본값으로 사용한다.

↓ switch 투두리스트 생성, 제거 만들러 ↓

switch 문을 사용하여 액션 타입에 따라 다른 동작을 수행함.

case '무언가':
  return {
  	실행 코드
  };

ADD_TODO, REMOVE_TODO로 할일 생성과 할일 제거를 가능하게 해주는 코드

    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, { id: Date.now(), text: action.payload.text }]
      };
    case 'REMOVE_TODO':
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.payload.id)
      };
    default:
      return state;
  }

'ADD_TODO' 액션 :
새로운 할 일을 추가하기 위한 액션.
새로운 할 일은 현재 시간을 이용하여 고유한 ID를 생성하고, 액션에서 받아온 텍스트를 가진 객체로 구성됨.
기존 상태(state)를 복제하고, todos 배열에 새로운 할 일 객체를 추가하여 새로운 상태를 반환.

'REMOVE_TODO' 액션 :
할 일을 삭제하기 위한 액션.
액션에서 받아온 ID와 일치하지 않는 할 일들로 이루어진 새로운 todos 배열을 만들어서 해당 할 일을 제거한 상태를 반환함.

기본 (default) 동작:
지정된 액션 타입이 없는 경우, 현재 상태를 그대로 반환합니다.

↓ 스토어(store) 생성 ↓

Redux의 createStore 함수를 사용하여 스토어를 생성합.
createStore 함수에 리듀서 함수를 전달하여 스토어를 초기화.
const store = createStore(todoReducer);
리듀서 함수를 이용해 스토어를 등록하면 그에 맞게 상태를 관리하고 ui를 갱신한다.

요약
스토어는 앱의 상태를 담고 있는 객체다.
리듀서는 액션을 통해 상태를 변경하는 함수로, 스토어를 생성할 때 함께 전달.
initialState는 앱의 초기 상태를 정의하는 객체.
이 예제에서는 빈 할일 목록을 초기 상태로 정의했다.

액션(Action)

액션(Action) 정의

const ADD_TODO = 'ADD_TODO';  // 액션 타입 정의
const addTodo = (text) => ({  // 액션 생성 함수
  type: ADD_TODO,
  payload: { text }
});
액션 타입 정의 부분

ADD_TODO라는 문자열을 액션 타입으로 정의함.
액션 타입은 보통 대문자 문자열 상수로 정의됨.
이건 액션을 식별하는 데 사용됨.

액션 생성 함수 부분

addTodo 함수는 할 일을 추가하는 액션을 생성하는 함수입.
이 함수는 text라는 매개변수를 받아와서 액션 객체를 반환함.
반환된 액션 객체는 typepayload 속성을 가짐.

type은 액션의 종류를 식별하는데 사용되는 문자열.
여기서는 위에서 정의한 ADD_TODO를 사용합니다.

payload는 액션과 관련된 데이터를 포함하는 객체.
여기서는 새로운 할 일의 텍스트를 payload로 포함함.

↓ 디스패치 사용하러 ↓

디스패치(dispatch) 사용

store.dispatch(addTodo('할 일 추가'));  // 할일 추가 액션 디스패치

위의 예시에서는 '할 일 추가'라는 텍스트를 가진 새로운 할일을 추가하는 액션을 생성하고, 이를 스토어디스패치하여 상태를 변경하는 것을 보여준다.
이런 식으로 액션을 정의하고 사용하는 것이 리덕스에서 상태를 변경하는 일반적인 패턴이다.

구독

이제 상태 변경을 구독하고, 상태가 변경될 때마다 콘솔에 현재 상태를 출력하는 코드가 추가할 차례.
이렇게 함으로써 스토어의 상태 변경을 실시간으로 감지할 수 있게 만들 예정.

구독
상태를 저장하고, 상태 변경을 실시간으로 감지

// 스토어 구독
store.subscribe(() => {
  console.log('Updated state:', store.getState());
});

이 코드는 Redux 스토어를 구독(subscribe)하는 부분이다.
Redux 스토어의 구독은 상태가 변경될 때마다 특정 동작을 수행할 수 있도록 해준다.
구독을 통해 상태가 업데이트될 때마다 콜백 함수가 호출되어 원하는 작업을 수행할 수 있습니다.

구독
store.subscribe() 함수를 사용하여 스토어를 구독한다.
이 함수는 콜백 함수를 매개변수로 받는다.
이 콜백 함수는 상태가 변경될 때마다 호출됨.

콜백 함수
구독된 상태가 변경될 때마다 호출되는 함수.
여기서는 단순히 변경된 상태를 콘솔에 출력하는 로그 메시지를 포함함.
store.getState() 함수를 사용하여 현재 상태를 가져옴.
이를 통해 상태의 변화를 추적하고 로그에 출력할 수 있음.


결과

import { createStore } from 'redux';

// 액션 타입 정의
const ADD_TODO = 'ADD_TODO';

// 액션 생성 함수
const addTodo = (text) => ({
  type: ADD_TODO,
  payload: { text }
});

// 리듀서 함수
const todoReducer = (state = { todos: [] }, action) => {
  switch(action.type) {
    case ADD_TODO:
      return {
        ...state,
        todos: [...state.todos, { id: Date.now(), text: action.payload.text }]
      };
    default:
      return state;
  }
};

// 스토어 생성
const store = createStore(todoReducer);

// 스토어 구독
store.subscribe(() => {
  console.log('Updated state:', store.getState());
});

// 할일 추가 액션 디스패치
store.dispatch(addTodo('할 일 추가'));

이 코드는 Redux를 사용하여 간단한 할 일 목록을 관리하는 코드이다.
각 부분의 작동 방식과 전체적인 작동 결과를 다시 되새겨 보자.

액션 타입 정의 및 액션 생성 함수
ADD_TODO라는 액션 타입을 정의하고, 이를 사용하여 할 일을 추가하는 액션을 생성하는 addTodo 액션 생성 함수를 정의함.
이 함수는 받은 텍스트를 페이로드로 가지고 ADD_TODO 타입의 액션 객체를 반환한다.

리듀서 함수
todoReducer 함수는 현재의 상태와 액션을 받아들여서 새로운 상태를 반환하는데, ADD_TODO 액션 타입을 처리하여 새로운 할 일을 추가함.
ADD_TODO 액션이 발생하면 현재 상태를 복제한 후, 새로운 할 일을 현재 할 일 목록에 추가하여 새로운 상태를 반환함.

스토어 생성
createStore 함수를 사용하여 Redux 스토어를 생성함.
이 때, 위에서 정의한 리듀서 함수를 전달하여 초기화한다.

스토어 구독
store.subscribe()를 호출하여 스토어를 구독함.
이는 스토어의 상태 변화를 감지하고, 상태가 업데이트될 때마다 등록된 콜백 함수가 호출되도록 설정함.
여기서는 상태가 업데이트될 때마다 현재 상태를 콘솔에 출력하는 콜백 함수를 등록한다.

할일 추가 액션 디스패치
store.dispatch()를 사용하여 addTodo 액션 생성 함수를 호출하고, 반환된 액션 객체를 스토어에 dispatch 한다.
이를 통해 할 일을 추가하는 액션이 발생하게 됨.

↓ 요약 ↓

결과적으로, 할일 추가 액션이 디스패치되면 Redux는 리듀서 함수를 실행하여 상태를 업데이트하고, 이에 따라 구독된 콜백 함수가 호출되어 새로운 상태가 콘솔에 출력된다.
이렇게 구독 및 액션 디스패치를 통해 Redux는 상태의 변화를 감지하고 관리하여 예상대로 동작하게 된다.

↓ 투두리스트를 만들기 위한 코드 추가 ↓

import React, { useState } from 'react';
import { createStore } from 'redux';

// 액션 타입 정의
const ADD_TODO = 'ADD_TODO';

// 액션 생성 함수
const addTodo = (text) => ({
  type: ADD_TODO,
  payload: { text }
});

// 리듀서 함수
const todoReducer = (state = { todos: [] }, action) => {
  switch(action.type) {
    case ADD_TODO:
      return {
        ...state,
        todos: [...state.todos, { id: Date.now(), text: action.payload.text }]
      };
    default:
      return state;
  }
};

// 스토어 생성
const store = createStore(todoReducer);

// 투두리스트 컴포넌트
const TodoList = ({ todos }) => (
  <ul>
    {todos.map(todo => (
      <li key={todo.id}>{todo.text}</li>
    ))}
  </ul>
);

// 앱 컴포넌트
const App = () => {
  // 투두리스트 상태 설정
  const [todos, setTodos] = useState([]);

  // 스토어 구독
  store.subscribe(() => {
    setTodos(store.getState().todos);
  });

  // 할일 추가 함수
  const handleAddTodo = () => {
    const todoText = prompt('할 일을 입력하세요:');
    if (todoText) {
      store.dispatch(addTodo(todoText));
    }
  };

  return (
    <div>
      <h1>투두리스트</h1>
      <TodoList todos={todos} />
      <button onClick={handleAddTodo}>할 일 추가</button>
    </div>
  );
};

export default App;

웹결과

처음 보았을 때

할 일 추가 버튼을 눌렀을 때

할 일 추가하였을 때

이렇게 Redux 공부와 Redux를 이용한 투두리스트 만들기를 해보았다.

redux-toolkit이란

Redux를 더 사용하기 쉽게 만들기 위해 Redux에서 공식 라이브러리이다.

Redux의 문제점

redux-toolkit은 redux의 3가지 단점을 보완하기 위해 만들어졌다.

  • 저장소 구성의 복잡성(store 환경이 복잡)
  • 많은 패키지 필요성(의존성)
  • 한 작업 시 필요한 수 많은 코드양('보일러플레이트')

redux-toolkit의 4가지 특징

Simple

스토어 설정, 리듀서 생성, 불변성 업데이트 로직 사용을 편리하게 하는 기능 포함

Opitionated

스토어 설정에 관한 기본 설정 제공, 일반적으로 사용되는 redux addon이 내장

Powerful

Immer에 영감을 받아 '변경'로직으로 '불변성'로직 작성 가능, state 전체를 slice로 자동으로 만들 수 있음

Effective

적은 코드에 많은 작업을 수행 가능

주요 개념

configureStore

Redux 스토어를 설정하는 함수.
Redux Toolkit은 configureStore 함수를 통해 스토어를 생성하며, 기본적으로 Redux DevTools Extension과 함께 사용됨.

createSlice

Redux Toolkit의 핵심 기능 중 하나로, 리듀서와 액션 생성 함수를 한 번에 정의할 수 있게 해준다.
createSlice 함수를 사용하면 리듀서 함수와 액션 생성 함수를 하나의 파일에 함께 정의하여 관리할 수 있다.

immer

Redux Toolkit에서 상태를 변경할 때 불변성을 유지하는 것을 간편하게 만들어주는 라이브러리.
immer를 사용하면 복잡한 불변성 로직을 작성하지 않아도 된다.

Thunk middleware

비동기 작업을 처리하기 위한 Redux 미들웨어.
Redux Toolkit은 Thunk middleware를 기본으로 포함하고 있어서 비동기 작업을 쉽게 처리하는 게 가능하다.

주요 개념 해석 ↓

configureStore

스토어(store)를 만들 때 사용되는 함수.
Redux DevTools Extension 기능도 딸려옴.

createSlice

Redux Toolkit에서 중요 기능 중 하나.
리듀서액션 생성 함수를 한번에 선언 가능.
즉, 우리가 상태를 어떻게 변경할지를 정의하는 리듀서
상태를 변경할 때 사용할 액션들을 한번에 관리할 수 있는 거죠.

immer

상태를 변경할 때 불변성을 유지하는 게 쉬워지도록 도와주는 라이브러리.
Redux를 사용하면 상태를 변경할 때 매우 복잡한 로직을 작성해야 한다.
하지만 immer를 사용하면 더 간단하게 상태를 변경할 수 있게 된다.

Thunk middleware

이건 비동기 작업을 처리하기 위한 Redux 미들웨어이다.
비동기 작업은 우리가 어떤 작업을 요청한 후에 결과를 기다리는 것이다.
Redux Toolkit은 이 Thunk middleware를 기본으로 포함하여 비동기 작업을 편하게 할 수 있다.

미들 웨어(middleware)

미들웨어(Middleware)는 소프트웨어 시스템에서 입력과 출력 사이에 위치하여 데이터를 처리하고 변환하는 소프트웨어 구성 요소.
특히, 웹 개발에서는 서버와 클라이언트 사이에 위치하여 요청을 처리하고 응답을 생성하는 데 사용됨.

Redux에서는 액션과 리듀서 사이에서 동작하여 액션을 가로채고 추가적인 작업을 수행함.

  1. 액션이 디스패치를 함
  2. 미들웨어는 해당 액션을 가로챔
  3. 필요에 따라 액션을 변형하거나 추가적인 작업을 수행
  4. 리듀서에게 액션을 전달

많이 사용되는 미들웨어

Redux에서 많이 사용되는 미들웨어는
Redux Thunk, Redux Saga, Redux Observable이 있다.

Redux DevTools Extension

Redux 개발 도구의 한 종류.
Redux 애플리케이션의 상태를 시각적으로 디버깅하고 모니터링할 수 있게 해주는 브라우저 확장 프로그램.

  • Redux 스토어의 상태를 실시간으로 확인
  • 액션들의 로그를 볼 수 있음
  • 시간에 따른 상태의 변화를 추적

기능을 보자면
상태 시각화, 액션 로깅, 시간 여행, 실시간 업데이트
4가지이다.

상태 시각화
스토어의 현재 상태를 트리나 차트 형태로 시각화하여 볼 수 있다.

액션 로깅
디스패치된 액션들의 로그를 확인할 수 있어서 액션이 어떻게 처리되는지를 추적 가능.

시간 여행
상태의 이전 버전과 현재 버전을 비교, 이전 상태로 되돌아가거나 앞으로 나아갈 수 있는 기능을 제공.
디버깅을 용이하게 해줌.

실시간 업데이트
애플리케이션이 실행 중일 때 실시간으로 상태 변화를 모니터링 가능.
개발자가 애플리케이션의 동작을 쉽게 이해 가능하게 해줌.


사용법

4가지 단계로 나눈다.

단계

  1. 스토어 생성 : configureStore 함수를 사용하여 Redux 스토어를 생성.
  2. 슬라이스 생성 : createSlice 함수를 사용하여 리듀서와 액션 생성 함수를 한번에 사용하는 슬라이스를 생성.
  3. 액션 디스패치 : 생성된 액션 생성 함수를 사용하여 액션을 디스패치하여 상태를 변경.
  4. 구독 : 필요한 경우, 스토어의 상태 변경을 구독하여 변화를 감지하고 처리.

작동 방식

Redux Toolkit은 Redux의 기본 원리를 기반으로 하며, Redux의 여러 기능들을 보다 효율적으로 사용할 수 있도록 지원합.

  1. createSlice 함수를 사용하여 슬라이스를 생성하면 해당 슬라이스에 대한 리듀서와 액션 생성 함수가 자동으로 생성됨.
  2. immer 라이브러리를 사용하여 상태를 변경할 때 불변성을 유지하면서 코드를 작성 가능.
  3. Thunk middleware를 사용하여 비동기 작업을 처리.

대표 API

configureStore : Redux 스토어를 생성하는 함수.
createSlice : 리듀서와 액션 생성 함수를 함께 정의하여 슬라이스를 생성하는 함수.
createAsyncThunk : 비동기 작업을 처리하는 Thunk 액션 생성 함수를 생성하는 함수.
createReducer : 리듀서 함수를 생성하는 함수로, 복잡한 리듀서 로직을 분리하여 관리.

useSelector

스토어의 상태를 선택하는 데 사용되는 hook.
컴포넌트에서 필요한 상태를 선택하여 가져올 수 있다.

useDispatch

액션을 디스패치하는 데 사용되는 hook.
useDispatch를 사용하여 컴포넌트에서 액션을 디스패치할 수 있다.

실습

저번에 redux를 배웠을 때 만든 투두리스트 코드를 redux-toolkit으로 재개발 해보았다.

과정

createSlice
Redux Toolkit의 createSlice 함수를 사용하여 슬라이스를 생성.
슬라이스는 상태와 액션 생성자(reducers)를 함께 정의하여 관련된 로직을 묶어준다. 위 코드에서는 'todos' 슬라이스를 생성하고, 초기 상태와 할일을 추가하는 액션 생성자를 정의함.

코드

const todoSlice = createSlice({
  name: 'todos',
  initialState: {
    todos: []
  },
  reducers: {
    addTodo: (state, action) => {
      state.todos.push({ id: Date.now(), text: action.payload });
    }
  }
});

코드 역할
여기서 createSlice 함수가 사용되었다.
이 함수는 'todos'라는 이름을 가진 슬라이스를 생성하고,
초기 상태로 빈 배열을 가지며, addTodo라는 액션 생성자(reducer)를 선언하고 있다.

configureStore
Redux Toolkit의 configureStore 함수를 사용하여 스토어를 생성합.
이 함수는 슬라이스 리듀서를 인자로 받아 Redux 스토어를 생성함.
이 예제에서는 'todos' 슬라이스의 리듀서를 스토어에 등록합니다.

코드

const store = configureStore({
  reducer: todoSlice.reducer
});

코드 역할
여기서 configureStore 함수가 사용됨.
이 함수는 Redux 스토어를 설정하고, todoSlice.reducer를 스토어의 리듀서로 설정함.

Provider
리액트 애플리케이션에서 Redux 스토어를 사용할 수 있도록 제공하는 컴포넌트. Provider 컴포넌트는 최상위에 위치하여 앱 전체에 Redux 스토어를 제공.

코드

const App = () => {
  return (
    <Provider store={store}>
      <div>
        <h1>투두리스트</h1>
        <TodoList />
        <AddTodo />
      </div>
    </Provider>
  );
};

코드 역할
여기서는 Provider 컴포넌트가 사용되었다.
이 컴포넌트는 Redux 스토어를 앱에 제공하는 역할이다.

useSelector
React Redux의 useSelector 훅을 사용하여 스토어의 상태를 가져옴.
이 훅은 함수형 컴포넌트 내에서 사용되며=고, 스토어의 상태를 선택하여 컴포넌트에 제공함.
이 예제에서는 useSelector를 사용하여 투두리스트의 상태를 가져오는 역할.

코드

const todos = useSelector(state => state.todos);

코드 역할
여기서 useSelector 훅이 사용되었다.
이 훅을 사용하여 스토어의 상태인 todos를 가져왔다.

useDispatch
React Redux의 useDispatch 훅을 사용하여 액션을 디스패치한다.
이 훅은 함수형 컴포넌트 내에서 사용되고, 디스패치 함수를 가져와서 액션을 디스패치할 수 있다.
이 예제에서는 useDispatch를 사용하여 할일을 추가하는 액션을 디스패치 하는 역할.

코드

const dispatch = useDispatch();

코드 역할
여기서 useDispatch 훅이 사용되었다.
이 훅을 사용하여 액션을 디스패치하는 dispatch 함수를 가져왔다.

최종 코드

import React from 'react';
import { configureStore, createSlice } from '@reduxjs/toolkit';
import { Provider, useDispatch, useSelector } from 'react-redux';

// 슬라이스 생성
const todoSlice = createSlice({
  name: 'todos',
  initialState: {
    todos: []
  },
  reducers: {
    addTodo: (state, action) => {
      state.todos.push({ id: Date.now(), text: action.payload });
    }
  }
});

// 스토어 생성
const store = configureStore({
  reducer: todoSlice.reducer
});

// 투두리스트 컴포넌트
const TodoList = () => {
  // useSelector를 사용하여 스토어의 상태를 가져오기
  const todos = useSelector(state => state.todos);

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
};

// 할일 추가 컴포넌트
const AddTodo = () => {
  const dispatch = useDispatch();

  // 할일 추가 함수
  const handleAddTodo = () => {
    const todoText = prompt('할 일을 입력하세요:');
    if (todoText) {
      dispatch(todoSlice.actions.addTodo(todoText));
    }
  };

  return (
    <button onClick={handleAddTodo}>할 일 추가</button>
  );
};

// 앱 컴포넌트
const App = () => {
  return (
    <Provider store={store}>
      <div>
        <h1>투두리스트</h1>
        <TodoList />
        <AddTodo />
      </div>
    </Provider>
  );
};

export default App;

결과

코드가 저번과 다르지만 생김새와 기능에 차이가 없다.
처음에는 제목인 투두리스트와 할 일 추가 버튼이 있고,

할 일 추가 버튼을 누르면 할 일의 제목을 적을 수 있는 alert가 뜬다.

alert에 제목을 적어주고 확인을 누르면 할 일이 추가 된다.

비교

redux-toolkit X

import React, { useState } from 'react';
import { createStore } from 'redux';

// 액션 타입 정의
const ADD_TODO = 'ADD_TODO';

// 액션 생성 함수
const addTodo = (text) => ({
  type: ADD_TODO,
  payload: { text }
});

// 리듀서 함수
const todoReducer = (state = { todos: [] }, action) => {
  switch(action.type) {
    case ADD_TODO:
      return {
        ...state,
        todos: [...state.todos, { id: Date.now(), text: action.payload.text }]
      };
    default:
      return state;
  }
};

// 스토어 생성
const store = createStore(todoReducer);

// 투두리스트 컴포넌트
const TodoList = ({ todos }) => (
  <ul>
    {todos.map(todo => (
      <li key={todo.id}>{todo.text}</li>
    ))}
  </ul>
);

// 앱 컴포넌트
const App = () => {
  // 투두리스트 상태 설정
  const [todos, setTodos] = useState([]);

  // 스토어 구독
  store.subscribe(() => {
    setTodos(store.getState().todos);
  });

  // 할일 추가 함수
  const handleAddTodo = () => {
    const todoText = prompt('할 일을 입력하세요:');
    if (todoText) {
      store.dispatch(addTodo(todoText));
    }
  };

  return (
    <div>
      <h1>투두리스트</h1>
      <TodoList todos={todos} />
      <button onClick={handleAddTodo}>할 일 추가</button>
    </div>
  );
};

export default App;

redux-toolkit O

import React from 'react';
import { configureStore, createSlice } from '@reduxjs/toolkit';
import { Provider, useDispatch, useSelector } from 'react-redux';

// 슬라이스 생성
const todoSlice = createSlice({
  name: 'todos',
  initialState: {
    todos: []
  },
  reducers: {
    addTodo: (state, action) => {
      state.todos.push({ id: Date.now(), text: action.payload });
    }
  }
});

// 스토어 생성
const store = configureStore({
  reducer: todoSlice.reducer
});

// 투두리스트 컴포넌트
const TodoList = () => {
  // useSelector를 사용하여 스토어의 상태를 가져오기
  const todos = useSelector(state => state.todos);

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
};

// 할일 추가 컴포넌트
const AddTodo = () => {
  const dispatch = useDispatch();

  // 할일 추가 함수
  const handleAddTodo = () => {
    const todoText = prompt('할 일을 입력하세요:');
    if (todoText) {
      dispatch(todoSlice.actions.addTodo(todoText));
    }
  };

  return (
    <button onClick={handleAddTodo}>할 일 추가</button>
  );
};

// 앱 컴포넌트
const App = () => {
  return (
    <Provider store={store}>
      <div>
        <h1>투두리스트</h1>
        <TodoList />
        <AddTodo />
      </div>
    </Provider>
  );
};

export default App;

redux-toolkit을 사용하여 개선된 점들

1. 간결한 코드
redux-toolkit을 사용하면 코드가 더 간결해졌다.
createSlice 함수를 사용하여 액션과 리듀서를 한 번에 정의하고,
configureStore 함수로 스토어를 설정하는 등 많은 반복적인 작업을 줄임.

2. 불변성 유지
redux-toolkit은 내부적으로 Immer를 사용하여 불변성을 유지하게 되었음.
이로 인해 상태를 변경하는 코드를 작성할 때 더 간편하고 직관적으로 작성할 수 있게됨.

3. DevTools 통합
redux-toolkit은 기본적으로 Redux DevTools Extension과 함께 사용됨.
이를 통해 개발자는 애플리케이션의 상태 변화쉽게 추적하고 디버깅할 수 있다.

4. 간편한 비동기 작업 처리
redux-toolkit은 Thunk middleware를 기본으로 포함하고 있어서 비동기 작업을 쉽게 처리 가능하게 되었다.
이를 통해 네트워크 요청이나 비동기 작업을 처리하는 데 더 간편하고 일관된 방식으로 코드를 작성할 수 있다.

5. 성능 개선
redux-toolkit은 내부적으로 성능 최적화가 적용되어 있다.
이로 인해 더 효율적으로 상태 업데이트가 이루어지며, 애플리케이션의 성능을 향상시킬 수 있다.
이러한 이유로 redux-toolkit을 사용하면 더 효율적으로 Redux를 관리하고 개발할 수 있다.

profile
프론트엔드 공부 중인 GSM 학생입니다.
post-custom-banner

3개의 댓글

comment-user-thumbnail
2024년 3월 13일

최대한 저티어 감수성으로 썼습니다...

답글 달기
comment-user-thumbnail
2024년 3월 21일

기본기에 매우 충실한 글이라고 생각합니다. 그리고, 터미널, Powershell 에서 폴더내의 경로에 위치하여 있다면 code . 명령어로 쉽게 VSCode (당연히 코드 파일들까지 다 있습니다)를 열 수 있습니다. 좋은 글 감사합니다~

1개의 답글