Jotai

Jotai的卖点

  • 极简
  • 灵活
  • 优化多余渲染问题
  • 由pmndrs的大神dai-shi开发
import { atom, useAtom } from 'jotai';
const countAtom = atom(0);

export default function JotaiDemo() {
  const [count, setCount] = useAtom(countAtom);
  return (
    <div>
      count: {count}
      <button
        onClick={() => {
          setCount((x) => x + 1);
        }}
      >
        +1
      </button>
    </div>
  );
}

atom 和 useAtom

Jotai解决直接用Context多余渲染的问题

import { useState, createContext, useContext } from 'react';

const CountContext = createContext();

export default function ContextDemo() {
  return (
    <CountContext.Provider value={useState(0)}>
      <Child />
      <OtherChild />
    </CountContext.Provider>
  );
}

function Child() {
  const [count, setCount] = useContext(CountContext);
  return (
    <div>
      count: {count}
      <button
        onClick={() => {
          setCount((x) => x + 1);
        }}
      >
        +1
      </button>
    </div>
  );
}

function OtherChild() {
  return Math.random();
}
import { atom, useAtom } from 'jotai';

export default function App() {
  return (
    <>
      <Child />
      <OtherChild />
    </>
  );
}

const countAtom = atom(0);

function Child() {
  const [count, setCount] = useAtom(countAtom);
  return (
    <div>
      count: {count}
      <button
        onClick={() => {
          setCount((x) => x + 1);
        }}
      >
        +1
      </button>
    </div>
  );
}

function OtherChild() {
  return Math.random();
}

jotai demo也增加OtherChild

读写分离

import { atom, useAtomValue, useSetAtom } from 'jotai';

const countAtom = atom(0);

function Count() {
  const count = useAtomValue(countAtom);
  return <div>count: {count}</div>;
}

function SetCount() {
  const setCount = useSetAtom(countAtom);
  return (
    <div>
      <button onClick={() => setCount((x) => x + 1)}>+1</button>
      {Math.random()}
    </div>
  );
}

export default function App() {
  return (
    <>
      <Count />
      <SetCount />
    </>
  );
}
import { useContext } from 'react';
import { memo } from 'react';
import { useState } from 'react';
import { createContext } from 'react';

const CountContext = createContext();
const CountSetContext = createContext();

const Count = memo(function Count() {
  const count = useContext(CountContext);
  return <div>count: {count}</div>;
});

const Button = memo(function Button() {
  const setCount = useContext(CountSetContext);

  return (
    <div>
      <button
        onClick={() => {
          setCount((x) => x + 1);
        }}
      >
        +1
      </button>
      {Math.random()}
    </div>
  );
});

export default function ContextDemo() {
  const [count, setCount] = useState(0);
  return (
    <CountSetContext.Provider value={setCount}>
      <CountContext.Provider value={count}>
        <Count />
        <Button />
      </CountContext.Provider>
    </CountSetContext.Provider>
  );
}

Context 读写分离

派生atom

从countAtom派生出一个jsx atom

import { atom, useAtomValue } from 'jotai';
import { useState } from 'react';
const countAtom = atom(1);

const derivedAtom = atom((get) => {
  return (
    <div>
      {Math.random()} {get(countAtom)}
    </div>
  );
});

function Derived() {
  return useAtomValue(derivedAtom);
}

export default function App() {
  const [mount, setMount] = useState(false);
  return (
    <>
      挂载
      <input
        type="checkbox"
        onChange={(e) => {
          setMount(e.target.checked);
        }}
        checked={mount}
      />
      {mount ? (
        <>
          <Derived />
          <Derived />
          <Derived />
          <Derived />
        </>
      ) : null}
    </>
  );
}

Action atoms

import { atom, useAtomValue, useSetAtom } from "jotai";

const scoresAtom = atom({
  101: 0,
  102: 0,
});

const setScoreAtom = atom(null, (get, set, id, score) => {
  const prevValue = get(scoresAtom);
  set(scoresAtom, {
    ...prevValue,
    [id]: typeof score === "function" ? score(prevValue[id]) : score,
  });
});

const scoresAtomView = atom((get) => {
  return JSON.stringify(get(scoresAtom), null, 4);
});

function ScoreView() {
  return useAtomValue(scoresAtomView);
}

export default function App() {
  const setScore = useSetAtom(setScoreAtom);
  return (
    <>
      <pre>
        <ScoreView />
      </pre>
      <div>
        <button
          onClick={() => {
            setScore(101, (prev) => prev + 1);
          }}
        >
          101 + 1
        </button>
      </div>
    </>
  );
}

Action Atoms:把操作单独封装

作为对比:用useStore和useCallback的实现

import { atom, useAtomValue, useStore } from 'jotai';
import { useCallback } from 'react';

const scoresAtom = atom({
  101: 0,
  102: 0,
});

const scoresAtomView = atom((get) => {
  return JSON.stringify(get(scoresAtom), null, 4);
});

function ScoreView() {
  return useAtomValue(scoresAtomView);
}

export default function App() {
  const store = useStore();
  const setScore = useCallback(
    (id, score) => {
      const { get, set } = store;
      const prevValue = get(scoresAtom);
      set(scoresAtom, {
        ...prevValue,
        [id]: typeof score === 'function' ? score(prevValue[id]) : score,
      });
    },
    [store]
  );
  return (
    <>
      <pre>
        <ScoreView />
      </pre>
      <div>
        <button
          onClick={() => {
            setScore(101, (prev) => prev + 1);
          }}
        >
          101 + 1
        </button>
        <button
          onClick={() => {
            setScore(102, (prev) => prev + 1);
          }}
        >
          102 + 1
        </button>
      </div>
    </>
  );
}

作为对比:用useAtomCallback的实现

import { atom, useAtomValue } from 'jotai';
import { useAtomCallback } from 'jotai/utils';
import { useCallback } from 'react';

const scoresAtom = atom({
  101: 0,
  102: 0,
});

const scoresAtomView = atom((get) => {
  return JSON.stringify(get(scoresAtom), null, 4);
});

function ScoreView() {
  return useAtomValue(scoresAtomView);
}

export default function App() {
  const setScore = useAtomCallback(
    useCallback((get, set, id, score) => {
      const prevValue = get(scoresAtom);
      set(scoresAtom, {
        ...prevValue,
        [id]: typeof score === 'function' ? score(prevValue[id]) : score,
      });
    }, [])
  );

  return (
    <>
      <pre>
        <ScoreView />
      </pre>
      <div>
        <button
          onClick={() => {
            setScore(101, (prev) => prev + 1);
          }}
        >
          101 + 1
        </button>
        <button
          onClick={() => {
            setScore(102, (prev) => prev + 1);
          }}
        >
          102 + 1
        </button>
      </div>
    </>
  );
}

Effect

import { atomEffect } from 'jotai-effect';
import { atom, useAtom, useAtomValue } from 'jotai';

const countAtom = atom(0);

const autoCountEffect = atomEffect((get, set) => {
  const timer = setInterval(() => {
    set(countAtom, (prev) => prev + 1);
  }, 1e3);
  return () => {
    clearInterval(timer);
  };
});

const loggingEffect = atomEffect((get) => {
  console.log(get(countAtom));
});

export default function JotaiEffect() {
  useAtom(autoCountEffect);
  useAtom(loggingEffect);
  return useAtomValue(countAtom);
}

用jotai-effect声明effect

然而最新版jotai和jotai-effect一起用,有bug

https://github.com/pmndrs/jotai/discussions/2218

一些实用的文档

Thank you!

jotai

By Xavier Peng

jotai

  • 73