๐Ÿฅณ 2024๋…„ 8์›”ํšŒ๊ณ 

@Troy ยท September 15, 2024 ยท 16 min read

์ •์‹ ์—†์ด 9์›”์„ ๋ณด๋‚ด๋‹ค ๋ณด๋‹ˆ 8์›”์ด ๋๋‚˜๊ณ ๋„ ๋ฒŒ์จ 15์ผ์ด๋‚˜ ์ง€๋‚˜๋ฒ„๋ ธ๋‹ค. ์กฐ๊ธˆ ๋Šฆ์—ˆ์ง€๋งŒ 8์›”์„ ํšŒ๊ณ ํ•˜๊ณ  9์›”์˜ ๊ณ„ํš์„ ์„ธ์›Œ๋ณด๋ ค ํ•œ๋‹ค.

8์›”์˜ ์•ก์…˜์•„์ดํ…œ

7์›”์— ๊ณ„ํšํ–ˆ๋˜ 8์›”์˜ ์•ก์…˜์•„์ดํ…œ์€ ์•„๋ž˜ 4๊ฐ€์ง€์ด๋‹ค. ์ด์ค‘ ์•ž์„  2๊ฐ€์ง€๋Š” ์™„๋ฃŒํ–ˆ์ง€๋งŒ, useFunnel ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ถ„์„ ํ›„ ์˜คํ”ˆ์†Œ์Šค ๋ถ„์„ ๊ณผ์ • ๊ณต์œ ํ•˜๊ธฐ์™€ ์ปดํฌ๋„ŒํŠธ ์„ค๊ณ„ ์ถ”๊ฐ€ ์ž‘์—… ์ง„ํ–‰ํ•˜๊ธฐ๋Š” ํ•˜์ง€ ๋ชปํ–ˆ๋‹ค.

  • MMKV ๋ฐ์ดํ„ฐ ์Šคํ† ๋ฆฌ์ง€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ ์šฉ ํ›„ ์„ฑ๋Šฅ ๊ฐœ์„  ๊ฒฐ๊ณผ ์ •๋ฆฌํ•˜๊ธฐ
  • React Query๋ฅผ ์ด์šฉํ•œ ๋น„๋™๊ธฐ ์ƒํƒœ ๊ฐœ์„  ๋ฐฉํ–ฅ ๊ณต์œ ํ•˜๊ธฐ
  • useFunnel ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ถ„์„ ํ›„ ์˜คํ”ˆ์†Œ์Šค ๋ถ„์„ ๊ณผ์ • ๊ณต์œ ํ•˜๊ธฐ
  • ์ปดํฌ๋„ŒํŠธ ์„ค๊ณ„ ์ถ”๊ฐ€ ์ž‘์—… ์ง„ํ–‰ํ•˜๊ธฐ

MMKV ๋ฐ์ดํ„ฐ ์Šคํ† ๋ฆฌ์ง€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ ์šฉ ํ›„ ์„ฑ๋Šฅ ๊ฐœ์„  ๊ฒฐ๊ณผ ์ •๋ฆฌํ•˜๊ธฐ

์ง€๋‚œ ๋‹ฌ์— ๋ฒค์น˜๋งˆํฌ ์ง€ํ‘œ๋“ค์„ ๊ณต์œ ํ•˜๋ฉด์„œ ๊ธฐ์กด ์‚ฌ์šฉํ•˜๋˜ Async Storage์—์„œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ MMKV๋กœ ์ •ํ–ˆ๊ณ , ์ด๋ฒˆ ๋‹ฌ์—๋Š” ์‹ค์ œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ง„ํ–‰ํ–ˆ๋‹ค.

์ด์ „ ๋ฐœํ‘œ์—์„œ ์•ฑ์‹œ์ž‘๊ณผ์ •์—์„œ API ํ˜ธ์ถœ๊ณผ ํ•จ๊ป˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์‹œ๊ฐ„์„ ์ธก์ •ํ–ˆ๋˜ ๋ถ€๋ถ„์ด API ์˜์กด์„ฑ์„ ์ œ๊ฑฐํ–ˆ์„ ๋•Œ ์ˆ˜์น˜๋ฅผ ๋ณด๋ฉด ๋” ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์˜๊ฒฌ์„ ์ฃผ์…จ๊ณ , ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์•ฑ์„ ๋นŒ๋“œํ•˜๊ณ  ๋น„๊ตํ•ด ๋ณด์•˜์„ ๋•Œ MMKV๋Š” 150ms์—์„œ 65ms๋กœ 50%์ด์ƒ ๊ฐœ์„ ๋œ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์˜€๋‹ค.

ํ•˜์ง€๋งŒ ๋กœ์ปฌ DB์ž์ฒด๋ฅผ ์ œํ’ˆ๋‚ด์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์ง€ ์•Š์•„ ์—„์ฒญ๋‚˜๊ฒŒ ํฐ ๊ฐœ์„ ์„ ๊ฐ€์ ธ์˜ค์ง€๋Š” ๋ชปํ•  ๊ฒƒ์œผ๋กœ ๋ณด์ด์ง€๋งŒ, Async Storage์—์„œ ๊ฐ„ํ˜น 200ms๊นŒ์ง€ ๊ฑธ๋ฆฌ๋Š” ์กฐํšŒ ์‹œ๊ฐ„์ด ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ด๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๊ฑด ์˜๋ฏธ์žˆ๋‹ค๊ณ  ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

์ž‘์—… ๋‚ด์šฉ

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ž‘์—…์€ ์ด์ „ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ž‘์—…๋“ค์— ๋น„ํ•ด ์ƒ๋Œ€์ ์œผ๋กœ ๊ฐ„๋‹จํ–ˆ์ง€๋งŒ, ํ…Œ์ŠคํŠธ๋‚˜ ๊ธฐ์กด ์ฝ”๋“œ๋“ค์ด Async Storage ์ž์ฒด์— ์˜์กดํ•˜๊ณ  ์žˆ์–ด์„œ ์ด๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ์ž‘์—…์ด ์กฐ๊ธˆ ๊นŒ๋‹ค๋กœ์› ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ ๊ธฐ์กด ์•„ํ‚คํ…์ฒ˜์—์„œ AsyncStorage ์ž์ฒด์— ์˜์กดํ•˜๊ธฐ ๋ณด๋‹ค ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์˜์กดํ•˜๊ฒŒ ํ•ด ์ดํ›„ ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์ „ํ™˜ํ•˜๋”๋ผ๋„ ์ตœ์†Œํ•œ์˜ ์ˆ˜์ •์œผ๋กœ ๊ฐ€๋Šฅํ•˜๋„๋ก ์„ค๊ณ„๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ์ž‘์—…์ด ํ•„์š”ํ–ˆ๋‹ค.

// ๊ธฐ์กด ์˜ˆ์‹œ

class KeyValueStorage {
  async setItem(key, value) {
    await AsyncStorage.setItem(key, value)
  }
  async getItem(key) {
    return AsyncStorage.getItem(key)
  }
}

class Repository {
  constructor() {
    this.storage = new KeyValueStorage()
  }

  async getLocalData(key) {
    return this.storage.getItem(key)
  }
}

// ์ดํ›„

interface Storage {
  getItem: (key: string) => Promise<string | null>
  setItem: (key: string, value: string) => Promise<void>
}
const storage: Storage = new MMKVStorage()

class MMKVStorage implements Storage {
  async setItem(key, value) {
    await storage.set(key, value)
  }
  async getItem(key) {
    // ๊ธฐ์กด๊ณผ ํ˜ธํ™˜์„ ์œ„ํ•ด JSON.parse๋ฅผ ์‚ฌ์šฉ
    const value = storage.getString(key)
    return value ? JSON.parse(value) : null
  }

  async setItem(key, value) {
    storage.set(key, JSON.stringify(value))
  }
}

class Repository {
  constructor(storage: Storage) {
    this.storage = storage
  }

  async getLocalData(key) {
    return this.storage.getItem(key)
  }
}

const repository = new Repository(storage)

์กฐ๊ธˆ ์•„์‰ฌ์› ๋˜ ๋ถ€๋ถ„์€ MMKV ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž์ฒด๊ฐ€ AsyncStorage์™€๋Š” ๋‹ค๋ฅด๊ฒŒ ๊ธฐ๋ณธ ์ž๋ฃŒํ˜•๋“ค์„ ๋ฐ”๋กœ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๊ฒŒ ์ง€์›ํ•˜์ง€๋งŒ, ๊ธฐ์กด AsyncStorage์™€์˜ ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด JSON.Stringinfy๊ฐ€ ํ•„์š”ํ•˜๊ณ , ๊ธฐ์กด ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋งž์ถ”๊ธฐ ์œ„ํ•ด ๋ถˆํ•„์š”ํ•œ promise๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ–ˆ๋‹ค.

์ถ”๊ฐ€๋กœ ์œ ํ‹ธ๋“ค์˜ ํ…Œ์ŠคํŠธ๊ฐ€ AsyncStorage์— ์˜์กดํ•˜๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ์— storage๋ฅผ ์ฃผ์ž…ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ˆ˜์ •ํ•˜๋‹ค๋ณด๋‹ˆ, ์˜คํžˆ๋ ค ์‚ฌ์šฉ์ฒ˜ ์ฝ”๋“œ๋“ค์ด ๋ณต์žกํ•ด๋ณด์ด๊ธฐ๋„ ํ–ˆ๋‹ค. ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•œ ๊ฒŒ ๋งž๋Š”์ง€ ์กฐ๊ธˆ ๋” ๊ณ ๋ฏผํ•ด๋ด์•ผ ํ•  ๊ฒƒ ๊ฐ™๋‹ค.

์ดํ›„ ์ž‘์—… ์ž์ฒด๋Š” ๋ชจ๋‘ ์™„๋ฃŒํ–ˆ์ง€๋งŒ ์•„์ง Native Module์—์„œ ์–ด๋–ป๊ฒŒ ํ† ํฐ์„ ๊ฐ€์ ธ์˜ค๊ณ  ํ• ์ง€์— ๋Œ€ํ•ด ๋…ผ์˜๊ฐ€ ์ง„ํ–‰๋˜๋ฉด์„œ ๋ฐฐํฌ๋Š” ๋ณด๋ฅ˜๋˜๊ฒŒ ๋˜์—ˆ๋‹ค. ๋ฐฐํฌ ์ดํ›„ ์„ฑ๋Šฅ์„ ์ธก์ •ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์ •๋ฆฌํ•ด๋ณด๋Š” ์ž‘์—…์„ 9์›” ๋˜๋Š” 10์›”์— ์ง„ํ–‰ํ•ด๋ณด๋ ค ํ•œ๋‹ค.

React Query๋ฅผ ์ด์šฉํ•œ ๋กœ๋”ฉ๊ฒฝํ—˜ ๊ฐœ์„  ์ž‘์—… ๊ณต์œ ํ•˜๊ธฐ

React Query๋ฅผ ์ด์šฉํ•ด Suspense๋กœ ๋น„๋™๊ธฐ ์ƒํƒœ๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ , ์ „์—ญ ๋กœ๋”ฉ๊ฒฝํ—˜์„ ๊ฐœ์„ ํ•˜๋Š” ์ž‘์—…์„ ์™„๋ฃŒํ–ˆ๋‹ค. ์ž‘์—… ์ž์ฒด๋Š” ์™„๋ฃŒํ–ˆ์ง€๋งŒ, ๋‹ค์–‘ํ•œ ๋„๋ฉ”์ธ์—์„œ ์ž‘์—…์ด ์ง„ํ–‰๋œ ๋งŒํผ QA๊ฐ€ ๊ฐ๊ฐ ์ง„ํ–‰์ด ํ•„์š”ํ•˜๋‹ค๋ณด๋‹ˆ ์™„์ „ํžˆ ๋ฐฐํฌ๋ฅผ ํ•˜์ง€๋Š” ๋ชปํ–ˆ๋‹ค.

์ž‘์—… ๊ณผ์ •์—์„œ ์ค‘๊ฐ„์ค‘๊ฐ„์— ๊ณต์œ ํ•˜๋ ค ํ–ˆ์ง€๋งŒ, ๋ฐœํ‘œ๋ฅผ ํ•˜๊ธฐ์— ๋‹ค๋ฅธ ๋ถ„๋“ค์˜ ๋ฐœํ‘œ ์ผ์ •์ด ๊ฒน์ณค๊ณ , ์˜ˆ๋น„๊ตฐ๋„ ๋‹ค๋…€์˜ค๋‹ค ๋ณด๋‹ˆ ์ž‘์—…๊ณผ์ •์ด ์•„๋‹ˆ๋ผ ๊ฒฐ๊ณผ๋ฅผ ๊ณต์œ ํ•˜๋Š” ๋ฐฉ์‹์ด ๋˜์–ด๋ฒ„๋ ธ๋‹ค. ์ฝ”๋“œ๋ฆฌ๋ทฐ์˜ ๋ฆฌ๋ทฐ์–ด๋กœ ๋ฐฐ์ •๋˜์‹  ๋ถ„๋“ค์€ ์ž‘์—…๊ณผ์ •์— ๋Œ€ํ•ด ์ดํ•ดํ•˜์‹ค ์ˆ˜ ์žˆ์—ˆ์ง€๋งŒ, ๋‹ค๋ฅธ ๋ถ„๋“ค์€ ๊ฒฐ๊ณผ๋งŒ์„ ๋ณด๊ฒŒ ๋œ ๊ฒƒ ๊ฐ™์•„ ์•„์‰ฌ์› ๊ณ , ๋ฌธ์ œ์˜ ํฌ๊ธฐ์— ๋น„ํ•ด ๋„ˆ๋ฌด ์–ด๋ ต๊ฒŒ ์ž‘์—…์ด ๋œ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›๊ธฐ๋„ ํ–ˆ๋‹ค.

ํ•ด๋‹น ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์œผ๋ฉด์„œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ, ํ•„์š”ํ•œ ๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ ์–ด๋–ค ๋ถ€๋ถ„์ด ๋ฌธ์ œ์˜€๋Š”์ง€ ์˜จ์ „ํžˆ ์ดํ•ดํ•˜๋Š”๋ฐ์—๋Š” ์‹œ๊ฐ„์ด ํ•„์š”ํ–ˆ๋‹ค. ์กฐ๊ธˆ ๋” ์ผ์ฐ ๊ณต์œ ํ–ˆ๋‹ค๋ฉด ๊ดœ์ฐฎ์•˜์„๊นŒ ์•„์‰ฌ์›€์ด ๋‚จ์•„ ํ•œ๋™์•ˆ ๊ดด๋กœ์›Œํ•˜๊ธฐ๋„ ํ–ˆ๋‹ค.

๊ธฐ์ˆ ์  ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•˜์ง€๋งŒ, ๊ทธ ๊ณผ์ •์—์„œ ๋‹ค๋ฅธ ๋ถ„๋“ค๊ณผ ์†Œํ†ตํ•˜๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•˜๋‹ค๋Š” ๊ฒƒ์„ ๋‹ค์‹œ ํ•œ๋ฒˆ ๋Š๋‚„ ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๋‚˜๋ฆ„์˜ ์„ฑ์žฅํ†ต์„ ๊ฒช์—ˆ๋˜ ์ž‘์—…์ด์—ˆ๊ณ , ํ•ด๋‹น ๋ฐœํ‘œ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด 9์›”์ค‘์œผ๋กœ ํ•ด๋‹น ์ž‘์—…์— ๋Œ€ํ•œ ๊ธ€์„ ์ž‘์„ฑํ•ด๋ณด๋ ค ํ•œ๋‹ค.

ํ•˜์ง€ ๋ชปํ•œ ์•ก์…˜์•„์ดํ…œ

useFunnel ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ถ„์„ ํ›„ ์˜คํ”ˆ์†Œ์Šค ๋ถ„์„ ๊ณผ์ • ๊ณต์œ ํ•˜๊ธฐ์™€ ์ปดํฌ๋„ŒํŠธ ์„ค๊ณ„ ์ถ”๊ฐ€ ์ž‘์—… ์ง„ํ–‰ํ•˜๊ธฐ๋Š” ์ง„ํ–‰ํ•˜์ง€ ๋ชปํ–ˆ๋‹ค.

8์›” ๋™์•ˆ ์•„์ง ํ”„๋ก ํŠธ์—”๋“œ์—”๋“œ accelarator ๋ฉ˜ํ† ๋ง ๊ณผ์ • ์ค‘์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‘˜์งธ์ฃผ๊นŒ์ง€ ๋งค์ฃผ ๋‹ค๋ฅธ ์ฃผ์ œ๋กœ ๊ณผ์ œ๋ฅผ ๊ฐœ์„ ์‹œ์ผœ๋‚˜๊ฐ€๋ฉด์„œ ์‹œ๊ฐ„์ด ๋ถ€์กฑํ–ˆ๋‹ค.

9์›”์—๋Š” ๊ผญ ์ด ๋‘๊ฐ€์ง€์— ๋Œ€ํ•ด ์ง„ํ–‰ํ•ด๋ณด๋ ค ํ•œ๋‹ค.

์ƒˆ๋กญ๊ฒŒ ์ง„ํ–‰ํ–ˆ๋˜ ํ™œ๋™๋“ค

7์›”๊ณผ ๋™์ผํ•˜๊ฒŒ ํ”„๋ก ํŠธ์—”๋“œ์—”๋“œ accelarator ๋ฉ˜ํ† ๋ง ๊ณผ์ •์˜ 3์ฃผ์ฐจ, 4์ฃผ์ฐจ ๊ณผ์ •์„ ์ง„ํ–‰ํ•ด ๋งˆ๋ฌด๋ฆฌํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. 3์ฃผ์ฐจ ์ฃผ์ œ๋Š” ์šฐ์•„ํ•œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ, 4์ฃผ์ฐจ๋Š” UX ๊ฐœ์„ ์— ๋Œ€ํ•œ ์ฃผ์ œ๋กœ ์ง„ํ–‰๋˜์—ˆ๋‹ค. ๊ฐ ์ฃผ์ฐจ๋ณ„ ์ฃผ์ œ์— ๋Œ€ํ•œ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด๋ณด์ž.

3์ฃผ์ฐจ - ์šฐ์•„ํ•œ ๋น„๋™๊ธฐ ์ƒํƒœ๊ด€๋ฆฌ

3์ฃผ์ฐจ ์ฃผ์ œ์˜ ์šฐ์•„ํ•œ ๋น„๋™๊ธฐ์—๋Š” Promise, async/await, Generator, Observable ๋“ฑ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์–ด๋–ป๊ฒŒ ํ•˜๋ฉด ๋” ์šฐ์•„ํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์„์ง€์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ–ˆ๋‹ค.

๋น„๋™๊ธฐ ์ƒํƒœ๋Š” ์‚ฌ์‹ค React์—์„œ ๊ฐ€์žฅ ์–ด๋ ค์šด ๋ถ€๋ถ„ ์ค‘ ํ•˜๋‚˜๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค. ํ•˜๋‚˜์˜ ์š”์ฒญ์— ๋Œ€ํ•ด์„œ๋Š” ๋กœ๋”ฉ/์„ฑ๊ณต/์‹คํŒจ๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ  n๊ฐœ์˜ ์š”์ฒญ์— ๋Œ€ํ•ด์„œ๋Š” 3^n๊ฐœ์˜ ์ƒํƒœ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ฐธ๊ณ ์ž๋ฃŒ: ํ”„๋ก ํŠธ์—”๋“œ ์›น ์„œ๋น„์Šค์—์„œ ์šฐ์•„ํ•˜๊ฒŒ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌํ•˜๊ธฐ

์š”์ฒญ์— ๋”ฐ๋ฅธ ๋น„๋™๊ธฐ ์ƒํƒœ์˜ ๊ฒฝ์šฐ์˜์ˆ˜.png
์š”์ฒญ์— ๋”ฐ๋ฅธ ๋น„๋™๊ธฐ ์ƒํƒœ์˜ ๊ฒฝ์šฐ์˜์ˆ˜.png

"์—์ด, ์™ ๋งŒํ•˜๋ฉด ๋‹ค ์„ฑ๊ณตํ•˜์ž–์•„" ๋ผ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ฐœ๋ฐœ์ž์˜ ์‹ค์ˆ˜, ์„œ๋ฒ„์˜ ์žฅ์• , ๋„คํŠธ์›Œํฌ์˜ ๋ฌธ์ œ ๋“ฑ ๋‹ค์–‘ํ•œ ์ด์œ ๋กœ ์‹คํŒจํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฌํ•œ ์ƒํƒœ๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ•œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ด๋Ÿฌํ•œ ๊ณ ๋ ค๋ฅผ ์ผ์ผ์ด manualํ•˜๊ฒŒ ๊ด€๋ฆฌํ•˜๊ธฐ์—๋Š” ์ฝ”๋“œ๊ฐ€ ๋„ˆ๋ฌด ๋ณต์žกํ•ด์ง€๊ฑฐ๋‚˜ ํŠน์ • ๋กœ์ง๊ฐ€ ๋น ์ง€๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์„ ์–ธ์ ์ธ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ–ˆ์„ ๋•Œ ๊ฐ ์ƒํƒœ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ๋†“์น˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.

// ๋ช…๋ น์ ์ธ ๋ฐฉ์‹
function Profile(){
	const foo=useAsyncValue(()=>{
		return fetchFoo()
	});

	const bar=useAsyncValue(()=>{
		if(foo.error||!foo.data){
			return undefined
		}
		return fetchBar(foo.data);
	});

	if(foo.error||bar.error) return <div> ๋กœ๋”ฉ ์‹คํŒจ.. </div>
	if(!foo.data||!bar.data) return <div> ๋กœ๋”ฉ์ค‘... </div>
	return <div>{foo.data.name}๋‹˜ {bar} ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค. </div>
}

// ์„ ์–ธ์ ์ธ ๋ฐฉ์‹

function FooBar(){
    const foo= useAsyncValue(()=> fetchFoo());
    const bar=useAsyncValue(()=>fetchBar(foo));
    return <div>{foo}{bar}</div>
}

function App(){
    return (
        <ErrorBoundary fallback={<MyErrorPage/>}>
            <Suspenese fallback={<Loader/>}>
                <FooBar/>
            </Suspense>
        </ErrorBoundary>
    )
}

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

์ด๋ ‡๊ฒŒ ์„ ์–ธ์ ์ธ ๋น„๋™๊ธฐ ์ƒํƒœ ๊ด€๋ฆฌ์— ๋Œ€ํ•ด ์˜๋…ผํ•˜๊ณ  ์•Œ์•„๊ฐ€๋ฉด์„œ, 3๋ถ„๊ธฐ ์ž‘์—…์ค‘ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ๋Š” ๋กœ๋”ฉ ๊ฒฝํ—˜๊ฐœ์„  ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๋ฉด์„œ Suspense๋ฅผ ์ด์šฉํ•ด ์„ ์–ธ์ ์œผ๋กœ ๋น„๋™๊ธฐ ์ƒํƒœ๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ ์šฉํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

์ถ”๊ฐ€๋กœ ๋‚˜๋Š” ๋†“์ณค๋˜ ๋ถ€๋ถ„์ด์ง€๋งŒ ํ•จ๊ป˜ ์ง„ํ–‰ํ•˜๋Š” ๋ถ„์€ ์ž‘์„ฑ๋œ ํผ ์ •๋ณด๋ฅผ ์—ฌ๋Ÿฌ๋ฒˆ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ๋ฅผ ๊ณ ๋ คํ•ด Post ์š”์ฒญ์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋””ํ…Œ์ผ์„ ๋ณด์—ฌ์ฃผ์‹œ๊ธฐ๋„ ํ•ด์„œ, ๋‚ด๊ฐ€ ๋†“์ณค๋˜ ๋””ํ…Œ์ผ์— ๋Œ€ํ•œ ๋ถ€๋ถ„๋“ค์„ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

function Form() {
  const [name, setName] = useState("")
  const [email, setEmail] = useState("")
  const { mutate, isPending } = useMutation({
    mutationFn: submitForm,
    onSuccess: () => {
      navigate("/success")
    },
  })

  return (
    <form
      onSubmit={e => {
        e.preventDefault()
        mutate({ name, email })
      }}
    >
      <input value={name} onChange={e => setName(e.target.value)} />
      <input value={email} onChange={e => setEmail(e.target.value)} />
      <button type="submit" disabled={isPending}>
        ์ œ์ถœ
      </button>
    </form>
  )
}

4์ฃผ์ฐจ - UX ๊ฐœ์„ 

UX๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ FCP/LCP ๊ฐœ์„  ๋ฐฉ๋ฒ•๊ณผ ์Šค์ผˆ๋ ˆํ†ค UI๋ฅผ ์ด์šฉํ•œ ์‚ฌ์šฉ์ž ์ฒด๊ฐ ๋กœ๋”ฉ ์†๋„๋ฅผ ์ค„์ด๋Š” ๋‚ด์šฉ์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ–ˆ๋‹ค.

FCP/LCP๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๋‚˜๋Š” ๋ฒˆ๋“ค์˜ ํฌ๊ธฐ๋ฅผ ์ค„์—ฌ์„œ ์ดˆ๊ธฐ๋กœ๋”ฉ ์‹œ๊ฐ„์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์ง€ ์•Š์„๊นŒ ์ƒ๊ฐํ•˜๊ณ  ์ž‘์—…ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ์ฝ”๋“œ์Šคํ”Œ๋ฆฟํŒ…๊ณผ CRA ๋Œ€์‹  vite๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•˜๋Š” ๋ฐฉ์‹์„ ์ ์šฉํ•ด๋ณด์•˜๋‹ค.

ํ•˜์ง€๋งŒ ์•„์‰ฝ๊ฒŒ๋„ ๊ณผ์ œ ์ž์ฒด์˜ ํŽ˜์ด์ง€ ์ˆ˜๊ฐ€ ๋งŽ์ง€ ์•Š๊ณ  CRA์˜ webpack์—์„œ vite์˜ esbuild์™€ rollup์œผ๋กœ ๋ณ€๊ฒฝํ•ด ๋ณธ ๊ฒฐ๊ณผ ๋ฒˆ๋“ค ํฌ๊ธฐ๋Š” ์ฐจ์ด๊ฐ€ ๋‚ฌ์ง€๋งŒ, FCP์— ํฐ ์˜ํ–ฅ์„ ์ฃผ์ง€ ๋ชปํ–ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์ ‘๊ทผ์€ ์‚ฌ์‹ค ๋ฐ์ดํ„ฐ์ ์œผ๋กœ ์ ‘๊ทผํ•˜๋Š”๋ฐ ๋„ˆ๋ฌด ๋งŽ์€ ์‹œ๊ฐ„์„ ํˆฌ์žํ–ˆ๊ณ , ๊ฒฐ๊ณผ์ ์œผ๋กœ๋Š” ํฐ ์„ฑ๊ณผ๋ฅผ ๋ณด์ง€ ๋ชปํ–ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

๋‹ค๋ฅธ ๋ถ„๋“ค์€ ์Šค์ผˆ๋ ˆํ†ค UI์™€ ํ™”๋ฉด ์ „ํ™˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๊ตฌํ˜„ํ•˜๊ณ , ํ™”๋ฉด ์ดํƒˆ์‹œ์— ์ดํƒˆ ๋ฐฉ์ง€ ๋ชจ๋‹ฌ์„ ๋…ธ์ถœํ•˜๋Š” ๋“ฑ ๋ณด๋‹ค ์œ ์ €๊ฐ€ ๋Š๋‚„ ์ˆ˜ ์žˆ๋Š” ์‹ค์งˆ์ ์ธ UX ๊ฐœ์„ ์„ ์ง„ํ–‰ํ•˜์…จ๋‹ค. ์ด๊ฑธ ๋ณด๋ฉด์„œ ์œ ์ €์—๊ฒŒ ์œ ์˜๋ฏธํ•œ ๊ฐœ์„ ์€ ๋ฌด์—‡์ผ์ง€, ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์„์ง€์— ๋Œ€ํ•ด ๋‹ค์‹œ ํ•œ๋ฒˆ ์ƒ๊ฐํ•ด๋ณด๊ณ  ๋ฐฐ์šธ ์ˆ˜ ์žˆ๋Š” ์ˆœ๊ฐ„์ด์—ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋‚˜์˜ ๊ณผ์ œ์— ๋Œ€ํ•ด ๋‹ค์–‘ํ•œ ์ฃผ์ œ๋กœ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๋‚ด๊ฐ€ ๋†“์น˜๊ณ  ์žˆ๋˜ ์ , ๋‹ค๋ฅธ ๋ถ„๋“ค๊ป˜์„œ ์ค‘์š”ํ•˜๊ฒŒ ์ƒ๊ฐํ•˜๋Š” ์ ๋“ค์„ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์—ˆ๋˜ ์ •๋ง ์†Œ์ค‘ํ•œ ์‹œ๊ฐ„์ด์—ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋งˆ์Œ ํ•œ์ผ ์— ์ด์ œ ์™ ๋งŒํ•œ ๊ฑด ๋‹ค ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ณ , ์ž˜ํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์ƒ๊ฐ์— ๋น ์ ธ์žˆ๋˜ ๋ชจ์Šต์„ ๋Š๊ผˆ๊ณ , ๋‹ค์‹œ ํ•œ๋ฒˆ ๊ฒธ์†ํ•ด์ง€๊ณ  ๋”์šฑ ๋” ๋ฐฐ์šฐ๊ณ  ์‹ถ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

React Native๋ฅผ ์ด์šฉํ•œ ์ž‘์—…๋„ ์ข‹์ง€๋งŒ ์›น๊ฐœ๋ฐœ์— ๋Œ€ํ•œ ์ง€์‹์„ ๋”์šฑ ๋” ๊นŠ๊ฒŒ ๋ฐฐ์šฐ๊ณ  ์‹ถ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

9์›”์˜ ์•ก์…˜์•„์ดํ…œ

๊ธ€์„ ์ž‘์„ฑํ•˜๋Š” ์‹œ์ ์— 9์›” ์ค‘์ˆœ์ด ์ง€๋‚˜๊ฐ€๊ณ  ์žˆ์ง€๋งŒ, 9์›”์˜ ์•ก์…˜์•„์ดํ…œ์„ ์ •๋ฆฌํ•ด๋ณด๋ คํ•œ๋‹ค.

  • MMKV ๋ฐ์ดํ„ฐ ์Šคํ† ๋ฆฌ์ง€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ ์šฉ ์ž‘์—… ์ •๋ฆฌํ•˜๊ธฐ
  • React Query๋ฅผ ์ด์šฉํ•œ ๋กœ๋”ฉ ๊ฒฝํ—˜ ๊ฐœ์„  ์ž‘์—… ์ •๋ฆฌํ•˜๊ธฐ
  • useFunnel ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ถ„์„ ํ›„ ์˜คํ”ˆ์†Œ์Šค ๋ถ„์„ ๊ณผ์ • ๊ณต์œ ํ•˜๊ธฐ

์ด๋ฒˆ ๋‹ฌ์—๋Š” 8์›”์— ๋ชปํ–ˆ๋˜ ์ž‘์—…๋“ค๊ณผ ํ•จ๊ป˜ ์™„๋ฃŒํ•œ ์ž‘์—…๋“ค์— ๋Œ€ํ•ด์„œ๋Š” ์„ฑ๊ณผ์™€ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…๋“ค์— ๋Œ€ํ•œ ๊ธ€์„ ์ž‘์„ฑํ•ด๋ณด๋ คํ•œ๋‹ค. ์ถ”๊ฐ€๋กœ useFunnel ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ถ„์„ ํ›„ ์˜คํ”ˆ์†Œ์Šค ๋ถ„์„ ๊ณผ์ •์„ ๊ณต์œ ํ•ด๋ณด๋ ค ํ•œ๋‹ค.

8์›” ํ•œ๋‹ฌ ๋™์•ˆ ๋‹ค์–‘ํ•œ ํ™œ๋™์„ ํ•˜๋ฉด์„œ ์ƒˆ๋กœ์šด ๊ฒฝํ—˜์„ ๋งŽ์ด ํ–ˆ๊ณ , ์ด๋ฅผ ํ† ๋Œ€๋กœ 9์›”์—๋Š” ๋”์šฑ ๋” ์„ฑ์žฅํ•˜๊ณ  ์‹ถ๋‹ค๋Š” ์˜์š•์ด ์ƒ๊ธฐ๋Š” ํ•œํŽธ, ๋‚ด๊ฐ€ ์–ด๋””๋กœ ๊ฐ€๊ณ  ์‹ถ์€์ง€, ์–ด๋–ค ๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜๊ณ  ์‹ถ์€์ง€์— ๋Œ€ํ•ด ๋‹ค์‹œ ํ•œ๋ฒˆ ๊ณ ๋ฏผ์ด ๊นŠ์–ด์ง€๋Š” ์‹œ๊ธฐ๋ฅผ ๋ณด๋‚ด๊ณ  ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

์•ž์œผ๋กœ๋„ ๋”์šฑ ๋” ์„ฑ์žฅํ•˜๊ณ  ์‹ถ๋‹ค๋Š” ์˜์ง€๋ฅผ ๊ฐ€์ง€๊ณ  9์›”์„ ๋ณด๋‚ด๋ณด๋ ค ํ•œ๋‹ค.

@Troy
๋งค์ผ์˜ ์‹œํ–‰์ฐฉ์˜ค๋ฅผ ๊ธฐ๋กํ•˜๋Š” ๊ฐœ๋ฐœ์ผ์ง€์ž…๋‹ˆ๋‹ค.