文章加密

;

2023年4月18日 星期二

vue3學習筆記

 快速學習課程: https://www.youtube.com/watch?v=rNQIA0Fe9KQ&list=PLbOfcOk7bN42Kzp1wQsoLuU0vPUmFBe-X



第一課:

vue-cli > create-vue

vscode擴充工具vetur > volar

vuex > pinia


vue3完全不支援ie


1. composation api寫法 > 推薦 (不用this去取得其他datas, methods)

2. options api 寫法 > 相對來說較像vue2,適合原vue2升vue3但有時程壓力時使用(要this)

不建議混合使用!



vue3底層是vite




第二課: 熟悉寫法

destory(){} > onUnmounted(()=>{})

將一個ref賦予到reactive的值裡,ref會自動被解包


<script setup>

import { computed } from "@vue/reactivity";

import {ref, onMounted, onUnmounted, watch} from "vue";

import { useRouter } from "vue-router";

const router = useRouter();

conat count = ref(1);

const name = ref("mike");  // ref()才會響應在畫面,其中可放任何型別的資料,不能用watch監控其children data的改變(除非加上深度監聽的參數)

const data = reactive({  // reactive也會響應在畫面,只能用object, array型別,能用watch監控其children data的改變

  name: David

})

const plusOne = computed({

  get:() =>{

     return count.value; // 取ref的值需要.value

  },

  set:(val)=>{ 

     count.value = val;

  },

})

console.log(plusOne.value) // 1

plusOne.value = 5;

console.log(plusOne.value) // 5

console.log(count.value) // 5 (computed裡改的ref也會一起改)

const changeName = () => {

  name.value = "Jason"

}


onMounted(()=>{

})

</script>


第三課: defneProps, defineEmits

parent component:

<script setup>  // setup語法糖讓我們不用寫export default 和 return

import Children from "./xxx";

import {ref } from "vue";

const resInt = ref(0)

const callBack = (res)=> {

  resInt.value = res

}

const callBack2 = (res)=> {

  console.log(res)

}

</script>


<vImg

  alt="Vue logo"

  className="logo"

  src="xxxx"

  width="125"

  height="125"

/>

<Children @AddInt="callback" @returnName="callback2" />


children component:


<script setup>

const props = defneProps(["alt", "className", "src", "width", "height"]);

const props2 = definedProps({

  alt: {

    type: String,

    default "Img alt"

  }

  obj: {

    type: Object,

    default: () => ({})  // 官方說定義object時會希望用函式回傳的方式定義一個乾淨的obj, 這個語法糖等同()=>({return ...})

  },

  arr: {

    type:Array,

    default: () => []

  },

  testFn: {

    type: Function, // 直接把func傳下來,one way data flow

    default: () => {}

  },

}

const emit = defineEmits(["AddInt"]);

const emit2 = defineEmits({

returnName:(payload) => {

if(payload === "mike") {

  return true;

} else {

  return false

}

}

});

const handleAddClick = (a, b) => {

  emit("AddInt", a + b);

  emit("returnName", "mike");

}


</script>

<template>

<img

  :alt="props.alt"

  :className="props.className"

  :src="props.src"

  :width="props.width"

  :height="props.height"

/>

<button @click="handleAddClick(3,4)" />

</template>



第四課:

listen to change event表示輸入框還在focus狀態時但有改變就還沒觸發change

listen to input event表示輸入框還在focus狀態時但有改變就已觸發input

v-model.lazy是change行為


v-model is used on customedComponent, customedComponent內部接收如下

<script setup>

defineProps(['modelValue']) // children component固定接收到的參數名

defineEmits(['update:modelValue'])

</script>


<template>

  <input

    :value="modelValue"

    @input="$emit('update:modelValue', $event.target.value)"

  />

</template>



第五課:composables

1.

// composables folder > useWindowPosition.js  (像以前的mixins)

import { ref, onMounted, onUnmounted } from 'vue'

export function useWindowPosition() { // 建議跟檔名相同名稱

  const x = ref(0)

  const y = ref(0)


  const movePosition = (event) => {

    x.value = event.pageX

    y.value = event.pageY

  })


  onMounted(() => {

window.addEventListener("mousemove", movePosition)

  })

  onUnmounted(() => {

window.removeEventListener("mousemove", movePosition)

  })


  return { x, y }

}



// 欲使用的vue檔裡如下寫入

import { useWindowPosition } from "./composables/useWindowPosition.js";

const {x, y} = useWindowPosition();

console.log(x, y)




2.

// composables folder > useSetData.js

import {ref, readonly} from "vue";

export function useSetData(val) {

  const x = ref(val);


  const setData = (a) => {

data.value = a;

  }


  return {

    data: readonly(data),

    setData  // 這種作法讓想修改data的一定要使用setData,規範較統一

  }

}



3. 此方式也可用來拆出fetch api



第六課:pinia

// store/counter.js (mutation沒有了)

import { defineStore } from 'pinia'

import { ref } from "vue

// 以下是類似composition api的寫法

export const useCounterStore = defineStore("counter", ()=>{

  const counter = ref(0);  // state


  const doubleCount = computed(() => { // getter

    return counter.value * 2;

  }


  const addCount = () => { // action

    counter.value++;

  }

  

  return {

counter, doubleCount, addCount

  }

})


// 以下是類似options api的寫法,相對來說不推

export const useCounterStore = defineStore({

  id:"counter", // 每個store都需要給id

  state: () => ({

counter: 0

  }),

  getters: {

doubleCount: (state) => state.counter * 2

  },

  actions: {

    addCount() {

      this.counter++;

    },

  },

})


// App.vue

<script setup>

import { useCounterStore } from "./store/counter.js";

const store = useCounterStore();

const clickAdd = () => {

  store.addCount();

}

<script>

<template>

<h1>{{ store.counter }}</h1>

<button @click="clickAdd">click</button>

</template>



或是想解構寫的簡潔則如下

<script setup>

import { storeToRefs } from "pinia";

import { useCounterStore } from "./store/counter.js";

const store = useCounterStore();

const { addCount } = store; // 解構,不能用在資料如state, getter

const { counter, doubleCount } = storeToRefs(store); // 用在資料如state, getter

const clickAdd = () => {

  addCount();

}


watch(doubleCount, (newVal, oldVal) => { // 如果要深度watch要加上deep

console.log(newVal);

console.log(oldVal);

})

store.$subscribe((mutation, state) => { // .$subscribe直接就是深度watch

  // import { MutationType } from 'pinia'

  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()

})

<script>

<template>

<h1>{{ counter }}</h1>

<button @click="clickAdd">click</button>

</template>


P.S.可以在./store/counter.js import另一個store去做處理



第七課

naive ui


第八課:router

import { createWebHistory, createRouter } from "vue-router";

import HomePage "@pages/HomePage.vue";


Vue.use(VueRouter) 

const router = createRouter({

  history: createWebHistory(), // 需要主機配置https://router.vuejs.org/zh/guide/essentials/history-mode.html#nginx,否則丟上主機router.push會壞掉。如果主機無法配合,可用createWebHashHistory(),但網址上會有#。

  routes: [

    {

        path: '/', 

        component: HomePage,

        name: 'HomePage',

    },

    {

        path: '/about', 

        component: () => import("@pages/HomePage.vue"), // 直接用函式載入,是動態的載入方式,只有在要切換時才會載入,適合用在靜態檔等確定載入時間不長的頁面

        name: 'About',

    },

    {

        path: '/:id', // 記得這個放最後面

        component: () => import("../views/[id].vue"), // [id].vue 是一種動態網址的命名方式

        name: 'userData',

    },

    {

        path: '/:domain(.*)*', // 記得這個放最後面

        component: import("../views/404.vue"),

        name: 'NotFound',

    },

})


// App.vue

<script setup>

import { RouterLink, RouterView, useRouter } from "vue-router";


const router = useRouter();  // route是參數,router是動作

setTimeout(()=>{

router.push("/about");

},3000)

</script>


<template>

  <RouterLink to="/">Home</RouterLink>

  <RouterView>

</template>


第九課

nuxt 3 前導


1.

在App.vue加入<NuxtPage />就可以把在pages folder下的 有其相對route設定,如下

index.vue 對應 /

about.vue 對應 /about

[id].vue 對應 /:id 


2.

const route = useRoute(); 即可,不用import route相關的東西,nuxt3自動引入


P.S. ref也自動引入,不用import

P.S. component也自動引入,不用import

P.S. composables也自動引入,不用import

P.S. 應該都可自動引入,有疑問再查文件


3.

npx nuxi add component TheHeader  // 用指令創建components folder 及其下TheHeader.vue


4. 

asyncData => useAsyncData // 只能在page裡用

axios => ohmyfetch (axios在server端好像不能用了)


// index.vue

<script setup>

const {data} = await useAsyncData("idName".() => { // 不用async了

$fetch("api url") // nuxt3整合了ohmyfetch, https://www.npmjs.com/package/ohmyfetch

})




參考網站:

https://vuejs.org/

https://pinia.vuejs.org/

https://router.vuejs.org/

https://nuxt.com/