React DnD v11(古いバージョン)の実装
はじめに
少し癖のあるReact DnDを古いバージョンで使わなければならない案件を対応しました
3 つくらい古いメジャーバージョンだったので、公式ドキュメントの Examples が動かず、かつ、ネット上にドキュメントが少なかったので、一応実装をこの記事に残しておきます
この記事の React DnD のバージョンは以下です
"react-dnd": "11.1.3", "react-dnd-html5-backend": "11.1.3",
ちなみに現在の最新は v14 です
v11 でも hooks に対応されていた分、まだ楽な方だったかなと思います
実装内容は以下の Example のような D&D(ドラッグ&ドロップ)による要素の入れ替えです
https://react-dnd.github.io/react-dnd/examples/sortable/simple
Example では要素をいい感じに並び替えていますが、今回は実装を簡単にするため swap(入れ替え)するだけにしました
ルートコンポーネントの設定
ルートコンポーネントで以下を設定
import { DndProvider } from "react-dnd"; import { HTML5Backend } from "react-dnd-html5-backend"; <DndProvider backend={HTML5Backend}> <Example /> </DndProvider>;
親コンポーネントの実装
親コンポーネントでは、State と D&D された時に要素を入れ替えるロジック(moveBox)を持っています
moveBox は fromId(ドラッグしたアイテムの ID)と toId(ドロップされたアイテムの ID)を引数で渡して、場所を入れ替えているだけです
import { useState, useCallback } from "react"; import produce from "immer"; const [items, setItems] = useState([ { id: 1, text: "アイテム1" }, { id: 2, text: "アイテム2" }, { id: 3, text: "アイテム3" }, ]); const findIdx = useCallback( (id: number) => items.findIndex((c) => c.id === id), [items] ); const moveBox = useCallback( (fromId: number, toId: number) => { const from = findIdx(fromId); const to = findIdx(toId); const swapItems = produce(items, (c) => { [c[from], c[to]] = [c[to], c[from]]; return c; }); setItems(swapItems); }, [items] ); const DND_TYPE = "items_dnd"; return ( <> {items.map((c) => ( <BoxDnD key={c.id} id={c.id} type={DND_TYPE} moveBox={moveBox}> <Example /> </BoxDnD> ))} </> );
後述する<BoxDnD />が View の部分ですが、要素を入れ替えるロジックは別で切り離しておいた方が良いでしょう
D&D ライブラリを別のものに替えても使えると思うので
子コンポーネントの実装
useDrag と useDrop という 2 つの hooks を駆使して実装します
interface BoxDndProps { id: number; type: string; moveBox(fromId: number, toId: number): void; children: React.ReactNode; } export const BoxDnD = ({ id, type, moveBox, children }: BoxDndProps) => { type ItemType = { type: typeof type; id: number; }; const [{ isDragging }, drag] = useDrag< ItemType, unknown, { isDragging: boolean } >({ item: { type, id }, collect: (m) => ({ isDragging: m.isDragging() }), }); const [, drop] = useDrop({ accept: type, drop({ id: fromId }: ItemType) { if (fromId !== id) moveBox(fromId, id); }, }); return ( // Boxの実装は省略 <Box ref={(node) => drag(drop(node))} isDragging={isDragging}> {children} </Box> ); };
useDrag
v14 だとコールバック関数になってますが、v11 だとただの関数ですね
useDrag のパラメーターの item は識別子となるプロパティです
type はドロップターゲットを特定するために必要で、上記実装では string 型にしていますが Symbol 型の方が良さそうですね
id はアイテム要素を特定するために一意の識別子を設定します
今回はアイテムの ID を渡してます
useDrag のパラメーターの collect はコールバック関数になっていて、ランタイムで実際にドラッグした時、引数の m(monitor)にドラッグの状態が入ってきます
上記の実装では、ドラッグされているかの boolean を返す isDragging を返しています
useDrag の戻り値は 3 つの要素を持つタプルで返ってきます
タプルの 1 つ目は、collect で収集したオブジェクトを受け取ります
collect 関数を定義しなかった場合、空オブジェクトを返します
タプルの 2 つ目は、drag の ref を返します
これをドラッグする DOM にアタッチします
タプルの 3 つ目は、drag の preview の ref を返します
今回は使用しませんが、ドラッグしている時は別の DOM へアタッチしたい場合に使用します
useDrop
こちらも v14 だとコールバック関数になってますが、v11 だとただの関数です
useDrop のパラメーターの accept はドロップターゲットの識別子です
useDrop のパラメーターの drop はドロップされた時に呼び出される関数です
drop の第 1 引数にドラッグされている item を保持しています
上記実装では、ドロップされた時に moveBox を呼び出しています
useDrop の戻り値は 2 つの要素を持つタプルで返ってきます
タプルの 1 つ目は、collect で収集したオブジェクトを受け取ります
今回は使用していません
タプルの 2 つ目は、drop の ref を返します
これをドロップターゲットの DOM にアタッチします
ドラッグされる DOM とドロップされる DOM が同じ場合
今回だと Box コンポーネントですが、以下の書き方でいけます
drag(drop(ref))
[追記 1/24] swapではなく、並び替えにした場合の実装
// 親コンポーネント const findItem = useCallback( (id: number) => { const idx = items.findIndex(c => c.id === id); return { idx, item: items[idx] }; }, [items] ); const moveBox = useCallback( (fromId: number, toId: number) => { const { idx: fromIdx, item } = findItem(fromId); const { idx: toIdx } = findItem(toId); const sortItems = produce(items, c => { c.splice(fromIdx, 1); c.splice(toIdx, 0, item); return c; }); setItems(sortItems); }, [items] ); // 子コンポーネント const [, drop] = useDrop({ accept: DND_TYPE, hover({ id: fromId }: ItemType) { if (fromId !== id) moveBox(fromId, id); }, });
TypeScript のユニオン型とインターセクション型について
はじめに
ユニオン型(|)とインターセクション型(&)の違いを簡単な例でおさらいしておこうと思います
以下の型定義を利用して、実装します
type A = { a: string } type B = { b: number } type U = A | B type I = A & B
すべてのプロパティを実装する
const u: U = { a: '', b: 0 } const i: I = { a: '', b: 0 }
上記は当然ですが、uとiのどちらも TS エラーは起きません
存在しないプロパティを実装する
cというAとBのどちらにも存在しないプロパティを設定する
const u: U = { a: '', b: 0, c: false } const i: I = { a: '', b: 0, c: false }
上記はuとiどちらもエラーになる
(そりゃそうだ)
ユニオン型で一部のプロパティを実装する
const u: U = { a: '' }
上記はエラーにならない
ユニオン型はAとBのプロパティのいずれかを実装していれば良い
よって、Aのプロパティ(a)を実装しているので問題ない
const u: U = {}
上記はエラー
AとBのプロパティのいずれかを実装できていない
インターセクション型で一部のプロパティを実装する
const i: I = { a: '' }
上記はエラーになる
インターセクション型はAとBのプロパティのすべてを実装する必要がある
よって、Bのプロパティ(b)を実装する必要がある
const i: I = {}
上記は当然エラー
Aのプロパティをオプショナル型にしてみる
プロパティをオプショナル型にするとどうなるか
type A = { a?: string } type B = { b: number } type U = A | B type I = A & B
const u: U = {}
上記はエラーにならない
ユニオン型はAとBのプロパティのいずれかを実装していれば良いので、上記はaの undefined が実装できているということになる
const i: I = {}
上記はエラー
インターセクション型の場合は、AとBのプロパティのすべてを実装する必要があるので、この場合はbを必ず実装する必要がある
ちなみにbもオプショナル型にすると、上記はエラーになりません
ここまでのまとめ
ここまでの例をまとめると、ユニオン型がAとBのプロパティのいずれかを実装で、インターセクション型がAとBのプロパティのすべてを実装と言えます
今度は以下の型定義を例に実装します
type A = { a: string b: number } type B = { b: number c: string } type U = A | B type I = A & B const u: U const i: I
上記のuとiの変数は初期値が設定されていないため、コンパイルエラーになっています
初期値は何を与えればよいでしょうか
uは{ a: "a", b: 1 }もしくは、{ b: 1, c: "c" }です(値は適当)
iは{ a: "a", b: 1, c: "c"}です(値は適当)
つまり、
uはAとBのプロパティのいずれかを実装(bは必須で実装し、aとcはいずれかが実装されていないといけない)
iはAとBのプロパティのすべてを実装
となります
また、uとi内のプロパティを参照する際も、
uはbは参照できるが、aとcは存在するか確定させるまで参照不可となります
つまり、型ガード(a in u)などを使って、aやcが存在することを確定させないと、参照できません
iはa,b,cのすべてのプロパティが参照できます
もし、上記の初期値を設定した上で、以下のようにbの型を異なるものに変えるとどうなるでしょうか
// bがnumber型 type A = { a: string b: number } // bがstring型 type B = { b: string c: string }
この場合、
A | Bの時のbはnumber | stringとなり、number型かstring型を設定すれば良い
A & Bの場合は、コンパイルエラー(後述のプリミティブ型データの場合を参照)
となります
プリミティブ型データの場合
最後にプリミティブ型の例です
type U = string | number type I = string & number
const us: U = '' // ok const ui: U = 0 // ok const ub: U = false // エラー const is: I = '' // エラー const ii: I = 0 // エラー const ib: I = false // エラー
ユニオン型は、分かりやすいですね
インターセクション型は、string 型と number 型の両方を表現するのは不可能なので、never 型になります
never 型は事前に起こることを想定してはいけない型なので、never 型の変数を定義し、何かを代入することは不可能です
つまり、プリミティブ型データのインターセクション型というものは定義できないということになります
SvelteKit(β 版)を見てみる
はじめに
Svelte+SvelteKit の環境がコマンド 1 つで構築できるので、その構成を軽く見てみようと思います
Web フレームワークのSvelteKitはSapperの後継で将来的にこちらに置き換わります
この記事の@sveltejs/kitのバージョンは1.0.0-next.151です
まだ β 版です
セットアップ
npm init svelte@next Project-name
インタラクティブに環境をセットアップできる
以下を選択
- Which Svelte app template?
- SvelteKit demo app
- Use TypeScript?
- Yes
- Add ESLint for code linting?
- Yes
- Add Prettier for code formatting?
- Yes
設定周りで気になった点
.prettierrcは最初に自分用の設定に変えておく- package.json に
"type": "module"が設定してある- これはスコープ内の JS ファイルを ES Modules(import/export)として扱う
- この設定の影響を受けるのは
svelte.config.jsで、試しに"type": "commonjs"とかにすると、この config が動かない - また、
.eslintrc.jsも影響を受けてて、こちらは ESM として扱うと動かないので、拡張子を変えて.eslintrc.cjsで CJS 化されている - JS ファイルは書かない想定なので、
"type": "module"を削除して、svelte.config.mjsにしようかと試したが、node_modules/@sveltejs/kit/dist/cli.jsでsvelte.config.jsという名前で読み込んでいるので、それもできなかった
- TypeScript を動かすにはプリプロセッサが必要
svelte-preprocessというパッケージで公開されていて、これがsvelte.config.jsで設定されている
svelte.config.jsのkit: { target: '#svelte' }の設定- これは SSR された HTML を
<div id="svelte">に hydrate する設定、OFF にすることは無さそう
- これは SSR された HTML を
- TODO デモアプリ(
/todos)で追加・削除したデータが永続化されている - ビルドが速すぎだった
- ビルドツールに Vite を使ってる
追加した設定
- ESLint に
eslint-config-airbnb-baseのルールを追加した
コマンド
- dev(
svelte-kit dev).svelte-kit/devにビルド結果が出力される- 同時にサーバーを立ち上げる
- HMR で開発できる
- build(
svelte-kit build)- 本番用ビルド
svelte.config.jsで adapter を指定し、デプロイ環境にあわせてビルド結果を切り替えることができる- 特定環境の adapter(
adapter-netlifyやadapter-vercelなど)もあり、自作することも可能(NPM レジストリに色んな adapter が公開されている)- この仕組みはいいなって思った
- とりあえず
@sveltejs/adapter-staticを使うことにしたが、npm i -D @sveltejs/adapter-static@nextにしないとエラーで動かなかった(β 版なのであまり参考にならないが)- 結果は
/buildディレクトリに出力される - ちゃんと静的ビルドされてるっぽかった
- 結果は
- preview(
svelte-kit preview)svelte-kit buildの結果を表示できる- 本番ビルドの結果をローカルで確認する用
- 注意点として、
/buildではなく、.svelte-kit/outputの方を参照してるので、adapter でビルドされた結果は見ていない
- check(
svelte-check)- 未使用 CSS、A11y、TS 型チェックを行う
- Lint やフォーマッターと一緒に CI で走らすと良さそう
ここまでのsvelte.config.jsは以下
import adapter from '@sveltejs/adapter-static' import preprocess from 'svelte-preprocess' /** @type {import('@sveltejs/kit').Config} */ const config = { preprocess: preprocess(), kit: { adapter: adapter(), target: '#svelte' } } export default config
コード(src配下)
SvelteKit のディレクトリ構成はsvelte.config.jsの files プロパティで定義されており、以下のドキュメントにデフォルト値が書いてある
https://kit.svelte.dev/docs#configuration
- hooks(
src/hooks) - lib(
src/lib) - routes(
src/routes) - serviceWorker(
src/service-worker)- ここに SW を置けば Vite が自動で登録してくれる
- template(
src/app.html)- テンプレート
このデモアプリのコード色々やってて、勉強になる
この後やること
静的サイトの Next.js を SPA でルーティングさせる
はじめに
この記事の内容は、以下のサイトと同じことをやってます
この記事は自分用のメモです
https://colinhacks.com/essays/building-a-spa-with-nextjs
https://blog.hey3.dev/posts/nextjs-spa
概要
Next.js アプリを静的ファイルにビルドし、S3 にデプロイ、CloudFront で公開していた
しかし、動的ルーティングを使うことになり、これ自体は Next.js がサポートしているので問題ないが、/task/[id].htmlに対して URL から直接アクセスが来た場合に 403 エラーとなってしまう
単純にルートの/index.htmlに飛ばすだけで良いなら、CloudFront のカスタムエラーページの設定で対応できるが、今回はシステムの仕様上、それはできず、/task/[id].htmlの内容を表示させる必要がある
今回やりたいことは以下である
- システムは静的サイトホスティングする
- サーバー上でシステムは動かさない
- ルートの JS でルーティングを実装する
- Next.js のルーティングを利用しない
- URL 直接入力の場合、必ずルートの JS を経由させ、ルーティングを通るようにする
Next.js はファイルシステムに沿ったルーティングを持つ(pages ディレクトリにファイルを配置すると自動でルーティングされる)
しかし、サーバー上で動いていない Next.js では動的ルーティング + URL 直接入力に対応できない
こちらはreact-routerで対応する
そうなると、create-react-appで良くて、Next.js 使わなくてもいいんじゃないって感じだけど、バンドルの分割機能やページ単位の SSR など Next.js 機能の恩恵もあるのと、途中で Next.js を使うのやめるのちょっとしんどいので、Next.js の利用を継続する
Nuxt.js であれば、mode: spaやssr: falseで対応できるのかもしれない
react-routerの実装
npm i react-router-dom
ルートの JS でルーティングを実装する
// pages/index.tsx import { BrowserRouter, Route, Switch } from 'react-router-dom' const App: React.VFC = () => ( <BrowserRouter> <Switch> <Route path="/sign-in" component={SignIn} exact /> <Route path="/" component={Home} exact /> <Route path="/task/:id" component={Task} exact /> </Switch> </BrowserRouter> )
エラーの対応
ルーティング実装後、サーバー上で動かすと以下のエラーが出る
Error: Invariant failed: Browser history needs a DOM
また、静的ビルドの場合は、以下のエラーになる
Error occurred prerendering page "/". Read more: https://err.sh/next.js/prerender-error
これはreact-routerがグルーバルオブジェクトの window を参照しているが、Node.js 上では window は未定義なので、エラーになっている
よって、react-routerを使うには Node.js のレンダリングを廃止する必要がある
// pages/_app.tsx const App = ({ Component, pageProps }: AppProps): JSX.Element => ( <div suppressHydrationWarning={process.env.NODE_ENV === 'development'}> {typeof window === 'undefined' ? null : <Component {...pageProps} />} </div> )
window が undefined の場合、null を返して、レンダリングさせないようにしている
また、上記を行ったことでクライアントとサーバーでレンダリング結果が異なるため、以下のエラーが出る
Warning: Expected server HTML to contain a matching <div> in <div>.
これはsuppressHydrationWarningを指定することで警告を消せる
URL 直接入力の対応
今の状態だと URL 直接入力で画面表示を行う場合、react-routerを通さないと 403/404 エラーになる
たとえば、/task/1でリロードすると、Next.js は/task/1.htmlを探しにいくが、これはないので 403/404 エラーを返す
以下の rewrites 設定を行うことで、すべてのパスをルート(ルーティング書いた場所)に書き換えることができる
// next.config.js module.exports = { async rewrites() { return [{ source: '/:path*', destination: '/' }] } }
ただし、rewrites はサーバー上でのみ機能するため、静的サイトホスティングしている場合は機能しない
CloudFront の場合の対応としては、カスタムエラーレスポンスの設定で 403 エラーが起きた場合、レスポンスコードを 200 に変更し、レスポンスページをルート(/)にすればいける
さいごに
サーバーレンダリングを抑制したり、next/routerとreact-routerが共存したりして、やってることは微妙かもしれないが、Next.js を利用しつつ、動的ルーティング + URL 直接入力に対応するにはおそらく 1 番簡単なやり方だと思う
react-routerを使わないやり方だと、Lambda@Edge による URL 書き換えや静的サイトホスティングをやめて、ECS 等で Next.js を動かすやり方があると思うが、Lambda の場合は複雑化したり、ECS の場合は途中から切り替えるのはしんどかったりするかなと思う
ECS Fargate に exec する
はじめに
トラブルシューティングなどで、ECS Fargate のコンテナーに入ってコマンドを実行する手順です
コンテナーの実行環境が EC2 であれば、普通に SSH でログインして、docker execすればよいのですが、Fargate の場合はこれが使えません
ECS Exec + AWS Systems Manager(SSM)を使うと、Fargate 内のコンテナーでも exec できるようになります
事前準備
macOS で作業しています
ECS タスクロールの作成
ロールを作成したら、ロールの信頼関係を以下に設定
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ecs-tasks.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }
ロールに以下のポリシーをアタッチ
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ssmmessages:CreateControlChannel", "ssmmessages:CreateDataChannel", "ssmmessages:OpenControlChannel", "ssmmessages:OpenDataChannel" ], "Resource": "*" } ] }
ECS のサービスを更新
aws ecs update-service --service <value> --cluster <value> --enable-execute-command
もし、サービスを新規作成する場合は、aws ecs create-serviceコマンドに--enable-execute-commandオプションを付与して実行する
実行中の ECS タスクを停止し、新しいタスクを起動させる(enableExecuteCommand が有効になる)
以下のコマンドで enableExecuteCommand オプションが有効になっているか確認できる
aws ecs describe-tasks --cluster <value> --task <value> | grep enableExecuteCommand
Session Manager plugin をインストール
brew install --cask session-manager-plugin
コンテナーに exec する
aws ecs execute-command --cluster <value> --task <value> --container <value> --interactive --command "/bin/sh"
さいごに
作業が終わったら、ECS Exec は無効にしておきましょう
aws ecs update-service --service <value> --cluster <value> --disable-execute-command
Digdag から Amazon SNS 経由でエラーを通知する方法を考えた
Digdag から Amazon SNS 経由で Slack にエラーを通知するやつを今までは AWS Lambda を使ってやっていたが、Lambda を使うとコード管理どうするとか色々考えると面倒なので、今年使えるようになった AWS Chatbot が使えたら、いいなーって思ったので調査した
ただし、こちらにあるようにSNS -> Chatbotという経路では、使えません
https://docs.aws.amazon.com/chatbot/latest/adminguide/related-services.html
でも簡単に設定できるので、一応試してみた
結果、できなかったが、やったことをメモ
Amazon SNS
トピックの作成
トピックの作成をクリックし、以下を設定
詳細
| 設定項目 | 設定値 |
|---|---|
| タイプ | スタンダード |
| 名前 | 任意の名前 |
AWS Chatbot
Slack のワークスペースの設定
クライアントの設定でSlackを選択

この後、Slack のワークスペースに Chatbot からのアクセスを許可を求める画面が出るので、許可する
チャネルの設定
新しいチャネルを設定をクリックし、以下を設定
設定の詳細
| 設定項目 | 設定値 |
|---|---|
| 設定名 | 任意の名前 |
| ログ記録 | Amazon CloudWatch Logs にログを発行する(エラーのみ) |
Slack チャネル
| 設定項目 | 設定値 |
|---|---|
| チャネル ID | Slack のチャネル ID |
Slack のチャネル ID の Slack の対象のチャネルを右クリックして、Copy linkで確認できる
アクセス許可
CloudWatch に関わるポリシーを付与した、IAM ロールが作成される
| 設定項目 | 設定値 |
|---|---|
| IAM ロール | 任意のロール名(新規にロールが作られる) |
| ポリシーテンプレート | 通知のアクセス許可 |
以下のポリシーでアタッチされたロールが作成された
{ "Version": "2012-10-17", "Statement": [ { "Action": ["cloudwatch:Describe*", "cloudwatch:Get*", "cloudwatch:List*"], "Effect": "Allow", "Resource": "*" } ] }
通知 - オプション
| 設定項目 | 設定値 |
|---|---|
| SNS トピック | 上記で作成した SNS トピック |
AWS CLI
必要なポリシー
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": "sns:Publish", "Resource": "[作成した SNS トピックのARN]" } ] }
実行
aws sns publish \ --topic-arn ${SNS トピックのARN} \ --message "SNS test message" \ --subject "SNS test subject" \ --region ap-northeast-1
以下のエラーが CloudWatch に出力された
Event received is not supported (see https://docs.aws.amazon.com/chatbot/latest/adminguide/related-services.html )
SNS -> Chatbotという連携はできないっぽい
まあ当然の結果ではありますが、、
たとえば、CloudWatch Alarms -> SNS -> Chatbotという経由であれば、できるのだと思う
ただ、今回のケースでは Digdag でエラーが起きる度に通知を飛ばしたいので、CloudWatch Alarm で閾値や期間を設定するのは、難しいのかなーと
Digdag を使っているので、現状だと解決方法は以下なのかな
- python-slack-sdk
- digdag-slack
- それか、AWS Lambda
Nuxt.js で Vuex を使わない場合に考えること
Vuex を使うのに慣れすぎて、Vuex を使わないパターンを考えた時に色々考えたのでメモ
方針
- 比較的規模が小さいシステムでは、Vuex の利用をまずは避けたい
- Nuxt.js を使用
- Composition API を使用
コンポーネント間のデータ受け渡しパターン
親から子
- 普通に props 経由
子から親
$emitで親の関数を呼ぶ.sync修飾子と$emit.updateで親の状態を更新する
子から子
- 禁止
- 必ず親を経由する
親から親
たとえば、pages コンポーネントから pages コンポーネントにデータを渡したい時など
- EventBus で親子間以外のデータ受け渡しが行えるらしい
- ただし、現在は非推奨(公式でも Vuex を使った方がよいという見解)
- Vue3 では
$onが削除される - Nuxt.js でも使えなくなるはず
代替手段
- URL に含めて vue-router から取る
- 渡したいデータが増えた時の拡張性が低い
- ブラウザのセッションストレージとかに入れとく
- なんか微妙(TypeScript 使っているし)
- Provide/Inject パターンを使う
- Vue の 2.2.0 からあるらしい
- Vuex の代わりになりそう
- これは小規模なアプリケーションには、よい気がする
Provide/Inject パターンの実装
Nuxt.js と Composition API での実装
Key/Value を定義する
Value にグローバルで管理したい状態とロジックを定義する
// composables/dashboard.ts import { Dashboard } from '@nuxt/types' import { ref, Ref, InjectionKey } from '@nuxtjs/composition-api' // Storeの型定義 export type DashboardStore = { dashboard: Ref<Dashboard> setDashboard: (d: Dashboard) => void } // Key export const DashboardKey: InjectionKey<DashboardStore> = Symbol( 'DashboardStore' ) // Value export const useDashboard = (): DashboardStore => { // 状態 const dashboard = ref<Dashboard>({ dashboard_id: 0, dashboard_name: '' }) // ロジック const setDashboard = (d: Dashboard) => (dashboard.value = d) return { dashboard, setDashboard } }
次に、上記の Key/Value で provide する
今回は pages コンポーネント間でデータ受け渡しを行いたいので、layout コンポーネントで実装した
// layouts/default.vue import { defineComponent, provide } from '@nuxtjs/composition-api' import { DashboardKey, useDashboard } from '@/composables/dashboard' export default defineComponent({ setup() { provide(DashboardKey, useDashboard()) } })
状態を変更する場合
// pages/set.vue import { defineComponent, inject } from '@nuxtjs/composition-api' import { DashboardKey, DashboardStore } from '@/composables/dashboard' export default defineComponent({ setup() { const { setDashboard } = inject<DashboardStore>(DashboardKey) || { setDashboard: () => console.error('Not found setDashboard') } setDashboard({...}) } })
状態を取得する場合
// pages/get.vue import { defineComponent, inject } from '@nuxtjs/composition-api' import { DashboardKey, DashboardStore } from '@/composables/dashboard' export default defineComponent({ setup() { const { dashboard } = inject<DashboardStore>(DashboardKey) || {} } })
データの種類と取得場所
静的なデータ
サーバーなどから 1 回取得したら、変更がないデータなど
「Nuxt の plugins で取得し、Nuxt のコンテキストに inject しておく」
- plugins 配下のスクリプトはルートの Vue コンポーネントがインスタンス化される前に実行される
- SSR していれば、クライアントとサーバーでそれぞれ実行される
nuxt.config.jsの plugins オプションで対象のモジュールを定義する際、modeというオプションで、そのプラグインの実行をクライアントのみ、もしくは、サーバーのみと指定可能
これはいい感じな気がする
動的なデータ
たとえば、アプリケーション内で変更可能なデータなど
「親コンポーネントで状態を管理および、変更処理(サーバーからデータ取得など)を行う」
親コンポーネントが肥大化する場合
親コンポーネントが pages の場合