Effect
jotai-effect는 반응형 사이드 이펙트를 위한 유틸리티 패키지입니다.
설치
npm install jotai-effect
atomEffect
atomEffect
는 Jotai에서 사이드 이펙트를 선언하고 아톰을 동기화하기 위한 유틸리티 함수입니다. 상태 변화를 관찰하고 반응하는 데 유용합니다.
파라미터
type CleanupFn = () => voidtype EffectFn = (get: Getter & { peek: Getter },set: Setter & { recurse: Setter },) => CleanupFn | voiddeclare function atomEffect(effectFn: EffectFn): Atom<void>
effectFn (필수): get
을 통해 상태 업데이트를 감지하고 set
을 통해 상태 업데이트를 작성하는 함수입니다. effectFn
은 다른 Jotai 아톰과 상호작용하는 사이드 이펙트를 생성할 때 유용합니다. 클린업 함수를 반환하여 이러한 사이드 이펙트를 정리할 수 있습니다.
사용법
Atom 변경 사항 구독하기
import { atomEffect } from 'jotai-effect'const loggingEffect = atomEffect((get, set) => {// 마운트 시 또는 someAtom이 변경될 때마다 실행됨const value = get(someAtom)loggingService.setValue(value)})
사이드 이펙트 설정 및 해제
import { atomEffect } from 'jotai-effect'const subscriptionEffect = atomEffect((get, set) => {const unsubscribe = subscribe((value) => {set(valueAtom, value)})return unsubscribe})
아톰 또는 훅으로 마운팅하기
atomEffect
를 사용해 효과를 정의한 후, 다른 아톰의 read 함수 내부에 통합하거나 Jotai 훅에 전달할 수 있습니다.
const anAtom = atom((get) => {// anAtom이 마운트될 때 atomEffect를 마운트get(loggingEffect)// ...})// 컴포넌트가 마운트될 때 atomEffect를 마운트function MyComponent() {useAtom(subscriptionEffect)// ...}
atomEffect
동작
클린업 함수: 클린업 함수는 컴포넌트가 언마운트되거나 재평가되기 전에 호출됩니다.
예제
atomEffect((get, set) => {const intervalId = setInterval(() => set(clockAtom, Date.now()))return () => clearInterval(intervalId)})무한 루프 방지:
atomEffect
는set
을 통해 감시 중인 값을 변경해도 다시 실행되지 않습니다.예제
const countAtom = atom(0)atomEffect((get, set) => {// 이 코드는 무한 루프를 발생시키지 않음get(countAtom) // 마운트 후, count는 1이 됨set(countAtom, increment)})재귀 지원:
set.recurse
를 사용하면 동기 및 비동기 상황에서 재귀를 지원합니다. 단, 클린업 함수에서는 재귀를 지원하지 않습니다.예제
const countAtom = atom(0)atomEffect((get, set) => {// 1초마다 count를 증가시킴const count = get(countAtom)const timeoutId = setTimeout(() => {set.recurse(countAtom, increment)}, 1000)return () => clearTimeout(timeoutId)})Peek 지원:
get.peek
을 사용하면 변경 사항을 구독하지 않고도 atom 데이터를 읽을 수 있습니다.예제
const countAtom = atom(0)atomEffect((get, set) => {// countAtom이 변경되어도 다시 실행되지 않음const count = get.peek(countAtom)})다음 마이크로태스크에서 실행:
effectFn
은 모든 Jotai 동기 읽기 평가가 완료된 후, 다음 사용 가능한 마이크로태스크에서 실행됩니다.예제
const countAtom = atom(0)const logAtom = atom([])const logCounts = atomEffect((get, set) => {set(logAtom, (curr) => [...curr, get(countAtom)])})const setCountAndReadLog = atom(null, async (get, set) => {get(logAtom) // [0]set(countAtom, increment) // 다음 마이크로태스크에서 effect 실행get(logAtom) // [0]await Promise.resolve().then()get(logAtom) // [0, 1]})store.set(setCountAndReadLog)동기 업데이트 배치 처리 (원자적 트랜잭션):
atomEffect
atom 의존성에 대한 여러 동기 업데이트는 배치 처리됩니다. 이펙트는 최종 값으로 단일 원자적 트랜잭션으로 실행됩니다.예제
const enabledAtom = atom(false)const countAtom = atom(0)const updateEnabledAndCount = atom(null, (get, set) => {set(enabledAtom, (value) => !value)set(countAtom, (value) => value + 1)})const combos = atom([])const combosEffect = atomEffect((get, set) => {set(combos, (arr) => [...arr, [get(enabledAtom), get(countAtom)]])})store.set(updateEnabledAndCount)store.get(combos) // [[false, 0], [true, 1]]조건부
atomEffect
실행:atomEffect
는 애플리케이션 내에서 마운트된 경우에만 활성화됩니다. 이는 필요하지 않을 때 불필요한 계산과 사이드 이펙트를 방지합니다. 언마운트를 통해 이펙트를 비활성화할 수 있습니다.예제
atom((get) => {if (get(isEnabledAtom)) {get(effectAtom)}})멱등성:
atomEffect
는 상태가 변경될 때 한 번만 실행되며, 몇 번 마운트되든 상관없습니다.예제
let i = 0const effectAtom = atomEffect(() => {get(countAtom)i++})const mountTwice = atom(() => {get(effectAtom)get(effectAtom)})store.set(countAtom, increment)Promise.resolve.then(() => {console.log(i) // 1})
의존성 관리
마운트 이벤트 외에도, 이펙트는 의존성 중 하나라도 값이 변경될 때 실행됩니다.
동기(Sync): 이펙트의 동기 평가 중에
get
으로 접근한 모든 아톰은 아톰의 내부 의존성 맵에 추가됩니다.예제
atomEffect((get, set) => {// `anAtom`이 변경될 때마다 업데이트되지만, `anotherAtom`이 변경될 때는 업데이트되지 않음get(anAtom)setTimeout(() => {get(anotherAtom)}, 5000)})비동기(Async): 비동기 이펙트의 경우, 보류 중인 fetch 요청과 Promise를 취소하기 위해 abort controller를 사용해야 합니다.
예제
atomEffect((get, set) => {const count = get(countAtom) // countAtom은 아톰 의존성const abortController = new AbortController();(async () => {try {await delay(1000)abortController.signal.throwIfAborted()get(dataAtom) // dataAtom은 아톰 의존성이 아님} catch (e) {if (e instanceof AbortError) {// 비동기 정리 로직} else {console.error(e)}}})()return () => {// countAtom이 변경될 때 abortabortController.abort(new AbortError())}})정리(Cleanup): 정리 함수에서
get
으로 아톰에 접근해도 아톰의 내부 의존성 맵에 추가되지 않습니다.예제
atomEffect((get, set) => {// 마운트 시 한 번 실행// `idAtom`이 변경되어도 업데이트되지 않음const unsubscribe = subscribe((valueAtom) => {const value = get(valueAtom)// ...})return () => {const id = get(idAtom)unsubscribe(id)}})의존성 맵 재계산: 의존성 맵은 매 실행마다 재계산됩니다. 현재 실행 중에 감시되지 않은 아톰은 현재 실행의 의존성 맵에 포함되지 않습니다. 활발히 감시되는 아톰만 의존성으로 간주됩니다.
예제
const isEnabledAtom = atom(true)atomEffect((get, set) => {// `isEnabledAtom` 이 true면, `isEnabledAtom` 또는 `anAtom`이 변경될 때 실행// 그렇지 않으면 `isEnabledAtom` 또는 `anotherAtom`이 변경될 때 실행if (get(isEnabledAtom)) {const aValue = get(anAtom)} else {const anotherValue = get(anotherAtom)}})
withAtomEffect
withAtomEffect
는 대상 아톰의 복제본에 이펙트를 바인딩합니다. 이는 대상 아톰의 복제본이 마운트될 때 활성화되는 이펙트를 생성하는 데 유용합니다.
Parameters_3PmHDWfvMww8aM5M8X9jZM
declare function withAtomEffect<T>(targetAtom: Atom<T>,effectFn: EffectFn,): Atom<T>
targetAtom (필수): 이펙트가 바인딩될 아톰.
effectFn (필수): get
을 통해 상태 업데이트를 감지하고, set
을 통해 상태 업데이트를 작성하는 함수.
반환값: 대상 아톰과 동일하지만 바인딩된 이펙트를 가진 아톰.
Usage_5M9S4SS2qJNBZop3dfCjZ3
import { withAtomEffect } from 'jotai-effect'const valuesAtom = withAtomEffect(atom(null), (get, set) => {// valuesAtom이 마운트될 때 실행됨const unsubscribe = subscribe((value) => {set(valuesAtom, value)})return unsubscribe})
Comparison with useEffect
컴포넌트 사이드 이펙트
useEffect는 컴포넌트를 외부 시스템과 동기화할 수 있게 해주는 React 훅입니다.
훅은 함수 컴포넌트에서 React의 상태와 생명주기 기능에 "연결"할 수 있게 해주는 함수입니다. 훅은 상태 관련 로직을 재사용할 수 있게 해주지만, 중앙 집중화하지는 않습니다. 각 훅 호출은 완전히 독립된 상태를 가집니다. 이러한 독립성을 컴포넌트 스코프라고 부를 수 있습니다. 컴포넌트의 props와 상태를 Jotai 아톰과 동기화하려면 useEffect 훅을 사용해야 합니다.
전역 사이드 이펙트
전역 사이드 이펙트를 설정할 때, useEffect
와 atomEffect
중 어떤 것을 사용할지는 개발자의 선호에 따라 결정됩니다.
이 로직을 컴포넌트에 직접 작성할지, 아니면 Jotai 상태 모델에 작성할지는 여러분이 채택한 멘탈 모델에 따라 달라집니다.
atomEffect
는 아톰 내부의 동작을 모델링하는 데 더 적합합니다.
이것은 컴포넌트가 아닌 스토어 컨텍스트에 범위가 한정됩니다.
이를 통해, 아무리 많은 호출이 발생하더라도 단일 이펙트가 사용된다는 것을 보장할 수 있습니다.
useEffect
훅을 사용할 때도, useEffect
가 멱등성을 보장하도록 하면 동일한 보장을 얻을 수 있습니다.
atomEffect
는 몇 가지 다른 점에서 useEffect
와 구별됩니다.
아톰 상태 변경에 직접 반응할 수 있고, 무한 루프에 강하며, 조건부로 마운트될 수 있습니다.
선택은 여러분에게 달려 있습니다
useEffect와 atomEffect 모두 각각의 장점과 활용 사례가 있습니다. 여러분의 프로젝트 요구사항과 편의에 따라 적절한 방식을 선택하세요. 더 부드럽고 직관적인 개발 경험을 제공하는 접근 방식을 선택하는 것이 좋습니다. 즐거운 코딩 되세요!