MAENG.
React

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 ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ์ด๋Š” ์ปดํฌ๋„ŒํŠธ ์ˆ˜๊ฐ€ ๋งŽ์•„์ง€๊ณ  ๊ณต์œ ํ•˜๋Š” ์ƒํƒœ ๊ฐ’๋“ค์ด ๋งŽ์•„์ง„๋‹ค๋ฉด, ์–ด๋””์„œ ์–ด๋–ป๊ฒŒ ์ƒํƒœ๊ฐ’์ด ๋ณ€ํ•˜๋Š”์ง€, ์ƒํƒœ๊ฐ’ ์—…๋ฐ์ดํŠธ๊ฐ€ ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ์—์„œ ์–ด๋–ป๊ฒŒ ๋ณ€ํ™”ํ•˜์˜€๋Š”์ง€, ์‰ฝ๊ฒŒ ์ถ”์ ํ•˜๊ธฐ ์–ด๋ ต๋‹ค.

props-drilling.gif

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 App

React์—์„œย 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๋ฅผ ์ด์šฉํ•ด๋ณด๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค๋ผ๋Š” ์ƒ๊ฐ์ด ๋“ ๋‹ค.