Pinia開発者の議論から学ぶ、良いコードを書くための4つのルール🍍
この記事では、PiniaのEslintに関するissueやDiscussionでの議論を通じて、どのように書き方を改善できるかを考えていきます!
こんにちはmocchantaiです。
Piniaは使ってないけど、Piniaが可愛いので布教したいと思っています。
先日Piniaについて以下の記事を書きました。
今回参考にしたGithubのissueとdiscussionは以下の2つです。
これらの内容を元に、読みやすく加工してみました。
この記事をきっかけに、vuejs, piniaのGithubリポジトリを覗いて見てください😁
ディスカッションの登場人物
-
MickL
- Pinia Eslintプラグインの必要性を感じてissueを最初に立てた人。
-
posva
- Piniaのメンテナーであり、Vue.jsのコアチームメンバー。
-
lisilinhart
- eslint-plugin-pinia を開発している人。
ディスカッション
↓詳しくはこちらのディスカッションをご覧ください!
MickL「PiniaにEslintプラグインがあったら最高じゃない?」
- 「PiniaにEslintプラグインがあったら最高じゃない?例えば、setupストアで定義した変数や関数をエクスポートし忘れたら警告してくれるとかさ。他にもベストプラクティスとかアンチパターンを指摘してくれると便利だと思うんだよね。」
lisilinhart「4つルール考えたんだけどどう?」
- 「それめっちゃいいね!以下の4つのルールはどうかな?」
-
prefer-use-store-naming-convention: ストア名を
use
で始め、ストア名の後に接尾辞(例:Store
)を付ける規約を強制。 - prefer-single-store-per-file: 1つのストアを1ファイルに定義することを推奨。
- require-setup-store-exports: setup関数内で定義されたstateプロパティがすべてエクスポートされているかをチェック。
-
no-return-global-properties: setupストアから
route
やappProvided
のようなグローバルプロパティを返すことを禁止し、警告を出す。
-
prefer-use-store-naming-convention: ストア名を
posva「素晴らしいルールだね!それぞれ少しフィードバックするね!」
-
1. prefer-use-store-naming-convention
- 「ストア名を
use
で始めるというのは良いアイデアだね。ただ、Store
という接尾辞を必須にするのではなく、Service
など他の名前を使いたいケースもあるから、これをオプションで設定可能にした方が良いと思う。」
- 「ストア名を
-
2. prefer-single-store-per-file
- 「1つのファイルに1つのストアを定義するルールも良いけど、これはデフォルトで有効にしない方がいいかもね。特にプライベートストアを使うケースでは、ストアを分けると不便な場合があるし、テスト目的でプライベートストアをエクスポートするケースも考えられるから。」
-
3. require-setup-store-exports
- 「setupストアのエクスポートについては、状態管理に関連する
ref
やreactive
だけを対象にするのが良さそうだね。functions, computed, raw dataは対象外で問題ないと思う。」
- 「setupストアのエクスポートについては、状態管理に関連する
-
4. no-return-global-properties
- 👍
posva「他にもこんなルールあったらいいかもね!」
- ストアIDの重複チェック: Warn if two stores are defined with the same name #1394
- 状態の直接変更の禁止:Add an option to disallow direct state modification from component #58 (should be off by default)
- storeToRefsの誤用防止 Using storeToRefs inside of another store is not working #2633
posvaのフィードバックを踏まえた推奨の書き方
1. prefer-use-store-naming-convention
ストア名はuseで始めることを推奨します。ただし、Storeという接尾辞はオプションで設定可能とし、開発者が自由に命名できる柔軟性を残します。
遵守している例
// useCounterStore.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
const increment = () => {
count.value++;
};
return { count, increment };
});
遵守していない例
// counter.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';
- export const useCounterStore = defineStore('counter', () => {
+ export const counter = defineStore('counter', () => {
const count = ref(0);
const increment = () => {
count.value++;
};
return { count, increment };
});
2. require-setup-store-exports
setup関数内で定義されたrefやreactiveのstateはすべてエクスポートされるべきです。ただし、計算プロパティや関数、その他の補助的なデータは対象外とします。
遵守している例
// useTodoStore.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useTodoStore = defineStore('todo', () => {
const todos = ref<string[]>([]);
const addTodo = (todo: string) => {
todos.value.push(todo);
};
return { todos, addTodo }; // 全てエクスポート
});
遵守していない例
// useTodoStore.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useTodoStore = defineStore('todo', () => {
const todos = ref<string[]>([]);
const addTodo = (todo: string) => {
todos.value.push(todo);
};
- return { todos, addTodo }; // 全てエクスポート
+ return { addTodo }; // `todos` をエクスポートし忘れている
});
3. prefer-single-store-per-file
1つのファイルに1つのストアを定義することを推奨します。ただし、テストやプライベートストアなど特定のユースケースでは無効化も可能とします。
遵守している例
// useUserStore.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useUserStore = defineStore('user', () => {
const name = ref('Alice');
const setName = (newName: string) => {
name.value = newName;
};
return { name, setName };
});
遵守していない例
// stores.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useUserStore = defineStore('user', () => {
const name = ref('Alice');
const setName = (newName: string) => {
name.value = newName;
};
return { name, setName };
});
+ export const useCounterStore = defineStore('counter', () => {
+ const count = ref(0);
+
+ const increment = () => {
+ count.value++;
+ };
+
+ return { count, increment };
+ });
4. no-return-global-properties
setupストアからrouteやappProvidedなどのグローバルプロパティを返すことを禁止します。これにより、予期せぬ副作用を防ぎます。
遵守していない例(routeのケース)
// useAuthStore.ts
import { defineStore } from 'pinia';
import { useRoute } from 'vue-router';
export const useAuthStore = defineStore('auth', () => {
const route = useRoute(); // グローバルプロパティ
return { route }; // routeをエクスポートしている
});
遵守していない例(appProvidedのケース)
// useAuthStore.ts
import { defineStore } from 'pinia';
import { inject } from 'vue';
export const useAuthStore = defineStore('auth', () => {
const appProvided = inject('someGlobalValue'); // グローバルプロパティ
return { appProvided }; // appProvidedをエクスポートしている
});
少しでもPiniaに興味をもってくれたら嬉しいです!
これからもPiniaについて少しずつブログを書いていきたいです🍍
Discussion