Hits

๐Ÿช store

Jotai์—์„œ ์‚ฌ์šฉํ•˜๋Š”ย Store๋Š”ย createStoreํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

createStore๋Š”ย 700์ค„์ด ๋„˜๊ธฐ ๋•Œ๋ฌธ์—, ์ „์ฒด ๊ตฌํ˜„ ์ฝ”๋“œ๋Š” ์ƒ๋žตํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์œ„ํ•ด ๋ฐ˜ํ™˜๊ฐ’๋งŒ ์‚ดํŽด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

return {
  get: readAtom,
  set: writeAtom,
  sub: subscribeAtom,
};
return {
  get: readAtom,
  set: writeAtom,
  sub: subscribeAtom,
};

๊ฐ๊ฐ์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด๋ฉด,ย get์˜ ๊ฒฝ์šฐย store์— ์กด์žฌํ•˜๋Š”ย atom์˜ ๊ฐ’์„ ์ฝ์„ ๋•Œ ์‚ฌ์šฉ๋˜๋ฉฐ,ย set์˜ ๊ฒฝ์šฐย store์˜ ๊ฐ’์„ ์“ธ ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

sub์˜ ๊ฒฝ์šฐ,ย atom๊ณผlistener๋ฅผ ๋งค๊ฒŒ๋ณ€์ˆ˜๋กœ ๋ฐ›์•„ย listenersย set์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

jotai์˜ ๋ชจ๋“  ๋™์ž‘์€ย store์˜ย get,ย set,ย subย ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์ด๋ฃจ์–ด์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

atom์˜ ๊ฐ’์„ ์—…๋ฐ์ดํŠธ ํ•  ๋•Œ ์‚ฌ์šฉ๋˜๋Š”ย useSetAtomย ํ›…์—์„œ๋Š”ย setย ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค.

useSetAtom
export function useSetAtom<Value, Args extends any[], Result>(
  atom: WritableAtom<Value, Args, Result>,
  options?: Options
) {
  const store = useStore(options)
  const setAtom = useCallback(
    (...args: Args) => {
      ...
      return store.set(atom, ...args)
    },
    [store, atom]
  )
  return setAtom
}
 
useSetAtom
export function useSetAtom<Value, Args extends any[], Result>(
  atom: WritableAtom<Value, Args, Result>,
  options?: Options
) {
  const store = useStore(options)
  const setAtom = useCallback(
    (...args: Args) => {
      ...
      return store.set(atom, ...args)
    },
    [store, atom]
  )
  return setAtom
}
 

atom์˜ ๊ฐ’์„ ์ฝ์„ ๋•Œ ์‚ฌ์šฉ๋˜๋Š”ย useAtomValueย ํ›…์—์„œ๋Š”ย get,ย subscribeย ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค.

useAtomValue
export function useAtomValue<Value>(atom: Atom<Value>, options?: Options) {
  const store = useStore(options);
 
  const [[valueFromReducer, storeFromReducer, atomFromReducer], rerender] =
    useReducer<
      ReducerWithoutAction<readonly [Value, Store, typeof atom]>,
      undefined
    >(
      (prev) => {
        const nextValue = store.get(atom);
        if (
          Object.is(prev[0], nextValue) &&
          prev[1] === store &&
          prev[2] === atom
        ) {
          return prev;
        }
        return [nextValue, store, atom];
      },
      undefined,
      () => [store.get(atom), store, atom]
    );
 
  let value = valueFromReducer;
  if (storeFromReducer !== store || atomFromReducer !== atom) {
    rerender();
    value = store.get(atom);
  }
 
  const delay = options?.delayuseEffect(() => {
    const unsub = store.sub(atom, () => {
      if (typeof delay === "number") {
        // delay rerendering to wait a promise possibly to resolve
        setTimeout(rerender, delay);
        return;
      }
      rerender();
    });
    rerender();
    return unsub;
  }, [store, atom, delay]);
 
  return isPromiseLike(value) ? use(value) : (value as Awaited<Value>);
}
useAtomValue
export function useAtomValue<Value>(atom: Atom<Value>, options?: Options) {
  const store = useStore(options);
 
  const [[valueFromReducer, storeFromReducer, atomFromReducer], rerender] =
    useReducer<
      ReducerWithoutAction<readonly [Value, Store, typeof atom]>,
      undefined
    >(
      (prev) => {
        const nextValue = store.get(atom);
        if (
          Object.is(prev[0], nextValue) &&
          prev[1] === store &&
          prev[2] === atom
        ) {
          return prev;
        }
        return [nextValue, store, atom];
      },
      undefined,
      () => [store.get(atom), store, atom]
    );
 
  let value = valueFromReducer;
  if (storeFromReducer !== store || atomFromReducer !== atom) {
    rerender();
    value = store.get(atom);
  }
 
  const delay = options?.delayuseEffect(() => {
    const unsub = store.sub(atom, () => {
      if (typeof delay === "number") {
        // delay rerendering to wait a promise possibly to resolve
        setTimeout(rerender, delay);
        return;
      }
      rerender();
    });
    rerender();
    return unsub;
  }, [store, atom, delay]);
 
  return isPromiseLike(value) ? use(value) : (value as Awaited<Value>);
}

reducerย ๋‚ด๋ถ€ ๋กœ์ง์„ ํ†ตํ•ด ๋งคย renderingย ์‹œ์ ,ย store.get(atom)์„ ํ†ตํ•ดย store์—์„œ ํ•ด๋‹น atom์˜ย value๋ฅผ ์ฝ์–ด์˜ค๋ฉฐ, ํ•ด๋‹น ๊ฐ’์„ ์ƒํƒœ๋กœ ์‚ฌ์šฉํ•ด, ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

const [[valueFromReducer, storeFromReducer, atomFromReducer], rerender] =
  useReducer<
    ReducerWithoutAction<readonly [Value, Store, typeof atom]>,
    undefined
  >(
    (prev) => {
      const nextValue = store.get(atom);
      if (
        Object.is(prev[0], nextValue) &&
        prev[1] === store &&
        prev[2] === atom
      ) {
        return prev;
      }
      return [nextValue, store, atom];
    },
    undefined,
    () => [store.get(atom), store, atom]
  );
 
let value = valueFromReducer;
 
return isPromiseLike(value) ? use(value) : (value as Awaited<Value>);
const [[valueFromReducer, storeFromReducer, atomFromReducer], rerender] =
  useReducer<
    ReducerWithoutAction<readonly [Value, Store, typeof atom]>,
    undefined
  >(
    (prev) => {
      const nextValue = store.get(atom);
      if (
        Object.is(prev[0], nextValue) &&
        prev[1] === store &&
        prev[2] === atom
      ) {
        return prev;
      }
      return [nextValue, store, atom];
    },
    undefined,
    () => [store.get(atom), store, atom]
  );
 
let value = valueFromReducer;
 
return isPromiseLike(value) ? use(value) : (value as Awaited<Value>);

์ปดํฌ๋„ŒํŠธ๊ฐ€ย mountย ๋œ ์ดํ›„,ย useEffectย ํ›…์„ ํ†ตํ•ด,ย store๋ฅผ ๊ตฌ๋…ํ•˜๋Š” ๊ฒƒ๋„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

useEffect(() => {
  const unsub = store.sub(atom, () => {
    // ...rerender()
  });
  rerender();
  return unsub;
}, [store, atom, delay]);
useEffect(() => {
  const unsub = store.sub(atom, () => {
    // ...rerender()
  });
  rerender();
  return unsub;
}, [store, atom, delay]);

์ด๋•Œ, ์ธ์ž๋กœย rerenderย ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•ด,ย store์—์„œ ๋ณ€ํ™”๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด, ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฆฌ๋ Œ๋”๋ง ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ข€ ๋” ํ™•์‹คํ•˜๊ฒŒ ์‚ดํŽด๋ณด๊ธฐ ์œ„ํ•ด,

subscribeAtomย ๋ฉ”์„œ๋“œ์™€ย listener๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๋ถ€๋ถ„์„ ์‚ดํŽด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

jotai/src/vanilla/store.t (function : createStore)
const subscribeAtom = (atom: AnyAtom, listener: () => void) => {
  const mounted = addAtom(atom);
  const flushed = flushPending();
  const listeners = mounted.l;
  listeners.add(listener);
  // ...
  return () => {
    listeners.delete(listener);
    delAtom(atom);
    // ...
  };
};
 
const flushPending = (): void | Set<AnyAtom> => {
  // ...
  mounted.l.forEach((listener) => listener());
  // ...
};
jotai/src/vanilla/store.t (function : createStore)
const subscribeAtom = (atom: AnyAtom, listener: () => void) => {
  const mounted = addAtom(atom);
  const flushed = flushPending();
  const listeners = mounted.l;
  listeners.add(listener);
  // ...
  return () => {
    listeners.delete(listener);
    delAtom(atom);
    // ...
  };
};
 
const flushPending = (): void | Set<AnyAtom> => {
  // ...
  mounted.l.forEach((listener) => listener());
  // ...
};

subscribeAtomย ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ดย listener๋ฅผ ์ €์žฅํ•˜๊ณ ,ย atom์˜ ๊ฐ’์ด ๋ณ€๊ฒฝ ๋  ๋•Œ ๋งˆ๋‹ค ์‹คํ–‰๋˜๋Š”ย flushPendingย ๋ฉ”์„œ๋“œ์—์„œ ํ•ด๋‹นย listener๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆผ์œผ๋กœ ์ˆœ์„œ๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

1691162129847_แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2023-08-05 แ„‹แ…ฉแ„Œแ…ฅแ†ซ 12 15 21

  1. Component Mount
  2. useEffect Execute and Subscribe Store(store.subscribeAtom)
  3. atom change in store
  4. publish and rerender called

๋‹ค์Œ ๋‚ด์šฉ์„ ์‚ดํŽด๋ณด๊ธฐ์ „์—, ์ง€๊ธˆ๊นŒ์ง€ ์‚ดํŽด๋ณธย Component,ย Store,ย Provider,ย Atom์˜ ๊ด€๊ณ„๋ฅผ ์‚ดํŽด๋ณด๋ฉด ์•„๋ž˜ ๊ทธ๋ฆผ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

1691162700796_แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2023-08-05 แ„‹แ…ฉแ„Œแ…ฅแ†ซ 12 24 51

  • ๊ฐ๊ฐ์˜ย atom์€ ํ•˜๋‚˜์˜ย store์— ์†ํ•œ๋‹ค.
  • Provider๋Š”ย Store๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ๊ป๋ฐ๊ธฐ์ด๋ฉฐ ๊ฐ๊ฐ์˜ย Subtree์— ๋…๋ฆฝ์ ์ธย Store๋ฅผ ๊ฐ–๋„๋ก ๋งŒ๋“ค์–ด์ค€๋‹ค.
  • ์ปดํฌ๋„ŒํŠธ์™€ย Store๋ฅผย Pub-Subย ๊ด€๊ณ„์ด๋ฉฐ,ย Store๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋ฉดย Subscribe๋ฅผ ํ†ตํ•ด ๋ฆฌ๋ Œ๋”๋ง ๋œ๋‹ค.

๐Ÿง ์ƒ์„ฑํ•œ Atom์€ ์–ธ์ œ Store์— ๋“ค์–ด๊ฐˆ๊นŒ?

๋‹ค์Œ์œผ๋กœ,ย atomย ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด ๋งŒ๋“ ย atom config๊ฐ€ ์–ธ์ œย store์— ๋“ค์–ด๊ฐ€๋Š”์ง€๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๋ก ๋ถ€ํ„ฐ ๋งํ•˜์ž๋ฉด,ย atom์€ ํ•ด๋‹นย atom์ดย Store์—์„œ ์ฒ˜์Œ ์‚ฌ์šฉ๋˜๋Š” ์‹œ์ ์—ย Store์— ๋“ค์–ด๊ฐ€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์กฐ๊ธˆ ๋” ์ž์„ธํžˆ ์„ค๋ช…ํ•˜์ž๋ฉด, ํ•ด๋‹นย atom์ดย Store์˜ ํŠน์ • ๋ฉ”์„œ๋“œ์˜ ๋งค๊ฒŒ๋ณ€์ˆ˜๋กœ ํ˜ธ์ถœ๋˜๋Š” ์‹œ์ ์— ๋“ค์–ด๊ฐ€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

Store๋‚ด๋ถ€์—์„œย Atom์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ย WeakMap์„ ํ†ตํ•ด ๊ด€๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

const atomStateMap = new WeakMap<AnyAtom, AtomState>();
const atomStateMap = new WeakMap<AnyAtom, AtomState>();

ํ•ด๋‹นย WeakMap์ดย Set๋˜๋Š” ์‹œ์ ์€ย setAtomState๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ์‹œ์ ๋ฐ–์— ์—†์Šต๋‹ˆ๋‹ค.

const setAtomState = <Value>(
    atom: Atom<Value>,
    atomState: AtomState<Value>
  ): void => {
    ...
    const prevAtomState = atomStateMap.get(atom)
    atomStateMap.set(atom, atomState)
    ...
  }
 
const setAtomState = <Value>(
    atom: Atom<Value>,
    atomState: AtomState<Value>
  ): void => {
    ...
    const prevAtomState = atomStateMap.get(atom)
    atomStateMap.set(atom, atomState)
    ...
  }
 

setAtomState๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ์‹œ์ ์„ ์—ญ์ถ”์ ํ•œ ๊ฒฐ๊ณผ ์•„๋ž˜์™€ ๊ฐ™์€ ํ˜ธ์ถœ ์ฒด์ด๋‹์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

1๏ธ. readAtomCalled when store.get(atom) called

const readAtom = <Value>(atom: Atom<Value>): Value => returnAtomValue(readAtomState(atom))

2๏ธ. readAtomState called

3๏ธ. setAtomValueOrPromise called

4๏ธ. setAtomValue called

5๏ธ. setAtomState called

6๏ธ. atomStateWeakMap.set(atom) called

Storeย ๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ๋“ค์˜ ํ˜ธ์ถœ์ˆœ์„œ๋ฅผ ์—ญ์ถ”์ ํ•ด๋ณธ ๊ฒฐ๊ณผ,ย store์— ์กด์žฌํ•˜๋Š”ย atom์„ ์ฝ์„ ๋•Œ (when store.get(atom) called), ํ•ด๋‹นย atom์ดย store์— ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ํ•ด๋‹นย atom์„ย store์— ๋„ฃ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ, ์•„๋ž˜์™€ ๊ฐ™์ด ์ปดํฌ๋„ŒํŠธ ๋ฐ”๊นฅ์—์„œย Store๋ฅผ ํ†ตํ•ดย atom์„ ์ ‘๊ทผํ•ด ๊ฐ’์„ ํ™•์ธํ•ด๋ณด๋ฉดย Hookย ํ˜ธ์ถœ ์ด์ „์—๋„,ย store์—ย atom์ด ๋“ค์–ด๊ฐ€ ์žˆ์œผ๋ฉฐ, ํ•ด๋‹น ๊ฐ’์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const counterAtom = atom(1);
const doubleCountAtom = atom((get) => {
  return get(counterAtom) * 2;
});
 
const store = getDefaultStore();
 
console.log("store.get(counterAtom) : ", store.get(counterAtom)); // 1 ์ถœ๋ ฅ
console.log("store.get(doubleCountAtom) : ", store.get(doubleCountAtom)); // 2 ์ถœ๋ ฅ
function App() {
  const [count, setCount] = useAtom(counterAtom);
  const doubleCount = useAtomValue(doubleCountAtom);
  return (
    <div className="App">
      <div>
        <button onClick={() => setCount((prev) => prev - 1)}>-</button>
        <span>{count}</span>
        <button onClick={() => setCount((prev) => prev + 1)}>+</button>
      </div>
      <div>
        <span>double Count Atom : {doubleCount}</span>
      </div>
    </div>
  );
}
const counterAtom = atom(1);
const doubleCountAtom = atom((get) => {
  return get(counterAtom) * 2;
});
 
const store = getDefaultStore();
 
console.log("store.get(counterAtom) : ", store.get(counterAtom)); // 1 ์ถœ๋ ฅ
console.log("store.get(doubleCountAtom) : ", store.get(doubleCountAtom)); // 2 ์ถœ๋ ฅ
function App() {
  const [count, setCount] = useAtom(counterAtom);
  const doubleCount = useAtomValue(doubleCountAtom);
  return (
    <div className="App">
      <div>
        <button onClick={() => setCount((prev) => prev - 1)}>-</button>
        <span>{count}</span>
        <button onClick={() => setCount((prev) => prev + 1)}>+</button>
      </div>
      <div>
        <span>double Count Atom : {doubleCount}</span>
      </div>
    </div>
  );
}

๐Ÿ› ๏ธย Atom์˜ Dependency๋Š” ์–ด๋–ป๊ฒŒ ๊ด€๋ฆฌํ• ๊นŒ?

๋จผ์ €,ย ๊ณต์‹๋ฌธ์„œ๋ฅผ ํ†ตํ•ด ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

To keep track of all the dependents, we need to add one more property to the atom's state.

Dependency๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด, ์•„๋ž˜์™€ ๊ฐ™์ดย dependents๋ผ๋Š”ย property๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

const atomState = {
  value: atom.init,
  listeners: new Set(),
  dependents: new Set(),
};
const atomState = {
  value: atom.init,
  listeners: new Set(),
  dependents: new Set(),
};

ํ•ด๋‹นย dependentsย ๋ผ๋Š”ย property๋ฅผ ํ†ตํ•ด ํ•ด๋‹นย atom์— ์˜์กด์ ์ธย atom์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ์•„๋ž˜์™€ ๊ฐ™์ดย atom X๊ฐ€ย atom Y์— ์˜์กดํ•˜๋Š” ๊ฒฝ์šฐ๋ผ๋ฉด,ย atom Y์˜ย dependentsย property์—๋Š”ย atom X๊ฐ€ ๋“ค์–ด๊ฐ€ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

const atomX = atom(1);
const atomY = atom((get) => get(atomX) + 1);
const atomX = atom(1);
const atomY = atom((get) => get(atomX) + 1);

1ํŽธ์—์„œย atom์„ ์‚ดํŽด๋ณผ ๋•Œ,ย atom์˜ ๊ฐ’์„ ์ฝ์„๋•Œ๋Š”ย atomย ๋‚ด๋ถ€์—์„œย read๋ผ๋Š”ย property๋ฅผ ํ†ตํ•ดย store์— ์กด์žฌํ•˜๋Š”ย atom์˜ย value๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

export function atom<Value, Args extends unknown[], Result>(
  read: Value | Read<Value, SetAtom<Args, Result>>,
  write?: Write<Args, Result>
) {
	...
  if (typeof read === 'function') {
    config.read = read as Read<Value, SetAtom<Args, Result>>
  } else {
		...
    config.read = (get) => get(config)
		...
  }
  return config
}
 
export function atom<Value, Args extends unknown[], Result>(
  read: Value | Read<Value, SetAtom<Args, Result>>,
  write?: Write<Args, Result>
) {
	...
  if (typeof read === 'function') {
    config.read = read as Read<Value, SetAtom<Args, Result>>
  } else {
		...
    config.read = (get) => get(config)
		...
  }
  return config
}
 

atom์˜ ์ฒซ ๋ฒˆ์งธ ๋งค๊ฒŒ๋ณ€์ˆ˜๋กœ ๋ฐ›๋Š”ย read์˜ ํƒ€์ž…์ด ํ•จ์ˆ˜์ธ์ง€,ย atomย config์ธ์ง€์— ๋”ฐ๋ผ,ย atom์ด ๋ฐ˜ํ™˜ํ•˜๋Š”ย read๊ฐ€ ๋‹ฌ๋ผ์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

  1. read์˜ ํƒ€์ž…์ด ํ•จ์ˆ˜์ธ ๊ฒฝ์šฐ(๋‹ค๋ฅธ atom์— ์˜์กด์ ์ธ atom) : ๋งค๊ฒŒ๋ณ€์ˆ˜๋กœ ๋ฐ›๋Š” read ๋ฐ˜ํ™˜
  2. read์˜ ํƒ€์ž…์ด ํ•จ์ˆ˜๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ(๋‹ค๋ฅธ atom์— ์˜์กด์ ์ด์ง€ ์•Š์€ atom) : ์ž๊ธฐ ์ž์‹ (atom config)์„ ๋งค๊ฒŒ๋ณ€์ˆ˜๋กœ ๋„ฃ๋Š” ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜

1ํŽธ์—์„œ ์‚ดํŽด๋ณด์•˜๋“ฏ, ๋งคย renderingย ๋งˆ๋‹ค,ย atom์˜ย read๊ฐ€ ์‹คํ–‰๋˜์–ดย store์—์„œ ํ•ด๋‹นย atom์˜ ๊ฐ’์„ ์ฝ์–ด์˜ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ, ๋‹ค์Œ์œผ๋กœ๋Š”ย store์—์„œ ํ•ด๋‹นย atom์˜ ๊ฐ’์„ ์ฝ๋Š” ๋ถ€๋ถ„์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

const readAtom = (atom) => {
  const atomState = getAtomState(atom);
  const get = (a) => {
    if (a === atom) {
      return atomState.value;
    }
    const aState = getAtomState(a);
    aState.dependents.add(atom); // XXX add onlyreturn
    readAtom(a); // XXX no caching
  };
  const value = atom.read(get);
  atomState.value = value;
  return value;
};
const readAtom = (atom) => {
  const atomState = getAtomState(atom);
  const get = (a) => {
    if (a === atom) {
      return atomState.value;
    }
    const aState = getAtomState(a);
    aState.dependents.add(atom); // XXX add onlyreturn
    readAtom(a); // XXX no caching
  };
  const value = atom.read(get);
  atomState.value = value;
  return value;
};

getํ•จ์ˆ˜๋ฅผ ์‚ดํŽด๋ณด๋ฉด ๋งค๊ฒŒ๋ณ€์ˆ˜๋กœ ๋ฐ›๋Š”ย a๊ฐ€ ์ž๊ธฐ์ž์‹ ์ด๋ผ๋ฉด(์•ž์„œ ์‚ดํŽด๋ณธ 2๋ฒˆ ์ผ€์ด์Šค โ†’ย if (a === atom)) ํ•ด๋‹นย atom์˜ย value๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ , ๋งค๊ฒŒ๋ณ€์ˆ˜๋กœ ๋ฐ›๋Š” a๊ฐ€ ์ž๊ธฐ์ž์‹ ์ด ์•„๋‹ ๊ฒฝ์šฐ(์•ž์„œ ์‚ดํŽด๋ณธ 1๋ฒˆ ์ผ€์ด์Šค), ๋งค๊ฒŒ๋ณ€์ˆ˜๋กœ ๋ฐ›๋Š”ย a์˜ ๊ฐ’์„ย store์—์„œ ๊ฐ€์ง€๊ณ  ์˜ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค(const aState = getAtomState(a)).

๊ทธ ์ดํ›„, ํ•ด๋‹นย atom์˜ย dependentsย property์—ย a๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ (aState.dependents.add(atom)), ์žฌ๊ท€์ ์œผ๋กœย readAtom์„ ํ˜ธ์ถœํ•ด์„œ ๋‹ค๋ฅธย atom์— ์˜์กด์ ์ธย atom์ธ์ง€ ์—ฌ๋ถ€์— ๋”ฐ๋ผย readย ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

atom์˜ ๊ฐ’์ด ๋ฐ”๋€” ๋•Œ๋Š”,ย dependentsย property์— ์ €์žฅ๋˜์–ด ์žˆ๋Š” ๋ชจ๋“ ย atom์—๊ฒŒย notify๋ฅผ ํ•˜๊ฒŒ ๋˜๊ณ , ๊ฐ๊ฐ์˜ atom๋“ค์€ listener ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.(์œ„์—์„œ ์‚ดํŽด๋ณธย rerender)

const notify = (atom) => {
  const atomState = getAtomState(atom);
  atomState.dependents.forEach((d) => {
    if (d !== atom) notify(d);
  });
  atomState.listeners.forEach((l) => l());
};
 
// writeAtom calls atom.write with the necessary params and triggers notify function
const writeAtom = (atom, value) => {
  const atomState = getAtomState(atom);
 
  // 'a' is some atom from atomStateMap
  const get = (a) => {
    const aState = getAtomState(a);
    return aState.value;
  };
 
  // if 'a' is the same as atom, update the value, notify that atom and return else calls writeAtom for 'a' (recursively)
  const set = (a, v) => {
    if (a === atom) {
      atomState.value = v;
      notify(atom);
      return;
    }
    writeAtom(a, v);
  };
 
  atom.write(get, set, value);
};
const notify = (atom) => {
  const atomState = getAtomState(atom);
  atomState.dependents.forEach((d) => {
    if (d !== atom) notify(d);
  });
  atomState.listeners.forEach((l) => l());
};
 
// writeAtom calls atom.write with the necessary params and triggers notify function
const writeAtom = (atom, value) => {
  const atomState = getAtomState(atom);
 
  // 'a' is some atom from atomStateMap
  const get = (a) => {
    const aState = getAtomState(a);
    return aState.value;
  };
 
  // if 'a' is the same as atom, update the value, notify that atom and return else calls writeAtom for 'a' (recursively)
  const set = (a, v) => {
    if (a === atom) {
      atomState.value = v;
      notify(atom);
      return;
    }
    writeAtom(a, v);
  };
 
  atom.write(get, set, value);
};

์ตœ์ข…์ ์œผ๋กœ ์•„๋ž˜์™€ ๊ฐ™์ด ์ •๋ฆฌ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

  • ๊ฐ๊ฐ์˜ย atom์€ย dependentsย property๋ฅผ ํ†ตํ•ด ์ž์‹ ์—๊ฒŒ ์˜์กด์ ์ธ atom๋“ค์„ ์ €์žฅํ•œ๋‹ค.
  • atom์„ ์ฝ์„ ๋•Œ(read๊ฐ€ ์‹คํ–‰๋˜์–ด store์—์„œ ํ•ด๋‹น atom์˜ ๊ฐ’์„ ์ฝ์„ ๋•Œ), ํ•ด๋‹นย atom์ด ๋‹ค๋ฅธย atom์— ์˜์กด์ ์ธ atom์ด๋ผ๋ฉด, ์˜์กดํ•˜๋Š”ย atom์˜ย dependents์— ์ถ”๊ฐ€ํ•˜๊ณ  ์žฌ๊ท€์ ์œผ๋กœย atom์˜ ๊ฐ’์„ ์ฝ๋Š”๋‹ค
  • atom์— ๊ฐ’์„ ์“ธ ๋•Œ, ํ•ด๋‹นย atom์„ ์˜์กดํ•˜๋Š”ย atom๋“ค์—๊ฒŒย notify๋ฅผ ํ•ด์ฃผ์–ด ๊ฐ๊ฐ์˜ย atom๋“ค์ด ์ƒˆ๋กญ๊ฒŒ ์—…๋ฐ์ดํŠธ๋œ ๊ฐ’์„ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.