Closed8

5分で理解するJotai

301 Moved Permanently301 Moved Permanently

https://tutorial.jotai.org/

基本系

useStateと同じ要領で利用できる。
けどこれすごいのが、コンポーネントの外でstateを扱えちゃってるよなぁ

import { atom, useAtom } from 'jotai';

const counter = atom(0);

export default function Page() {
  const [count, setCounter] = useAtom(counter);
  const onClick = () => setCounter(prev => prev + 1);
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={onClick}>Click</button>
    </div>
  )
}
301 Moved Permanently301 Moved Permanently

なるほど、コンポーネントの外で状態をモテちゃってるから、グローバルなstateとして扱えるのか。

import { atom, useAtom } from 'jotai';

const theme = atom('light');

export default function Page() {
  const [appTheme, setAppTheme] = useAtom(theme);
  const handleClick = () => setAppTheme(appTheme === 'light'? 'dark': 'light');
  return (
    <div className={appTheme}>
      <h1>This is a theme switcher</h1>
      <button onClick={handleClick}>{appTheme === 'light'? 'DARK': 'LIGHT'}</button>
    </div>
  )
}
301 Moved Permanently301 Moved Permanently

けどstateとして扱うにはuseAtomしないといけなくて、これはカスタムhookだからコンポーネントの中でしか呼べない。

atom()自体はこれを返してるらしい。

{ toString: function toString() {}, init: "light", read: function () {}, write: function () {} }

とりあえずuseContextのあのクソダル定義をしなくていいのは楽でよい。かつ、値の扱いにもいい感じに気が回ってる感じがある。

301 Moved Permanently301 Moved Permanently

Read Only Atom

import { atom, useAtom } from 'jotai';

const textAtom = atom('readonly atoms')
const uppercase = atom((get) => get(textAtom).toUpperCase())

export default function Page() {
  const [lowercaseText, setLowercaseText] = useAtom(textAtom);
  const [uppercaseText] = useAtom(uppercase);
  const handleChange = (e) => setLowercaseText(e.target.value);
  return (
    <div className="app">
      <input value={lowercaseText} onChange={handleChange} />
      <h1>{uppercaseText}</h1>
    </div>
  )
}
301 Moved Permanently301 Moved Permanently

Write Only atoms

const textAtom = atom('write only atoms')
const uppercase = atom(null, (get, set) => {
    set(textAtom, get(textAtom).toUpperCase())
})

直接setXXXじゃダメなん?という感じだけど、これは

You must be thinking that why we not updating the atoms value directly, why we use a write-only atom to update it's value. Well updating the value using the write-only atom prevents the extra rerenders in our app.

ということで、再レンダリングを抑えられれてパフォーマンス向上になるらしい。
裏側でいい感じにやってくれてるってことでとりあえず納得して次へ進む。

301 Moved Permanently301 Moved Permanently

Read Write atoms

ひとつのモジュールの中で、

const count = atom(1);
export const readWriteAtom = atom((get) => get(count),
(get, set) => {
    set(count, get(count) + 1);
  },
);

こう書くことによって元の値を隠しつつ、アクセサのみを提供できるのでpriveteプロパティ的な状態を表現できている。

301 Moved Permanently301 Moved Permanently

Atom Creators

stateをまとめて扱いたい時に作ってたカスタムhook的な感じ。

const createCountIncAtoms = (initialValue) => {
  const baseAtom = atom(initialValue)
  const valueAtom = atom((get) => get(baseAtom))
  const incAtom = atom(null, (get, set) => set(baseAtom, (c) => c + 1))
  return [valueAtom, incAtom]
}

共通の振る舞いをするatomが増えていった場合に、DRYを防止するためのテクニック。

301 Moved Permanently301 Moved Permanently

Async Read Atoms

僕の知ってる限りだとswrっぽく非同期でatomの初期値を取得するための方法って感じ。

import { loadable } from "jotai/utils"

const countAtom = atom(0);
const asyncAtom = atom(async (get) => get(countAtom));
const loadableAtom = loadable(asyncAtom)
const AsyncComponent = () => {
  const [value] = useAtom(loadableAtom)
  if (value.state === 'hasError') return <div>{value.error}</div>
  if (value.state === 'loading') {
    return <div>Loading...</div>
  }
  return <div>Value: {value.data}</div>

lodableAtomはこれを返す。

{
    state: 'loading' | 'hasData' | 'hasError',
    data?: any,
    error?: any,
}
このスクラップは2024/12/24にクローズされました