본문 바로가기
Front-End, CS 스터디/항해99 - React 스터디

[리액트 React] 숙련 학습 자료 정리

by 제니운 2022. 7. 29.
728x90

✅ 컴포넌트 꾸미기(CSS-in-JS)

 

1️⃣ CSS-in-JS란?

 

단어 그대로 자바스크립트 코드로 CSS 코드를 작성하여 컴포넌트를 꾸미는 방식

새로운 패키지를 사용한다.

 

2️⃣ styled-components란?

 

리액트에서 CSS-in-JS 방식으로 컴포넌트를 꾸밀 수 있게 도와주는 패키지

 

👌 yarn add styled-components

 

👌 기본적인 사용법

 

SC의 기본적인 원리는 꾸미고자 하는 컴포넌트를 SC의 방식대로 먼저 만들고 그 안에 스타일 코드를 작성하는 방식으로 진행된다.

 

* App.js

import React from "react";
// styled-components에서 styled 라는 키워드를 import 한다.
import styled from "styled-components";

// styled키워드를 사용해서 styled-components 방식대로 컴포넌트를 만든다.

const StBox = styled.div`
// 그리고 이 안에 스타일 코드를 작성한다. 백틱 사용
  width: 100px;
  height: 100px;
  border: 1px solid red;
  margin: 20px;
`;

const App = () => {
// 그리고 우리가 만든 styled-components를 JSX에서 html 태그를 사용하듯이 사용한다.
  return <StBox>박스</StBox>;
};

export default App;

 

🔹 div : styled.div

🔹 span : styled.span

🔹 button : styled.button

 

 

👌 조건부 스타일링이란?

 

className을 사용해서 구현하기는 조금 까다로운 조건부 스타일링을 styled-components를 이용하면 간편하게 할 수 있다. CSS-in-JS 방식의 강점이 바로 스타일 코드를 JS코드 작성하듯이 작성할 수 있다는 점.

ex) if문 사용, switch문 사용, 삼항연산자 사용

 

 

props는 부모 컴포넌트에서 자식 컴포넌트로 어떤 정보를 전달하고자 할 때 사용한다. style-components도 말 그대로 컴포넌트이므로, box들에게 props를 통해서 border color에 대한 정보를 전달해줄 수 있다.

 

* App.js

import React from "react";
import styled from "styled-components";

const StBox = styled.div`
  width: 100px;
  height: 100px;
  border: 1px solid ${(props) => props.borderColor}; // 4.부모 컴포넌트에서 보낸 props를 받아 사용합니다. 
  margin: 20px;
`;

const App = () => {
  return (
    <div>
{/*props를 통해 borderColor라는 값을 전달 */}
      <StBox borderColor="red">빨간 박스</StBox>
      <StBox borderColor="green">초록 박스</StBox>
      <StBox borderColor="blue">파랑 박스</StBox>
    </div>
  );
};

export default App;

 

👌 Switch문과 map을 사용해서 리팩토링 해보기

 

* App.js

import React from "react";
import styled from "styled-components";

const StContainer = styled.div`
  display: flex;
`;

const StBox = styled.div`
  width: 100px;
  height: 100px;
  border: 1px solid ${(props) => props.borderColor};
  margin: 20px;
`;

const App = () => {
// 박스의 색을 배열에 담는다
  const boxList = ["red", "green", "blue"];

  // 색을 넣으면, 이름을 반환해주는 함수를 만듭니다.
  const getBoxName = (color) => {
    switch (color) {
      case "red":
        return "빨간 박스";
      case "green":
        return "초록 박스";
      case "blue":
        return "파란 박스";
      default:
        return "검정 박스";
    }
  };

  return (
    <StContainer>
{/* map을 이용해서 StBox를 반복하여 화면에 그립니다. */}
      {boxList.map((box) => (
        <StBox borderColor={box}>{getBoxName(box)}</StBox>
      ))}
    </StContainer>
  );
};

export default App;

 

✅ React Hooks(useState)

 

1️⃣ useState

 

가장 기본적인 hook이며, 함수 컴포넌트에서 가변적인 상태를 가지게 해준다.

 

const [state, setState] = useState(initialState);

 

useState라는 함수가 배열을 반환하고, 이것을 구조 분해 문법으로 꺼내놓은 모습이다.

state라는 변수로 사용했고 setState를 이용해서 state의 값을 수정할 수 있었다. 

그리고 만약 state가 원시 데이터 타입이 아닌 객체 데이터 타입인 경우에는 불변성을 유지해주어야 한다.

 

2️⃣ 함수형 업데이트

 

setState를 사용하는 함수형 업데이트 방식도 있다.

 

// 기존에 사용하던 방식
setState(number + 1);

// 함수형 업데이트 
setState(() => {});

 

위 코드와 같이 setState ( ) 안에 수정할 값이 아니라, 함수를 넣을 수 있다. 그리고 그 함수의 인자에서는 현재의 state를 가지고 올 수 있고 { } 안에서는 이 값을 변경하는 코드를 작성할 수 있다.

 

// 현재 number의 값을 가져와서 그 값에 +1을 더하여 반환한 것

setState((currentNumber)=>{ return currentNumber + 1 });

 

👌 두 방식의 차이점

 

일반 업데이트 방식으로 onClick 안에서 setNumber(number + 1) 를 3번 호출 했을 때, number가 1씩 증가한다.

 

* App.js

import { useState } from "react";

const App = () => {
  const [number, setNumber] = useState(0);
  return (
    <div>
{/* 버튼을 누르면 1씩 플러스된다. */}
      <div>{number}</div> 
      <button
        onClick={() => {
          setNumber(number + 1); // 첫번째 줄 
          setNumber(number + 1); // 두번쨰 줄
          setNumber(number + 1); // 세번째 줄
        }}
      >
        버튼
      </button>
    </div>
  );
}

export default App;

 

함수형 업데이트 방식으로 동일하게 작동시켰을 때 number가 3씩 증가한다.

 

import { useState } from "react";

const App = () => {
  const [number, setNumber] = useState(0);
  return (
    <div>
{/* 버튼을 누르면 3씩 플러스 된다. */}
      <div>{number}</div>
      <button
        onClick={() => {
          setNumber((previousState) => previousState + 1);
          setNumber((previousState) => previousState + 1);
          setNumber((previousState) => previousState + 1);
        }}
      >
        버튼
      </button>
    </div>
  );
}

export default App;

 

🔹 일반 업데이트 방식

 

버튼을 클릭했을 때 첫번째 줄 ~ 세번째 줄에 있는 setNumber가 각각 실행되는 것이 아니라 배치로 처리한다.즉, onClick을 했을 때 setNumber라는 명령을 세번 내리지만, 리액트는 그 명령을 하나로 모아 최종적으로 한번만 실행을 시킨다. 그래서 setNumber을 3번 명령하던, 100번 명령하던 1번만 실행된다.

 

 

🔹 함수형 업데이트 방식

 

3번을 동시에 명령을 내리면, 그 명령을 모아 순차적으로 각각 1번만 실행시킨다. 0에 1을 더하고 그 다음 1에 1을 더하고 2에 1을 더해서 3이라는 결과가 눈에 보이는 것

 

 

✅ React Hook(useEffect)

 

1️⃣ useEffect

 

리액트 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook

어떤 컴포넌트가 화면에 보여졌을 때 내가 무언가를 실행하고 싶다면? 또는 어떤 컴포넌트가 화면에서 사라졌을 때 무언가를 실행하고 싶다면? useEffect를 사용한다.

 

useState와 마찬가지로 React에서 제공하는 훅(기능)이므로,  import React, {useEffect} from "react"로 import해서 사용한다.

 

 

👌 useEffect 기초

 

App컴포넌트를 눈으로 보는 순간, 즉 App 컴포넌트가 화면에 렌더링될 때 useEffect안에 있는 console.log가 실행된다. '컴포넌트가 렌더링 될 때 실행된다' 이게 useEffect 핵심 기능

 

* App.js

import React, { useEffect } from "react";

const App = () => {

  useEffect(() => {
// 이 부분이 실행된다.
    console.log("hello useEffect");
  });

  return <div>Home</div>;
}

export default App;

 

👌 useEffect와 리렌더링(re-rendering)

 

import React, { useEffect, useState } from "react";

const App = () => {
  const [value, setValue] = useState("");

  useEffect(() => {
    console.log("hello useEffect");
  });

  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(event) => {
          setValue(event.target.value);
        }}
      />
    </div>
  );
}

export default App;

 

해당 코드를 보면 input이 있고 그 안에 value라는 state를 생성하여 연결시켰다. 이렇게 구현하고 브라우저에서 input에 어떤 값을 입력하면 useEffect가 계속 실행되는 것을 볼 수 있다.

 

 

👌왜 useEffect안에 있는 console이 계속 실행되는 것일까?

 

1. input에 값을 입력한다.

2. value, 즉 state가 변경된다.

3. state가 변경되었기 때문에 App컴포넌트가 리렌더링된다.

4. 리렌더링되었기 때문에, useEffect가 다시 실행된다.

5. 1~5번 과정이 계속 순환한다.

 

 

2️⃣ 의존성 배열

 

useEffect에는 의존성 배열이라는 것이 있다.

이 배열에 값을 넣으면 그 값이 바뀔 때만 useEffect를 실행해라 라는 것

 

// useEffect의 두번째 인자가 의존성 배열이 들어가는 곳

useEffect(()=>{
// 실행하고 싶은 함수
}[의존성배열])

 

👌 의존성 배열이 빈 배열인 경우

 

* App.js

import React, { useEffect, useState } from "react";

const App = () => {
  const [value, setValue] = useState("");

  useEffect(() => {
    console.log("hello useEffect");
  }, []); // 비어있는 의존성 배열

  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(event) => {
          setValue(event.target.value);
        }}
      />
    </div>
  );
}

export default App;

위 코드에서 의존성 배열만 추가되었다.

 

의존성 배열은 이 배열에 값을 넣으면 그 값이 바뀔 때만 useEffect를 실행한다는 의미이므로, 어떠한 값도 넣지 않으면 useEffect는 딱 한번만 실행되고 그 이후로 어떤일이 일어나도 실행이 되어서는 안된다.

따라서 어떤 함수를 컴포넌트가 렌더링 될 때 단 한번만 실행하고 싶으면 의존성 배열을 [ ] 빈 상태로 넣으면 된다.

 

 

👌 의존성 배열에 값이 있는 경우

 

* App.js

import React, { useEffect, useState } from "react";

const App = () => {
  const [value, setValue] = useState("");
  useEffect(() => {
    console.log("hello useEffect");
  }, [value]); // value를 넣음

  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(event) => {
          setValue(event.target.value);
        }}
      />
    </div>
  );
}

export default App;

 

의존성 배열에 value를 넣었다. value는 state이고 우리가 input을 입력할 때마다 그 값이 변하게 되니 useEffect도 계속 실행될 것이다.

 

 

3️⃣ clean up

 

🔹 컴포넌트가 나타났을 때 (= 렌더링 됐을 때, 함수 컴포넌트가 실행됐을 때) : useEffect의 effect함수가 실행했다.

🔹 컴포넌트가 사라졌을 때 : 무언가를 어떻게 실행하는지, 이 과정을 클린 업(clean up)이라고 표현한다.

 

 

👌 클린 업 하는 방법

 

* App.js

import React, { useEffect } from "react";

const App = () => {

useEffect(()=>{
// 화면에 컴포넌트가 나타났을(mount) 때 실행하고자 하는 함수를 넣기

return ()=>{
// 화면에서 컴포넌트가 사라졌을(unmount) 때 실행하고자 하는 함수를 넣기
}
}, [])

return <div>hello react!</div>
};

export default App;

 

예시)

// src/SokSae.js

import React, { useEffect } from "react";
import { useNavigate } from "react-router-dom";

const 속세 = () => {
  const nav = useNavigate();

  useEffect(() => {
    return () => {
      console.log(
        "안녕히 계세요 여러분! 전 이 세상의 모든 굴레와 속박을 벗어 던지고 제 행복을 찾아 떠납니다! 여러분도 행복하세요~~!"
      );
    };
  }, []);

  return (
    <button
      onClick={() => {
        nav("/todos");
      }}
    >
      속세를 벗어나는 버튼
    </button>
  );
};

export default 속세;

 

버튼을 누르면 useNavigate 에 의해서 /todos로 이동하면서 속세 컴포넌트를 떠날 것이다. 그러면 화면에서 컴포넌트가 사라질 것이고 useEffect의 return 부분이 실행될 것이다.

 

 

✅ Redux part 1

 

1️⃣ 리덕스가 필요한 이유

 

👌 useState의 불편함

 

어떤 컴포넌트에서 생성한 state를 다른 컴포넌트로 보고자 할 때 Props를 통해서 부모 컴포넌트에서 자식 컴포넌트로 그 값을 보내주었다. 그런데, Props로 State를 공유하는 방법에는 불편한 점이 있다.

 

 

 

- 컴포넌트에서 컴포넌트로 state를 보내기 위해서는 반드시 부,모 관계가 되어야 한다.

 

- 조부모 컴포넌트에서 손자 컴포넌트로 값을 보내고자 할 때도, 반드시 부모 컴포넌트를 거쳐야 한다. 즉, 정작 부모컴포넌트에서는 그 값이 필요가 없어도 단순히 손자 컴포넌트에게 전달하기 위해 불필요하게 거쳐야만 하는 것을 의미한다(조부모 -> 부모 -> 손자)

 

- 자식 컴포넌트에서 부모 컴포넌트로 값을 보낼 수 없다.

 

리덕스를 사용하면 State를 공유하고자 할 때, 부모 관계가 아니어도 되고 중간에 의미없이 컴포넌트를 거치지 않아도 된다. 그리고 자식 컴포넌트에서 만든 State를 부모 컴포넌트에서도 사용할 수 있게 된다.

 

 

👌 Local state와 Global state

 

🔹 Local state(지역 상태)란?

 

컴포넌트에서 useState를 이용해서 생성한 state이다. 좁은 범위 안에서 생성된 state

 

 

🔹 Global state(전역 상태)란?

 

Global state는 컴포넌트에서 생산되지 않는다. 중앙화 된 특별한 곳에서 State들이 생성된다. "중앙 state 관리소"라고 생각하면 된다.

 

중앙 State 관리소에서 State를 생성하고 만약 어떤 컴포넌트에서 State가 필요하다면 컴포넌트가 어디에 위치하고 있든 상관없이 State를 불러와서 사용할 수 있게 된다. 이러한 값들을 관리하는 것을 [전역 상태 관리]라고 한다.

 

 

2️⃣ 리덕스란 무엇인가?

 

"중앙 state 관리소"를 사용할 수 있게 도와주는 패키지(라이브러리)이다.

리덕스를 전역 상태관리 라이브러리라고 많이 표현한다. 전역 상태, 즉 Global State를 의미하고 그것을 관리하게 도와주는 라이브러리(패키지)이다.

 

 

✅ Redux part 2

 

1️⃣ 리덕스 설정

 

👌 리덕스 패키지 설치

yarn add redux react-redux

 

2️⃣ 폴더 구조 생성

 

1. src 폴더 안에 redux 폴더 생성

2. redux 폴더 안에 config, modules 폴더 생성

3. config 폴더 안에 configStore.js 파일 생성

 

🔹 redux : 리덕스와 관련된 코드를 모두 모아 놓을 폴더

🔹 config : 리덕스 설정과 관련된 파일들을 놓을 폴더

🔹 configStore : "중앙 state 관리소" 인 Store를 만드는 설정 코드들이 있는 파일

🔹 modules : 만들 State들의 그룹

 

 

2️⃣ 설정 코드 작성

 

1. 우리가 작성하는 설정코드는 이해할 필요가 없는 코드들이다. 코드 분석 금지 X, 설정 코드를 작성하는 이유는 리덕스를 만든 리덕스팀에서 이렇게 설정을 하라고 안내하고 있기 때문이다.

 

2, 리덕스 사용 방법을 중심으로 공부할 것

 

 

👌 src/congifStore.js

 

아래 코드 입력

 

import { createStore } from "redux";
import { combineReducers } from "redux";

const rootReducer = combineReducers({});
const store = createStore(rootReducer);

export default store;

 

👌 index.js

 

// 원래부터 있던 코드
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import reportWebVitals from "./reportWebVitals";

// 우리가 추가할 코드
import store from "./redux/config/configStore";
import { Provider } from "react-redux";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(

//App을 Provider로 감싸주고, configStore에서 export default 한 store를 넣어준다.
  <Provider store={store}>
    <App />
  </Provider>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

 

✅ Redux part 3

 

1️⃣ 모듈 만들기

 

useState로 만들었던 카운터 프로그램을 리덕스로 다시 만들어보기

 

모듈이란, State의 그룹이다. 우리의 첫 모듈은 카운터 프로그램에 필요한 state들이 모여있는 모듈이 될 것이다.

 

1. modules 폴더에 counter.js 파일 생성

// src/modules/counter.js

// 초기 상태값
const initialState = {
  number: 0,
};

// 리듀서
const counter = (state = initialState, action) => {
  switch (action.type) {
    default:
      return state;
  }
};

// 모듈파일에서는 리듀서를 export default 한다.
export default counter;

 

👌 initialState === 초기 상태값

 

// 초기 상태값
const initialState = {
  number: 0,
};

 

위 코드에서 만든 state의 초기값은 { }(객체)이고 그 안에 number라는 변수에 초기값 0을 할당해준것, 초기값은 꼭 객체가 아니어도 된다. 배열이 되어도 되고 원시데이터가 되도 된다. 객체에도 여러개의 변수를 넣어줄 수 있다.

 

// 초기값이 0
const initialState = 0;

// 초기값이 0이 있는 배열 
const initialState = [0];

// 초기값이 number = 0, name = '석구'인 객체
const initialState = {
number: 0,
name: '석구'
};

useState를 사용했을 때 괄호 안에 초기값을 지정해주던 것과 같은 이치

 

const [number, setNumber] = useState(0) // < 여기

 

👌 Reducer === 변화를 일으키는 함수

 

리듀서는 함수이다.

 

// 리듀서 
const counter = (state = initialState, action) => {    // 리듀서 인자 첫 번째 자리에선 state를, 두번째 자리에선 action이라는 것을 꺼내서 사용할 수 있다.
  switch (action.type) {
    default:
      return state;
  }
};

 

useState()를 사용할 때, number라는 값을 바꾸고 싶으면 setNumber을 사용했다.

// 예시 코드

const onClickHandler = () => {
   setNumber(number + 1); // setState를 이용해서 state 변경
}

 

리덕스에서는 리듀서가 이 역할을 한다.

리듀서에 number +1 을 해줘 라고 명령하면 ,  리듀서는 number에 +1을 더해주기 때문에, 변화를 일으키는 함수인 것

 

👌 카운터 모듈 스토어에 연결하기

 

아직까진 모듈과 스토어가 각각 따로 분리되어 있는 상태이기 때문에 만든 state를 스토어에서 꺼낼 수 없다.

 

configStore.js로 이동해서 코드 추가

* configStore : 중앙 state 관리소

 

// 원래 있던 코드
import { createStore } from "redux";
import { combineReducers } from "redux";

// 새롭게 추가한 부분
import counter from "../modules/counter";

const rootReducer = combineReducers({
  counter: counter, // <-- 새롭게 추가한 부분
});
const store = createStore(rootReducer);

export default store;

 

3️⃣ 스토어와 모듈 연결 확인하기

 

👌 useSelector = 스토어 조회

 

생성한 모듈을 스토어에 잘 연결했는지 확인하는 방법은, 컴포넌트에서 스토어를 직접 조회하면 된다.

컴포넌트에서 리덕스 스토어를 조회하고자 할때는 useSelector라는 'react-redux'훅을 사용해야 한다.

 

🔹 useSelector의 사용법

// 1. store에서 꺼낸 값을 할당 할 변수를 선언한다.
const number = 

// 2. useSelector()를 변수에 할당한다.
const number = useSelector() 

// 3. useSelector의 인자에 화살표 함수를 넣어준다.
const number = useSelector( ()=>{} )

// 4. 화살표 함수의 인자에서 값을 꺼내 return 한다. 
// state가 어떤 것인지 콘솔로 확인해보기
const number = useSelector((state) => {
console.log(state)
return state
)

 

🔹 useSelector : 컴포넌트에서 스토어를 조회할 때 react-redux에서 제공하는 useSelector라는 훅을 사용한다. 중요!

 

 

👌 컴포넌트에서 number 라는 값을 사용하고자 한다면 아래 코드처럼 꺼내서 사용하기

 

const number = useSelector(state => state.counter.number); // 0

 

 

✅ Redux part 4

 

1️⃣ 리덕스 스토어 state 수정하는 방법

 

👌 counter.js 모듈의 state 수정 기능 만들기(+1 기능 구현)

 

🔹 어떻게 counter.js 모듈에 있는 state의 값을 변경할 수 있을까?

 

useState()를 사용해서 number에 +1을 할 때는 setNumber을 이용해서 +1을 해주었다.

 

// 예시 코드

// local state
const [number, setNumber] = useState(0)


// click handler
const onClickHandler = () => {
setNumber(number + 1)
}

 

리덕스에서 값의 수정은 리듀서에서 일어난다.

만약 counter.js 모듈에 있는 number에 +1을 하고 싶을 땐?

 

 

▶️ 리듀서에게 보낼 number를 +1을 하라는 "명령"을 만든다.

 

리듀서에게 number에 +1을 하라고 명령을 보내야 한다. 명령을 보내기 전에 "명령"을 우선 만들어야 하는데, 리덕스에서는 그 명령을 Action이라고 한다. 즉, 리듀서에게 내가 어떤 Action을 하길 원한다라고 표현하는 것

행동을 코드로 나타내면 객체로 만든다. 그래서 이것을 액션 개체라고 한다.

// 예시 코드
//number에 +1 을 하는 액션 객체

{ type : "PLUS_ONE" };   // 액션객체 type의 value는 대문자로 작성한다.

액션 객체는 반드시 type 이라는 key를 가져야 한다. 우리가 이 액션 객체를 리듀서에게 보냈을 때 리듀서는 객체 안에서 type이라는 key를 보기 때문이다.

 

 

▶️ "명령"(액션 개체) 보내기

 

명령을 만들었으니 이제 리듀서에 명령을 보내야 한다. 즉, 액션 개체를 보내야 한다.

 

 

🔹 useDispatch : 액션 객체를 리듀서로 보내기 위해서는 새로운 훅인 useDispatch 을 사용해야 한다. reach-redux에서 import해서 사용할 수 있으며, 우리가 만든 액션 객체를 리듀서로 보내주는 역할을 하는 훅이다.

 

useDispatch라는 훅은 사용하기 위해서는 컴포넌트 안에서 먼저 코드를 작성해서 dispatch라는 변수를 생성해줘야 한다. 이렇게 생성한 dispatch는 함수이다. 그래서, dispatch를 사용할 때 ( ) 를 붙여서 함수를 실행해야 한다.

 

* App.js

import React from "react";
import { useDispatch } from "react-redux"; // import 하기

const App = () => {
  const dispatch = useDispatch(); // dispatch 생성
  return (
    <div>
      <button>+ 1</button> {/* 버튼을 하나 추가하기. */}
    </div>
  );
};

export default App;

 

그리고 dispatch를 사용할 때 ( ) 안에 액션개체를 넣어주면 된다. 만약 어떤 버튼을 클릭했을 때 리듀서로 액션객체를 보내고 싶다면

 

import React from "react";
import { useDispatch } from "react-redux"; // import 하기

const App = () => {
  const dispatch = useDispatch(); // dispatch 생성
  return (
    <div>
      <button
        onClick={() => {
// 마우스를 클릭했을 때 dispatch가 실행되고, ()안에 있는 액션객체가 리듀서로 전달된다.
          dispatch({ type: "PLUS_ONE" }); 
        }}
      >
+ 1
      </button>
    </div>
  );
};

export default App;

 

▶️ 액션 객체 받기

 

액션 객체를 리듀서로 보냈으므로, 리듀서에서 액션 객체가 잘 왔는지 확인해야한다.

 

// src/redux/modules/counter.js

// 초기 상태값
const initialState = {
  number: 0,
};

// 리듀서
const counter = (state = initialState, action) => {
console.log(action); // 여기에 console.log(action) 추가
  switch (action.type) {
    default:
      return state;
  }
};

// 모듈파일에서는 리듀서를 export default 한다.
export default counter;

버튼을 클릭하면 counter.js에서 콘솔로 찍은 action이 보이는 것을 확인할 수 있다. 

 

 

▶️ 액션 객체 명령대로 리듀서가 state값을 변경하는 코드 구현하기

 

state에 있는 number를 실제로 변경하는 로직 코드 구현하기.

로직코드는 리듀서 안에 있는 스위치문으로 작성한다.

 

🔹 리듀서가 액션객체를 받아 상태를 바꾸는 원리

1. 컴포넌트로부터 dispatch를 통해 액션객체를 전달 받는다.

2. action 안에 있는 type을 스위치문을 통해 하나씩 검사해서, 일치하는 case를 찾는다.

3. type과 case가 일치하는 경우에, 해당 코드가 실행되고 새로운 state를 반환(return)한다.

4. 리듀서가 새로운 state를 반환하면, 그게 새로운 모듈의 state가 된다.

 

// src/modules/counter.js

// 초기 상태값
const initialState = {
  number: 0,
};

// 리듀서
const counter = (state = initialState, action) => {
  console.log(action);
  switch (action.type) {

// PLUS_ONE이라는 case를 추가한다.
// 여기서 말하는 case란, action.type을 의미한다.
// dispatch로부터 전달받은 action의 type이 "PLUS_ONE" 일 때
// 아래 return 절이 실행된다. 

    case "PLUS_ONE":
      return {
// 기존 state에 있던 number에 +1을 더한다.
        number: state.number + 1,
      };

    default:
      return state;
  }
};

// 모듈파일에서는 리듀서를 export default 한다.
export default counter;

 

▶️ useSelector로 변경된 state값 확인하기

* useSelector : 스토어 조회

 

* App.js

import React from "react";
import { useDispatch, useSelector } from "react-redux";

const App = () => {
  const dispatch = useDispatch();

// 👇 코드 추가
  const number = useSelector((state) => state.counter.number); 

  console.log(number); // 콘솔 추가
  return (
    <div>
{/* 👇 코드 추가 */}
      {number}
      <button
        onClick={() => {
          dispatch({ type: "PLUS_ONE" });
        }}
      >
        + 1
      </button>
    </div>
  );
};

export default App;

 

✅ Redux part 6

 

1️⃣ Payload란?

 

위에서 +1 기능과 -1 기능을 만들엇는데, 만약 1로 정해진 기능을 만드는 것이 아니라 카운터 프로그램을 사용하는 사용자가 직접 정할 수 있게 기능을 만드려면 어떻게 해야할까?

 

다시말해, 사용자가 5를 더하고 싶으면 어떤 input에 5를 입력해서 버튼을 누르면 5가 더해지고 10을 더하고 싶으면 10을 입력하고 버튼을 누를 때 10이 더해지는 프로그램이 필요하다.

 

1을 더하는 것이 아닌 "N을 더해"라고 N을 같이 리듀서에게 보내야 한다. 이 목적어도 액션 객체에 담아 같이 보내줘야 하고 이렇게 액션객체에 같이 담아 보내주는 것을 payload라고 한다.

 

// payload가 추가된 액션객체

{type: "ADD_NUMBER", payload: 10} // type뿐만 아니라 payload라는 key와 value를 같이 담는다.

 

 

2️⃣ payload를 이용해서 기능 구현하기

 

👌 작업 순서

 

1. 사용자가 입력한 값을 받을 input 구현하기

2. Action Creator 작성하기

3. 리듀서 작성하기

4. 구현된 기능 테스트하기

 

▶️ 사용자가 입력한 값을 받을 input 구하기

 

대략적인 html을 마크업 해주기

 

* App.js

import React from "react";

const App = () => {
  return (
    <div>
      <input type="number" />
      <button>더하기</button>
      <button>빼기</button>
  </div>
  );
};

export default App;

 

input의 값을 state로 관리하기 위해 훅을 사용하여 state 사용하고, 이벤트핸들러(onChangeHandler)를 작성하여 input과 연결하여 준다.

 

🔹 input의 onChange 핸들러를 input 안에서 인라인으로 작성하지 않고 위쪽에서 함수를 별도로 분리하여 작성하고 그것을 넣어주는 방식

 

* App.js

import React from "react";
import { useState } from "react";

const App = () => {
  const [number, setNumber] = useState(0);

  const onChangeHandler = (event) => {
    const { value } = event.target;
// event.target.value는 문자열 
// 이것을 숫자형으로 형변환해주기 위해서 +를 붙여 주었다.
    setNumber(+value);
  };

// 콘솔로 onChangeHandler가 잘 연결되었는지 확인해보기
// input에 값을 넣을 때마다 콘솔에 그 값이 찍히면 연결 성공!
  console.log(number);

  return (
    <div>
      <input type="number" onChange={onChangeHandler} />
      <button>더하기</button>
      <button>빼기</button>
    </div>
  );
};

export default App;

 

▶️ counter.js 모듈 작성 : Action Creator

 

작성해야 할 것을 미리 리스트업해두는 것이 좋다.

 

// src/redux/modules/counter.js

// Action Value

// Action Creator

// Initial State

// Reducer

// export default reducer

 

payload가 필요한 Action Creator에서는 함수를 선언할 때 매개변수 자리에 payload를 넣어줘야 한다. 왜냐하면 Action Creator를 사용하는 컴포넌트에서 리듀서로 보내고자 하는 payload를 인자로 넣어줘야 하기 때문이다.

 

🔹 인자로 payload를 넣어줌으로써 Action Creator가 액션객체를 생성할때 payload를 같이 담아 생성하는 원리

 

// src/redux/modules/counter.js

// Action Value
const ADD_NUMBER = "ADD_NUMBER";

// Action Creator
export const addNumber = (payload) => {
  return {
    type: ADD_NUMBER,
    payload: payload,
  };
};

// Initial State

// Reducer

// export default reducer

 

🔹 ES6에서는 객체의 key와 value 가 같으면 줄여서 작성할 수 있다

export const addNumber = (payload) => {
  return {
    type: ADD_NUMBER,
    payload,
  };
};

 

 

▶️ counter.js 모듈 작성 : initial State, Reducer, 내보내기(export default)

 

initial State와 리듀서의 기본 형태 만들어주기

// src/redux/modules/counter.js

// .. 중략

// Initial State
const initialState = {
  number: 0,
};

// Reducer 기본형태
const counter = (state = initialState, action) => {
  switch (action.type) {
    default:
      return state;
  }
};

// export default reducer
export default counter;

 

리듀서 기본형태에 ADD_NUMBER로직 구현

 

// 리듀서

const counter = (state = initialState, action) => {
  switch (action.type) {
    case ADD_NUMBER:
      return {
// state.number (기존의 nubmer)에 action.paylaod(유저가 더하길 원하는 값)을 더한다.
        number: state.number + action.payload,
      };
    default:
      return state;
  }
};

 

▶️ 구현된 기능테스트하기

 

🔹 App.js에서 useSelector를 이용해서 Store의 값을 조회하고 그것을 화면상에 렌더링 하는 기능 추가

// src/App.js

import React from "react";
import { useState } from "react";
import { useSelector } from "react-redux";

const App = () => {
  const [number, setNumber] = useState(0);
// 1. 아래 코드 추가 👇
  const globalNumber = useSelector((state) => state.counter.number);

  const onChangeHandler = (evnet) => {
    const { value } = evnet.target;
    setNumber(+value);
  };

  return (
    <div>
{/* 2. 아래 코드 추가  */}
      <div>{globalNumber}</div>
      <input type="number" onChange={onChangeHandler} />
      <button>더하기</button>
      <button>빼기</button>
    </div>
  );
};

export default App;

 

🔹 Action Creator를 import하고 payload를 담아 dispatch하기

 

import React from "react";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";

// 4. Action Creator를 import 합니다.
import { addNumber } from "./redux/modules/counter";

const App = () => {
// 1. dispatch를 사용하기 위해 선언해줍니다.
  const dispatch = useDispatch();
  const [number, setNumber] = useState(0);
  const globalNumber = useSelector((state) => state.counter.number);

  const onChangeHandler = (evnet) => {
    const { value } = evnet.target;
    setNumber(+value);
  };

// 2. 더하기 버튼을 눌렀을 때 실행할 이벤트핸들러를 만들어줍니다.
  const onClickAddNumberHandler = () => {
// 5. Action creator를 dispatch 해주고, 그때 Action creator의 인자에 number를 넣어줍니다.
    dispatch(addNumber(number));
  };

  return (
    <div>
      <div>{globalNumber}</div>
      <input type="number" onChange={onChangeHandler} />
{/* 3. 더하기 버튼 이벤트핸들러를 연결해줍니다. */}
      <button onClick={onClickAddNumberHandler}>더하기</button>
      <button>빼기</button>
    </div>
  );
};

export default App;

 

 

3️⃣ Ducks 패턴

 

리덕스의 구성 요소를 모두 만들어야만 사용이 가능하다보니 개발자마다 제각각 구현하면 문제가 발생하기 때문에, 이것을 패턴화하여 작성하는 것을 제안한 것이 Ducks 패턴이다.

 

🔹 Erik Rasmussn의 Ducks 제안 : https://github.com/erikras/ducks-modular-redux

 

1. Reducer 함수를 export default 한다.

2. Action creator 함수들을 report 한다.

3. Action type은 app/reducer/ACTION_TYPE 형태로 작성한다.

(외부 라이브러리로서 사용될 경우 또는 외부 라이브러리가 필요로 할 경우에는 UPPER_SNAKE_CASE로만 작성해도 괜찮다)

 

 

✅ react-router-dom part 1

 

1️⃣ react-router-dom이란?

 

👌 페이지를 구현할 수 있게 해주는 패키지

 

🔹 패키지 설치 방법

yarn add react-router-dom

 

2️⃣ react-router-dom 사용

 

👌  사용방법 순서

 

1. 페이지 컴포넌트 생성

2. Router.js 생성 및 router 설정 코드 작성

3. App.js에 import 및 적용

4. 페이지 이동 테스트

 

 

▶️ 페이지 컴포넌트 생성

 

src 폴더에 pages라는 폴더를 만들고 그 안에 만들었다. 

 

Home, About, Contact, Works 컴포넌트 생성

 

 

▶️ [중요] Router.js 생성 및 router 설정 코드 작성

 

 

브라우저에 우리가 URL을 입력하고 이동했을 때 우리가 원하는 페이지 컴포넌트로 이동하게끔 만드는 부분.

URL 1개당 페이지 컴포넌트를 매칭해주는 것, 이 한개의 URL을 Router라고 한다.

 

* src안에 shared라는 폴더를 생성해주고 그 안에 Router.js 파일 생성

import React from "react";
// 1. react-router-dom을 사용하기 위해서 아래 API들을 import 
import { BrowserRouter, Route, Routes } from "react-router-dom";

// 2. Router 라는 함수를 만들고 아래와 같이 작성합니다.
const Router = () => {
  return (
    <BrowserRouter>
      <Routes>
      </Routes>
    </BrowserRouter>
  );
};

export default Router;

 

위처럼 하면 Route를 설정할 뼈대가 완성된 것이고 만든 4개의 페이지 컴포먼트마다 Route를 설정해준다.

 

import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "../pages/Home";
import About from "../pages/About";
import Contact from "../pages/Contact";
import Works from "../pages/Works";

const Router = () => {
  return (
    <BrowserRouter>
      <Routes>
{/* 
Routes안에 이렇게 작성한다.
Route에는 react-router-dom에서 지원하는 props들이 있다.

paht는 우리가 흔히 말하는 사용하고싶은 "주소"를 넣어주면 되고
element는 해당 주소로 이동했을 때 보여주고자 하는 컴포넌트를 넣어준다.
 */}
        <Route path="/" element={<Home />} />
        <Route path="about" element={<About />} />
        <Route path="contact" element={<Contact />} />
        <Route path="works" element={<Works />} />
      </Routes>
    </BrowserRouter>
  );
};

export default Router;

 

 

▶️ App.js에 Router.js import 해주기

 

 

import React from "react";
import Router from "./shared/Router";    // 아래에 <Router />를 기입하면 import {Router} from "react-router-dom"이 나올 수 있어, 꼭 파일을 끌어올 수 있도록 변경해주어야 한다.

function App() {
  return <Router />;
}

export default App;

 

Router.js를 App 컴포넌트에 넣어주는 이유는 우리가 만든 프로젝트에서 가장 최상위에 존재하는 컴포넌트이기 때문이다.

 

우리가 어떤 컴포넌트를 화면에 띄우던, 항상 App.js를 거쳐야만 하고 그래서 path 별로 분기가 되는 Router.js를 App.js에 위치시키고 우리의 서비스를 이용하는 모든 사용자가 항상 App.js -> Router.js 를 거치도록 코드를 구현해주는 것

 

 

3️⃣ react-router-dom Hooks

 

리액트에서 useState와 같은 Hook을 제공하는 것처럼, react-router-dom에서도 hook을 제공한다.

 

🔹 공식문서 : https://reactrouter.com/docs/en/v6

 

 

👌 useNavigate

 

다른 페이지로 보내고자 할 때 사용할 수 있다. 위에서 테스트했을 때 브라우저에 직접 path를 입력해서 페이지를 이동했었는데 사실 사용자들이 브라우저에 path를 입력해서 페이지를 이동하게 하는 서비스는 거의 없을 것이다. 보통 어떤 버튼을 누르면 페이지로 이동하거나 또는 어떤 컴포넌트를 눌렀을 때 페이지를 이동하게 만든다.

 

그럴 때 사용하는 훅으로, 버튼의 클릭 이벤트 핸들러에 코드를 작성하면 버튼을 클릭했을 때 우리가 보내고자 하는 path로 페이지를 이동시킬 수 있다. 꼭 버튼이 아니더라도, 컴포넌트의 클릭 이벤트 핸들러를 통해서 활용할 수 있다.

 

 

* Home.jsx : 강의자료에는 App.js에 하라고 되어 있으나, App.js에는 router가 걸려 있어 home으로 하면 오류가 뜨지 않는다.

const Home = () => {
  const navigate = useNavigate();

  return (
    <button
      onClick={() => {
        navigate("/works");
      }}
    >
      works로 이동
    </button>
  );
}

export default Home;

 

👌 useLocation

 

react-router-dom을 사용하면, 우리가 현재 위치하고 있는 페이지의 여러가지 정보를 추가적으로 얻을 수 있다. 이 정보들을 이용해서 페이지 안에서 조건부 렌더링에 사용하는 등 여러가지 용도로 활용할 수 있다.

 

* About.jsx

import React from "react";
import { useLocation } from "react-router-dom";

const About = () => {
  const location = useLocation();
  console.log(location)     // 강의자료에는 이 console 찍는 내용이 없는데 console을 찍지 않으면 오류가 난다.
return <div></div>
}

export default About;

useLocation을 사용해서 페이지의 정보를 얻어낸다

 

 

👌 Link

 

Link는 훅은 아니지만 꼭 알아야 할 API이다.

 

Link는 html 태그 중에 a 태그의 기능을 대체하는 API이다. 만약 JSX에서 a태그를 사용해야 한다면, 반드시 Link를 사용해서 구현해야 한다. a태그를 사용하면 페이지를 이동하면서 브라우저가 새로고침(refresh)되기 때문이다. 브라우저가 새로고침 되면 모든 컴포넌트가 다시 렌더링되야 하고 우리가 리덕스나 useState를 통해 메모리 상에 구축해놓은 모든 상태값이 초기화된다. 이것은 곧 성능에 악영향을 줄 수 있고 불필요한 움직임이 된다.

 

* Contact.jsx : 강의자료에는 App.js에서 하라고 되어 있으나 오류가 난다.

import React from "react";
import { Link } from "react-router-dom";

const App = () => {
  return <Link to="/">Home</Link>;
};

export default App;

 

 

✅ react-router-dom part 2

 

1️⃣ 상세 페이지 구현하기

 

👌 Dynamic Route란?

 

Dynamic Route란 동적 라우팅이라고도 말하는데 path에 유동적인 값을 넣어서 특정 페이지로 이동하게끔 구현하는 방법을 말한다.

 

예를 들어, works 페이지에 여러개의 work가 보이고 우리가 work마다 독립적인 페이지를 가지도록 구현하려면 어떻게 해야 할까?

 

import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "../components/Home";
import Todos from "../components/Todos";

const Router = () => {
  return (
    <BrowserRouter>
      <Routes>

{/* 이렇게 하면 될까? */}
        <Route path="/" element={<Home />} />
        <Route path="/works/1" element={<Work />} />
        <Route path="/works/2" element={<Work />} />
        <Route path="/works/3" element={<Work />} />
      </Routes>
    </BrowserRouter>
  );
};

export default Router;

 

이렇게 하는 것보다 Dynamic Routes 기능을 이용해서 간결하게 처리할 수 있다.

 

 

👌 Dynamic Route 설정하기

 

Works 페이지에 여러개의 work가 있고 그것을 클릭했을 때 각각의 상세페이지로 이동하게끔 구현한다. 일단 work라는 페이지가 먼저 있도록 Work 컴포넌트 추가

 

 

* Work.js

 

위 Route한 컴포넌트들은 jsx로 만들었었다. Work는 js 파일로 만들어야 한다. jsx 파일로 만들면 works:id를 주어도 페이지 이동이 되지 않았다.

import React from "react";

const Work = () => {
  return <div>Work</div>;
};

export default Work;

 

Router.js 이동해서 코드 추가하기

 

import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "../pages/Home";
import About from "../pages/About";
import Contact from "../pages/Contact";
import Works from "../pages/Works";
import Work from "../pages/Work";


const Router = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="about" element={<About />} />
        <Route path="contact" element={<Contact />} />
        <Route path="works" element={<Works />} />
{/* 아래 코드를 추가 👇 */}
        <Route path="works/:id" element={<Work />} />     // Work.js에서 element 가져와서 works/:id로 보내기
      </Routes>
    </BrowserRouter>
  );
};

export default Router;

 

path에 works:id라고 들어간다. :id라는 것이 바로 동적인 값을 받겠다는 의미이다. 그래서 works/1로 이동해도 <Work/>로 이동하고 works/2, works/3....works/100 모두 <Work />로 이동하게 해준다.

 

그리고 :id는 useParam 훅에서 조회할 수 있는 값이 된다.

 

 

2️⃣ Dynamic Routes와 useParam

 

👌 query parameter 조회하기

 

Dynamic Routes를 사용하면 element에 설정된 같은 컴포넌트를 렌더링하게 된다.

 

<Route path="works/:id" element={<Work />} />

 

하지만 useParam을 이용하면 같은 컴포넌트를 렌더링하더라도 각각의 고유한 id 값을 조회할 수 있다.

 

useParam은 path에 있는 id값을 조회할 수 있게 해주는 훅이다. 만약 우리가 works/1로 이동하면 1이라는 값을 주고 works/100으로 이동하면 100이라는 값을 사용할 수 있게 해준다.

 

 

* Work.js

import React from "react";
// 아래 코드를 추가👇
import { useParams } from "react-router-dom";

const Work = () => {
// 아래 코드를 추가👇
  const param = useParams();
// 훅을 사용해서 생성한 param을 콘솔에 찍어보기
  console.log(param);
  return <div>Work</div>;
};

export default Work;

param 콘솔로 찍었을 때

 

{ } 형태이고 그 안에 id와 value 가 있는 것을 알 수 있다.

728x90