React

jotai state management

Asset Type
File Type
When to use
2022/03/19
Reference
Created by
Created time
2022/03/19 18:56
Last edited time
2022/04/29 21:18
Jotai는 React를 위한 비교적 새로운 상태 관리 라이브러리입니다. 간단하지만 실수하지 마십시오. 강력한 라이브러리입니다.
Jotai는 Facebook의 새로운 Recoil 패턴 및 라이브러리를 기반으로 합니다. 50여 년 전 Facebook은 Flux 라는 React의 상태 관리를 위한 패턴과 라이브러리를 만들었습니다 .
이 패턴에서 Facebook이 아닌 일부 개발자는 더 강력하고 사용하기 쉬운 자체 라이브러리를 새로 만들었으며 React 세계를 폭풍으로 몰아넣었습니다. 
이 라이브러리는 Redux입니다. 이제 Facebook에는 Flux와 다른 이데올로기를 가진 Recoil이 있습니다.
Jotai와 Redux도 마찬가지입니다. 이 중 몇 가지를 살펴보겠습니다.

Jotai?

최소한의 API
- Jotai는 간단한 API 디자인을 가지고 있으며 함께 작업하는 것이 즐겁습니다.
작은 번들 크기
- Jotai의 설치 공간은 정말 작으며 사이트/앱의 성능을 방해하지 않습니다.
Loaded to the brim
- Jotai는 많은 걸 갖고 있다.
Performant - Jotai
BLAZING FAST입니다. 런타임 성능이 미쳤습니다!
TYPESCRIPT!! 
- 일류 TypeScript 지원!! Typings가 사전 설치된 상태로 제공되며 TypeScript 작성 경험은 천국을 초월합니다.

Redux와 이념적 차이

Jotai는 거의 모든 면에서 Redux 및 React Context API 와 매우 다릅니다. 그러나 포괄하는 한 가지 핵심 개념이 있습니다. 바로 internalize해야 하는 것입니다.
Redux 저장소는 monolithic이지만 Jotai는 atomic입니다.
즉, Redux에서는 앱에 필요한 모든 전역 상태를 하나의 큰 개체에 저장하는 패턴입니다. 조타이에서는 그 반대입니다. 상태를 원자로 나눕니다. 즉, 하나의 단일 저장소 또는 밀접하게 관련된 상태에 대한 하나의 저장소입니다.

Jotai 시작하기

Jotai 설치

# pnpm pnpm add jotai # npm npm install jotai # Or if you're a yarn person yarn add jotai
Plain Text
복사

앱에서 설정하기

Jotai는 공급자가 사용 중인 현재 구성 요소의 부모에 있어야 합니다. 가장 간단한 방법은 아래와 같이 공급자에서 전체 앱을 래핑하는 것입니다
// index.jsx (or index.tsx) import React from 'react'; import ReactDOM from 'react-dom'; import { App } from './App';// Jotai provider import { Provider } from 'jotai'; ReactDOM.render( <React.StrictMode> <Provider> <App /> </Provider> </React.StrictMode>, document.getElementById('root'), );
TypeScript
복사
이제 앱 어디에서나 jotai를 사용할 수 있습니다!

기본 구문

이제 기본 설정이 완료되었으므로 구문을 살펴보겠습니다!

첫 번째 원자 만들기

원자는 우주의 구성 요소이며 함께 분자로 뭉칩니다.
아니요, 그 원자가 아닙니다 .
Jotai 원자는 작은 고립 된 상태 조각입니다. 이상적으로는 하나의 원자에 매우 작은 데이터가 포함되어 있습니다(단순한 관례일 뿐입니다. 여전히 모든 상태를 하나의 원자에 넣을 수 있지만 성능 면에서 매우 느립니다).
첫 번째 원자를 만드는 방법은 다음과 같습니다.
import { atom } from 'jotai'; const themeAtom = atom('light');
TypeScript
복사
그리고 그게 다야! 당신은 당신의 첫 번째 상태를 가지고 있습니다!!
temaAtom에서와 같이 내 Atom 이름에 Atom을 붙였습니다. 규칙이나 공식 규약이 아닙니다. 저는 단지 큰 프로젝트에서 명확성을 위해 제 원자에 이렇게 이름을 붙일 뿐입니다. 테마Atom가 아닌 테마로만 명명할 수 있습니다.
자, 어떻게 사용하나요? useState 훅과 useContext 훅을 혼재시키는 것입니다.
import { useAtom } from 'jotai'; export const ThemeSwitcher = () => { const [theme, setTheme] = useAtom(themeAtom); return <main>{theme}</main>; };
TypeScript
복사
아시겠죠? useState와 똑같습니다만, 유일하게 다른 점은 작성한 atom이 useState에 전달된다는 것입니다. useAtom은 크기 2의 배열을 반환합니다. 여기서 첫 번째 요소는 값이고 두 번째 요소는 함수입니다. 원자 값을 설정합니다.
이것에 의해, 이 아톰에 의존하는 모든 컴포넌트가 갱신 및 재렌더 됩니다.
모두 합치면 코드 전체가 이렇게
import { atom, useAtom } from 'jotai'; const themeAtom = atom('light'); export const ThemeSwitcher = () => { const [theme, setTheme] = useAtom(themeAtom); return <main>{theme}</main>; };
TypeScript
복사
그리고 setTheme가 아직 사용되지 않았음을 주목하십시오. 바꿔봅시다
import { atom, useAtom } from 'jotai'; const themeAtom = atom('light'); export const ThemeSwitcher = () => { const [theme, setTheme] = useAtom(themeAtom); return ( <main> <p>Theme is {theme}</p> <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle Theme </button> </main> ); };
TypeScript
복사
친구여, 이건 시작에 불과해요 조타이는 더 많은 걸 할 수 있습니다!
하지만 이것만으로는 큰 시야를 주지 못합니다. 값을 전환하는 버튼의 특별한 점은 무엇입니까? 그리고 나는 동의합니다. 이 예는 꽤 지루합니다. Jotai를 사용하여 실제 테마 전환기를 만듭시다.

Jotai In Practice: 테마 스위처 후크

요즘은 모든 앱, 웹사이트, 심지어 블로그 사이트(특히 블로그)에서 테마 전환이 필요합니다. 그리고 테마 전환기를 만드는 것은 상당히 어려울 수 있습니다.
먼저 CSS 변수를 설정해야 합니다. 그런 다음 테마로 시작해야 합니다. 테마를 바꿀 수 있는 버튼을 만들어야 합니다.
그런 다음 localstorage API를 사용하여 기본 설정을 기억해야 합니다.
하지만 이렇게 하면 페이지가 로딩될 때 적절한 값을 얻을 수 있고 SSR 및 프리렌더링에 영향을 주지 않을 수 있습니다.
네, 꽤 복잡해요 개발자가 시도하기 전에 두려워할 문제입니다.
그러니까 조타이를 이용해서 만들어봅시다. 조타이가 얼마나 간단하게 만들 수 있는지 알면 놀라실 거예요.
우리의 목표는 다음과 같습니다.
서버 사이드에서 동작합니다(As in not referring to document or window without protection).
localstorage에 로컬로 저장된 값을 선택합니다.
로컬 값이 없는 경우 디바이스 테마가 밝은지 어두운지 여부에 관계없이 device preference를 얻으려고 합니다.
현재 테마는 사용 중인 컴포넌트를 리렌더링하는 state로 사용할 수 있어야 합니다.
state를 변경하면 localstorage가 그에 따라 업데이트됩니다.
이제 목록이 완성되었으므로 코드를 살펴보겠습니다
import { atom, useAtom } from 'jotai'; import { useEffect } from 'react'; const browser = typeof window !== 'undefined'; const localValue = browser ? localStorage.getItem('theme') : 'light'; const systemTheme = browser && matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; // The atom to hold the value goes here const themeAtom = atom(localValue || systemTheme); /** Sitewide theme */ export function useTheme() { const [theme, setTheme] = useAtom(themeAtom); useEffect(() => { if (!browser) return; localStorage.setItem('theme', theme); document.body.classList.remove('light', 'dark'); document.body.classList.add(theme); }, [theme]); return [theme, setTheme]; }
TypeScript
복사
여기서 많은 일들이 일어나고 있어요. 여기 내역이 있습니다.
브라우저에서 현재 코드가 실행 중인지 확인합니다. SSR에서 코드를 실행 중이거나 프리렌더링할 경우 이 값은 거짓이 됩니다.
로컬 스토리지에 저장된 값을 가져옵니다. 로컬 스토리지에 테마가 있는 경우 사용자가 선택한 것이므로 가장 높은 우선순위로 간주합니다.
또한 노드에는 로컬 스토리지가 없기 때문에 SSR 모드에서 실행 중인 경우 기본값인 light로 폴백해야 합니다.
또한 로컬 스토리지 값이 존재하지 않는 경우 preferred-color-scheme: dark를 사용하여 디바이스 기본 설정을 가져옵니다. 장치 기본 설정이 어둡거나 SSR에서 코드가 실행 중인 경우 다시 값 light로 돌아갑니다.
마지막으로 원자를 만듭니다. 현재 테마를 실제로 보관하고 있는 메인 스토어로, 상태 그대로의 사용성과 변경이 가능합니다. 제공하는 값: localValue || systemTheme.
이러한 값으로 인해 발생할 수 있는 일은 다음과 같습니다.
SSR/프레렌더링 모드에서 실행 중인 경우 localValue = 'light' and systemTheme = 'light', localValue || systemTheme는 light가 됩니다.
여기서 중요한 점: SSR의 앱은 가벼운 테마로 되어 있기 때문에, 앱을 프리렌더 하면, 플레인 HTML의 관점에서 가벼운 테마로 끝납니다. JavaScript가 로드되면, 가능한 한 적절한 테마로 동기화됩니다.
왜 그냥 후크 안에 localValuesystemTheme을 넣지 않았을까요?
이유: 훅에 넣으면 훅이 컴포넌트에서 초기화되거나 컴포넌트가 다시 렌더링될 때마다 이 훅이 다시 실행되어 로컬 스토리지 및 미디어 쿼리에서 이러한 값을 다시 가져옵니다. 이것들은 꽤 빠르지만, 로컬 스토리지로 인해 차단되고 있으며, 많이 사용하면 jank가 발생할 수 있습니다.
이 두 개의 변수를 앱의 라이프 타임에 한 번 초기화합니다. 이것은 초기값을 얻기 위해서만 필요하기 때문입니다.
마지막으로 후크를 시작합니다.
이 원자를 useAtom: const [set, setTheme] = useAtom(temeAtom);을 사용하여 로컬 상태로 만듭니다. 이것들은 state의 형식으로 우리의 theme가 될 것이다. 테마는 setTheme를 사용하여 수정할 수 있습니다.
다음으로 현재 테마를 CSS에 실제로 알리는 가장 중요한 부분을 알아냈습니다.
useEffect(() => { if (!browser) return; localStorage.setItem('theme', theme); document.body.classList.remove('light', 'dark'); document.body.classList.add(theme); }, [theme]);
TypeScript
복사
두 번째 인수 의 useEffect배열에서 볼 수 있듯이 테마가 변경될 때마다 실행됩니다. 이것이 실행되면 코드가 브라우저에서 실행 중인지 확인합니다. 그렇지 않은 경우 반환을 수행하여 추가 실행을 중지합니다.
성공하면 계속해서 put theme에 해당하는 모든 클래스를 제거하고 <body>테마 변수의 최신 값에 해당하는 클래스를 추가합니다.
마지막으로 [theme, setTheme]쌍을 있는 그대로 반환하므로 사용하는 것처럼 사용할 수 있습니다 useState{ theme, setTheme }명시적인 이름을 지정하는 객체로 반환할 수도 있습니다 .
이것은 이 후크를 위한 것입니다.
그리고 내 TypeScript 친척도 포함되어 있습니다
import { atom, useAtom } from 'jotai'; import { useEffect } from 'react'; export type Theme = 'light' | 'dark'; const browser = typeof window !== 'undefined'; const localValue = (browser ? localStorage.getItem('theme') : 'light') as Theme; const systemTheme: Theme = browser && matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; // The atom to hold the value goes here const themeAtom = atom<Theme>(localValue || systemTheme); /** Sitewide theme */ export function useTheme() { const [theme, setTheme] = useAtom(themeAtom); useEffect(() => { if (!browser) return; localStorage.setItem('theme', theme); document.body.classList.remove('light', 'dark'); document.body.classList.add(theme); }, [theme]); return [theme, setTheme] as const; }
TypeScript
복사
이것이 테마를 강력하게 전환하기 위해 얻은 최종 코드입니다. 이 후크는 Jotai의 단순성으로 인해 간단하고 이해하기 쉽습니다.
하지만 여기에 문제가 있습니다. Context API를 사용하는 것에 비해 Jotai를 사용하여 많은 코드를 저장하지 않았습니다. 그 코드는 거의 이 정도로 간단할 것입니다. 단지 조금 더 상용구만 있으면 됩니다. 따라서 여기에서는 실제로 큰 차이가 없습니다.
그러나 여기에 반전이 있습니다.
Jotai가 제공하는 것을 사용하여 더 많은 코드를 제거할 수 있습니다.atomWithStorage
localstorage후크 내부와 외부 모두에서 동기화 논리를 완전히 이동할 수 있습니다 .

atomWithStorage로 후크 재작성

atomWithStorage는 제공된 값을 localStorage 또는 sessionStorage(또는 React Native와 함께 사용되는 경우 AsyncStorage)와 자동으로 동기화하여 첫 번째 로드 시 자동으로 값을 선택하는 특수한 원자입니다. jotai/utils 모듈에서 사용할 수 있으며 Jotai Core의 2.4KB 이외의 바이트가 추가됩니다.
이를 다시 작성하는 방법은 다음과 같습니다.
import { useAtom } from 'jotai'; import { atomWithStorage } from 'jotai/utils'; import { useEffect } from 'react'; const browser = typeof window !== 'undefined'; // The atom to hold the value goes here const themeAtom = atomWithStorage( 'theme', browser && matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light', );/** Sitewide theme */ export function useTheme() { const [theme, setTheme] = useAtom(themeAtom); useEffect(() => { if (!browser) return; document.body.classList.remove('light', 'dark'); document.body.classList.add(theme); }, [theme]); return [theme, setTheme]; }
TypeScript
복사
보시다시피 코드에서 localstorage를 완전히 제거하여 atomWithStorage으로 새로운 기능을 구현했습니다.
첫 번째 인수는 localstorage에 저장하는 키입니다. 여기서 테마를 값으로 지정한 경우
localstorage.getItem('theme')을 사용하여 로컬 스토리지에서 테마를 가져옵니다.
에러
보시다시피 코드 자체는 줄 바꿈에 있어서 그다지 작지 않습니다. 크기가 20% 더 작을 뿐인데, 이미 크기가 작은
파일인 경우에는 큰 숫자가 아닙니다. 여기서 중요한 것은 atomWithStorage 덕분에 복잡함을 감출 수 있었다
는 것입니다.
이제 로컬값을 유지하기 위해 스토리지를 염두에 둘 필요가 없습니다. 주요 논리에 초점을 맞추고 이 값이 로컬로 동기화된다는 것을 기억하면 됩니다.
이 후크를 사용하는 것은 궁극적으로 매우 간단합니다.
import { useTheme } from './use-theme'; export const ThemeSwitcher = () => { const [theme, setTheme] = useTheme(); return ( <main> <p>Theme is {theme}</p> <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle Theme</button> </main> ); };
TypeScript
복사
그리고 그것은 단지 작동합니다!! 

한눈에 보는 조타이

이것이 죠타이에 대한 기본적인 소개였습니다. 
atomWithStorage코드를 얼마나 강력하고 간단하게 만들 수 있는지 보여주기 위해 유틸리티 함수를 추가했습니다 . 이 유틸리티는 나중에 다루겠습니다. 
지금은 기본 원자 및 useAtom, 그리고 그것들이 어떻게 초능력을 부여하는지에 대해 더 자세히 살펴보겠습니다.

파생 원자

때로는 원자가 다른 원자(들)에 의존하도록 만들고 싶습니다. 즉, 여러 원자를 하나의 큰 계산 원자로 함께 구성하려는 것입니다. 그것은 Jotai와 함께 매우 간단합니다.

읽기 전용 원자

읽기 전용 원자는 다른 원자에 의존하는 파생 원자이며 우리는 그 값을 직접 변경할 수 없습니다.
예를 들어, 이러한 원자의 사용법은 다음과 같습니다
const [derivedValue] = useAtom(derivedAtom);
TypeScript
복사
여기 setDerivedValue에는 setter 기능이 없습니다. 우리는 이 원자만 읽을 수 있습니다. 파생된 원자를 변경하면 이 값이 자동으로 업데이트됩니다.
하지만 충분한 이야기! 이제 이러한 파생 원자를 만드는 방법을 살펴보겠습니다.
지금까지 이 원자를 보았습니다
const store = atom('someValue');
TypeScript
복사
하지만 그거 알아? Atom은 매개변수로 함수를 사용할 수 있습니다
const store = atom((get) => get(someAtomDefinedSomewhere));
TypeScript
복사
여기서 원시 값 대신 콜백을 전달합니다. 이 콜백에는 get다른 원자의 실제 값에 액세스할 수 있는 매개변수가 있습니다. 이 원시 값으로 무엇이든 할 수 있습니다. 곱하고, 연결하고, 매핑하고, 줄이십시오. 하늘이 한계입니다.
그리고 이것으로 더 많은 것을 할 수 있습니다. 예를 들어, 한 가지 간단한 예는 특정 기준과 일치하는 개체의 모든 키 목록이 배열에 포함되도록 하는 것입니다.
여기 개체가 있습니다
export const appsStateStore = atom({ finder: false, launchpad: false, safari: false, messages: false, mail: true, maps: true, photos: false, facetime: true, calendar: false, });
TypeScript
복사
어레이에서 열린 앱을 보유할 원자를 정의하십시오
const openAppsStore = atom((get) => { const apps = get(openAppsStore); // Gives the raw value { finder: false, launchpad: false, ...// Filter out the values who are marked as false const openAppsList = Object.keys(apps).filter((appName) => apps[appName]);return openAppsList; });
TypeScript
복사
그리고 이것이다!! 에서 값을 조정하면 값 appStateStore을 true 및 false로 설정 openAppsStore하면 변경 사항이 반영되고 이 저장소를 사용하는 구성 요소도 새 값으로 업데이트됩니다.
다양한 원자를 함께 구성할 수도 있습니다
const xCoordinateAtom = atom(0); const yCoordinateAtom = atom(0);// Compose 'em all const distanceFromOriginAtom = atom((get) => Math.sqrt(get(xCoordinateAtom) ** 2 + get(yCoordinateAtom) ** 2), );
TypeScript
복사
xCoordinateAtom원자 및 yCoordinateAtom를 조정할 수 있으며 distanceFromOriginAtom새 값으로 업데이트됩니다!!)
원점(0, 0)에서 한 점까지의 거리를 계산하는 수학 공식입니다. 이해하지 못했다면 걱정하지 마세요. 다른 원자를 매끄럽게 함께 구성할 수 있다는 요점을 이해하고 싶습니다. 그게 다야! 

읽기 및 쓰기 가능한 원자

이들은 다른 원자에서 파생된 원자이지만 사용자가 자체적으로 수정할 수도 있습니다.
const readWriteAtom = atom( (get) => get(priceAtom) * 2, (get, set, newPrice) => { set(priceAtom, newPrice / 2); // you can set as many atoms as you want at the same time }, );
TypeScript
복사
이 원자는 값을 설정하면 우리가 제공하는 사용자 정의 쓰기 기능을 트리거하고 의존하는 원자를 수정할 수 있습니다. 기본적으로 양방향 데이터 바인딩입니다. 변경 priceAtom하면 readWriteAtom업데이트됩니다. 업데이트 readWriteAtom하면 priceAtom업데이트됩니다. 정신이 번쩍 들죠 ?!?
하지만 조심하세요. 마법처럼 보이지만 양방향 데이터 바인딩입니다. 과거에 이에 대해 논란이 있었고, 디버깅과 데이터 흐름을 정상으로 유지하는 것이 이러한 문제로 극도로 어려워지기 때문에 당연히 그렇습니다. 그렇기 때문에 React 자체에는 단방향 데이터 바인딩만 있습니다. 따라서 이 원자를 주의해서 사용하십시오.

비동기 원자

이 시점에서 우리는 매우 위험한 영역인 비동기 렌더링(Async Rendering, 일명 React Suspense)에 들어갑니다.
때때로 원자는 비동기식이어야 합니다. 즉, 즉시 값을 가져오는 것이 아니라 가져오기를 사용하여 원격 소스에서 가져옵니다. 이 때 렌더링을 일시 중단하고 데이터가 돌아올 때까지 기다려야 합니다.
다음은 비동기 원자 사용에 대한 간단한 코드 데모입니다
const fetchCountAtom = atom( (get) => get(countAtom), async (_get, set, url) => { const response = await fetch(url); set(countAtom, (await response.json()).count); }, );function Controls() { const [count, compute] = useAtom(fetchCountAtom); return <button onClick={() => compute('http://count.host.com')}>compute</button>; }
TypeScript
복사
그러나 위의 내용은 컨트롤을 서스펜스로 묶지 않으면 작동하지 않습니다
<Suspense fallback={<span />}> <Controls /> </Suspense>
TypeScript
복사
Async Atoms는 실제 앱을 구축하는 데 매우 유용합니다. 이러한 앱은 대부분 데이터 가져오기가 추가된 CRUD 앱이기 때문입니다.

최고의 유틸리티

당신이 사랑 atomWithStorage하고 당신의 머리가 잠금 해제할 수 있는 모든 가능성으로 회전한다면, 나는 당신을 위해 더 많은 멋진 Jotai 유틸리티를 얻었습니다.

atomWithStorage

useTheme나는 이 특별한 원자를 사용하기 위해 후크를 리팩토링할 때 기사의 맨 처음에 이것을 다루었습니다 . 키( 에 저장되는 이름 localstorage)와 초기 값을 허용합니다. 그런 다음 이 원자를 변경하면 해당 값이 로컬로 유지되고 페이지가 다시 로드된 후 선택됩니다.
import { atomWithStorage } from 'jotai/utils';const darkModeAtom = atomWithStorage('darkMode', false);
TypeScript
복사
이 원자는 또한 SSR 친화적이므로 전혀 문제 없이 앱을 SSR할 수 있습니다.
이 아톰 sessionStorage도 값을 저장할 수 있으므로 브라우저가 닫힐 때까지 아톰의 값이 유지됩니다. 짧은 세션이 선호되는 뱅킹 웹 앱을 구축하는 경우에 유용합니다.
React Native와도 작동하므로 거의 보편적입니다.

atomWithReset

때로는 상태를 원래 상태로 재설정해야 합니다. 전통적으로 이를 수행하는 방법은 초기 값을 변수에 저장하고 해당 변수를 값으로 사용하여 상태를 만들고 필요할 때 setState초기 값으로 되돌리는 것이었습니다. 코드는 다음과 같을 것입니다
import { atom, useAtom } from 'jotai';const initialValue = 'light';const themeAtom = atom(initialValue);function ThemeSwitcher() { const [theme, setTheme] = useAtom(themeAtom);const toggleTheme = () => setTheme(theme === 'light' ? 'dark' : 'light'); const resetTheme = () => setTheme(initialValue);return ( <> <button onClick={toggleTheme}>Toggle theme</button><button onClick={resetTheme}>Reset theme</button> </> ); }
TypeScript
복사
이것은 상당히 쉽지만 같은 일을 하는 좀 더 조타이식한 방법이 있습니다
import { useAtom } from 'jotai'; import { atomWithReset, useResetAtom } from 'jotai/utils';const themeAtom = atomWithReset('light');function ThemeSwitcher() { const [theme, setTheme] = useAtom(themeAtom); const reset = useResetAtom(themeAtom);const toggleTheme = () => setTheme(theme === 'light' ? 'dark' : 'light');return ( <> <button onClick={toggleTheme}>Toggle theme</button><button onClick={reset}>Reset theme</button> </> ); }
TypeScript
복사
보시다시피 구성 요소를 약간 단순화했습니다. 이 경우에는 매우 간단한 예이므로 많지 않습니다. 하지만 저는 개인적으로 이 재설정 원자를 완전히 복잡한 논리 기반 구성 요소와 함께 앱에서 사용했으며 코드를 훨씬 더 제정신이고 관용적이며 버그가 없도록 만듭니다.

셀렉트 아톰

라이브러리와 프레임워크에 대한 쿨함 카운터가 있었다면 Jotai만으로도 이 작은 유틸리티로 그것을 깨뜨렸을 것입니다.
큰 물건이 있다고 가정해 봅시다.
const defaultPerson = { name: { first: 'Jane', last: 'Doe', }, birth: { year: 2000, month: 'Jan', day: 1, time: { hour: 1, minute: 1, }, }, };// Original atom. const personAtom = atom(defaultPerson);
TypeScript
복사
그리고 많은 구성 요소가 이 특정 원자에 의존하지만 이 중 일부만 필요합니다.
문제는 이 원자를 업데이트할 때 이 원자에 의존하는 모든 구성 요소가 다시 렌더링된다는 것입니다. 만 변경하더라도 birth.time.minute전체가 업데이트로 간주되고 모든 구성 요소가 다시 렌더링됩니다. 불행히도 이것이 React가 작동하는 방식입니다.
그러나 걱정하지 마십시오. Jotai도 이에 대한 해결책을 가지고 있습니다! selectAtom전체 개체의 하위 경로만 사용하여 파생된 원자를 만들 수 있습니다.
const firstNameAtom = selectAtom(personAtom, (person) => person.name.first);
TypeScript
복사
firstNameAtom속성이 변경 될 때만 트리거되는 읽기 전용 파생 원자 person.name.first이며 person.name.first 값을 보유합니다.
필드 를 업데이트할 수 있으며 birth.time.hour(전체 원자를 새 값으로 업데이트하여) 의존하는 구성 요소 firstNameAtom는 변경되지 않은 상태로 유지됩니다. 놀랍죠?

개체에 적용

문제가 발생합니다. 개체인 person.birth 필드에 귀를 기울이면 이 원자는 매우 효율적이지 않을 것입니다. Jotai는 동등성 검사(===)를 사용하여 원자의 부분이 변경되었는지 여부와 다시 렌더링해야 하는지 여부를 확인합니다. 문제는 2개의 객체가 결코 동일하지 않다는 것입니다. ===는 값이 아닌 참조로 개체를 확인합니다. 따라서 기본적으로 이 원자는 해당 시나리오에서 거의 쓸모가 없습니다. 하지만 별로!
이것에 대한 세 번째 인수를 제공할 수 있습니다 selectAtom. 이는 동등성 검사의 고유한 버전입니다. 사용자 정의 함수를 작성하여 개체를 확인할 수 있습니다.
const birthAtom = selectAtom(personAtom, (person) => person.birth, deepEqual);
TypeScript
복사
OFC, 직접 작성하는 deepEqual것은 어렵기 때문에 lodash-es의 isEqual기능을 사용하는 것이 좋습니다.
import { isEqual } from 'lodash-es';const birthAtom = selectAtom(personAtom, (person) => person.birth, isEqual);
TypeScript
복사
lodash를 보고 번들 크기에 대한 걱정이 생긴다면 lodash-es는 트리를 흔들 수 있고 4.4KB로 축소되며 gzip/brotli에서는 훨씬 더 작습니다. 그러니 걱정마세요
이것은 앱의 성능을 0에서 영웅으로 끌어올릴 수 있습니다. 문자 그대로!

프리즈아톰

import { atom } from 'jotai'; import { freezeAtom } from 'jotai/utils';const objAtom = freezeAtom(atom({ count: 0 }));
TypeScript
복사
freezeAtom기존 원자를 취하고 새로운 파생 원자를 반환합니다. 반환된 원자는 "고정"됩니다. 즉 useAtom, 구성 요소에서 원자를 사용하거나 다른 원자를 가져올 때 원자 값이 로 완전히 고정됩니다 Object.freeze예기치 않은 동작으로 이어질 수 있는 실수로 개체를 변경하려고 시도한 버그를 찾는 것이 유용할 것입니다.
이 원자는 대부분 디버그 가능성을 위한 것입니다. 객체 상태를 변경할 때(React에서는 해서는 안 되지만, 우리는 모두 인간입니다). 이것은 매우 일반적인 경우이므로 Jotai 사람들이 고품질 디버깅 도구를 제공하게 되어 매우 기쁩니다.

모두 기다렸습니다.

비동기 원자에 대한 위의 섹션을 기억하십니까? 이 유틸리티는 이를 위한 것이며 매우 편리한 유틸리티입니다.
const dogsAtom = atom(async (get) => { const response = await fetch('/dogs'); return await response.json(); });const catsAtom = atom(async (get) => { const response = await fetch('/cats'); return await response.json(); });const App = () => { const [dogs] = useAtom(dogsAtom); const [cats] = useAtom(catsAtom); // ... };
TypeScript
복사
따라서 이 2개의 비동기 원자가 있고 앱에서 사용하고 있습니다. 괜찮습니다.
그러나 여기에 약간의 문제가 있습니다. 구성 요소는 첫 번째 원자가 dogsAtom데이터를 가져오고 반환할 때까지 기다렸다가 다음 원자로 이동합니다 catsAtom. 우리는 이것을 원하지 않습니다. 이 두 원자는 서로 독립적이므로 병렬로 가져와야 합니다(또는 하드코어 JavaScripter인 경우 동시에 )
그래서, 우리는 기본적으로 Promise.all(...)이 원자들을 기다리는 것과 같은 일을 하고 싶습니다. 그 방법은 waitForAllutil을 사용하는 것입니다.
사용 후 코드는 가 됩니다.
const dogsAtom = atom(async (get) => { const response = await fetch('/dogs'); return await response.json(); });const catsAtom = atom(async (get) => { const response = await fetch('/cats'); return await response.json(); });const App = () => { const [[dogs, cats]] = useAtom(waitForAll([dogsAtom, catsAtom])); // ... };
TypeScript
복사
이제 둘 다 해결될 때까지 기다린 다음 둘 다에서 반환된 데이터 배열을 반환합니다. 일종의 await Promise.all성명서처럼.
말 그대로 이 시점에서 React는 Jotai를 자체적으로 흡수해야 합니다. 너무 좋습니다!!
그리고 이것들은 Jotai에서 제공하는 모든 유틸리티의 절반에 불과합니다. 너무 많아서 그것에 대해 한 권의 책을 쓸 수도 있습니다. em에 대해 알아보려면 Jotai 문서로 이동 하십시오 .

조타이는 친척들과 사이가 좋다

Jotai는 다른 라이브러리와 다릅니다. "당신은 당신의 package.json!!!"
아니, 죠타이는 그런 식으로 작동하지 않습니다! Jotai 자체는 훌륭한 상태 관리 라이브러리이지만 다른 상태 관리 라이브러리와 원활하게 통합할 수도 있습니다.
다음은 Jotai와 함께 제공되는 모든 공식 통합입니다.
이머
광학
리액트 쿼리
엑스스테이트
발티오
주스탄트
리덕스
URQL
이제 이 시점에서 블로그 게시물은 이미 위의 통합을 다루기에는 너무 길지만 Immer를 다루고 싶습니다. 왜요? React 상태의 가장 큰 문제점인 불변성 때문입니다.
불변성은 훌륭하고 React State 주위에 머리를 감는 것을 쉽게 만들지만 상태가 객체일 때 일을 매우 어렵게 만들 수 있습니다. 그런 다음 개체를 펼치고 업데이트하려는 속성과 병합하는 전체 노래와 춤을 수행해야 합니다.
function UpdateUser() { const [user, setUser] = useState({ id: 23, name: 'Luke Skywalker', dob: new Date('25 December, 19 BBY'), });// Update the dob const updateDob = () => setUser({ ...user, dob: new Date('25 November, 200ABY') });return <button onClick={updateDob}>Update DOB</button>; }
TypeScript
복사
메서드 에서 볼 수 있듯이 updateDob원본 개체를 확산하고 업데이트하려는 필드를 전달해야 합니다. 괜찮습니다. 그러나 객체가 여러 수준 깊이이고 객체를 매우 깊게 업데이트하려는 경우에는 어떻게 될까요?
너무 복잡 해져서 개인적으로 시도조차 하지 않았습니다. 나는 내 상태를 어떤 식으로든 더 얕게 재설계한 다음 업데이트했습니다. 저는 React 사람보다 Svelte 사람에 가깝고 Svelte에서는 상태를 변경하기만 하면 작동합니다.
user.dob = new Date('25 November, 200ABY');
TypeScript
복사
그리고 그것은 또한 매우 깊습니다!
state.depth1.depth2.depth3.depth4 = 'something';
TypeScript
복사
그래서 React에 필요한 모든 노래와 춤은 항상 나에게 잘못 느껴졌습니다.
그러나 이것이 Immer가 등장하는 곳입니다. Immer를 사용하면 상태를 직접 변경할 수 있으며 제대로 작동합니다. 자신을 살펴보세요
import { atomWithImmer } from 'jotai/immer';const userAtom = atomWithImmer({ id: 23, name: 'Luke Skywalker', dob: new Date('25 December, 19 BBY'), });function UpdateUser() { const [user, setUser] = useAtom(userAtom);// Update the dob const updateDob = () => setUser((user) => { user.dob = new Date('25 November, 200ABY'); return user; });return <button onClick={updateDob}>Update DOB</button>; }
TypeScript
복사
여기에서는 setUser다르게 작동합니다. 상태의 현재 값을 전달하는 콜백입니다. 이 값은 원래 값의 복사본입니다. 콜백 내에서 이 사본을 원하는 만큼 변경할 수 있으며 마지막으로 반환하기만 하면 Jotai와 Immer가 변경과 함께 발생하는 버그 없이 변경 사항을 자동으로 조정합니다. 굉장해요!

결론

좋습니다. 이것은 긴 기사였습니다! 끝까지 해낸 자신을 축하합니다.
이 기사는 Jotai를 살짝 엿볼 수 있습니다. Jotai 문서 에는 더 많은 것이 있습니다 당신은 확실히 그들을 확인해야합니다.