JotaiJotai

상태
React를 위한 기본적이고 유연한 상태 관리

Bunja

Bunja는 가벼운 상태 수명 관리자입니다.

이 라이브러리는 jōtai 아톰을 위한 RAII 래퍼를 제공합니다.


참고 자료:

설치

npm install bunja

Bunja 정의하기

bunja 함수를 사용하여 bunja를 정의할 수 있습니다.

정의된 bunja를 useBunja 훅으로 접근하면 bunja 인스턴스가 생성됩니다.

만약 bunja를 참조하는 렌더 트리의 모든 컴포넌트가 사라지면, bunja 인스턴스는 자동으로 소멸됩니다.

bunja의 생명주기가 시작되고 끝날 때 효과를 트리거하고 싶다면, bunja.effect 필드를 사용할 수 있습니다.

import { bunja } from 'bunja'
import { useBunja } from 'bunja/react'
const countBunja = bunja([], () => {
const countAtom = atom(0)
return {
countAtom,
[bunja.effect]() {
console.log('mounted')
return () => console.log('unmounted')
},
}
})
function MyComponent() {
const { countAtom } = useBunja(countBunja)
const [count, setCount] = useAtom(countAtom)
// 여기에 컴포넌트 로직을 작성하세요
}

다른 Bunja에 의존하는 Bunja 정의하기

넓은 범위의 상태와 좁은 범위의 상태를 관리하고 싶다면, (넓은 범위의) bunja에 의존하는 (좁은 범위의) bunja를 만들 수 있습니다.

예를 들어, 페이지 상태를 담당하는 bunja와 모달 상태를 담당하는 bunja를 생각해볼 수 있습니다.

페이지 상태는 모달 상태보다 더 오래 유지되며, 모달 상태는 모달이 열리는 순간부터 닫힐 때까지 존재해야 합니다.

이런 경우 다음과 같이 코드를 작성할 수 있습니다.

const pageBunja = bunja([], () => {
const pageStateAtom = atom({})
return { pageStateAtom }
})
const childBunja = bunja([pageBunja], ({ pageStateAtom }) => {
const childStateAtom = atom((get) => ({
...get(pageStateAtom),
child: 'state',
}))
return { childStateAtom }
})
const modalBunja = bunja([pageBunja], ({ pageStateAtom }) => {
const modalStateAtom = atom((get) => ({
...get(pageStateAtom),
modal: 'state',
}))
return { modalStateAtom }
})
function Page() {
const [modalOpen, setModalOpen] = useState(false)
return (
<>
<Child />
{modalOpen && <Modal />}
</>
)
}
function Child() {
const { childStateAtom } = useBunja(childBunja)
const childState = useAtomValue(childStateAtom)
// ...
}
function Modal() {
const { modalStateAtom } = useBunja(modalBunja)
const modalState = useAtomValue(modalStateAtom)
// ...
}

pageBunja가 직접 useBunja로 사용되지 않는다는 점에 주목하세요.

childBunjamodalBunjauseBunja로 사용할 때, 이들은 pageBunja에 의존하기 때문에 pageBunja도 마치 useBunja로 사용된 것과 같은 효과를 냅니다.

모달이 언마운트되면, 더 이상 useBunja(modalBunja)를 사용하는 곳이 없으므로 modalBunja의 인스턴스는 자동으로 제거됩니다.

스코프를 사용한 의존성 주입

로컬 상태 관리를 위해 bunja를 사용할 수 있습니다.

bunja의 의존성으로 스코프를 지정하면, 스코프에 주입된 값에 따라 별도의 bunja 인스턴스가 생성됩니다.

import { bunja, createScope } from 'bunja'
const UrlScope = createScope()
const fetchBunja = bunja([UrlScope], (url) => {
const queryAtom = atomWithQuery((get) => ({
queryKey: [url],
queryFn: async () => (await fetch(url)).json(),
}))
return { queryAtom }
})

React context를 통해 의존성 주입하기

스코프를 React context에 바인딩하면, 해당 스코프에 의존하는 분자는 해당 React context에서 값을 가져올 수 있습니다.

아래 예제에서는 동일한 fetchBunja를 참조하는 두 개의 React 인스턴스(<ChildComponent />)가 있지만, 각각 다른 context 값을 참조하기 때문에 두 개의 별도 분자 인스턴스가 생성됩니다.

import { createContext } from 'react'
import { bunja, createScope } from 'bunja'
import { bindScope } from 'bunja/react'
const UrlContext = createContext('https://example.com/')
const UrlScope = createScope()
bindScope(UrlScope, UrlContext)
const fetchBunja = bunja([UrlScope], (url) => {
const queryAtom = atomWithQuery((get) => ({
queryKey: [url],
queryFn: async () => (await fetch(url)).json(),
}))
return { queryAtom }
})
function ParentComponent() {
return (
<>
<UrlContext value="https://example.com/foo">
<ChildComponent />
</UrlContext>
<UrlContext value="https://example.com/bar">
<ChildComponent />
</UrlContext>
</>
)
}
function ChildComponent() {
const { queryAtom } = useBunja(fetchBunja)
const { data, isPending, isError } = useAtomValue(queryAtom)
// 컴포넌트 로직 작성
}

createScopeFromContext 함수를 사용하면 스코프 생성과 context 바인딩을 한 번에 처리할 수 있습니다.

import { createContext } from 'react'
import { createScopeFromContext } from 'bunja/react'
const UrlContext = createContext('https://example.com/')
const UrlScope = createScopeFromContext(UrlContext)

스코프에 직접 의존성 주입하기

React 컴포넌트 내부에서 스코프에 주입할 값을 생성하고 바로 사용하고 싶을 수 있습니다. 이럴 때는 별도로 컨텍스트를 감싸지 않고 inject 함수를 사용해 스코프에 값을 주입할 수 있습니다.

import { inject } from 'bunja/react'
function MyComponent() {
const { queryAtom } = useBunja(
fetchBunja,
inject([[UrlScope, 'https://example.com/']]),
)
const { data, isPending, isError } = useAtomValue(queryAtom)
// 컴포넌트 로직 작성
}