3. 상태관리, 라우팅
3-1. 상태관리
- 상태관리란? :
상태관리는 쉽게 말해 전역 데이터 관리예요.
렌더링과 연결된 데이터를 컴포넌트끼리 주고 받기는 생각보다 번거롭습니다.
이런 번거로움을 줄여주기 위해 전역 데이터를 만들고 관리해주는 게 리액트에서 말하는 상태관리예요.
- 형제 컴포넌트끼리 데이터를 주고 받으려면? :
1. 부모 컴포넌트에서 state를 생성하고 자식 컴포넌트들에게 props로 데이터와 state를 변경할 함수를 넘겨줌.
2. 자식 컴포넌트에서는 props로 받아온 값을 참조해서 쓰고, 값 변경이 필요한 경우는 넘겨받은 함수로 해당 값을 변경해줌.
3. [생각해보기] 만약 1촌 관계 컴포넌트가 아니라 6촌 관계 컴포넌트가 같은 데이터를 사용하려고 한다면?
→ 생각만 해도 끔찍... props로 어디까지 넘겨줘야 하는건지... 🤢
- 많이 사용하는 상태관리 툴
1. ContextAPI()
2. Redux
3. Recoil
4. zustand
Redux는 보일러 플레이트가 많이 나옴.
Recoil, zustand는 보일러 플레이트를 뺀 라이트한 Redux 같음.
클라이언트 상태관리를 정말 가볍게 하고 싶다!하면 Recoil, zustand를 사용하면 됨.
5. react-query (서버 상태관리에 조금 더 특화되어 있음.)
서버에 세 번 정도 데이터 요청을 함. 세 번 모두 실패시 에러를 띄워줌.
이런 기능들이 react-query 라이브러리에 내장이 되어 있음.
6. mobx
- 전역 저장소는 내게 필요하지 않을 수 있음.
흔히 말하는 상태 관리는 어디서든 접근할 수 있는 데이터 모음을 만들어두고,
어떤 컴포넌트 건 데이터를 꺼내서 보고 수정 요청을 보낼 수 있는, 전역 저장소 개념입니다.
내 프로젝트는 컴포넌트간 데이터가 오갈 일이 많지 않다면 정말x100 전역 저장소는 필요 없어요.
모든 프로젝트에 상태관리가 필요하지 않다는 걸 꼭 기억해주세요.
3-2. 상태관리 해보기 - ContextAPI()
리액트 16.3 버전부터 제공하기 시작한 리액트 자체 상태관리 API.
변경이 잦지는 않은 데이터를 전역으로 사용하고 싶으면 ContextAPI()를 사용해봐라! 하고
리액트가 제공해주는 것입니다!
간단하게 작은 데이터 조각들을 여기저기서 사용하고 싶을 때 사용하면 효율이 좋습니다:)
- 데이터를 저장할 공간부터 만들자!
React.createContext()
const MyStore = React.createContext();
- 데이터를 가져다 쓰려면?
1) Context.Provider
데이터를 주입할 차례네요!
Provider는 Context를 구독한 컴포넌트들에게 앗, 나 지금 데이터 변했어! 하고 알려줍니다.
여러 컨텍스트가 있다면 중첩해서 써도 괜찮아요.😊
Provider라는 걸 사용해서 위치 지정을 해줍니다.
어떤 데이터를 사용할 컴포넌트들을 Provider로 감싸주면 됩니다.
1-2) Provider 예시
<MyStore.Provider value={state}>
<Component1 />
<Component2 />
</MyStore.Provider>
2) Context.Consumer
주입한 데이터를 구독해봅시다! Consumer는 컴포넌트가 context를 구독하게 해줍니다.
Provider로 감싸주기 이전에 데이터를 사용할 컴포넌트들을 Consumer로 감싸줍니다.
2-1) Consumer 예시
function App() {
// Context의 Value는 App 컴포넌트에서 관리하자!
const [name, setName] = useState("민영");
return (
<MyStore.Provider value={{ name, setName }}>
<MyStore.Consumer>
{(value) => {return <div>{value.name}</div>}}
</MyStore.Consumer>
</MyStore.Provider>
);
}
Consumer는 위처럼 사용하지만 useContext()를 쓰면 굳이 쓰지 않아도 괜찮아요.😊
2-2) useContext() 예시
// MyStore.Provider를 찾는데 성공할 컴포넌트
const SomeComponentInProvider = () => {
const { name, setName } = useContext(MyStore);
return (
<div>
{name}
<button onClick={() => setData("perl")}>바꾸기</button>
</div>
);
}
- 데이터 수정하기
context를 만들 때, 값과 함수를 만들었죠?
값은 가져다 쓰기 위함이고 함수는 값을 고쳐쓰기 위함입니다.
만든 함수를 사용해 값을 고쳐봅시다.
import React, { useState, useContext } from "react";
// Context를 만들기
const MyStore = React.createContext();
const MyStoreConsumer = () => {
const { name, setName } = useContext(MyStore);
return (
<div>
{name}
<button onClick={() => setName("perl")}>바꾸기</button>
</div>
);
};
function App() {
// Context의 Value는 App 컴포넌트에서 관리
const [name, setName] = useState("민영");
return (
<MyStore.Provider value={{ name, setName }}>
<MyStoreConsumer />
</MyStore.Provider>
);
}
export default App;
- ContextAPI의 단점 :
Store가 여러 개가 되면 굉장히 관리하기 어려워짐.
Store 한 다섯 개를 MyStoreCounsumer가 구독해야 한다고 생각해보면, 각 Store 별로 Provider가 생성되어야 함.
굉장히 너저분해 보이고 관리하기도 어려움.
어떤 Store의 데이터가 변하면 계속 리렌더링이 되기 때문에 효율면에서도 좋지 않음.
그래서 규모가 너무 크지 않을 때 ContextAPI()를 사용하는 게 좋음.
3-3. 상태관리 해보기 - Redux 1
- 상태관리 흐름을 알아보자!
[상태관리 흐름도] 딱 4가지만 알면 됩니다!
Store, Action, Reducer, 그리고 Component! 아주 큰 흐름만 잘 파악해도 굳굳!
(1) 리덕스 Store를 Component에 연결.
(2) Component에서 상태 변화가 필요할 때 Action을 부름.
(3) Reducer를 통해서 새로운 상태 값을 만들고,
(4) 새 상태값을 Store에 저장.
(5) Component는 새로운 상태값을 받아옴. (props를 통해 받아오니까, 다시 랜더링 됨)
- redux 복습
🔥 리덕스는 아주 흔히 사용하는 상태관리 라이브러리입니다.
전역 상태관리를 편히 할 수 있게 해주는 고마운 친구죠!
공식문서 보러가기 →
(1) State :
👉 리덕스에서는 저장하고 있는 상태값("데이터"라고 생각하셔도 돼요!)를 state라고 부름.
딕셔너리 형태({[key]: value})형태로 보관함.
(2) Action :
👉 상태에 변화가 필요할 때(=가지고 있는 데이터를 변경할 때) 발생하는 것.
액션은 객체. 이런 식으로 쓰임. type은 이름같은 것! 우리가 정하는 임의의 문자열을 넣음.
{type: 'CHANGE_STATE', data: {...}}
(3) ActionCreator :
👉 액션 생성 함수라고도 부름. 액션을 만들기 위해 사용.
/ /이름 그대로 함수!
const changeState = (new_data) => {
// 액션을 리턴함! (액션 생성 함수니까.
return {
type: 'CHANGE_STATE',
data: new_data
}
}
(4) Reducer :
👉 리덕스에 저장된 상태(=데이터)를 변경하는 함수.
우리가 액션 생성 함수를 부르고 → 액션을 만들면 → 리듀서가 현재 상태(=데이터)와 액션 객체를 받아서 →
새로운 데이터를 만들고 → 리턴해줌.
// 기본 상태값을 임의로 정해줌.
const initialState = {
name: 'mean0'
}
function reducer(state = initialState, action) {
switch(action.type){
// action의 타입마다 케이스문을 걸어주면,
// 액션에 따라서 새로운 값을 돌려줌!
case CHANGE_STATE:
return {name: 'mean1'};
default:
return false;
}
}
(5) Store :
우리 프로젝트에 리덕스를 적용하기 위해 만드는 것!
스토어에는 리듀서, 현재 애플리케이션 상태, 리덕스에서 값을 가져오고 액션을 호출하기 위한
몇 가지 내장 함수가 포함되어 있음.
생김새는 딕셔너리 혹은 json처럼 생겼음.
내장함수를 어디서 보냐고? → 공식문서에서! 😉
(6) dispatch :
👉 디스패치는 우리가 앞으로 정말 많이 쓸 스토어의 내장 함수! 액션을 발생 시키는 역할을 함.
// 실제로는 이것보다 코드가 길지만,
// 간단히 표현하자면 이런 식으로 우리가 발생시키고자 하는 액션을 파라미터로 넘겨서 사용함.
dispatch(action);
- 나는 리덕스가 필요하지 않을 수 있음.
리덕스가 굉장히 무겁다는 이야기, 들어보셨나요?
”리덕스 사용이 과연 프로젝트를 나이스하게 만들어줄까?”에 관한 고민은 아주아주 오래 이어져왔습니다.
저는 리덕스를 아주 좋아하는 사람이라 가급적 리덕스를 사용하지만,
아래의 경우에 해당한다면 가차없이 다른 라이브러리를 택합니다.
1. 페이지 간 공유할 데이터가 없는 경우
2. 페이지 이동 시 리패칭이 잦게 일어날 경우
3. 비즈니스 로직이 획일화되기 어려울 경우
간단하게 Redux를 이용하여 고양이 이름 바꾸기를 해봄.
먼저 터미널을 이용하여 라이브러리를 설치해줌.
yarn add redux react-redux
Store와 index.js 세팅을 해줌.
굳이 방식을 이해할 필요는 없음.
난 src 폴더 내에 redux라는 폴더를 생성한 후 그 안에 config라는 폴더를 하나 더 생성하여
config 폴더 내에 configStore.js를 넣어줌.
- configStrore.js :
index.js의 경우에는 App을 Provider로 감싸줌.
Provider는 react-redux에 내장된 기능.
그리고 Provider의 props로 store를 넘겨줌. store는 configStore 내에 있음.
- index.js :
그리고 redux 폴더 내에 modules라는 폴더를 하나 더 생성해줌.
modules 폴더 내에 cat.js라는 reducer를 하나 만들어줌.
이 안에는 위에서 설명한 대로 action value, action creator, initialState, reducer가 들어가야 함.
- action value :
- action creator :
- initialState :
- reducer :
이렇게 reducer에 세팅을 해줬다면 이제 redux에 저장된 값을 가져와보겠음.
값을 가져올 때는 redux에 내장된 기능인 useSelector hook을 이용하면 됨.
useSelector를 사용하여 state를 받아오는데, 어느 reducer의 값을 받아올 것인지 지정해줘야 함.
난 cat이라는 reducer를 만들어줬으므로 state.cat에서 받아올 거임.
이렇게 해서 값을 받아오고 콘솔을 찍어보면
{ name : "펄이 고양이", age : 5 }가 들어와 있는 것을 볼 수 있음.
redux를 이용하여 값을 수정하기 위해서는 redux에 내장된 기능인 useDispatch hook을 사용해야 함.
useDispatch를 이용하여 어떤 action creator함수에 어떤 값을 보내 데이터를 수정할 건지 지정해줘야 함.
App.js에 button을 하나 만들어준 후, button을 클릭시 dispatch가 일어나도록 해줌.
이렇게 해주면 button을 클릭시 name "펄이 고양이"가 "perl"로 변경됨.
이렇게 해주면 기존의 redux 끝.
3-4. 상태관리 해보기 - Redux 2
redux는 상태관리하기에 정말 좋지만, 보일러 플레이트가 나온다는 단점이 있음.
이러한 단점을 보완하기 위하여 Redux Tool Kit이 탄생함.
Redux Tool Kit 리덕스툴킷은 리덕스를 편히 쓰라고 리덕스 팀에서 만든 패키지입니다.
리덕스를 사용하는데 유용한 패키지가 몽땅 들어있는 도구 모음이에요. 😊
툴 킷을 쓰게 되면 액션타입, 액션 생성함수, 리듀서, 기초값을 한 번에 묶어서 사용할 수 있어요.
→ 보일러 플레이트가 많이 줄어듭니다.
먼저, 터미널을 통해 redux-toolkit을 설치해주면 됨.
yarn add @redux.js/toolkit
toolkit을 사용해도 index.js는 똑같이 세팅해주면 됨.
configStore 같은 경우에는 이렇게 수정을 해주면 됨.
그냥 redux를 사용할 때에 비해 내용이 많이 줄어듦.
reducer에 자신이 사용할 reducer를 넣어주면 됨.
그리고 기존에 있던 modules 폴더 내에 catSlice.js를 만들어주면 됨.
Redux의 cat.js reducer와 같은 기능을 함.
여기서 액션타입, 액션 생성함수, 리듀서, 기초값을 한 번에 묶어서 사용할 거임.
toolkit의 내장 기능인 createSlice로 Slice를 만들어 준 후, 객체를 인자로 받음.
객체 안에 액션타입, 액션 생성함수, 리듀서, 기초값을 다 넣어주면 됨.
객체에는 name, initialState, reducers가 필수 요소.
name: 이름, initialState: 초기 상태, reducers: 메소드(함수)로 이루어져 있음.
toolkit은 reducer 내에 action creator 함수가 들어가게 됨.
여기서 하나 주의해야 할 점.
redux는 { ...state, newData } 이런식으로 새로운 객체를 생성해 데이터를 수정해주었음.
하지만 redux-toolkit은 그렇게 할 필요가 없다는 것.
toolkit은 immer라고 부르는 불변성 유지 패키지를 이미 가지고 있음.
immer가 불변성 유지를 알아서 해주기 때문에 새객체를 만들거나 하지 않아도 됨.
immer는 프록시를 사용해서 불변을 유지함.
프록시 => 순회, 열거 등을 하는 등 이런 기본적인 동작의 새로운 행동을 만들어낼 때 사용
counterSlice는 store에서 사용도 하지만, dispatch 할때도 사용해야 하기에 export를 해줌.
dispatch에서 메소드에 접근하기 쉽게 구조분해 할당을 해줌.
그리고 마지막으로 App.js로 이동하여 action creator import 주소를 변경해주면 됨.
3-5. 라우팅
- 라우팅이란? :
라우팅은 페이지 전환이에요.
SPA 방식에서는 첫 로딩 때 모든 정적 자원(js, css 등)을 한 번에 가져오죠!
MPA 방식에서는 주소에 맞는 html을 열어주었지만
SPA는 주소창을 보고 주소에 맞게 매번 페이지를 조립해서 보여주어야 합니다.
우리는 react-router-dom 패키지를 사용해서 해볼거예요.
먼저, 새로운 프로젝트를 만들어 준 후 react-router-dom을 설치해줌.
- react-router-dom 설치하기
yarn add react-router-dom
- 라우팅 해보기.
1) App.js에 BrowserRouter 적용하기
router의 최상위에는 항상 BrowserRouter가 있음.
router를 사용하기 위해서는 반드시 BrowserRouter로 감싸져 있어야 한다는 얘기임.
난 BrowserRouter로 App.js를 감싸줌.
App.js에 가장 최상위 컴포넌트이기 때문임. (컴포넌트 어디서든지 router를 사용하고 싶다는 의미.)
index.js에 BrowserRouter를 넣어줌.
이렇게 안하고 App.js에 BrowserRouter를 추가해줘도 됨.
2) 세부 화면 만들기
이제 나는 src 폴더 내에 pages라는 폴더를 만들어 그 안에 페이지 몇 개를 집어넣어 줄 거임.
Home.js, Cat.js 페이지를 만들어주겠음.
페이지 만드는 과정은 컴포넌트가 즉 페이지이므로 생략.
3) Router 만들기
페이지를 만들어줬다면 이제 src 폴더 내에 shared라는 폴더를 하나 더 만들어 안에 Router.js를 넣어줄 거임.
Router를 사용하기 위해서는 안에 BrowserRouter, Routes, Route가 들어가야 함.
Route는 Routes로 감싸줘야 하고, Routes는 BrowserRouter로 감싸줘야 함.
난 index.html에서 App.js를 BrowserRouter로 감싸줬으므로 BrowserRouter는 생략.
- Route 사용방법 1: 넘겨줄 props가 없을 때
<Route path="/cat" element={<Cat />} />
- Route 사용방법 1: 넘겨줄 props가 있을 때
<Route path="/cat" element={<Cat data="전달된 props" />} />
이렇게 해주면 Router를 사용할 수 있음.
페이지 이동 가능.
4) URL 파라미터 사용하기
이제 useParams이라는 react hook을 사용하여 url parameter를 가져와보겠음.
url parameter를 받아오기 위해서는
먼저 router에서 슬래쉬(/)를 해준 다음 parameter 이름부터 지정을 해줘야 함.
그리고 parameter를 지정해준 component에 가서 useParams를 이용해 url parameter를 받아옴.
5) 링크 이동시키기
매번 주소창을 찍고 페이지를 돌아다닐 순 없겠죠!
react-router-dom으로 페이지를 이동하는 방법을 알아봅시다!
- <Link/> 사용하기
링크 컴포넌트는 html 중 a 태그와 비슷한 역할을 함.. 리액트 내에서 페이지 전환을 도와줌.
<Link to="주소">[텍스트]</Link>
- navigate 사용하기
useNavigate 훅을 사용해서 이동하기
import { useNavigate } from "react-router-dom";
function App() {
let navigate = useNavigate();
function handleClick() {
navigate("/home");
}
return (
<div>
<button onClick={handleClick}>go home</button>
</div>
);
}
'(심층)리액트' 카테고리의 다른 글
다시 시작하는 리액트 - 리액트 심화 3-1 Quiz (0) | 2023.05.05 |
---|---|
다시 시작하는 리액트 - 리액트 심화 3-1 (0) | 2023.05.04 |
다시 시작하는 리액트 - 리액트 심화 2-2 (1) | 2023.05.03 |
다시 시작하는 리액트 - 리액트 심화 2 Quiz (0) | 2023.05.02 |
다시 시작하는 리액트 - 리액트 심화 2-1 (0) | 2023.05.01 |
github : https://github.com/dnjfht
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!