😑
「Reactに有利なベンチマークを作ってみた」にJotai実装を追加してみた
JotaiもReactと変わらないので、ベンチマークで競いたいと言うよりは書き心地を試すのが目的。あと、ベンチマークで大きく差が出ないことを確認するのも別の目的。
コード
react-jotai/src/components/Item/index.tsx
import { atom, useAtomValue, useSetAtom } from "jotai";
import { atomFamily } from "jotai/utils";
import { itemMap } from "../../data/items";
import classes from "./Item.module.css";
const searchQueryAtom = atom("");
export const useSetSearchQuery = () => {
const setSearchQuery = useSetAtom(searchQueryAtom);
return setSearchQuery;
};
export const itemFamily = atomFamily((id: string) => atom((get) => {
const searchQuery = get(searchQueryAtom);
const item = itemMap.get(id);
if (!item) {
throw new Error(`no item found for ${id}`);
}
const name = item.en.toLowerCase();
if (!searchQuery) {
return {
displayName: item.ja,
nameMarked: <span className={classes.name}>{item.en}</span>,
};
}
const searchIndex = name.indexOf(searchQuery);
if (searchIndex === -1) {
return {
displayName: item.ja,
nameMarked: (
<span className={`${classes.name} ${classes.unmatchedName}`}>
{item.en}
</span>
),
};
}
return {
displayName: item.ja,
nameMarked: (
<span className={classes.name}>
{item.en.substring(0, searchIndex)}
<mark>
{item.en.substring(searchIndex, searchIndex + searchQuery.length)}
</mark>
{item.en.substring(searchIndex + searchQuery.length)}
</span>
),
};
}));
type Props = {
/**
* ID of Item
*/
id: string;
};
export const Item: React.FC<Props> = ({ id }) => {
const { displayName, nameMarked } = useAtomValue(itemFamily(id));
return (
<div className={classes.wrapper}>
<div className={classes.id}>{id}</div>
<div>{nameMarked}</div>
<div>{displayName}</div>
</div>
);
};
react-jotai/src/App.tsx
import { useCallback, useState, useTransition } from "react";
import classes from "./App.module.css";
import { Item, useSetSearchQuery } from "./components/Item";
import { SearchBox } from "./components/SearchBox";
import { itemMap } from "./data/items";
function App() {
const [input, setInput] = useState("");
const setSearchQuery = useSetSearchQuery();
const [, startTransition] = useTransition();
const onChange = useCallback((input: string) => {
setInput(input);
startTransition(() => {
setSearchQuery(input.toLowerCase());
});
}, []);
return (
<>
<div className={classes.pokemonList}>
{Array.from(itemMap.keys()).map((id) => {
return <Item key={id} id={id} />;
})}
</div>
<footer className={classes.footer}>
<p>
Data is obtained from{" "}
<a href="https://pokeapi.co/" rel="external">
PokéAPI
</a>
.
</p>
</footer>
<div className={classes.searchBox}>
<SearchBox input={input} onChange={onChange} />
</div>
</>
);
}
export default App;
結果
ベンチマーク結果はreactとreact-jotaiでほぼ変わらず。Jotaiの方が多少のオーバヘッドあるのか、描画が追いついていないことがある。これって多少のオーバヘッドがあっても、startTransitionの効果でレンダリングがスキップしちゃうだけなので、ベンチマークの数値は変わらないのかも。
おわりに
特にオチはないけれど、書いたコードを消すのはもったいない精神で載せてみた。
ちなみに元のコードがuseMemoにJSX Element入れていたので、形をそのままにatom化したけど、この書き方はそれほど一般的ではない。しかし、テクニックとしてはあり 👇
Discussion