๐ŸŽ‰ berenickt ๋ธ”๋กœ๊ทธ์— ์˜จ ๊ฑธ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค. ๐ŸŽ‰
Mobile
React Native
06-global-state-management

1. ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ

1.1 state๋ž€ ?

  • state : React component์—์„œ ๊ด€๋ฆฌ๋˜๊ณ  ์žˆ๋Š” ์–ด๋–ค ๊ฐ’
  • ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ(global state management)
    • Globalํ•œ ์–ด๋–ค๊ฒƒ์— ๋„ฃ๊ณ  ๊ฐ์ž ๋”ฐ๋กœ ํ•„์š”ํ•œ๊ฒƒ๋“ค๋งŒ ๋ณด๊ฒŒ ๋งŒ๋“œ๋Š”๊ฒƒ
  • ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ (e.g. redux, context API, recoil ๋“ฑ)

2. Flux

  • Facebook์—์„œ ๋งŒ๋“  ์˜คํ”ˆ์†Œ์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • ํ˜„์žฌ๋Š” ์œ ์ง€๋ณด์ˆ˜๋งŒ ๋˜๋Š”์ค‘.
  • Redux๋‚˜ Recoil๋“ฑ์„ ์‚ฌ์šฉํ•˜๊ธฐ๋ฅผ ๊ถŒ์žฅ
  • cf. https://facebook.github.io/flux/

2.1 Flux ๊ธฐ๋ณธ ๊ตฌ์„ฑ

1
โ† Action โ†
2
Action โ†’ Dispatcher โ†’ Store โ†’ View

Action, Dispatcher, Store, View ๋“ฑ์œผ๋กœ ๊ตฌ์„ฑ๋จ

  • Action
    • ์‚ฌ์šฉ์ž์˜ Input์„ ํ†ตํ•ด์„œ, ๋˜๋Š” ์ƒํƒœ๋ฅผ ๋ฐ”๊ฟ”์•ผ๋งŒ ํ• ๋•Œ ๋ฐœ์ƒ
    • Action (type, parameter)
  • Dispatcher (๋ฐœ์†ก์ธ)
    • ๋ชจ๋“  ๋ฐ์ดํ„ฐ์˜ ํ๋ฆ„์„ ๊ด€๋ฆฌํ•˜๋Š” ์ค‘์•™ ํ—ˆ๋ธŒ
    • Store๋กœ์˜ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅ
  • Store, View
    • ์ƒํƒœ ์ €์žฅ์†Œ. ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜๋ฅผ ์ œ๊ณต
    • ๋ณ€๊ฒฝ๋œ ๋’ค์—๋Š” View๋กœ ์ „๋‹ฌ

3. Redux

1
Action โŽฏ
2
โ”œโ†’ Reducer โŽฏโ†’ Store New State
3
Prev State โŽฏ
  • Flux์—์„œ Reducer์˜ ๊ฐœ๋…์ด ๋“ค์–ด๊ฐ„๊ฒƒ
  • Reducer : Action๊ณผ ๋งˆ์ง€๋ง‰ Store์˜ ์ƒํƒœ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ๋Š” ๊ฒƒ

3.1 Redux ์‚ฌ์šฉ ๊ทœ์น™

(1) Single source of truth

  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ชจ๋“  ์ƒํƒœ๋Š” ํ•˜๋‚˜์˜ ์ €์žฅ์†Œ ์•ˆ์— ์ €์žฅํ•ด์•ผ ํ•œ๋‹ค
  • ์—ฌ๋Ÿฌ๊ฐœ ์ผ ๊ฒฝ์šฐ ์ถฉ๋Œ ํ˜น์€ ๋™๊ธฐํ™”์— ๋Œ€ํ•œ ์ด์Šˆ๊ฐ€ ๋ฐœ์ƒ
  • ๋””๋ฒ„๊น…๊ณผ ์ƒ์‚ฐ์„ฑ ํ–ฅ์ƒ์˜ ์ด์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Œ

(2) State is read-only

  • ์ƒํƒœ๋Š” ์ฝ๊ธฐ๋งŒ ํ—ˆ์šฉ
  • ์•ก์…˜์„ ํ†ตํ•ด์„œ๋งŒ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ
  • ๋ณ€ํ™”์˜ ์˜๋„๋ฅผ ํŒŒ์•…ํ•˜๊ณ  ์ค‘์•™์—์„œ ํ๋ฆ„ ๊ด€๋ฆฌ๋ฅผ ์—„๊ฒฉํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•จ

(3) Changes are made with pure functions

  • ๋ณ€ํ™”๋Š” ์ˆœ์ˆ˜ํ•จ์ˆ˜๋กœ๋งŒ ํ•ด์•ผํ•จ
  • ์ˆœ์ˆ˜ํ•จ์ˆ˜ : ์™ธ๋ถ€ ๊ฐ’์— ์˜์กดํ•˜์ง€ ์•Š๊ณ  ๋งค๊ฐœ๋ณ€์ˆ˜๋งŒ์„ ํ†ตํ•ด์„œ ๋ฐ˜ํ™˜๊ฐ’์„ ๋งŒ๋“ค์–ด ๋‚ด๋Š” ๊ฒƒ
1
let c = 3
2
const sum = (a, b) => {
3
return a + b + c
4
}
5
console.log(sum(1, 2)) //return 6
6
c = 5
7
console.log(sum(1, 2)) //return 8

4. Redux middleware

4.1 Redux ๋ฐ์ดํ„ฐ ํ๋ฆ„

1
โ† Action โ†
2
Reducer โ†’ Store โ†’ View

4.2 Middleware

1
middleware โ† store.dispatch โ† Action
2
โ†“ โ†‘
3
Reducer โ†’ Store โ†’ View
  • store.dispatch ํ•จ์ˆ˜์˜ ์‹คํ–‰ ๋’ค ์–ด๋– ํ•œ ์ž‘์—…์„ ํ•˜๊ธฐ ์œ„ํ•ด ํ˜ธ์ถœ

4.3 redux logger

  • prev state์™€ next state, action ๋“ฑ์„ ๋‚˜์—ดํ•ด ๋ณด์—ฌ์คŒ
  • ๋””๋ฒ„๊น…์„ ์œ„ํ•˜์—ฌ ์‚ฌ์šฉ

4.4 thunk

ํŠน์ • ์ž‘์—…์„ ๋‚˜์ค‘์— ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋งŒ๋“ค์–ด๋‘” ํ•จ์ˆ˜

1
const a = 5
2
console.log(a)
3
4
const func = () => {
5
return 5
6
}
7
8
console.log(func())

4.5 redux-thunk

  • ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ• ๋•Œ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” middleware
  • ๊ฐ์ฒด ๋Œ€์‹  ํ•จ์ˆ˜๋ฅผ Dispatch ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฒƒ
1
const increase = () => {
2
return {
3
type: 'INCREASE',
4
}
5
}
6
const increaseCountOnSleep = () => dispatch => {
7
dispatch({ type: 'WAIT' })
8
setTimeout(() => {
9
dispatch(increase())
10
}, 1000)
11
}
1
const successGetMyInfo = myInfo => dispatch => {
2
return {
3
type: 'SUCCESS_MY_INFO',
4
}
5
}
6
const getMyInfo = () => dispatch => {
7
dispatch({ type: 'REQUEST_MY_INFO' })
8
try {
9
//API ํ˜ธ์ถœ ํ•˜๋Š” ์ฝ”๋“œ
10
dispatch(successGetMyInfo('๋‚ด ์ •๋ณด'))
11
} catch (ex) {
12
dispatch(failureGetMyInfo())
13
}
14
}

4.6 redux-saga

action์˜ ๋ฐœ์ƒ์—ฌ๋ถ€๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋ง ํ•˜๋‹ค๊ฐ€ ๊ทธ ๋’ค ์ž‘์—…์„ ์ง„ํ–‰ ํ•˜๋„๋ก ํ•จ

1
const increase = () => ({ type: 'INCREASE' })
2
function* increaseSaga() {
3
yield delay(1000)
4
yield put(increse())
5
}
6
7
function* counterSaga() {
8
// takeEvery : ์•ก์…˜์„ ๋ชจ๋‹ˆํ„ฐ๋ง ํ•˜๊ณ  ๋ฐœ์ƒํ•˜๋ฉด increaseSaga ํ˜ธ์ถœ
9
yield takeEvery('INCREASE_ASYNC', increaseSaga)
10
}

4.7 redux-thunk vs redux-saga

๋‘˜๋‹ค ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ๊ฒƒ์œผ๋กœ, ์ƒํ™ฉ์— ๋งž๊ฒŒ ์‚ฌ์šฉ

  • redux thunk
    • pros
      • ๋‚ฎ์€ Boilerplate
      • ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ
    • cons
      • ์ž˜๋ชป ๋‹ค๋ค„์ง€๋ฉด ์ˆ˜์—†์ด ๋งŽ์€ ์ฝœ๋ฐฑ ์ง€์˜ฅ์— ๋น ์ง
  • redux saga
    • pros
      • ์ดˆ๊ธฐ์— ๊ตฌํ˜„ํ•ด์•ผํ•  Boilerplate๊ฐ€ ๋งŽ์Œ
      • ์ˆœ์ˆ˜ํ•จ์ˆ˜๋กœ ์ž‘์„ฑ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ ์ ์šฉ์ด ์‰ฌ์›€
    • cons
      • ๋†’์€ ๋Ÿฌ๋‹์ปค๋ธŒ (ES6 ์ œ๋„ˆ๋ ˆ์ดํ„ฐ)

5. Redux์—์„œ ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” hook

5.1 useSelector

store์— ์žˆ๋Š” ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•จ

1
const componentA = () => {
2
const value = useSelector(state => state.value)
3
return (
4
// ๋˜ ๋‹ค๋ฅธ View code
5
<View></View>
6
)
7
}

5.2 useDispatch

redux action์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ hook

1
const componentA = () => {
2
const dispatch = useDispatch()
3
const someAction = useCallback(() => {
4
dispatch(someActions())
5
}, [])
6
return (
7
// ๋˜๋‹ค๋ฅธ View code
8
<View></View>
9
)
10
}

5.3 useSelector (before hook)

hook์ด ์žˆ๊ธฐ ์ „์—๋Š” connect ํ•จ์ˆ˜๋ฅผ ํ†ตํ•˜์—ฌ ์ง„ํ–‰

1
const componentA = () => {
2
/* ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ์ฝ”๋“œ */
3
}
4
const mapStateToProps = state => {
5
return {
6
/* ๋‹ด๊ณ  ์‹ถ์€ state ์ง€์ •*/
7
}
8
}
9
const mapDispatchToProps = dispatch => {
10
return {
11
/*์‚ฌ์šฉํ•  action ์ €์žฅ*/
12
}
13
}
14
export default connect(mapStateToProps, mapDidpatchToProps)

5.4 createSelector

  • reselect package์— ์žˆ๋Š” ํ•จ์ˆ˜
  • Memoization๋“ฑ ์บ์‹ฑ์„ ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
1
const valueSelector = createSelector(
2
state => state.value,
3
value => {
4
/* ์–ด๋– ํ•œ ์—ฐ์‚ฐ*/
5
},
6
)
7
const componentA = () => {
8
const value = useSelector(valueSelector)
9
return (
10
// ๋˜๋‹ค๋ฅธ View code
11
<View></View>
12
)
13
}

6. Context API

  • React 16.3 ๋ฒ„์ „๋ถ€ํ„ฐ ์ง€์›
  • props-drilling์„ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด ํƒ„์ƒ
  • ๊ฐ„๋‹จํ•œ ์ „์—ญ๋ณ€์ˆ˜(e.g. theme, intl)๋ฅผ ์„ ์–ธํ• ๋•Œ ์‚ฌ์šฉ

6.1 Context API ๊ตฌ์„ฑ์š”์†Œ, Provider

๊ฐ’์„ ์ œ๊ณต ํ•ด์ฃผ๊ธฐ ์œ„ํ•˜์—ฌ root component๋กœ ์‚ฌ์šฉ

1
const SomeContext = createContext()
2
3
const componentA = () => {
4
return (
5
// ๋˜๋‹ค๋ฅธ View code
6
<SomeContext.Provider value={'testValue'}>{/* ๊ฐ’์„ ์‚ฌ์šฉํ•ด์•ผํ•˜๋Š” ์ปดํผ๋„ŒํŠธ๋“ค */}</SomeContext.Provider>
7
)
8
}

6.2 Context API ๊ตฌ์„ฑ์š”์†Œ, Consumer

์ œ๊ณต๋œ ๊ฐ’์— ์ ‘๊ทผ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š”๊ฒƒ

1
const componentB = () => {
2
return (
3
// ๋˜๋‹ค๋ฅธ View code
4
<SomeContext.Consumer>
5
{context => {
6
/* child์— ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ์—์„œ๋งŒ ๊ฐ’ ์‚ฌ์šฉ ๊ฐ€๋Šฅ */
7
}}
8
</SomeContext.Consumer>
9
)
10
}

6.3 Redux vs Context API

  • Context API : ์ƒํƒœ ๊ด€๋ฆฌ ๋„๊ตฌ X, ์ „์—ญ ๋ณ€์ˆ˜ ๊ด€๋ฆฌ O
  • ์ƒํƒœ๊ด€๋ฆฌ ๋„๊ตฌ์˜ ์กฐ๊ฑด
    • ์ดˆ๊ธฐ๊ฐ’์„ ์ €์žฅํ•˜๋Š”๊ฐ€?
    • ์Šค์Šค๋กœ ๊ฐ’์„ ์ฝ์–ด์˜ฌ ์ˆ˜ ์žˆ๋Š”๊ฐ€?
    • ์Šค์Šค๋กœ ๊ฐ’ ์—…๋ฐ์ดํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•œ๊ฐ€?
  • Context API
    • ์ดˆ๊ธฐ๊ฐ’์„ ์ €์žฅํ•˜๋Š”๊ฐ€? (Provider์—์„œ value๋“ฑ์„ ์„ค์ • ๊ฐ€๋Šฅ)
    • ์Šค์Šค๋กœ ๊ฐ’์„ ์ฝ์–ด์˜ฌ ์ˆ˜ ์žˆ๋Š”๊ฐ€? (์Šค์Šค๋กœ state๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š์•„ ๊ฐ’์„ ์ „๋‹ฌ ํ•ด์ค˜์•ผํ•จ)
    • ์Šค์Šค๋กœ ๊ฐ’ ์—…๋ฐ์ดํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•œ๊ฐ€ ? (์Šค์Šค๋กœ state๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š์•„ updateํ•จ์ˆ˜๋ฅผ ํ•จ๊ป˜ ์ „๋‹ฌํ•ด์ค˜์•ผ ํ•จ)
  • Redux
    • ์ดˆ๊ธฐ๊ฐ’์„ ์ €์žฅํ•˜๋Š”๊ฐ€? (reducer ์ƒ์„ฑ์‹œ ์ดˆ๊ธฐ๊ฐ’ ์ง€์ • ๊ฐ€๋Šฅ)
    • ์Šค์Šค๋กœ ๊ฐ’์„ ์ฝ์–ด์˜ฌ ์ˆ˜ ์žˆ๋Š”๊ฐ€? (selector, mapStateToProps ๋“ฑ ํ•จ์ˆ˜๋ฅผ ํ†ตํ•˜์—ฌ ์ฝ์–ด์˜ค๊ธฐ ๊ฐ€๋Šฅ)
    • ์Šค์Šค๋กœ ๊ฐ’ ์—…๋ฐ์ดํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•œ๊ฐ€? (dispatch action์„ ํ†ตํ•ด์„œ ๊ฐ€๋Šฅ)
  • ์–ธ์ œ Context API๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๊ฐ€
    • ์ฃผ๋กœ staticํ•œ ์ž˜ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ์ •๋ณด์— ๋Œ€ํ•ด์„œ ์ ์šฉ
    • App theme ์ €์žฅ (light, dark) ๋‹ค๊ตญ์  ์•ฑ์—์„œ ์–ธ์–ด ํŒฉ ๋“ฑ

7. Recoil

  • 2020๋…„๋„ ๋ฐœํ‘œ, (cf. https://recoiljs.org/ko/ )
  • Redux, MobX๋“ฑ ๊ธฐ์กด ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์•„์‰ฌ์šด ์ ๋“ค์„ ๊ทน๋ณตํ•˜๊ณ ์ž ํƒ„์ƒ
  • Redux์˜ ์žฅ๋‹จ์ 
    • pros
      • ๊ทธ๋™์•ˆ ๋งŽ์€ ๊ฒ€์ฆ์„ ๊ฑฐ์นœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
      • redux-logger ๋“ฑ ๋””๋ฒ„๊ทธ๋ฅผ ์œ„ํ•œ ํŽธ์˜๊ฐ€ ์ž˜ ๊ฐ–์ถฐ์ ธ ์žˆ์Œ
    • cons
      • ๋†’์€ ํ•™์Šต๋น„์šฉ(๋Ÿฌ๋‹์ปค๋ธŒ)
      • boilerplate๊ฐ€ ๋‹ค์†Œ ์žˆ๋Š” ํŽธ

7.1 Recoil ๊ตฌ์„ฑ์š”์†Œ

7.1.1 Atom

์ƒํƒœ์˜ ๋‹จ์œ„, ์—…๋ฐ์ดํŠธ ๋˜๋Š” subscribe ๋“ฑ์ด ๊ฐ€๋Šฅ

1
const fontSize = atom({
2
key: 'UNIQUE_KEY',
3
value: {
4
/* ์ƒํƒœ ๊ด€๋ฆฌ์‹œ ์‚ฌ์šฉ ํ•  value*/
5
},
6
})
7
8
const componentA = () => {
9
const [fontSize, setFontSize] = useRecoilState(myState)
10
return /* View return */ <View></View>
11
}

7.1.2 selectors

atoms๋‚˜ selector์˜ ํŒŒ์ƒ๋ฐ์ดํ„ฐ๋ฅผ ๊ณ„์‚ฐํ•˜๋Š”๋ฐ ์‚ฌ์šฉ

1
const fontSizeLabelState = selector({
2
key: 'UNIQUE_KEY',
3
get: ({ get }) => {
4
const fontSize = get(fontSizeState)
5
return `fontSize is ${fontSize}`
6
},
7
})
8
9
const componentA = () => {
10
const fontSizeLabel = useRecoilValue(fontSizeLabelState)
11
}