Hits

๐Ÿ“ย ๋ถ„์„์— ์•ž์„œ

์ด ํฌ์ŠคํŠธ๋Š” ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ Jotai V2๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

ํ˜„์žฌ ์ง„ํ–‰์ค‘์ธ ํ”„๋กœ์ ํŠธ์—ย Jotai๋ฅผ ์‚ฌ์šฉํ•ด ์ „์—ญ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ ๊ณผ์ •์—์„œ ์•„๋ž˜์™€ ๊ฐ™์€ ๋ช‡ ๊ฐ€์ง€ ๊ถ๊ธˆ์ ์ด ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.

  1. ์–ด๋–ป๊ฒŒย Root๋ฅผ ๊ฐ์‹ธ๋Š”ย Providerย ์—†์ด ๊ฐ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋™์ผํ•œ ์ƒํƒœ๋ฅผ ๊ตฌ๋…ํ•  ์ˆ˜ ์žˆ์ง€?
  2. Provider๋ฅผ ๊ฐ์‹ธ๊ฒŒ ๋  ๊ฒฝ์šฐ, ๊ฐ€์žฅ ๊ฐ€๊นŒ์šดย Provider์˜ ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์˜ค๊ฒŒ ๋˜๋Š”๋ฐ ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ๊ฑธ๊นŒ?
  3. Provider๊ฐ€ ์—†์„ ๋•Œ, ๊ฐ’์„ย updateย ํ•  ๊ฒฝ์šฐ, ์–ด๋–ค ๋กœ์ง์œผ๋กœย rerendering์ด ์ด๋ฃจ์–ด์งˆ๊นŒ?

๊ถ๊ธˆ์ค‘์„ ํ•ด๊ฒฐํ• ๊ฒธ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌย Jotai์˜ ๋™์ž‘์›๋ฆฌ๋ฅผ ๋ถ„์„ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ด์ „์—,ย Redux์˜ ๋™์ž‘์›๋ฆฌ๋ฅผ ์‚ดํŽด๋ณธ ๊ฒฝํ—˜์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

Redux์˜ ๊ฒฝ์šฐ์—๋Š”ย Vanila JavaScript์—์„œ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์Šคํ† ์–ด ๊ตฌํ˜„๋ถ€(Pub-Subย ํŒจํ„ด ์‚ฌ์šฉ)๊ฐ€ ์žˆ์—ˆ๊ณ ,ย React์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•œ๋ฒˆ Wrapping์„ ํ•ด์„œย React์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋„๋ก(React-Redux) ๋งŒ๋“œ๋Š” ํ˜•ํƒœ์˜€์Šต๋‹ˆ๋‹ค.

Jotaiย ๋Š”ย Reactย ์ „์šฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด์ง€๋งŒ,ย Atom๊ณผ ์ด๋ฅผ ์ €์žฅํ•˜๋Š”ย Store์˜ ๊ฒฝ์šฐย Vanila JavaScript๋ฅผ ์ด์šฉํ•ด ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š”ย Hook(useAtom, useSetAtom โ€ฆ)์˜ ๊ฒฝ์šฐย React์—์„œ ์ œ๊ณตํ•˜๋Š”ย Hook์„ ์ด์šฉํ•ด ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์— ๋”ฐ๋ผ,ย Vanila JavaScript๋ฅผ ์ด์šฉํ•ด ๊ตฌํ˜„๋œย Atom์„ ๋จผ์ € ์‚ดํŽด๋ณด๊ณ , ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ›…์„ ์ฐจ๋ก€๋Œ€๋กœ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Jotai
โ”œโ”€ vanila
โ””โ”€ react
Jotai
โ”œโ”€ vanila
โ””โ”€ react

โš›๏ธย Atom

๊ณต์‹๋ฌธ์„œ์—์„œ๋Š”ย Atom์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

The atom function is to create an atom config. We call it "atom config" as it's just a definition and it doesn't yet hold a value. We may also call it just "atom" if the context is clear.

Atomย ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ดย Atom Config๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

Jotai์—์„œ ์ •์˜ํ•œย Atom์€ ๊ทธ ์ž์ฒด๋กœ ๊ฐ’์„ ๊ฐ€์ง€์ง€ ์•Š์œผ๋ฉฐ,ย Atom์„ ํ†ตํ•ดย Store์— ์ ‘๊ทผํ•ด, ๊ฐ’์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค(read)

๊ณต์‹๋ฌธ์„œ์— ๋‚˜์™€์žˆ๋Š” ๋ฐ”์— ๋”ฐ๋ฅด๋ฉด,ย Atom์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์ด 4๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. (primitive, ์ฝ๊ธฐ์ „์šฉ, ์“ฐ๊ธฐ์ „์šฉ, ์ฝ๊ธฐ and ์“ฐ๊ธฐ)

import { atom } from "jotai";
 
const primitiveAtom = atom(10);
 
const readOnlyAtom = atom((get) => get(priceAtom) * 2);
 
const writeOnlyAtom = atom(null, (get, set, update) => {
  set(priceAtom, get(priceAtom) - update.discount);
});
 
const readWriteAtom = atom(
  (get) => get(priceAtom) * 2,
  (get, set, newPrice) => {
    set(priceAtom, newPrice / 2);
  }
);
import { atom } from "jotai";
 
const primitiveAtom = atom(10);
 
const readOnlyAtom = atom((get) => get(priceAtom) * 2);
 
const writeOnlyAtom = atom(null, (get, set, update) => {
  set(priceAtom, get(priceAtom) - update.discount);
});
 
const readWriteAtom = atom(
  (get) => get(priceAtom) * 2,
  (get, set, newPrice) => {
    set(priceAtom, newPrice / 2);
  }
);

atom์˜ ๊ตฌํ˜„๋ถ€๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

jotai/src/vanilla/atom.ts
export function atom<Value, Args extends unknown[], Result>(
  read: Value | Read<Value, SetAtom<Args, Result>>,
  write?: Write<Args, Result>
) {
  const key = `atom${++keyCount}`const config = {
    toString: () => key,
  } as WritableAtom<Value, Args, Result> & { init?: Value }
  if (typeof read === 'function') {
    config.read = read as Read<Value, SetAtom<Args, Result>>
  } else {
    config.init = read
    config.read = (get) => get(config)
    config.write = ((get: Getter, set: Setter, arg: SetStateAction<Value>) =>set(
        config as unknown as PrimitiveAtom<Value>,
        typeof arg === 'function'
          ? (arg as (prev: Value) => Value)(get(config))
          : arg
      )) as unknown as Write<Args, Result>
  }
  if (write) {
    config.write = write
  }
  return config
}
 
jotai/src/vanilla/atom.ts
export function atom<Value, Args extends unknown[], Result>(
  read: Value | Read<Value, SetAtom<Args, Result>>,
  write?: Write<Args, Result>
) {
  const key = `atom${++keyCount}`const config = {
    toString: () => key,
  } as WritableAtom<Value, Args, Result> & { init?: Value }
  if (typeof read === 'function') {
    config.read = read as Read<Value, SetAtom<Args, Result>>
  } else {
    config.init = read
    config.read = (get) => get(config)
    config.write = ((get: Getter, set: Setter, arg: SetStateAction<Value>) =>set(
        config as unknown as PrimitiveAtom<Value>,
        typeof arg === 'function'
          ? (arg as (prev: Value) => Value)(get(config))
          : arg
      )) as unknown as Write<Args, Result>
  }
  if (write) {
    config.write = write
  }
  return config
}
 

์ด์ „์— ๊ณต์‹๋ฌธ์„œ์— ๋‚˜์™€ ์žˆ๋Š” ์„ค๋ช…๋Œ€๋กœ,ย atomย ์ž์ฒด๋Š” ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ €์žฅํ•˜์ง€ ์•Š๊ณ ,ย init,ย read,ย write,ย toStringย ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง€๋Š”ย config๋ฅผ ๋งŒ๋“ค์–ด ๋ฐ˜ํ™˜ํ•  ๋ฟ์ž…๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ ,ย Store์— ์ €์žฅ๋˜๋Š” ๊ฐ๊ฐ์˜ย atom์ด ์œ ๋‹ˆํฌํ•œย key๋ฅผ ๊ฐ€์ง€๊ธฐ ์œ„ํ•ด ๋ณ€์ˆ˜ย keyCount๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ’์„ ์ฝ๊ณ  ์“ฐ๋Š”๋ฐ ์‚ฌ์šฉ๋˜๋Š”ย read์™€ย writeย ๋ฉ”์„œ๋“œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋จผ์ €ย read์ž…๋‹ˆ๋‹ค.

๊ณต์‹๋ฌธ์„œ์—ย read์— ๋Œ€ํ•ด ์•„๋ž˜์™€ ๊ฐ™์€ ์„ค๋ช…์ด ์žˆ์Šต๋‹ˆ๋‹ค.

read: a function that's called on every re-render. The signature of read is (get) => Value | Promise(Value), and get is a function that takes an atom config and returns its value stored in Provider as described below. Dependency is tracked, so if get is used for an atom at least once, the read will be reevaluated whenever the atom value is changed.

๊ณต์‹๋ฌธ์„œ์˜ ๋‚ด์šฉ์„ ํ†ตํ•ด,ย read๋Š”ย get์ด๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ ๋งค๊ฒŒ๋ณ€์ˆ˜๋กœ ๋ฐ›๋Š”๋ฐ, ํ•ด๋‹นย getย ํ•จ์ˆ˜๋Š”ย atom config์„ ๋งค๊ฒŒ๋ณ€์ˆ˜๋กœ ๋ฐ›์•„,ย Store์— ์ €์žฅ๋˜์–ด ์žˆ๋Š” ๊ฐ’(value)๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ถ”๊ฐ€๋กœย read์˜ ํƒ€์ž…(Read)์„ ํ†ตํ•ด์„œ๋„,ย read๊ฐ€ย Store์— ์ €์žฅ๋˜์–ด ์žˆ๋Š” ๊ฐ’(value)๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

type Read<Value, SetSelf = never> = (
  get: Getter,
  options: { readonly signal: AbortSignal; readonly setSelf: SetSelf }
) => Value;
 
type Getter = <Value>(atom: Atom<Value>) => Value;
type Read<Value, SetSelf = never> = (
  get: Getter,
  options: { readonly signal: AbortSignal; readonly setSelf: SetSelf }
) => Value;
 
type Getter = <Value>(atom: Atom<Value>) => Value;

Atom์˜ ๊ฐ’์„ ์“ฐ๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š”ย write์˜ ๊ฒฝ์šฐ์—๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๊ตฌํ˜„๋ถ€๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

config.write = ((get: Getter, set: Setter, arg: SetStateAction<Value>) =>
  set(
    config as unknown as PrimitiveAtom<Value>,
    typeof arg === "function"
      ? (arg as (prev: Value) => Value)(get(config))
      : arg
  )) as unknown as Write<Args, Result>;
config.write = ((get: Getter, set: Setter, arg: SetStateAction<Value>) =>
  set(
    config as unknown as PrimitiveAtom<Value>,
    typeof arg === "function"
      ? (arg as (prev: Value) => Value)(get(config))
      : arg
  )) as unknown as Write<Args, Result>;

์ฒซ ๋ฒˆ์งธ ๋งค๊ฒŒ๋ณ€์ˆ˜๋กœ ์œ„์—์„œ ํ™•์ธํ–ˆ๋˜ย Store์—์„œย Atom์˜ ๊ฐ’(Value)์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š”ย get์„ ์ „๋‹ฌํ•˜๊ณ , ๋‘ ๋ฒˆ์งธ ๋งค๊ฒŒ๋ณ€์ˆ˜๋กœย Atom์˜ ๊ฐ’์„ ์“ฐ๋Š” ํ•จ์ˆ˜๋กœ ์˜ˆ์ƒ๋˜๋Š”ย set์„ ์ „๋‹ฌํ•˜๋ฉฐ, ๋งˆ์ง€๋ง‰์œผ๋กœ ํŠน์ • ๋™์ž‘ ํ˜น์€ ์„ธํŒ…ํ•˜๋ ค๋Š” ๊ฐ’์„ ์œ„ํ•œ ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋˜๋Š”ย arg๋ฅผ ๋ฐ›๋Š” ๊ฒƒ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ง€๊ธˆ๊นŒ์ง€์˜ ๊ตฌ์กฐ๋ฅผ ๊ทธ๋ฆผ์œผ๋กœ ๋‚˜ํƒ€๋‚ด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.ย Store์— ์—ฌ๋Ÿฌ ๊ฐœ์˜ย atom์ด ์ €์žฅ๋˜์–ด ์žˆ๋Š” ํ˜•ํƒœ์ด๋ฉฐ, ์™ธ๋ถ€์—์„œย atom์˜ ๊ฐ’์„ ์ฝ๊ฑฐ๋‚˜ ์“ด๋‹ค๋ฉด,ย Store(Provider)์— ์กด์žฌํ•˜๋Š”ย atom์„ read, writeํ•˜๋Š” ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.

1691160240950_แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2023-08-04 แ„‹แ…ฉแ„’แ…ฎ 11 43 48


๐Ÿ ย Provider

์ด๋ฒˆ์—๋Š” ๊ฐ’์„ ์ฝ์–ด์˜ค๋Š”ย Store์˜ ์—ญํ• ์„ ํ•˜๋Š”ย Provider์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋จผ์ € ๊ณต์‹๋ฌธ์„œ์˜ ์„ค๋ช…์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Provider์— ๋Œ€ํ•œ ์„ค๋ช…์ž…๋‹ˆ๋‹ค.

The Provider component is to provide state for a component sub tree. Multiple Providers can be used for multiple subtrees, and they can even be nested. This works just like React Context. If an atom is used in a tree without a Provider, it will use the default state. This is so-called provider-less mode.

Provider๋Š”ย Component Subtree๋ฅผ ์œ„ํ•œ ์ปดํฌ๋„ŒํŠธ์ด๋ฉฐ,ย React Context์™€ ๋น„์Šทํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ ย Provider๊ฐ€ ์—†๋‹ค๋ฉด,ย default state๋ฅผ ์‚ฌ์šฉํ•œ๋‹คโ€™ ๋ผ๊ณ  ๋‚˜์™€์žˆ์Šต๋‹ˆ๋‹ค.

Provider์˜ ์†Œ์Šค์ฝ”๋“œ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค

const StoreContext = createContext<Store | undefined>(undefined);
 
export const Provider = ({
  children,
  store,
}: {
  children?: ReactNode;
  store?: Store;
}): FunctionComponentElement<{ value: Store | undefined }> => {
  const storeRef = useRef<Store>();
  if (!store && !storeRef.current) {
    storeRef.current = createStore();
  }
  return createElement(
    StoreContext.Provider,
    {
      value: store || storeRef.current,
    },
    children
  );
};
const StoreContext = createContext<Store | undefined>(undefined);
 
export const Provider = ({
  children,
  store,
}: {
  children?: ReactNode;
  store?: Store;
}): FunctionComponentElement<{ value: Store | undefined }> => {
  const storeRef = useRef<Store>();
  if (!store && !storeRef.current) {
    storeRef.current = createStore();
  }
  return createElement(
    StoreContext.Provider,
    {
      value: store || storeRef.current,
    },
    children
  );
};

์†Œ์Šค์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ๊ฐ๊ฐ์˜ย Provider๋Š”ย createContext๋ฅผ ํ†ตํ•ด ๋งŒ๋“ค์–ด์ง„, ๋…์ž์ ์ธย Reactย Context๋ผ๋Š” ์‚ฌ์‹ค์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ,ย Store๋ฅผย Prop์œผ๋กœ ๋ฐ›์•„, ํ•ด๋‹นย Subtree์—์„œ ์‚ฌ์šฉํ• ย store๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ,ย store๋ผ๋Š” ๋งค๊ฒŒ๋ณ€์ˆ˜๋Š”ย Optionalย ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, store๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ,ย createStore๋ฅผ ํ†ตํ•ดย Store๋ฅผ ๋งŒ๋“ค์–ดย Provider์— ์ €์žฅํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ณต์‹๋ฌธ์„œ์˜ ์„ค๋ช…์—์„œ๋Š” โ€œย Provider๊ฐ€ ์—†๋‹ค๋ฉด,ย default state๋ฅผ ์‚ฌ์šฉํ•œ๋‹คโ€ ๋ผ๊ณ  ๋‚˜์™€ ์žˆ๋Š”๋ฐ, ์ด๋Š” ์•„๋ž˜๋ฅผ ํ†ตํ•ด์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๊ธฐ๋ณธ ํ๋ฆ„์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. Jotai์˜ ๊ธฐ๋ณธ ๋™์ž‘์ด ๋˜๋Š” Hook์ธย useAtomValue,ย useSetAtom์€ย Store๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š”ย useStoreย Hook์„ ํ†ตํ•ด, ํ˜„์žฌ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋Š”ย Provider์— ๋งค์นญ๋œย Store๋ฅผ ๊ฐ€์ ธ์˜ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  2. ์ด ๊ณผ์ •์—์„œย useStoreย Hook์€ ํ•ด๋‹น ํŠธ๋ฆฌ๋ฅผ ๋”ฐ๋ผ ํƒ์ƒ‰ํ•˜๊ณ  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋Š”ย Provider๊ฐ€ ์—†๋‹ค๋ฉด,ย getDefaultStoreย ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ดย defaultStore๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์†Œ์Šค์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

export function useAtomValue<Value>(atom: Atom<Value>, options?: Options) {
  const store = useStore(options);
  // get value from store
  // ...
  return isPromiseLike(value) ? use(value) : (value as Awaited<Value>);
}
 
export function useSetAtom<Value, Args extends any[], Result>(
  atom: WritableAtom<Value, Args, Result>,
  options?: Options
) {
  const store = useStore(options);
  // set value in store
  const setAtom = useCallback(
    (...args: Args) => {
      // ...
      return store.set(atom, ...args);
    },
    [store, atom]
  );
  return setAtom;
}
 
export const useStore = (options?: Options): Store => {
  const store = useContext(StoreContext);
  return options?.store || store || getDefaultStore();
};
export function useAtomValue<Value>(atom: Atom<Value>, options?: Options) {
  const store = useStore(options);
  // get value from store
  // ...
  return isPromiseLike(value) ? use(value) : (value as Awaited<Value>);
}
 
export function useSetAtom<Value, Args extends any[], Result>(
  atom: WritableAtom<Value, Args, Result>,
  options?: Options
) {
  const store = useStore(options);
  // set value in store
  const setAtom = useCallback(
    (...args: Args) => {
      // ...
      return store.set(atom, ...args);
    },
    [store, atom]
  );
  return setAtom;
}
 
export const useStore = (options?: Options): Store => {
  const store = useContext(StoreContext);
  return options?.store || store || getDefaultStore();
};

๊ทธ๋ ‡๋‹ค๋ฉด, ์„œ๋กœ ๋‹ค๋ฅธย Subtree(Provider)์—์„œ, ๊ฐ™์€ย store๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์–ด๋–ค ์ƒํ™ฉ์ด ๋ฒŒ์–ด์งˆ๊นŒ์š”?

Counter ์˜ˆ์ œ ์†Œ์Šค์ฝ”๋“œ

const counter1Atom = atom(0);
const counter2Atom = atom(10);
 
const store1 = createStore();
const store2 = createStore();
 
function App() {
  return (
    <div className="App">
      <Provider store={store1}>
        <ComponentA />
      </Provider>
      <Provider store={store2}>
        <ComponentB />
      </Provider>
    </div>
  );
}
 
const ComponentA = () => {
  const [count, setCount] = useAtom(counter1Atom);
  const count2 = useAtomValue(counter2Atom);
  console.log("count2 : ", count2);
  return <Counter count={count} setCount={setCount} />;
};
 
const ComponentB = () => {
  const [count, setCount] = useAtom(counter2Atom);
 
  return <Counter count={count} setCount={setCount} />;
};
 
const Counter = ({ count, setCount }: CounterProps) => {
  return (
    <>
      <button onClick={() => setCount((prev: any) => prev - 1)}>-</button>
      <span>{count}</span>
      <button onClick={() => setCount((prev: any) => prev + 1)}>+</button>
    </>
  );
};
const counter1Atom = atom(0);
const counter2Atom = atom(10);
 
const store1 = createStore();
const store2 = createStore();
 
function App() {
  return (
    <div className="App">
      <Provider store={store1}>
        <ComponentA />
      </Provider>
      <Provider store={store2}>
        <ComponentB />
      </Provider>
    </div>
  );
}
 
const ComponentA = () => {
  const [count, setCount] = useAtom(counter1Atom);
  const count2 = useAtomValue(counter2Atom);
  console.log("count2 : ", count2);
  return <Counter count={count} setCount={setCount} />;
};
 
const ComponentB = () => {
  const [count, setCount] = useAtom(counter2Atom);
 
  return <Counter count={count} setCount={setCount} />;
};
 
const Counter = ({ count, setCount }: CounterProps) => {
  return (
    <>
      <button onClick={() => setCount((prev: any) => prev - 1)}>-</button>
      <span>{count}</span>
      <button onClick={() => setCount((prev: any) => prev + 1)}>+</button>
    </>
  );
};

์„œ๋กœ ๋‹ค๋ฅธย Subtree์—์„œ ๋‹ค๋ฅธย Store๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋  ๊ฒฝ์šฐ, ๋‹ค๋ฅธย Subtree์—์„œ ๊ฐฑ์‹ ๋œ ๊ฐ’์„ ์ฝ์–ด ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. 257190028-d7a3e534-7f3f-423d-a9d3-0df18a7cf594

๋ฐ˜๋ฉด, ์„œ๋กœ ๋‹ค๋ฅธย Subtree์—์„œ ๊ฐ™์€ย store๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋  ๊ฒฝ์šฐ, ๋‹ค๋ฅธย Subtree(Provider)์ผ์ง€๋ผ๋„ ์—…๋ฐ์ดํŠธ ๋œ ๊ฐ’์„ ์ฝ์–ด ์˜ฌ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 257190799-5817be72-43f8-4d58-804d-8da59f843a0b

<Provider store={store1}>
  <ComponentA />
</Provider>
<Provider store={store1}>
  <ComponentB />
</Provider>
<Provider store={store1}>
  <ComponentA />
</Provider>
<Provider store={store1}>
  <ComponentB />
</Provider>

์ถ”๊ฐ€์ ์œผ๋กœ, ๋‹ค๋ฅธย Store๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ์™€ ๋‹ค๋ฅด๊ฒŒ,ย counter2Atom์˜ ๊ฐ’์ด ์—…๋ฐ์ดํŠธ ๋˜๋ฉด,ย ComponentA๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋˜์–ด ์ฝ˜์†”์ด ์ถœ๋ ฅ๋˜๋Š”๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ํ†ตํ•ด,ย Provider(Context)๋Š” ๊ป๋ฐ๊ธฐ์ด๋ฉฐ, ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ณ  ์—…๋ฐ์ดํŠธํ•˜๋ฉฐ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฆฌ๋ Œ๋”๋ง ์‹œํ‚ค๋Š” ๋กœ์ง์€ย Store์— ๋“ค์–ด์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿ˜ƒ ๋งˆ์น˜๋ฉฐ

1ํŽธ์—์„œ๋Š” ๊ฐ€์žฅ ๊ธฐ๋ณธ์ด ๋˜๋Š”ย atom๊ณผย store์˜ ๊ป๋ฐ๊ธฐ๊ฐ€ ๋˜๋Š”ย Provider์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค.

์ง€๊ธˆ๊นŒ์ง€ ํŒŒ์•…ํ•œ ๋‚ด์šฉ์„ ์š”์•ฝํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • atom์€ ๊ทธ ์ž์ฒด๋กœ ๊ฐ’์„ ๊ฐ€์ง€์ง€ ์•Š๊ณ ย Store์— ์ €์žฅ๋˜๋ฉฐ, ๋ชจ๋“  ๋™์ž‘์€ย store์—์„œ ๊ฐ’์„ ์ฝ๊ณ  ์“ฐ๋Š” ๊ณผ์ •์„ ํ†ตํ•ด ์ด๋ฃจ์–ด์ง„๋‹ค.
  • Provider๋Š”ย Store๋ฅผ ์ €์žฅํ•˜๋Š” ๊ป๋ฐ๊ธฐ ์—ญํ• ์„ ํ•˜๋ฉฐ,ย Provider๊ฐ€ ์—†์„ ๊ฒฝ์šฐ,ย defaultStore๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

๋‹ค์Œ 2ํŽธ์—์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๋‚ด์šฉ์„ ์‚ดํŽด๋ณผ ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

  • Jotai์˜ ํ•ต์‹ฌ ๋กœ์งย Store
  • atom์ด ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ, ์ปดํฌ๋„ŒํŠธ๋Š” ์–ด๋–ป๊ฒŒ ๋ฆฌ๋ Œ๋”๋ง ๋˜๋Š”๊ฐ€
  • atom์€ ์–ด๋–ค ์‹œ์ ์—ย store์— ๋“ค์–ด๊ฐ€๋Š”๊ฐ€
  • ๊ฐย atom์˜ย dependency๋ฅผ ์–ด๋–ป๊ฒŒ ๊ด€๋ฆฌ๋˜๋Š”๊ฐ€