iT邦幫忙

2022 iThome 鐵人賽

DAY 11
0
Modern Web

LV的全端開發體驗系列 第 11

Day11 前端頁面狀態管理 - Pinia

  • 分享至 

  • xImage
  •  

選單位置記憶

拆完頁面後,我們來解決選單項目位置的問題,原先的做法是每個頁面去手動變更class,讓當前的頁面選單會有高亮反白的顯示,這個動作如果每個頁面都要做就太累了,所以我們要讓選單自動記得現在誰被點了,然後被點選的項目應該維持反白的狀態。

Vue本身有提供 emit 這個機制來讓子層資料可以傳遞到父層去,這個機制在組件層級太多時會很繁瑣,所以我們希望有一個可以全域管理狀態的機制,在之前大多是使用Vuex,近期Vue官方推薦使用Pinia。

安裝pinia

npm install pinia

resources/js/app.js 中掛載pinia

import './bootstrap';
import '../css/app.css';

import { createApp, h } from 'vue';
import { createPinia } from 'pinia'; //引入pinia模組
import { createInertiaApp } from '@inertiajs/inertia-vue3';
import { InertiaProgress } from '@inertiajs/progress';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m';

const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';
const pinia = createPinia() //建立pinia物件
createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
    setup({ el, app, props, plugin }) {
        return createApp({ render: () => h(app, props) })
            .use(plugin)
            .use(ZiggyVue, Ziggy)
            .use(pinia)  //掛載pinia進vue app
            .mount(el);
    },
});

InertiaProgress.init({ color: '#4B5563' });

建立一個資料夾來存放所有的狀態檔案,我個人習慣把所有的狀態分門別類不同的檔案做管理,而pinia允許狀態物件間相互引用,所以不需要把狀態都寫在一個檔案中。

建立一個選單專用的狀態管理檔

import { defineStore } from "pinia";

export const menuStore=defineStore('menuStore',{
    state:()=>({  }),
    getters:{  },
    actions:{  },
})

為了之後的擴充方便,我希望在增加選單項目時,選單項目可以自己在狀態管理中註冊自己,之後選單組件會自己去監測每個選單項目的變化,並通知每個項目更新自己的狀態:

resources/js/Stores/MenuStore.js

import { defineStore } from "pinia";

export const menuStore=defineStore('menuStore',{
    state:()=>({
        selected:{},  //以物件的方式來儲存狀態
    }),
    actions:{
        setItem(item){
            
            //把項目的KEY值取出成為一個陣列
            let items=Object.keys(this.selected)
            
            //如果陣列中沒有這個項目,就加入
            if(items.indexOf(item)<0){
                this.selected[item]=false;
            }
        },
        select(item){
            
           //把選單項目全部設為false
            Object.keys(this.selected).forEach(key=>{
                this.selected[key]=false
            })
            
            //把點選的項目設為true
            this.selected[item]=true
        }
    },
})

resources/js/QuizComponents/MenuItem.vue

<script setup>
import { Link } from "@inertiajs/inertia-vue3";
import { storeToRefs } from 'pinia'
import { menuStore } from '@/Stores/MenuStore.js';
import { ref, onMounted ,watch} from 'vue';

//onSelected是外部傳入的一個值,型態為布林值,預設為false
defineProps({
  href: String,
  onSelected:{type:Boolean,default:false}
});

const store=menuStore();
const {selected}=storeToRefs(store)
const {setItem,select} = store
const itemText=ref(); //設定一個變數來代表DOM物件

onMounted(()=>{
    
    //當單一選單項目被掛載到畫面上時,去狀態管理器中註冊一下自己
    //註冊的方式是把項目文字傳過去
  store.setItem(itemText.value.innerText)
});

</script>
<template>
    
    //在項目本身建立一個click事件,觸發時去通知狀態管理那個項目被點選了
    //class的部份綁定傳入的onSelected屬性,進行不同class的切換
  <Link
    :href="href"
    class="block my-2 py-2 text-center text-xl"
    :class="{'hover:bg-slate-50 hover:text-slate-900':!onSelected,
             'bg-slate-50 text-slate-900':onSelected}"
    @click="select(itemText.innerText)"
  >
    <span ref="itemText"><slot /></span>
  </Link>
</template>

resources/js/Layouts/BackstageLeftMenu.vue

<script setup>
import { storeToRefs } from 'pinia'
import { menuStore } from '@/Stores/MenuStore.js';
import MenuItem from "@/QuizComponents/MenuItem.vue";

const store=menuStore();
const { selected } = storeToRefs(store)

</script>
<template>
  <!--左側選單-->
  <div
    class="w-48 max-w-96 bg-black text-white py-8 px-2 shadow-lg 
           h-[calc(100vh_-_138px)]">
    <!--把每個項目的狀態傳進去-->
    <MenuItem :href="route('backstage.banks')" 
              :onSelected="selected['管理題庫']">管理題庫</MenuItem>
    <MenuItem :href="route('backstage.quizzes')" 
              :onSelected="selected['管理試卷']">管理試卷</MenuItem>
    <MenuItem :href="route('backstage.tests')" 
              :onSelected="selected['管理測驗']">管理測驗</MenuItem>
    <MenuItem :href="route('backstage.groups')" 
              :onSelected="selected['管理群組']">管理群組</MenuItem>
  </div>
</template>

以上就是Pinia的狀態管理的使用方式,基本上就是當它是個全域變數來看,所以連帶的也要注意在某些情境下,要記得先還原狀態,否則會影響到其它的組件狀態。

今天就先這樣了,我們總算是把一個全端開發的流程建立起來了,之後的每個功能,大多都是像這樣,在不同的檔案間來回穿梭,看起來好像很簡單,但開發專案難的不是在於這些套件或框架的使用,而是專案本身的商業邏輯如何和這些套件及框架整合在一起。


上一篇
Day10 拆分前端的組件及頁面流程
下一篇
Day12 完善題庫設定功能-善用ORM
系列文
LV的全端開發體驗30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言