Introduce to Pinia🍍

v-kansai Vue.js/Nuxt Meetup #13【v-Shinnenkai】

chan_kaku

chan_kakuz

chan_kaku

takumiz19

Who?

FURYU Corp. @Kyoto

ServerSide Engineer

  • Java
  • Kotlin

 

Spring/Vue.js/Architecture/Test

角田拓己

Piniaのお話

#1

Piniaとは?

Composition APIを利用してStoreがどうあるべきか
追求するためのプロジェクト

元々はスペイン語のPiña(パイナップル)からきているがパッケージ名にスペイン語を使えなかったため一番近い発音のものに

そもそもComposition API
とは?

#2

来たるVue 3.0で追加される

目玉のAPI

ちなみに現状でもComposition APIを利用することができます!

機能を要素ごとにまとめることができる

良いところ

  • this地獄がなくなる

  • よりviewとロジックを分離させやすくなった

this地獄がなくなる

<template>
  <button @click="increment">
    Count is: {{ state.count }}, double is: {{ state.double }}
  </button>
</template>

<script>
import { reactive, computed } from 'vue'

export default {
  setup() {
    const state = reactive({
      count: 0,
      double: computed(() => state.count * 2)
    })

    function increment() {
      state.count++
    }

    return {
      state,
      increment
    }
  }
}
</script>

よりviewとロジックを
分離させやすくなった

function useCreateFolder (openFolder) {
  // originally data properties
  const showNewFolder = ref(false)
  const newFolderName = ref('')

  // originally computed property
  const newFolderValid = computed(() => isValidMultiName(newFolderName.value))

  // originally a method
  async function createFolder () {
    if (!newFolderValid.value) return
    const result = await mutate({
      mutation: FOLDER_CREATE,
      variables: {
        name: newFolderName.value
      }
    })
    openFolder(result.data.folderCreate.path)
    newFolderName.value = ''
    showNewFolder.value = false
  }

  return {
    showNewFolder,
    newFolderName,
    newFolderValid,
    createFolder
  }
}

mixinでもできたけど、、、、

微妙なところ

自由度が高くなったことで規約をちゃんと作らないともとよりすごいスパゲッティコードが出来上がる

注意

Piniaの特徴

#3

  • Vuexより軽量

  • 基本的にはstateとgettersだけ

  • SSRサポート

  • DevToolsサポート

SSRサポート

export function createApp() {
  // Here there could also be a router
  const store = useStore(true)

  // we can change the state now!
  store.state.counter++

  // create the app instance
  const app = new Vue({
    render: h => h(App),
  })

  // expose the app and the store.
  return { app, store }
}

基本的にはVuexと同じ書き方でSSRも対応できる

DevToolsサポート

実際に使ってみる

#4

Storeの実装

import { createStore } from 'pinia'

export const useMainStore = createStore(
  // name of the store
  // it is used in devtools and allows restoring state
  'main',
  // a function that returns a fresh state
  () => ({
    counter: 0,
    name: 'Eduardo',
  }),
  // optional getters
  {
    doubleCount: state => state.counter * 2,
  }
)

Storeを利用

import { useMainStore } from '@/stores/main'

export default createComponent({
  setup() {
    const main = useMainStore()

    return {
      // gives access to the whole store
      main,
      // gives access to the state
      state: main.state,
      // gives access to specific getter,
    }
  },
})

MutationとActionは?

まずはMutation

//単純に
main.state.counter++

//もしくは複数を一気に変更したい場合
main.patch({
  counter: 3,
  name:'hoge'
})

続いてAction

export async function login(user, password) {
  const store = useUserStore()
  const userData = await apiLogin(user, password)

  store.patch({
    name: user,
    ...userData,
  })
}

ただのビジネスロジックを含んだ関数

Piniaを使った応用

#5

Shared Getters

import { computed } from '@vue/composition-api'
import { useUserStore } from './user'
import { useCartStore } from './cart'

export const summary = computed(() => {
  const user = useUserStore()
  const cart = useCartStore()

  return `Hi ${user.state.name}, you have ${cart.state.list.length} items in your cart. It costs ${cart.price}.`
})

Shared Actions

import { useUserStore } from './user'
import { useCartStore, emptyCart } from './cart'

export async function orderCart() {
  const user = useUserStore()
  const cart = useCartStore()

  try {
    await apiOrderCart(user.state.token, cart.state.items)
    emptyCart()
  } catch (err) {
    displayError(err)
  }
}

でっかいStoreではなくある構成ごとに切り出すことができる

ユニットテストがやりやすく

Creating Pinias

import { pinia } from 'pinia'
import { useUserStore } from './user'
import { useCartStore, emptyCart } from './cart'

export const useCartUserStore = pinia(
  {
    user: useUserStore,
    cart: useCartStore,
  },
  {
    combinedGetter: state =>
      `Hi ${user.state.name}, you have ${cart.state.list.length} items in your cart. It costs ${cart.price}.`,
  }
)

export async function orderCart() {
  const store = useCartUserStore()

  try {
    await apiOrderCart(store.state.user.token, store.state.cart.items)
    emptyCart()
  } catch (err) {
    displayError(err)
  }
}

まだ実装されてません

注意

まとめ

  • Vuex以外の状態管理の手段を紹介

  • まだ実験的なプロジェクトなので、本番運用は推奨されていない

  • Vue 3.0が出てきてからいろんなStoreのあり方を考えて行くのも面白いかも

reference

fin.

Introduce to Pinia

By chan_kakuz

Introduce to Pinia

  • 6,330