スキップしてメイン コンテンツに移動

VueJSのPiniaのまとめ【基礎編】

 


目次

Vue 基本構造

Pinia 基本構造

State (Vue)

State (Pinia)

Getters (Vue)

Getters (Pinia)

Actions (Vue)

Actions (Pinia)

 

 

Vue 基本構造

  • 基本構造説明 - Composition API 方式( 別の方式ではOption API方式がある export default{} )
<script setup>
// storeを import
import { useTodosStore } from '@/stores/todos'

// 宣言
const todos = useTodosStore()

// --- stateの値をレスポンシブで参照する方法
const { todos, count } = storeToRefs(todos)

// --- actionは分割代入して使える
const { increment } = todos 

// --- stateの修正方法3つ
// 1. 修正
counter.count++
// 2. $patch 利用
counter.$patch({ count: counter.count + 1 })
// 3. action 이용
counter.increment()

</script>

<template>
  <!-- Storeのstateに直接アクセス -->
  <div>Current Count: {{ counter.count }}</div>
</template>

 

Pinia 基本構造

  • 基本構造説明 Option Store方式
    • 既存と同様state, getters, actionsを直感的に表現する
    • stores/todos-store.js
    • import { defineStore } from 'pinia'
      
      export const useTodosStore = defineStore('todos', {  //-- storeの名前 
        state: () => ({
          /** @type {{ text: string, id: number, isFinished: boolean }[]} */
          todos: [],
          /** @type {'all' | 'finished' | 'unfinished'} */
          filter: 'all',
          // type will be automatically inferred to number
          nextId: 0,
      	count: 0 
        }),
        getters: {
          finishedTodos(state) {
            return state.todos.filter((todo) => todo.isFinished)
          },
          unfinishedTodos(state) {
            return state.todos.filter((todo) => !todo.isFinished)
          },
          
          filteredTodos(state) {
            if (this.filter === 'finished') {
              return this.finishedTodos
            } else if (this.filter === 'unfinished') {
              return this.unfinishedTodos
            }
            return this.todos
          },
        },
        actions: {
          increment() {
            this.count++
          },
          addTodo(text) {
            this.todos.push({ text, id: this.nextId++, isFinished: false })
          },
        },
      })

 

State (Vue)

  • 基本的にインスタンスを通じてstateに接近し読み書きできる
    • ただし, stateに予め定義しておくべき
    const store = useStore()
    store.count++
    

 

  • 初期化(Option Store方式のみ可能, Setup Store 方式は直接生成するべき)
  • const store = useStore()
    store.$reset()

 

  • state 変更
    1. 直接変更
      const store = useStore()
      store.count++​
    2. patch利用
      const store = useStore()
      store.$patch({
        count: store.count + 1,
        age: 120,
        name: 'DIO',
      })​

       
    3. arrayを修正(push, remove, spliceなど)するケースならmutationをグループ化させることも可能
      const store = useStore()
      store.$patch((state) => {
        state.items.push({ name: 'shoes', quantity: 1 })
        state.hasChanged = true
      })​
  • state変更時には注意
  • // ❌ $stateを使えない
    store.$state = { count: 24 }
    // ⭕ 内部的に $patch()を呼び出し
    store.$patch({ count: 24 })
  • state 変更追跡 (subscribe, 購読)
  • <script setup>
    const someStore = useSomeStore()
    
    // この追跡は componentがunmountされたあとでも維持される
    someStore.$subscribe(callback, { detached: true })
    </script>

 

 

State (Pinia)

  • stateの基本構造(Option Store 方式)
  • state: () => {
      return {
        // 最初は空いているlistの場合
        userList: [] as UserInfo[],
        // まだloadされていないデータの場合
        user: null as UserInfo | null,
      }
    },
    interface UserInfo {
      name: string
      age: number
    }

 

  • State インターフェースを利用する場合(Option Store 方式)
  • interface State {
      userList: UserInfo[]
      user: UserInfo | null
    }
    
    export const useUserStore = defineStore('user', {
      state: (): State => {
        return {
          userList: [],
          user: null,
        }
      },
    })
    
    interface UserInfo {
      name: string
      age: number
    }

 

  • state 変更追跡 (subscribe, 購読)
  • import { MutationType } from 'pinia'
    
    cartStore.$subscribe((mutation, state) => {
      mutation.type // 'direct' | 'patch object' | 'patch function'
      // same as cartStore.$id
      mutation.storeId // 'cart'
      // only available with mutation.type === 'patch object'
      mutation.payload // patch object passed to cartStore.$patch()
    
      // 変更されるたびに全体の状態をローカルストレージにセット
      localStorage.setItem('cart', JSON.stringify(state))
    })​
     

 

 

 

Getters (Vue)

  • gettersに接近
  • <script setup>
    import { useCounterStore } from './counterStore'
    
    const store = useCounterStore()
    </script>
    
    <template>
      <p>Double count is {{ store.doubleCount }}</p>
    </template>

 

  • gettersに因数(argument)を渡す
    • computed 属性であるためパラメータを伝達することはできないが、許容する方法はある。
    <script setup>
    import { storeToRefs } from 'pinia'
    import { useUserListStore } from './store'
    
    const userList = useUserListStore()
    
    // <script setup> 내에서 
    // getUserById.value에 접근 해야함.
    const { getUserById } = storeToRefs(userList)
    </script>
    
    <template>
      <p>User 2: {{ **getUserById(2)** }}</p>
    </template>
    
    • この方法を使えばgetterはもはやキャッシュされない。単純に呼び出す関数

 

Getters (Pinia)

  • vueの computed, vuexの gettersと類似
  • getters の基本構造
  • export const useCounterStore = defineStore('counter', {
      state: () => ({
        count: 0,
      }),
      getters: {
        // 返却タイプを自動的にnumberと推論
        doubleCount(state) {
          return state.count * 2
        },
        // 返却タイプは、「必ず」明示的に設定する必要がある
        doublePlusOne(): number {
          // 全体storeに対する自動完成及びtypings ✨
          return this.doubleCount + 1
        },
      },
    })
  • gettersに因数(argument) 伝達
    • computed 属性なのでパラメータを伝達することはできないが、許容する方法はある。
    export const useStore = defineStore('main', {
      getters: {
        getUserById: (state) => {
          return (userId) => state.users.find((user) => user.id === userId)
        },
      },
    })
    • この方法を使えばgetterはもはやキャッシュされていない、単純に呼び出す関数
  • 他のストアのgetterにアクセス
    • import { useOtherStore } from './other-store'
      
      export const useStore = defineStore('main', {
        state: () => ({
          // ...
        }),
        getters: {
          otherGetter(state) {
            const otherStore = useOtherStore()
            return state.localData + otherStore.data
          },
        },
      })

Actions (Vue)

  • actionsの基本構造
  • <script setup>
    const store = useCounterStore()
    // storeで actionを呼び出す
    store.randomizeCounter()
    </script>
    
    <template>
      <button @click="store.randomizeCounter()">Randomize</button>
    </template>

 

  • action 変更追跡(subscribe、購読)
  • <script setup>
    const someStore = useSomeStore()
    
    //この購読は、コンポーネントがunmountされた後も維持される
    someStore.$onAction(callback, true)
    </script>

 

Actions (Pinia)

  • actions の基本構造
  • export const useCounterStore = defineStore('counter', {
      state: () => ({
        count: 0,
      }),
      actions: {
        // thisに依存するためラムダを使えない
        increment() {
          this.count++
        },
        randomizeCounter() {
          this.count = Math.round(100 * Math.random())
        },
      },
    })

 

  • 違うstoreの actionsにアクセス
  • import { useAuthStore } from './auth-store'
    
    export const useSettingsStore = defineStore('settings', {
      state: () => ({
        preferences: null,
        // ...
      }),
      actions: {
        async fetchUserPreferences() {
          const auth = useAuthStore()
          if (auth.isAuthenticated) {
            this.preferences = await fetchPreferences()
          } else {
            throw new Error('User must be authenticated')
          }
        },
      },
    })

 

  • action変更追跡(subscribe、購読)
  • const unsubscribe = someStore.$onAction(
      ({
        name, // actionの名前
        store, // storeインスタンス(例:someStore)
        args, // actionに伝達されるパラメータ配列
        after, // action 返却またはresolve後のhook
        onError, // action throw あるいはrejenct時のhook
      }) => {
        // 特定のaction呼び出しに対する共有変数(shared variable)
        const startTime = Date.now()
        // このコードは、storeに対するaction実行前にトリガーされる
        console.log(`Start "${name}" with params [${args.join(', ')}].`)
    
        // このコードはactionが成功し、完全に実行された後にトリガーされる
        // promisedを返すまで待つ
        after((result) => {
          console.log(
            `Finished "${name}" after ${
              Date.now() - startTime
            }ms.\nResult: ${result}.`
          )
        })
    
        // action throw あるいはrejenct時に実行
        onError((error) => {
          console.warn(
            `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
          )
        })
      }
    )
    
    // listnerを手動で除去
    unsubscribe()

コメント

このブログの人気の投稿

Entity Framework Coreの概念 / メリット / コード例 / 使用方法 / データのインポート、修正、削除 / サンプルコード

  EF Core? EF Coreは [Entity Framework]を軽量化したバージョンで、.NET Core 及び.NET 5 以上で使用できる。 データベースとの相互作用を単純化し、開発者がデータベースに対するクエリおよび操作を行うことができるORM(Object-Relational Mapping)ツールである。 主な機能 データベースに対するCRUD(Create、Read、Update、Delete)作業を支援する。 LINQ(Language Integrated Query)を使ってデータベースクエリを作成することができる。 データベーススキーマをコードで定義できるCode Firstアプローチを提供する。 多様なデータベースシステムと互換性がある。 EF Coreのメリット 開発者がデータベースとの相互作用を容易にすることができる。 データベースに対する抽象化階層を提供し、メンテナンスと拡張性を向上できる。 多様なデータベースシステムとの互換性を提供する。 LINQを使って簡単にデータベースクエリを作成することができる。 EF Coreの限界 EF Core は、まだすべてのEntity Framework の機能をサポートしない。 一部の高度なデータベース機能に対するサポートが制限されているケースがある。 性能の側面で、一部の状況では原始SQLクエリを作成する方がより効率的である可能性がある。 EF Coreの使い方 プロジェクトにEF Core NuGet パッケージを追加する。 DbContextクラスを作成し、データベース接続情報を設定する。 モデルクラスを作成し、データベース テーブルとマッピングする。 LINQを使用してデータベースクエリを作成して実行する。 変更内容をデータベースに保存または更新する。 Entity Framework Coreを使ったデータベースのインストール SQL Serverのインストール dotnet add package Microsoft.EntityFrameworkCore.SqlServer テストのためのInMemoryインストール dotnet add package Microsoft.EntityFrameworkCore.InMemory DbContext サブクラス...

CQRSパターンとは?CQRSパターンの説明とメリットまとめ

  CQRS Pattern こちらは勉強のため翻訳したポストです。 元のページは こちら をクリック アプリケーションで 読み取り と 書き込み を分離することを CQRSパターン という。 CQRSパターン は 物理的 、 論理的 に分けることができ、次のような 長所 を持つ。 ・複雑度の管理 ・パフォーマンスの向上 ・拡張性 ・柔軟性 ・保安性 CQRSパターンが何か? Command Query Responsibility Segregation。 直訳すると 命令クエリ責任分離 で、データを読み込みと書き込みをそれぞれのモデルを使用するという意味である。 標準とされてきた方式としては、同一のモデルを用いてデータを読み上げアップデートしていたが、この方式は簡単で、ほとんどのCRUD作業に適している。 しかし、複雑なアプリケーションでは維持管理が難しくなる。 データの書き込みのために複雑なビジネスロジックや有効性検査を行うこともあり データを読み取るためには、さまざまなクエリを実行する必要がある場合がある。 また、どのようにデータモデルを作成するかを考慮する必要がある。 SQL データモデリングのベストプラクティスは正規化されたデータベースが提供されるが、これは一般的には問題ないが、データを書くことに最適化されている。 コマンドとクエリをそれぞれのモデルとして使用すると、個別に拡張することができる。 同じデータベースを使用するとしても論理的に分離できる。 コマンド(命令)およびクエリに対する下位システムをそれぞれのサービスに分割することができる。 また、データの書き込み、読み取りに最適化された複数のデータベースを使用することもできる。 CQSとはどう違うか? CQS (Command Query Separtion) : 命令クエリ分離 基本前提はオブジェクトのメソッドを コマンド と クエリ に分割したこと。 コマンド :システムのステータスを変更するが、値を返さない。 クエリ:値を返すが、システムの状態を変更しない。 (サイドエフェクトなし) コマンドからは値を 返せないという意味ではない 。  一般的な例としては、値を返した後にシステムの状態を変更するスタックから値を引き出してくる場合がある。 CQRSはCQSが進化したもので、CQR...