tag:crieit.net,2005:https://crieit.net/tags/Recoil/feed 「Recoil」の記事 - Crieit Crieitでタグ「Recoil」に投稿された最近の記事 2022-02-02T08:58:21+09:00 https://crieit.net/tags/Recoil/feed tag:crieit.net,2005:PublicArticle/17962 2022-02-02T08:58:21+09:00 2022-02-02T08:58:21+09:00 https://crieit.net/posts/React-async-await Reactでasync/awaitだけで確認ダイアログを出せるようにする <p>今回はNext.jsですが、Reactで確認ダイアログを出す時、confirm関数的なものをawaitで呼べたら楽ちんだよなあと思うのでその実装方法を書きます。そのページに別途ダイアログ用のテンプレートは記述しない方法です。</p> <p>例えば下記のような感じです。</p> <pre><code class="typescript">const onClick = useCallback(async () => { const isConfirmed = await confirm('タイトル', 'OKですか?') if (!isConfirmed) { return } doSomething() }, []) </code></pre> <h2 id="ダイアログの状態管理を作成"><a href="#%E3%83%80%E3%82%A4%E3%82%A2%E3%83%AD%E3%82%B0%E3%81%AE%E7%8A%B6%E6%85%8B%E7%AE%A1%E7%90%86%E3%82%92%E4%BD%9C%E6%88%90">ダイアログの状態管理を作成</a></h2> <p>まずはダイアログの状態管理を行います。今回は状態管理としてRecoilを使っています。下記のような <code>useConfirmDialog.ts</code> を作成します。</p> <pre><code class="typescript">import { useCallback } from 'react' import { atom, useRecoilState } from 'recoil' type OpenArgs = { title?: string body: React.ReactNode } type DialogProps = OpenArgs & { resolve: (value: boolean) => void } const dialogPropsState = atom<DialogProps>({ key: 'confirmDialog/dialogProps', default: undefined, }) export function useConfirmDialog() { const [dialogProps, setDialogProps] = useRecoilState(dialogPropsState) const close = useCallback( (result: boolean) => { console.log({ dialogProps }) if (dialogProps.resolve) { dialogProps.resolve(result) } setDialogProps(undefined) }, [dialogProps] ) function open({ title, body }: OpenArgs): Promise<boolean> { return new Promise((resolve) => { setDialogProps({ title, body, resolve, }) }) } return { isOpen: !!dialogProps, open, close, dialogProps } } </code></pre> <p>ダイアログが開いているかの <code>isOpen</code>、開くための関数 <code>open</code>、閉じるための <code>close</code>、ダイアログの設定である <code>dialogProps</code> を出力します。</p> <p>ここで重要なのがPromiseのresolveをdialogPropsとして保存しているところです。これを保存しておき、ダイアログが終わるタイミングでそのresolveを呼び出すことで呼び出し元のawaitを終了させることができます。</p> <p>ちなみにこのdialogPropsという形ではなく、タイトルや内容、resolveをそれぞれのstateにすることも考えられますが、これはだめでした。なぜかresolveが勝手にundefinedになってしまい即awaitが終了してしまいます。何か勝手に解放されてしまうのでしょうか。ということでここが一番の肝でした。</p> <h2 id="ダイアログの表示を行う"><a href="#%E3%83%80%E3%82%A4%E3%82%A2%E3%83%AD%E3%82%B0%E3%81%AE%E8%A1%A8%E7%A4%BA%E3%82%92%E8%A1%8C%E3%81%86">ダイアログの表示を行う</a></h2> <p>ダイアログ自体は ConfirmDialogProvider というコンポーネントを作り、アプリケーション全体を囲むレイアウトとして設置しておきます。</p> <p>Next.jsであれば <code>pages/_app.tsx</code> に下記のような感じです。</p> <pre><code class="jsx">function MyApp({ Component, pageProps }) { const colors = useColors() return ( <RecoilRoot> <ConfirmDialogProvider> <Component {...pageProps} /> </ConfirmDialogProvider> </RecoilRoot> ) } </code></pre> <p>ConfirmDialogProviderは下記のような感じです。Chakra UIのダイアログを使った例です。</p> <pre><code class="jsx">import { useConfirmDialog } from '@/hooks/dialog/useConfirmDialog' import { useLocale } from '@/hooks/useLocale' import { AlertDialog, AlertDialogOverlay, AlertDialogContent, AlertDialogHeader, AlertDialogBody, AlertDialogFooter, Button, } from '@chakra-ui/react' import { useRef } from 'react' type Props = { children: React.ReactNode } export default function ConfirmDialogProvider(props: Props) { const { isOpen, close, dialogProps } = useConfirmDialog() const { title, body } = dialogProps ?? {} const cancelRef = useRef() return ( <> {props.children} <AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={() => close(false)} > <AlertDialogOverlay> <AlertDialogContent> {title && ( <AlertDialogHeader fontSize="lg" fontWeight="bold"> {title} </AlertDialogHeader> )} <AlertDialogBody mt={title ? undefined : 4}>{body}</AlertDialogBody> <AlertDialogFooter> <Button ref={cancelRef} onClick={() => close(false)}> Cancel </Button> <Button colorScheme="teal" onClick={() => close(true)} ml={3}> OK </Button> </AlertDialogFooter> </AlertDialogContent> </AlertDialogOverlay> </AlertDialog> </> ) } </code></pre> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>これで確認が楽ちんになりました。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/16704 2021-03-01T01:31:55+09:00 2021-03-01T01:31:55+09:00 https://crieit.net/posts/Next-js-Vercel-Recoil-Material-Table-AWS Next.jsとVercelとRecoilとMaterial Tableを使ってAWSのステータスダッシュボードを作ってみた話 <h2 id="AWSのステータス確認難しいよね"><a href="#AWS%E3%81%AE%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E7%A2%BA%E8%AA%8D%E9%9B%A3%E3%81%97%E3%81%84%E3%82%88%E3%81%AD">AWSのステータス確認難しいよね</a></h2> <p>AWSを使ったことのある人ならばわかると思いますが、公式がAWSの障害情報を掲載する<a target="_blank" rel="nofollow noopener" href="https://status.aws.amazon.com/">AWS Service Health Dashboard</a>があまり使いやすくないです。</p> <p><img src="https://i.imgur.com/XghDulZ.png" alt="img" /></p> <p>それぞれのリージョンの障害がRSSで配信される形式になっているのですが、わざわざRSSを登録するのもめんどくさいし、Slackとかの連携に乗っけるのもそれはそれで便利なのですが、そもそもSlackを見ていないほかの人でも障害情報を共有したいです。</p> <p>実は、AWS Service Health Dashboardの情報はJSONで取得することができます。</p> <p><a target="_blank" rel="nofollow noopener" href="https://status.aws.amazon.com/data.json">https://status.aws.amazon.com/data.json</a></p> <p>こちらのJSONを活用して勉強がてら使いやすいダッシュボードを作っていきます。</p> <h2 id="クビになるぞ!"><a href="#%E3%82%AF%E3%83%93%E3%81%AB%E3%81%AA%E3%82%8B%E3%81%9E%EF%BC%81">クビになるぞ!</a></h2> <p>最近、これといった新しい技術に触れておらず、このままだとクビになりそうなので、そろそろ重い腰を上げてNext.jsを勉強することにしました。</p> <p>また、Next.jsを使う場合はVercelが便利だよーとのことですので、こちらも使っていきます。</p> <h2 id="Next.js"><a href="#Next.js">Next.js</a></h2> <p>Next.jsではpages/api配下に格納したコードについては、サーバーサイドとして振る舞います。</p> <p>クライアントから直接status情報がかかれたJSONを読みとってもよかったのですが、HTMLの面倒なサニタイジング処理やら、値の補完など面倒なことはサーバーサイドに持ってこようということで、<br /> statusJSONを取得して、フロントに返却するサーバーコードを書いていきます。</p> <p>次のようなコードになりました。</p> <pre><code class="typescript">import { NextApiRequest, NextApiResponse } from 'next' import axios from 'axios' export interface AwsStatusResp { archive: AwsStatusArchive[] } export interface AwsStatusArchive { service_name: string summary: string date: string status: string details: string description: string service: string } const handler = (req: NextApiRequest, res: NextApiResponse) => { axios .get<AwsStatusResp>('https://status.aws.amazon.com/data.json') .then((resp) => { const handlerResp = resp.data.archive.map((x) => ({ // eslint-disable-next-line @typescript-eslint/camelcase service_name: x.service_name, summary: x.summary, region: x.service.includes('management-console') ? 'global' : x.service.split('-').slice(1).join('-') === '' ? 'global' : x.service.split('-').slice(1).join('-'), date: x.date, status: x.status, details: x.details, service: x.service.includes('management-console') ? 'management-console' : x.service.split('-')[0], description: x.description .replace(/<("[^"]*"|'[^']*'|[^'">])*>/g, '') .replace(/&nbsp;/g, '\n'), })) res.statusCode = 200 // eslint-disable-next-line no-console console.log(handlerResp) res.json(handlerResp) }) .catch((error) => { console.error(error.response) res.statusCode = error.response.status || 500 res.statusMessage = error.response.statusText || 'InternalServerError' res.json({ error: error.response.statusText || 'InternalServerError' }) }) } export default handler </code></pre> <p>注意点として、必ずハンドラーはexport defaultを指定してあげないこと以外はいたって直感的なコードとなっております。</p> <p>Vercelに載っけるとわかるのですが、こちらのコード、Lambdaにデプロイされることになります。たしかに見覚えある感じですね。</p> <p>また、Next.jsと関係ないのですが、axiosのレスポンスに型がつけられるって知ってましたか?</p> <pre><code class="typescript">export interface AwsStatusResp { archive: AwsStatusArchive[] } export interface AwsStatusArchive { service_name: string summary: string date: string status: string details: string description: string service: string } axios .get<AwsStatusResp>('https://status.aws.amazon.com/data.json') .then((resp) => { .......... </code></pre> <h2 id="Material Table"><a href="#Material+Table">Material Table</a></h2> <p>Material UI準拠のテーブルとして、Material Tableなるものがありましたので今回採用することにしました。</p> <pre><code class="typescript">import MaterialTable from 'material-table' import tableIcons from '../components/tableIcons' <MaterialTable icons={tableIcons} columns={[ { title: 'Service Name', field: 'service_name' }, { title: 'Service', field: 'service', width: 10 }, { title: 'Region', field: 'region', lookup: regionNameMapping }, { title: 'Summary', field: 'summary' }, { title: 'Date (' + dayjs.tz.guess() + ')', field: 'date', render: (rowData) => ( <div> {dayjs .unix(Number(rowData.date)) .format('YYYY-MM-DDTHH:mm:ssZ[Z]')} </div> ), defaultSort: 'desc', type: 'string', }, { title: 'Status', field: 'status', lookup: statusMapping, }, ]} data={aws} detailPanel={[ { tooltip: 'Details', render: (rowData) => { return ( <> <div className="title">{rowData.summary}</div> <div className="description"> {dayjs .unix(Number(rowData.date)) .format('YYYY-MM-DDTHH:mm:ss')}{' '} {rowData.service_name} </div> <div className="code">{rowData.description}</div> </> ) }, }, ]} options=<span>{</span><span>{</span> filtering: true, grouping: true, exportButton: true, exportFileName: 'exported', headerStyle: { backgroundColor: '#e77f2f', color: '#FFF', }, <span>}</span><span>}</span> isLoading={loading} actions={[ { // Issue: https://github.com/mbrn/material-table/issues/51 //@ts-ignore icon: tableIcons.BarChartIcon, tooltip: 'Show Bar Chart', isFreeAction: true, disabled: loading, onClick: async () => { setShowGraph(!showG) }, }, { // Issue: https://github.com/mbrn/material-table/issues/51 //@ts-ignore icon: tableIcons.Refresh, tooltip: 'Refresh Data', isFreeAction: true, disabled: loading, onClick: async () => { setLoading(true) await getAwsStatus() }, }, ]} title={ <div className="header"> <img src="/awslogo.png" /> <a href="https://aws-health-dashboard.vercel.app/"> AWS Health Dashboard </a> </div> } /> </code></pre> <p>使い方もシンプルかつ比較的高機能でいい感じです。</p> <p>いい感じですが後述するRecoilとの相性問題とDatetimeの扱いが微妙なのがツラミでした。</p> <p>本当はDate型を渡してあげるとSearchableの際、カレンダーでの絞り込みができるのかなと思ったのですが、こちらがうまくいきませんでした。</p> <p>あと、微妙に型もおかしく例えば、actionsはactionを複数指定することができるはずですが、型チェックで怒られるので、仕方なくts-ignoreしてます。</p> <p>あなたが直せばいいじゃんアゼルバイジャンって言われそうですが、めんどくさくなってしまいIssueだけあげてしまいました。申し訳ねぇ...。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/mbrn/material-table/issues/2762">https://github.com/mbrn/material-table/issues/2762</a></p> <h2 id="Recoil"><a href="#Recoil">Recoil</a></h2> <p>RecoilとはReactの新しい状態管理ライブラリで、いわゆるReact HooksでGlobal Storeを作ろうというものです。</p> <p>基本的な使い方はまず、storeとしてatomという共有ステートを作成します。</p> <p>atomのkeyはプロジェクトで一意にする必要がありますが、今回はそこまで大規模なプロジェクトではないのでawsとかいうクソ名をつけてます。</p> <p>storeなので、store/aws.ts として格納します。</p> <pre><code class="typescript">import { atom } from 'recoil' const awsState = atom({ key: 'aws', default: [ { // eslint-disable-next-line @typescript-eslint/camelcase service_name: 'Auto Scaling (N. Virginia)', summary: '[RESOLVED] Example Error', date: '1542849575', status: '1', details: '', description: 'The issue has been resolved and the service is operating normally.', service: 'autoscaling', region: 'us-east-1', }, ], dangerouslyAllowMutability: true, }) export default awsState </code></pre> <p>次にステートを共有したいコンポーネントのルートにRecoilRootを設置します。</p> <p>Next.jsの場合、_app.tsxが全ページのルートにあたるのでここに置けばいいですね。</p> <pre><code class="typescript">import { AppProps } from 'next/app' import Head from 'next/head' import { RecoilRoot } from 'recoil' import React from 'react' const App = ({ Component, pageProps }: AppProps) => ( <> <RecoilRoot> <Head> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> <title>AWS Health Dashboard</title> </Head> <Component {...pageProps} /> </RecoilRoot> </> ) export default App </code></pre> <p>そして、利用するときはuseRecoilStateをReact Hooksのように利用するだけです。簡単ですね。</p> <pre><code class="typescript">import React, { useState, useEffect } from 'react' import { useRecoilState } from 'recoil' import awsState from '../store/aws' import showGraph from '../store/showGraph' import axios from 'axios' import dayjs from 'dayjs' dayjs.extend(utc) dayjs.extend(timezone) const Alert = (props: AlertProps) => { return <MuiAlert elevation={6} variant="filled" {...props} /> } export const Table = (): JSX.Element => { // 20200112: dangerouslyAllowMutabilityでできた const [aws, setAws] = useRecoilState(awsState) const [showG, setShowGraph] = useRecoilState(showGraph) const [loading, setLoading] = useState(true) const [slackBarOpen, setSlackBarOpen] = React.useState(false) const [apiErrorMsg, setApiErrorMsg] = React.useState('') useEffect(() => { getAwsStatus() setLoading(false) }, []) const getAwsStatus = () => { axios .get('/api/aws') .then((resp) => { setAws(resp.data) setLoading(false) }) .catch((error) => { console.error(error.response) setSlackBarOpen(true) setApiErrorMsg(error.response.statusText || 'Error') setAws([]) setLoading(false) }) } </code></pre> <p>stateの読み込みはgetterから、書き込みはsetterから行います。</p> <p>React Hooksに慣れていれば簡単ですね。</p> <h2 id="思わぬ落とし穴 Material TablesでRecoilが使えない"><a href="#%E6%80%9D%E3%82%8F%E3%81%AC%E8%90%BD%E3%81%A8%E3%81%97%E7%A9%B4+Material+Tables%E3%81%A7Recoil%E3%81%8C%E4%BD%BF%E3%81%88%E3%81%AA%E3%81%84">思わぬ落とし穴 Material TablesでRecoilが使えない</a></h2> <p>Recoilのatomは基本値の書き換えはset stateを使うことが求められます。ですが、material tablesはテーブルを作るときにdataにIDの書き込みが発生するようでそのままだと怒られてしまいます。</p> <pre><code>Cannot add property tableData, object is not extensible </code></pre> <p>これの解決策はRecoilにstateへの直接的な書き換えを許可することです。こちらはatomのoptionでdangerouslyAllowMutabilityを有効にすることで解決できます。</p> <pre><code class="typescript">import { atom } from 'recoil' const awsState = atom({ key: 'aws', default: [ { }, ], dangerouslyAllowMutability: true, }) </code></pre> <p>これがわかるのに半日くらい使っちまいました。</p> <h2 id="Chart.js"><a href="#Chart.js">Chart.js</a></h2> <p>さて、無事にRecoilでstateの共有ができたのでせっかくなので別コンポーネントも作ります。</p> <p>意味があるかどうか不明ですが、AWSの障害発生状況を可視化してみようと思います。</p> <p>ということで、採用したのがChart.js。</p> <p>次のようにデータを渡すだけできれいめなグラフを書いてくれます。</p> <pre><code class="typescript">import { useRecoilValue } from 'recoil' import awsState from '../store/aws' import React from 'react' import { regionNameMapping, } from './const' import BarGraph from './barGraph' export const AlertPerRegion = (): JSX.Element => { const aws = useRecoilValue(awsState) const labels = Array.from( new Set(aws.map((data) => regionNameMapping[data.region])) ) const data = [] for (const r of labels) { data.push( aws .map((data) => regionNameMapping[data.region]) .reduce((total, x) => { return x === r ? total + 1 : total }, 0) ) } return ( <div className="container"> <BarGraph labels={labels} data={data} title="Alert per region" /> </div> ) } </code></pre> <p>どうでもいい実装ですが、各グラフを一覧で見れる画面を用意し、実際のグラフは遷移先で表示するようにしてます。</p> <p><img src="https://i.imgur.com/tfnpq4w.png" alt="img" /></p> <p><img src="https://i.imgur.com/hpJ70fR.png" alt="img" /></p> <h2 id="Vercelにデプロイ"><a href="#Vercel%E3%81%AB%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4">Vercelにデプロイ</a></h2> <p>さて、実装ができたので後はVercelにあげるだけです。</p> <p>もうここはほとんど書くことがないのですが、Next.jsで作ったアプリケーションはVercelでレポジトリと使っているフレームワークを設定するだけで簡単にデプロイ出来てしまいます。</p> <p>これはすごい。</p> <h2 id="完成"><a href="#%E5%AE%8C%E6%88%90">完成</a></h2> <p>ということで、AWS Health Dashboardが完成しました。</p> <p>アクセスすると、Next.jsのapiをコールし、AWSのstatusを取得加工したものを返却します。</p> <p>フロントでは受け取ったデータをRecoilのatomに格納しつつ、material tableで表として描画します。</p> <p>また右上のグラフボタンを押すことで色々な切り口の可視化を行うことができます。</p> <p><a target="_blank" rel="nofollow noopener" href="https://aws-health-dashboard.vercel.app/">https://aws-health-dashboard.vercel.app/</a></p> <p>できれば使う場面にならないことを祈りつつ、ご活用いただければとおもいます。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>食わず嫌いでやらなかったNext.js+Recoilをやってみましたが、楽しく実装ができました。</p> <h2 id="2021/02/20追記"><a href="#2021%2F02%2F20%E8%BF%BD%E8%A8%98">2021/02/20追記</a></h2> <p>2021/02/19~20にかけて起きた<a target="_blank" rel="nofollow noopener" href="https://status.aws.amazon.com/rss/ec2-ap-northeast-1.rss">AWS EC2障害</a>ですが、本ダッシュボードでは更新がされませんでした。</p> <p>どうやら、data.jsonはRSSとは違い、同期的に更新されないようです。</p> <p>大変ご迷惑をおかけしました。改めて、改修しRSS更新にも対応できるように頑張ります。</p> tubone24 tag:crieit.net,2005:PublicArticle/16068 2020-09-23T21:25:57+09:00 2020-09-23T21:25:57+09:00 https://crieit.net/posts/Recoil Recoilの書き方はこんな感じかなというイメージ <p>主に細かいコンポーネントをいくつも作って開発するようなプロジェクトを進め始めたので、丁度マッチしているかなと思いReduxを使い始めていたのをやめてRecoilを使ってみました。</p> <p>書いているうちにRecoilがこの仕様であればだいたいこんな感じに使っていくことになるのかな……と思ったのでなんとなく書いていきます。</p> <p>まず基本的なステートの定義は下記のようです。</p> <pre><code class="typescript">const textState = atom({ key: 'textState', default: '', }) </code></pre> <p>key-valueのハッシュみたいなもので、keyに対して値を保存するような非常にシンプルなイメージです。</p> <p>これを実際に使うとなるとこのatomなどをたくさん定義していくわけですが、基本的にRecoilが必要な場面となるとその値をコンポーネントにまたがって使用したい時になると思います。そうすると複数のコンポーネントで毎回文字列でキーを指定してステートを利用していくというのはあまり望ましくないと思います。変更にも弱いですし、単なる書き間違えも発生します。</p> <p>ということで、下記のどちらかのパターンになるのではないかと思います。</p> <ul> <li>カスタムフックでまとめる</li> <li>定義を個別のファイルで行う</li> </ul> <p>後者でも良いのですが、一つ二つしか値を使わない、というパターンもそんなになさそうに思いますのでだいたいカスタムフックでまとめる形がよくある形になるのでは、という気がしています。</p> <h2 id="カスタムフックのパターン"><a href="#%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%95%E3%83%83%E3%82%AF%E3%81%AE%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3">カスタムフックのパターン</a></h2> <p>例えばカスタムフックの場合はこんな感じです。</p> <pre><code class="typescript">import { atom, useRecoilState } from 'recoil' import { Post, createEmptyPost } from '@/models/Post' export type Mode = 'input' | 'confirm' | 'completed' const modeState = atom<Mode>({ key: 'post/form/mode', default: 'input' }) const postState = atom<Post>({ key: 'post/form/post', default: createEmtpyPost() }) const fileState = atom<File | null>({ key: 'post/form/file', default: null }) export default function usePostForm() { const [mode, setMode] = useRecoilState(modeState) const [post, setPost] = useRecoilState(postState) const [file, setFile] = useRecoilState(fileState) return { mode, setMode, post, setPost, file, setFile, } } </code></pre> <p>実際にコンポーネント上で使う時は下記のような感じです。</p> <pre><code class="jsx">export default function MyComponent() { const { mode, post, setPost } = usePostForm() return ( <div> {mode === 'input' ? ( <input type="text" value={post.title} onChange={(e) => setPost({ ...post, title: e.target.value })} /> ) : ( <span>{post.title}</span> )} </div> ) } </code></pre> <p>どのコンポーネントでも同様の使い方ができますので細分化も苦にならず、シンプルに使うことができます。キーを何度も入力する必要もありません。というか、結局ReduxのStoreを定義しているようなイメージになってきますね。書き方はそれよりは簡単ですが。まあ結局どういい感じに管理するかを考えると何を使うにしろ似てくるのかもしれません。</p> <h2 id="個別に定義するパターン"><a href="#%E5%80%8B%E5%88%A5%E3%81%AB%E5%AE%9A%E7%BE%A9%E3%81%99%E3%82%8B%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3">個別に定義するパターン</a></h2> <p>さっきの例でいうと、</p> <pre><code class="typescript">export type Mode = 'input' | 'confirm' | 'completed' export const modeState = atom<Mode>({ key: 'post/form/mode', default: 'input' }) </code></pre> <p>のような定義をそれぞれファイルとして作り、使用する場合は</p> <pre><code class="jsx">import { modeState } from '../hooks/mode_state' export default function MyComponent() { const [mode, setMode] = useRecoilState(modeState) : } </code></pre> <p>という感じになると思います。これもまあ間違いは少なくて良いと思いますが、アプリケーション全体で単独で使い回すという目的が大きくなってくると思いますので、あまり利用するパターンは多くなさそうな気もします。ちょっと処理が増えるとカスタムフックにしちゃえばいいか、となってしまうと思いますし。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>雑に使おうと思えば使えますが、やはり使い方をある程度決めてまとめた方があとあと困らないかなという気がします。シンプルに使えるライブラリですが、開発が進むにつれて一体どのキーがどう使われているのか、使われているのか分からなくなってしまってぐちゃぐちゃになってしまう状況をなるべく避けるように使って行ったほうが良さそうに思いました。</p> だら@Crieit開発者