React ์ํ๊ด๋ฆฌ
2022. 03. 11
1. ์ํ
์ํ๋, ๋ณํํ๋ ๋ฐ์ดํฐ ์ฆ, UI์ ๋์ ์ผ๋ก ํํ๋๋ ๋ฐ์ดํฐ, ์ฌ์ฉ์์ action์ ๋ฐ๋ผ์ ๋ณ๊ฒฝ๋ ์ ์๋ ์ปดํฌ๋ํธ์ ๋ถ๋ถ, ์๋ฐ์คํฌ๋ฆฝํธ์ ๊ฐ์ฒด๋ก, ์นํ์ด์ง๋ด์์ ๋์ ๋ณด์ด๋ ๋ฐ์ดํฐ๋ค(ex. ๋ฉ๋ด, ๊ฒ์๊ธ ์ ๋ชฉ, ๊ฒ์๊ธ ๋ด์ฉ)๋ฟ๋ง ์๋๋ผ ์๋ฒ์ ์ฃผ๊ณ ๋ฐ์์ผํ๋ ๋ฐ์ดํฐ๋ฅผ ๋งํ๋ค. ๋ค์์ React์์ ๋งํ๋ ์ํ(state)์ด๋ค.
Plain Javascript Object hold information influences the output of render ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ ๋(render)ํ๋๋ฐ ์์ด ์ํฅ์ ๋ฏธ์น ์ ์๋ ๊ฐ
2. ์ํ๊ด๋ฆฌ
๋ฐ๋ผ์ ์ํ๊ด๋ฆฌ๋, ๋ณํํ๋ ๋ฐ์ดํฐ๋ฅผ ์ค๊ณ๋ UI, UX์ ๋ง๊ฒ ์ค๊ณํ๊ณ ๊ตฌํํ๊ณ ์๋ฒ์ ์ฃผ๊ณ ๋ฐ๋ ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ๋ ์ผ์ด๋ค. ์ํ๊ด๋ฆฌ๋ฅผ ํตํด ์ฐ๋ฆฌ๋ ๋ฐ์ดํฐ๊ฐ ๋ณํ ๋๋ง๋ค ๋ฐ์ดํฐ์ ๊ด๋ จ๋ DOM์ ์ผ์ผํ ์ฐพ์์ ์กฐ์ํ์ง ์์๋ ๋๋ฉฐ ์ ์ฒด ๋ฐ์ดํฐ์ ํํ์ ๋ฆฌ์คํธ๋ฅผ ํ ๊ณณ์์ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์๋ค.
3. React์์์ ์ํ๊ด๋ฆฌ
์ด๋ค ๊ฐ์ด ๋์ ์ผ๋ก ๋ณํ๋ฉฐ, ๋์ ์ผ๋ก ๋ณํ๋ ๊ฐ์ด DOM์ ๋ฐ์๋๋ ์ฆ, ์ ํ๋ฆฌ์ผ์ด์
๋ ๋๋ง์ ์ํฅ์ ์ฃผ๋ ์ด๋ฌํ ๋ณํ๋ ๊ฐ์ React์์๋ useState์ useReducer๋ผ๋ Hook์ ํตํด ๊ด๋ฆฌํ ์ ์๋ค.
์ฐธ๊ณ ๋ก, ์ปดํฌ๋ํธ ์ธ๋ถ ์์๋ก ์ฐธ์กฐํ๊ฑฐ๋ ๋๋ย useRef์ ์ด์ฉํ์ฌ ์ฐธ์กฐํ ์ ์์ผ๋ฉฐ, useRef๋ก ๊ด๋ฆฌํ๋ ๋ณ์๋ ๊ฐ์ด ๋ฐ๋๋ค๊ณ ํด์ ๋ฆฌ๋ ๋๋ง์ด ์ผ์ด๋์ง ์๋๋ค.
4. ์ ์ญ์ํ ๊ด๋ฆฌ
React์์ ํ๋์ ์ปดํฌ๋ํธ ๋ด์์ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ๊ฒ์ด ์๋ ํ๋์ ์ํ๋ฅผ ์ฌ๋ฌ ์ปดํฌ๋ํธ๊ฐ ๊ณต์ ํด์ผ ํ๋ ๊ฒฝ์ฐ๋ฅผ ๋งํ๋ค. ๋ณดํต ์ธ์ด, ui ํ ๋ง ๋ฑ๊ณผ ๊ฐ์ด ์ดํ๋ฆฌ์ผ์ด์ ์ ์ฌ๋ฌ ๊ณณ์์ ์ฌ์ฉ๋๋ state๋ฅผ ๋งํ๋ฉฐ, ์ด์ธ์๋ ์๋ฒ state๋ฅผ ์ ์ญ ์คํ ์ด์์ ๊ด๋ฆฌํ๋ค.
5. ์ํ ๋์ด์ฌ๋ฆฌ๊ธฐ(Lifting State Up)
์ฌ๋ฌ ์ปดํฌ๋ํธ๋ค์ด ๊ณตํต์ผ๋ก ๊ฐ์ง๊ณ ์๋ ๋ถ๋ชจ ์ปดํฌ๋ํธ์ ์ํ๋ฅผ ์ ์ธํ๋ ๊ฒ์ ๋งํ๋ค. ์ํ๋ฅผ ๊ณต์ ํ๋ ์ปดํฌ๋ํธ๋ค์ ๋ถ๋ชจ ์ปดํฌ๋ํธ๋ฅผ ์ฐพ๊ณ ํด๋น ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ ์ํ๋ฅผ ์ ์ธ ํย props ๋ฅผ ํตํด ์ ๋ฌํ๋ ๋ฐฉ์์ผ๋ก ์ํ๋ฅผ ๊ด๋ฆฌํ ์ ์๋ค.
// Parent.tsx
import React from 'react'
import Child from './Child'
const Parent: React.FC = () => {
const [state, setState] = useState<string>('')
return (
<div>
<Child state={state} setState={setState} />
<Child state={state} setState={setState} />
...
</div>
)
}ํ์ง๋ง ํด๋น ๋ฐฉ์์ ์ปดํฌ๋ํธ ๊ฐฏ์๊ฐ ๋์ด๋๊ณ ๊ณต์ ํด์ผ ํ๋ ์ํ๊ฐ ๋ง์์ง ๊ฒฝ์ฐ, ์ํ๊ฐ์ ์ ๋ฌํ๊ณ ์ํ๊ฐ์ ๋ณ๊ฒฝํ๋ ํจ์๋ ๊ฐ์ด ์ ๋ฌํ๋ ค๊ณ ๋ถ๋ชจ-์์ ์ ๋ฌ ๊ณผ์ ์ ๋งค๋ฒ ์์ฑํด์ผํ๋ ๊ณผ๋ํย prop drilling ๊ฐ ๋ฐ์ํ๋ค. ์ด๋ ์ปดํฌ๋ํธ ์๊ฐ ๋ง์์ง๊ณ ๊ณต์ ํ๋ ์ํ ๊ฐ๋ค์ด ๋ง์์ง๋ค๋ฉด, ์ด๋์ ์ด๋ป๊ฒ ์ํ๊ฐ์ด ๋ณํ๋์ง, ์ํ๊ฐ ์
๋ฐ์ดํธ๊ฐ ์ด๋ค ์ปดํฌ๋ํธ์์ ์ด๋ป๊ฒ ๋ณํํ์๋์ง, ์ฝ๊ฒ ์ถ์ ํ๊ธฐ ์ด๋ ต๋ค.

1. Context API
React์์๋ ์์ฒด์ ์ผ๋ก ์ ์ญ ์ํ ๊ด๋ฆฌ๋ฅผ ํ ์ ์๋ Context API๋ฅผ ์ ๊ณตํ๋ค. Context API๋ฅผ ์ด์ฉํ๋ฉด, ํธ๋ฆฌ ๋จ๊ณ๋ง๋ค ๋ช
์์ ์ผ๋ก props๋ฅผ ๋๊ฒจ์ฃผ์ง ์์๋ ๋ง์ ์ปดํฌ๋ํธ๊ฐ ์ด๋ฌํ ๊ฐ์ ๊ณต์ ํ๋๋ก ํ ์ ์๋ค.
context๋ฅผ ์ด์ฉํ๋ฉด ๋จ๊ณ๋ง๋ค ์ผ์ผ์ด props๋ฅผ ๋๊ฒจ์ฃผ์ง ์๊ณ ๋ ์ปดํฌ๋ํธ ํธ๋ฆฌ ์ ์ฒด์ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ ์ ์์ต๋๋ค. (16.3 ๋ฒ์ ์ดํ ์ ์)
import React, { createContext, useState, useContext } from 'react'
const UserContext = createContext(null)
const UserProvider = ({ children }) => {
const [user, setUser] = useState(null)
return <UserContext.Provider value={{ user, setUser }}>{children}</UserContext.Provider>
}
const UserInfo = () => {
const { user } = useContext(UserContext)
if (!user) return <div>์ฌ์ฉ์ ์ ๋ณด๊ฐ ์์ต๋๋ค.</div>
return <div>{user.username}</div>
}
const Authenticate = () => {
const { setUser } = useContext(UserContext)
const onClick = () => {
setUser({ username: 'velopert' })
}
return <button onClick={onClick}>์ฌ์ฉ์ ์ธ์ฆ</button>
}
const App = () => {
return (
<UserProvider>
<UserInfo />
<Authenticate />
</UserProvider>
)
}
export default AppReact์์ย Context API๊ฐ ๋์
๋จ์ ๋ฐ๋ผ ์ ์ญ ์ํ ๊ด๋ฆฌ์ ๋ํ ๋ถํธํจ์ด ์ด๋์ ๋ ๊ฐ์ ์ด ๋์์ผ๋ย Context API๋ ์ฑ๋ฅ์ ์ธ ์ด์๊ฐ ์กด์ฌํ๋ค. ๊ฐ์ ๋ณํ๊ฐ ๋ฐ์ํ์ ๋ย Context๋ฅผ ๊ตฌ๋
ํ๊ณ ์๋ ๋ชจ๋ ์ปดํฌ๋ํธ๋ค์ ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํ๋ค.๋ฐ๋ผ์ ๋ฐ๋ณต์ ์ด๊ณ ๋ณต์กํ ์
๋ฐ์ดํธ์ ๊ด๋ จ๋ ๋ถ๋ถ์์ ๋นํจ์จ์ ์ด๋ค. ์๋ฅผ ๋ค์ด ์ปดํฌ๋ํธ์์ ๋ง์ฝ Context์ ํน์ ๊ฐ์ ์์กดํ๋ ๊ฒฝ์ฐ, ํด๋น ๊ฐ ๋ง๊ณ ๋ค๋ฅธ ๊ฐ์ด ๋ณ๊ฒฝ ๋ ๋์๋ ์ปดํฌ๋ํธ์์๋ ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ด๋ค. ๋ฐ๋ผ์, Context ๋ฅผ ์ฌ์ฉํ๊ฒ ๋ ๋์๋ ๊ด์ฌ์ฌ์ ๋ถ๋ฆฌ๊ฐ ์ค์ํ๋ค. ์๋ก ๊ด๋ จ์ด ์๋ ์ํ๋ผ๋ฉด ๊ฐ์ Context ๊ฐ ์๋ ๋ณ๋์ Context๋ฅผ ๋ง๋ค์ด์ผํ๋ค. ์ด์ธ์๋ Context API๋ ์ฌ๋ฌ Context๋ฅผ ๊ตฌ๋
ํ๊ธฐ ์ํด์ ์ฌ๋ฌ Consumer ์ปดํฌ๋ํธ๋ฅผ ์ค์ฒฉํด์ผ ํ๋ ๋ถํธํจ๋ ์กด์ฌํ๋ค.
ํ์ง๋ง Redux์ ๊ฒฝ์ฐ์๋ ์์ฒด์ ์ผ๋ก ๋ฆฌ๋ ๋๋ง๊ณผ ๊ด๋ จ๋ ๋ถ๋ถ์ ์ต์ ํ๊ฐ ์ ์ฉ๋์ด ์๊ธฐ ๋๋ฌธ์, ๋ถ๋ถ์ ์ผ๋ก ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํ๋ค. ์ด๋ฌํ ์ฑ๋ฅ์ ์ธ ์ด์ ๋ย useReducer๋ฅผ ํตํ ๋ณ๊ฒฝ ํ๋ฆ ์กฐ์ ์ด๋ ๋ฉ๋ชจ์ด์ ์ด์
์ ํ์ฉ, ๋๋ ๋
ผ๋ฆฌ์ ๊ด์ ์์ย Provider๋ฅผ ์ฌ๋ฌ ๊ฐ๋ก ๋ถ๋ฆฌํ๊ณ , ๊ฐ๋ฅํ ๊ทธ ์ํ๋ฅผ ํ์๋ก ํ๋ ๊ณณ ๊ทผ์ฒ์ ๋๋ ๋ฑ ์์ฒด์ ์ธ ์ต์ ํ๊ฐ ๊ฐ๋ฅํ์ง๋ง ์ด๋ฌํ ๊ณผ์ ์ด ๋ค์ ๋ฒ๊ฑฐ๋กญ๊ฒ ์์ฉํ ์ ์๋ค.
2. Redux
Redux ๋ storeย ๋ผ๋ state ์ ์ฅ ๊ณต๊ฐ์ ๋์ด, ์ฌ๋ฌ ์ปดํฌ๋ํธ๊ฐ ํ๋์ store๋ฅผ ์ฐธ์กฐํ๋ ๋ฐฉ์์ผ๋ก ์ ์ญ์ํ๋ฅผ ๊ด๋ฆฌํ๋ค. Redux์ ๊ฒฝ์ฐ ์์ฒด์ ์ผ๋ก ๋ฆฌ๋ ๋๋ง๊ณผ ๊ด๋ จ๋ ๋ถ๋ถ์ ์ต์ ํ๊ฐ ์ ์ฉ๋์ด ์๊ธฐ ๋๋ฌธ์, ๋ถ๋ถ์ ์ธ ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํ๋ฉฐ Redux๋ฅผ ์ฌ์ฉํ๋ค๋ฉดย Context API์์ ์ ๊ณตํ ์ ์๋ ์ฌ๋ฌ ๋ค์ํ ๊ธฐ๋ฅ๋ค์ ๋ฏธ๋ค์จ์ด(thunk, saga, devTools...)๋ฅผ ์ฌ์ฉํด ๊ด๋ฆฌํ ์ ์๋ค. ๋ํ, ๋ชจ๋ ์ํ ์
๋ฐ์ดํธ๋ฅผ ์ก์
์ผ๋ก ์ ์ํ๊ณ , ์ก์
์ ๋ณด์ ๊ธฐ๋ฐํ์ฌ Reducer์์ ์ํ๋ฅผ ์
๋ฐ์ดํธํ๋ ์ด ๊ฐ๋จ๋ช
๋ฃํ ๋ฐ์ ๋๋ถ์, ์ํ๋ฅผ ๋์ฑ ์ฝ๊ฒ ์์ธก ๊ฐ๋ฅํ๊ฒ ํ์ฌ ์ ์ง๋ณด์ ์ธก๋ฉด์ ๊ธ์ ์ ์ธ ํจ๊ณผ๊ฐ ์๋ค.
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { createStore, combineReducers } from 'redux';
import { Provider, useDispatch, useSelector } from 'react-redux';
// ์ก์
ํ์
const OPEN = 'msgbox/OPEN';
const CLOSE = 'msgbox/CLOSE';
// ์ก์
const open = (message) => ({ type: OPEN, message });
// Initail State
const initialState = {
open: false,
message: '',
};
// ๋ฆฌ๋์
const msgReducer(state = initialState, action) {
switch (action.type) {
case OPEN:
return { ...state, open: true, message: action.message };
case CLOSE:
return { ...state, open: false };
default:
return state;
}
}
// ๋ฆฌ๋์ ๋ฌถ๊ธฐ
const rootReducer = combineReducers({
msgReducer,
});
// ์คํ ์ด ์์ฑ
const store = createStore(rootReducer);
const App = () => {
const dispatch = useDispatch();
const { open, message } = useSelector(state => state.msgReducer);
useEffect(() => {
dispatch(open("Hello World"));
},[])
return open ? <div>{message}</div> : null;
}
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);"๋ฌด์์ด ์ผ์ด๋๋๊ฐ"์ "์ด๋ป๊ฒ ๋ฐ๊พธ๋๊ฐ"ย Redux๊ฐ ์ ์ํ๋ ์๊ตฌ์ฌํญ์ Redux์ ์ฅ์ ์ด์ ๋จ์ ์ด๋ค. ๋ฌด์์ด ์ผ์ด๋๋์ง๋ย dispatch๋ฅผ ์ด์ฉํด์ ์๋ฆฌ๋ฉฐ ์ด๋ป๊ฒ ๋ฐ๊ฟ์ง๋ย reducer๋ฅผ ์ด์ฉํด์ย state๋ฅผ ์กฐ์ํ๋ค.
Flux ํจํด์ ์ด์ฉํด์ ๋จ๋ฐฉํฅ ํ๋ฆ์ผ๋ก ์์ ์ ์ธ ์ํ ์ด์ฉ์ด ๊ฐ๋ฅํ์ง๋ง ์ํ๋ ์ํ์ ๊ธฐ๋ฅ์ถ๊ฐ๋ฅผ ์ํด์๋ย dispatch๋ฅผ ์ํย action, ์ํ ๋ณํ๋ฅผ ์ํreducer, ์ปดํฌ๋ํธ์์ย state ๋ฅผ ๊ฐ์ ธ๋ค ์ฐ๋ ๋ถ๋ถ ๋ชจ๋ ์๋ด์ผ ํ๊ธฐ ๋๋ฌธ์ ๋๋ฌด ์ฅํฉํ๋ค. ์ด์ฒ๋ผ ์ดํ๋ฆฌ์ผ์ด์
์ด ๋น๋ํ ๋ ์๋ก ์ด๋ฐ ์ํ ๊ด๋ฆฌ ์ฌ์ดํด ๊ด๋ฆฌ๋ฅผ ์ํ ์ฝ๋์ ๋ณต์ก๋๊ฐ ์ฌํ๋์ด์ ํ์ฅ์ฑ๋ ๋จ์ด์ง๋ค.
์ด๋ฌํ ๋ถํธํจ์ Redux Toolkit์ด๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋์ค๋ฉด์ ๋ง์ ๋ถ๋ถ์ด ๊ฐ์ ๋์๋ค. ๋ฆฌ๋์, ์ก์ ํ์ , ์ก์ ์์ฑํจ์, ์ด๊ธฐ์ํ๋ฅผ ํ๋์ ํจ์๋ก ํธํ๊ฒ ์ ์ธํ ์ ์๊ฒ๋๋ฉด์, ๊ธฐ์กด์ ๋ฒ๊ฑฐ๋กญ๊ณ ๋ณต์กํ๋ ์ฌ์ฉ๋ฐฉ๋ฒ์ด ํจ์ฌ ๊ฐํธํด์ก๋ค.
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { combineReducers } from 'redux';
import { Provider, useDispatch, useSelector } from 'react-redux';
import { configureStore, createSlice } from '@reduxjs/toolkit';
// Slice
const messageSlice = createSlice({
name: "message",
initialState: {
open: false,
message: '',
},
reducers: {
open: (state, { payload }) => {
state.open = true;
state.message = payload;
};
close: (state) => {
state.open = false;
};
},
});
// ๋ฆฌ๋์ ๋ฌถ๊ธฐ
const rootReducer = combineReducers({
msgReducer: messageSlice.reducer,
});
// ์คํ ์ด ์์ฑ
const store = configureStore({
reducer: rootReducer
});
const App = () => {
const dispatch = useDispatch();
const { open, message } = useSelector(state => state.msgReducer);
useEffect(() => {
dispatch(open("Hello World"));
},[])
return open ? <div>{message}</div> : null;
}
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);์์ ์ฝ๋์์์์๋ ์ค๋ช
ํ์ง ์์์ง๋ง, ๋ฆฌ์กํธ์์ ๋๊ฐ server state๋ฅผ ๊ด๋ฆฌํ ๊ฒฝ์ฐ, Redux์ redux-thunk ํน์ redux-saga ๋ฏธ๋ค์จ์ด๋ฅผ ๋ถ์ฌ ๊ด๋ฆฌํ ๊ฒ์ด๋ค. ํ์ง๋ง ์ฌ๊ธฐ์๋ ๋ช ๊ฐ์ง ๋ฌธ์ ์ ์ด ์๋ค. ์ฌ๋ฌ ์ปดํฌ๋ํธ๊ฐ ๋์ผ API๋ฅผ ํธ์ถํ๋ ์ก์
์ dispatch ํ๋ค๋ฉด ์ค๋ณต API ์์ฒญ์ด ๋ฐ์ํ ์ ์๊ณ , ์๋ฒ๋ก๋ถํฐ ๊ฐ์ ๊ฐ์ ธ์ redux์ ์ ์ฅํ๊ธฐ ์ํด์ ํน์ ์์ ์ ์ก์
์ด dispatch๋์ด์ผ ํ๋๋ฐ ์ก์
์ด dispatch ๋๋ ์์ ์ย ์บก์ณ(capture)๋ย ๋ฐ์ดํฐ๊ฐ ์ ์ฅ๋๊ธฐ ๋๋ฌธ์ ์ด ์์ ์ดํ์ ๋ณ๊ฒฝ๋ ์๋ฒ์ ๊ฐ์ ์ก์
์ ๋ค์ dispatch ํ๊ธฐ ์ ๊น์ง ๋ฐ์๋์ง ์๋๋ค. ๋ฐ๋ผ์ server state๊ฐ ํน์ ์์ ์ backend(์๋ฒ)์ ์ํ์ผ ๋ฟ์ด๋ผ๋ ๊ฒ์ด๋ค. server state์ ์๊ด์์ด backend ์ํ๋ ์ผ๋ง๋ ์ง ๋ณํ ์ ์๊ธฐ๋๋ฌธ์ server state๋ฅผ ์ ์ ํ ๋ค๋ฃจ๊ธฐ ์ํด์๋ ๋ค์๊ณผ ๊ฐ์ ์ผ๋ค์ด ํ์ํ๋ค.
- ์บ์ฑ
- ๋์ผ ๋ฐ์ดํฐ์ ๋ํ ์ค๋ณต ์์ฒญ ์ ๊ฑฐ
- ์ค๋๋ ๋ฐ์ดํฐ ์ ๋ฐ์ดํธ
- ๋ฐ์ดํฐ ๋ณ๊ฒฝ ์์ฒญ ์ดํ ์ ๋ฐ์ดํธ
2020๋
์ react-query์ย swr ๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋ฆด๋ฆฌ์ฆ๋์๊ณ ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ server state์ ๋ํ ํจ์นญ, ๋ด์ฅ ์บ์ฑ๊ณผย ์ค๋ณต ์ ๊ฑฐ ๊ธฐ๋ฅ์ ํตํ ๋ถํ์ํ ๋คํธ์ํฌย ์์ฒญ ์๋ต ๋ฐ ์
๋ฐ์ดํธ๋ฟ ์๋๋ผ, ์์ฒญ ์ํ ์ฒ๋ฆฌ, ์์ฒญ ์คํจ์ retry, window focus์ ์
๋ฐ์ดํธ ๋ฑ ๋น๋๊ธฐ ์์ฒญ๊ณผ ๊ด๋ จ๋ ์ฌ๋ฌ๊ฐ์ง ํธ๋ฆฌํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
3. SWR
SWR์ Next.js๋ฅผ ๋ง๋ Vercelํ์์ ๋ง๋ ๊ฒ์ผ๋ก, ๊ณต์ํํ์ด์ง์ ๋ด์ฉ์ ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ฐ์ฌ์๋ค.
"SWR"์ด๋ผ๋ ์ด๋ฆ์ย HTTP RFC 5861 ์ ์ํด ์๋ ค์ง HTTP ์บ์ ๋ฌดํจ ์ ๋ต์ธย
stale-while-revalidate์์ ์ ๋๋์์ต๋๋ค. SWR์ ๋จผ์ ์บ์(์คํ์ผ)๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ ํ, fetch ์์ฒญ(์ฌ๊ฒ์ฆ)์ ํ๊ณ , ์ต์ข ์ ์ผ๋ก ์ต์ ํ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ์ ๋ต์ ๋๋ค.
SWR์ ๋จ ํ ์ค์ ์ฝ๋๋ก ํ๋ก์ ํธ ๋ด์ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ ๋ก์ง์ ๋จ์ํํ ์ ์์ผ๋ฉฐ, ๋น ๋ฅด๊ณ ,ย ๊ฐ๋ณ๊ณ ,ย ์ฌ์ฌ์ฉ ๊ฐ๋ฅํย ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ , ๋ด์ฅ๋ ์บ์ ๋ฐ ์์ฒญ ์ค๋ณต์ ์ ๊ฑฐํ๋ฉฐ, ์ค์๊ฐ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ค. ๋ํ ํฌ์ปค์ค์ ์ฌ๊ฒ์ฆ, ๋คํธ์ํฌ ํ๋ณต์ ์ฌ๊ฒ์ฆ๊ณผ ๊ฐ์ด ๋ค์ํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((res) => res.json())
const Profile = () => {
const { data, error } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return <div>hello {data.name}!</div>
}์น ์ฑ์ ๊ตฌ์ถํ ๋, UI์ ๋ง์ ๊ณณ์์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฌ์ฉํ ํ์๊ฐ ์๋๋ฐ SWR์ ์ฌ์ฉํ์ ๊ฒฝ์ฐ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๋ฐ์ดํฐ hook์ ๋ง๋๋ ๊ฒ์ด ๋งค์ฐ ์ฌ์ฐ๋ฉฐ, ์ํ๋ ์ปดํฌ๋ํธ์ ์ํ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฃ๋ ์ ์ธ์ ๋ฐฉ์์ผ๋ก ์ฌ์ฉํ ์ ์๋ค.
๋ค๋ง, SWR์ ํ ๊ฐ์ง ์์ฌ์ด ์ ์ fetching ์ธ์ post/patch/put/delete๋ฅผ ํตํด ์๋ฒ์ ์ํ๋ฅผ ๋ณํ์ํค๋ ๋ฎคํ
์ด์
์ ์ ๊ณตํ์ง ์๋๋ค. useSWR()์ ํตํด ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ ํด๋ผ์ด์ธํธ ์ฌ์ด๋์์ ๋ณํ์์ผ ์
๋ฐ์ดํธํด ์ฃผ๋ ๊ฐ๋
์ผ๋ก, ๋จผ์ ํด๋ผ์ด์ธํธ ๋ฐ์ดํฐ๋ฅผ ์
๋ฐ์ดํธ ํ ๋ณ๋์ API ์์ฒญ์ ํ์ฌ ์๋ฒ ๋ฐ์ดํฐ๋ฅผ ์
๋ฐ์ดํธ ํ์ ์ ์์ ์ผ๋ก ๋ฐ์ดํฐ๊ฐ ์
๋ฐ์ดํธ๋์๋์ง ํ์ธํ๋ ์์๋ก ์ํํด์ผ ํ๋ ๋ถํธํจ์ด ์๋ค.
import useSWR, { mutate } from 'swr'
const fetcher = (url) => fetch(url).then((res) => res.json());
const Profile () => {
const { data } = useSWR('/api/user', fetcher)
return (
<div>
<h1>My name is {data.name}.</h1>
<button onClick={async () => {
const newName = data.name.toUpperCase()
// ๋ก์ปฌ ๋ฐ์ดํฐ๋ฅผ ๋ฐ๋ก ์
๋ฐ์ดํธํ๋ค. ๋์ 3๋ฒ์งธ ์ธ์๋ฅผ false๋ก ๋์ด ์ฌ์์ฒญ์ ํ์ง ์๋๋ค.
mutate('/api/user', { ...data, name: newName }, false)
// ๋ฐ์ดํฐ๋ฅผ ์
๋ฐ์ดํธํ๋ ์์ฒญ์ ํ๋ค.
await requestUpdateUsername(newName)
// ์ฌ์์ฒญ์ ํ๋ค.
mutate('/api/user')
}}>Uppercase my name!</button>
</div>
)
}4. React-query
์ด๋ฒ์ react-query์ ๋ํด ์์๋ณด์. ๊ณต์ํํ์ด์ง์ ๋ด์ฉ์ ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ฐ์ฌ์๋ค.
React Query๋ "์ ์ญ ์ํ"๋ฅผ ๊ฑด๋๋ฆด ์ผ ์์ด ๋ฆฌ์กํธ์ ๋ฆฌ์กํธ ๋ค์ดํฐ๋ธ ์ดํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์ดํฐ๋ฅผ ํจ์น(fetch), ์บ์(cache) ๊ทธ๋ฆฌ๊ณ ์ ๋ฐ์ดํธํฉ๋๋ค.
react-query์ ๊ฒฝ์ฐ, SWR๊ณผ ๋ง์ ๋ถ๋ถ์ ์์ด ์ ์ฌํ์ง๋ง, devtools ์ ๊ณต, Data Optimization, Auto Garbage Collection ๋ฑ ๋ ๋ง์ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
const queryClient = new QueryClient()
const Example = () => {
const { isLoading, error, data } = useQuery('repoData', () =>
fetch('https://api.github.com/repos/tannerlinsley/react-query').then((res) => res.json()),
)
if (isLoading) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
<strong>๐ {data.subscribers_count}</strong> <strong>โจ {data.stargazers_count}</strong>{' '}
<strong>๐ด {data.forks_count}</strong>
</div>
)
}
const App = () => {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}React ์ํ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก redux์ swr๊ณผ react-query ์ ๋ํ ๋ง์ ํฌ์คํ ๊ธ๋ค์ด ์๋๋ฐ ๊ธ์ ์ฝ๋ค๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ ๋ฌผ์์ด ์๊ธด๋ค.
SWR๊ณผ React-query๋ก Redux๋ฅผ ์์ ํ ๋์ฒด๊ฐ๋ฅํ๊ฐ?
SWR๊ณผ React-query๋ ๊ธฐ๋ณธ์ ์ผ๋ก server state๋ฅผ ๊ด๋ฆฌํ๋๋ฐ ์ ํฉํ ๋๊ตฌ์ด๋ค. ๋ฌผ๋ก , ์ด๋ฅผ ์์ฉํ์ฌ ์ ์ญ์ํ๊ด๋ฆฌ๋ก์์ ์ญํ ์ ์ํํ๋๋ก ๊ตฌํํ ์๋ ์๋ค. ํ์ง๋ง, Redux์ SWR, React-query๊ฐ ์ฒ์๋ถํฐ ํด๊ฒฐํ๊ณ ์ ํ๋ ๋ฌธ์ ๊ฐ ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ Redux์ ๋ชจ๋ ๊ธฐ๋ฅ์ ๋์ฒดํ ์ ์์ผ๋ฆฌ๋ผ๊ณ ์๊ฐ๋์ง ์๋๋ค.
๊ทธ๋ ๋ค๋ฉด ์๋ฒ์ํ๊ด๋ฆฌ๋ฅผ ์ํด SWR ํน์ React-query๋ฅผ ์ฌ์ฉํ๊ณ ๊ทธ์ธ์ ์ ์ญ์ํ๊ด๋ฆฌ๋ฅผ ์ํด์๋ Redux ๋ฅผ ๊ฐ์ด ์ฌ์ฉํ๋๊ฒ ์ข์๊น? ๋ผ๋ ์๊ฐ์ด ๋ค๋์ค Recoil์ด๋ผ๋ ๋
์์ ์ ํ๊ฒ๋์๋ค.
5. Recoil
Recoil์ ํ์ด์ค๋ถ์์ ๊ฐ๋ฐํ ์ ์ญ ์ํ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, Redux์ ๊ฐ์ด store์ ๊ฐ์ ์ธ๋ถ ์์ธ์ด ์๋ React ๋ด๋ถ์ ์ํ๋ฅผ ํ์ฉํ๊ณ Context API๋ฅผ ํตํด ๊ตฌํ๋์ด ์๊ธฐ ๋๋ฌธ์ ๋ ๋ฆฌ์กํธ์ ๊ฐ๊น์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค. ๋ํ, ์์์ ์ธ๊ธํ๋ Context API์ ๋ฌธ์ ์ ์ ๊ฒช์ง ์์๋ ๋๋ฉฐ Redux๋ณด๋ค๋ ํจ์ฌ ์ฌ์ฉ์ด ๊ฐํธํ๋ค. Recoil์ย atom์ด๋ผ๋ ์์ ๋ฐ์ดํฐ ์กฐ๊ฐ์ ๋ง๋ค์ด์ ํด๋นย Stateย ๋ณํ ์์ ์ด๋ฅผ ์ฐธ์กฐํ๋ ์ปดํฌ๋ํธ๋ค๋ง ๋ฆฌ๋ ๋๋ฅผ ์ํค๋ ๋จ์ํ ๋ก์ง์ผ๋ก ๋์ด์๋ค. Recoil๋ก ๊ฐ๋ฐํ ๊ฒฝ์ฐ ์ด์ ๋ค์ ์์ฝํด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
- ๋ฆฌ์ฝ์ผ์ atom์ด๋ผ๋ ์ํ๋ฅผ store ์์ด ์ ์ญ์ผ๋ก ๊ด๋ฆฌํ ์ ์๋ค.
- atom์ ๊ตฌ๋ , ์ต์ ๋ฒ์ ๊ฐ์ ๊ฐ๋ ์ ์๊ฐํ ํ์์์ด hook์ ์ฌ์ฉํ๋ฏ์ด ์ฌ์ฉํ๋ฉด ๋๋ค.
- selector๋ฅผ ์ด์ฉํ๋ฉด ๋ฆฌ๋์ ์์ด ๋ณต์กํ ์ฐ์ฐ๋ ๊ฐ๋จํ๊ฒ ํ ์ ์๋ค.
- ๋น๋๊ธฐ ๋ฐ์ดํฐ ํ๋ฆ์ ์ํ ๋ด์ฅ ์๋ฃจ์ ๊น์ง ์ ๊ณตํ๋ค.
- react ๋ด๋ถ์ ๋ํ ์ ๊ทผ์ด ๊ฐ๋ฅํ์ฌย
React์ ๋์์ฑ ๋ชจ๋,ยSuspenseย ๋ฑ์ ์์ฝ๊ฒ ์ง์ ๊ฐ๋ฅํ๋ค.
import React from 'react'
import { RecoilRoot, atom, selector, useRecoilState, useRecoilValue } from 'recoil'
const textState = atom({
key: 'textState', // unique ID (with respect to other atoms/selectors)
default: '', // default value (aka initial value)
})
const charCountState = selector({
key: 'charCountState', // unique ID (with respect to other atoms/selectors)
get: ({ get }) => {
const text = get(textState)
return text.length
},
})
const TextInput = () => {
const [text, setText] = useRecoilState(textState)
const onChange = (event) => {
setText(event.target.value)
}
return (
<div>
<input type="text" value={text} onChange={onChange} />
<div>Echo: {text}</div>
</div>
)
}
const CharacterCount = () => {
const count = useRecoilValue(charCountState)
return <>Character Count: {count}</>
}
const CharacterCounter = () => {
return (
<div>
<TextInput />
<CharacterCount />
</div>
)
}
const App = () => {
return (
<RecoilRoot>
<CharacterCounter />
</RecoilRoot>
)
}์์ ์ฝ๋์์ ๋ณด๋ฏ์ด Redux์ ๋ณต์กํ ๋ณด์ผ๋ฌ ํ๋ ์ดํธ ์ฝ๋๋ฅผ ์ค์ ํ ํ์๊ฐ ์์ผ๋ฉฐ, useState Hook์ ์ฌ์ฉํ๋ ๊ฒ ์ฒ๋ผ ์ฝ๊ฒ ์ํ๊ด๋ฆฌ๋ฅผ ํ ์ ์์ ๊ฒ ๊ฐ๋ค. ํ์ง๋ง, ์์ง ๊ณต์์ ์ผ๋ก๋ experimental ๋จ๊ณ์ด๊ธฐ ๋๋ฌธ์ ๊ฐ๋ฐ์คํ์ด ๋ฐ๋ ์ ์์ผ๋ ์กฐ์ฌ์ค๋ฝ๊ฒ ์ ๊ทผํ๋๊ฒ ์ข์๊บผ ๊ฐ๋ค.
๋ฐ๋ผ์ ๋ด๊ฐ ๋ด๋ฆฐ ๊ฒฐ๋ก ์... ๋ง์ฝ, SSR์ ์ง์ํ๊ณ ์ถ๋ค๋๊ฐ Flux ํจํด์ ์ด์ฉํ ์ ์ธ์ ์ด๊ณ ์์ ์ ์ธ ์ํ๊ด๋ฆฌ๋ฅผ ์ํ๋ค๋ฉด Redux๋ฅผ ์ฌ์ฉํ๊ณ , ์๋ก์ด ๊ธฐ์ ์ ๋์ ํด๋ณด๊ณ ์ถ๊ณ ์ฝ๊ณ ๊ฐ๋จํ๊ฒ ๊ตฌํํด๋ณด๊ณ ์ถ๋ค๋ฉด Recoil์ ์ฌ์ฉํ์ฌ ์ ์ญ์ํ๋ฅผ ๊ด๋ฆฌํ๋ฉฐ ์ถ๊ฐ๋ก, ์๋ฒ์ํ๊ด๋ฆฌ๊ฐ ํ์ํ ๊ฒฝ์ฐ, SWR ํน์ธ React-query๋ฅผ ์ด์ฉํด๋ณด๋ ๊ฒ์ด ์ข์ ๊ฒ ๊ฐ๋ค๋ผ๋ ์๊ฐ์ด ๋ ๋ค.