Try   HackMD

VueJS 3.0 教學筆記: 從2.0銜接到3.0

綱要

前言

Vue3.0在去年2020年9月的時候正式發布,但這次的大改版是向下兼容的,Vue.js 開發團隊在盡量不變動 API 的前提下,對 Vue.js 進行了底層核心的重構,針對過去2.0中幾個明顯缺點進行了重大的改進,並在此基礎上添加了一些更好用的新功能。

所以你同樣可以在Vue3.0中寫2.0的語法,初次接觸的人也不必煩惱「現在應該學習 Vue 2 或者 Vue 3」,直接從3.0起手是沒有問題的!

生命週期的更新與擴充

Lifecycle Hooks的變動示意圖:

import { ref, reactive, onMounted // 生命週期改以import引入的方式調用 } export default { name: 'Home', components: {}, setup() { ... return { ... } } }

Router的設置

原則上和2.x版的差別不大,但使用了createWebHashHistory取代以往的mode: hash

import { createRouter, createWebHashHistory } from 'vue-router' const routes = [ { path: '/', name: 'Home', component: () => import('../views/Home.vue') } ] const router = createRouter({ history: createWebHashHistory(), // 2.x版這裡的設置是mode: hash routes }) export default router

設置v-model與methods、computed的方式

import { ref, reactive, computed, onMounted } from 'vue' export default { name: 'Home', components: {}, setup() { // v-models const sampleText = ref('Home Component.') let anotherText = reactive('Another Component.') const newObj = reactive({ name: 'Ann', age: 20 }) // methods const checkResult = onMounted(() => { console.log(sampleText, anotherText) }) const changeText = () => { sampleText.value = 'changed text...0' newObj.name = 'Kelu' anotherText = 'change reactive' } // computed const checkText = computed(() => { return {0: sampleText.value} }) return { newObj, sampleText, checkResult, anotherText, checkText, changeText } } }

ref與reactive

由上頭的範例22行中我們可以看到,定義v-model時,ref 回傳的會是一個reference。
也就是說你必須指定底下的value才取到正確的值。

reactive 卻是完完整整的將定義後的值顯示出來,乍看下好像 reactive 比較方便,不必寫成refVariable.value才能取值,但實際上這兩種方法有各自不同的適用狀況。

假如你有一個 Watch 偵聽事件,可以試試看這個例子:

import { ref, reactive, onMounted, watch } export default { setup() { const refVal = ref({num: 0}) const reactiveVal = reactive({num: 0}) watch(refVal, (val)=>{ console.log("is ref:", val) }) watch(reactiveVal, (val)=>{ console.log("is reactive:", val) }) // 2秒後同時更新兩者物件內的值試試看 setTimeout(() => { refVal.value.num = Date.parse(new Date); reactiveVal.num = Date.parse(new Date); }, 2000); return { refVal, reactiveVal } } }

最後的結果應該只會看到reactive的log:

ref 不會對Object或Array內部的屬性變動做偵聽,而 reactive 正好相反

Provide & Inject

父組件用來定義給予子組件資料的方式不只是props。可以在父組件上設置一組Provide,於子組件中使用Inject將資料取回。

以往使用prop傳遞時,假如子組件中又有其他的子組件時,這個prop資料就需要再被中間的所有組件重新傳遞一次,而Inject則能簡化這件事。

我們用一個透過取得的json內容設為provide,更改子組件Inject後的例子來說明用法。

完整範例:https://github.com/fortes1219/2021_vue3

製作一個命名為config.json的JSON檔案放置於public下,稍後用異步處理Fetch內容,大概是長這樣:

public/config.json

{ "name": "Provide", "child": "Inject", "baseUrl": "localhost:8080/Provide" }

接下來需要import父組件中使用的provide

views/Provide.vue

<template> <div class="page" data-inset="1rem"> <div class="row horizontal v_center"> <el-button type="primary" @click="reRender">Re Render</el-button> </div> <childComponent :text="oldText" /> </div> </template>
import { ref, reactive, computed, onMounted, provide } from 'vue' import childComponent from '@/components/Child_inject.vue' export default { components: { childComponent }, setup() { // v-models let config = reactive({}) let oldText = ref('old text') const newObj = reactive({}) // methods // fetch剛才設置的json檔案內容,讓config這個變數成為抓取到的物件 const getConfig = onMounted(async () => { await fetch('http://localhost:8080/config.json').then(res => res.json()).then(res => { config = {...res} }) console.log('###root: ', config) }) // 按下button後覆寫子組件prop,也就是:text="oldText"的部分 const reRender = () => { console.log(config.baseUrl) oldText.value = config.baseUrl // newObj更新為CONFIG的內容 newObj.name = config.name newObj.child = config.child newObj.baseUrl = config.baseUrl } // 設置兩組provide,rootObj傳出newObj給Inject、rootText則傳出oldText provide('rootObj', newObj) provide('rootText', oldText) // 注意,並不需要把provide定義的內容從setup中return return { config, oldText, getConfig, newObj, reRender } } }

再新增第一個子組件內容,引入layer2:

Child_inject.vue

<template> <div class="row vertical" data-inset="1rem"> <el-button @click="check">check child data</el-button> <div>{{ 'layer 1: ' + text }}</div> </div> <!-- layer2裡面還會包覆一層layer3,事件流也完全一樣 --> <layer2 :text="obj.name" /> </template>
<script> import { inject, reactive, computed, onMounted, compile } from 'vue' // 引入layer2並註冊組件 import layer2 from '@/components/Child_inject_layer2.vue' export default { name: 'ChildInject', props: { text: String }, components: { layer2 }, setup() { // 把父組件的provide內容透過inject直接引用進來 const rootObj = inject('rootObj') const rootText = inject('rootText') // 使用computed偵聽rootObj的變化 const obj = computed(() => { return rootObj }) const defaultText = computed(() => { return rootText }) // 測試是否有順利抓取到json的按鈕事件 const check = () => { console.log('### child check: ', obj.value.name, rootObj.name) } return { obj, defaultText, check } } } </script>

Child_inject_layer2.vue

<template> <div class="row vertical" data-inset="1rem"> <div>{{ 'layer 2: ' + text }}</div> <div>{{ 'layer 2 URL: ' + obj.baseUrl }}</div> </div> <!-- 引入第三層子組件 --> <layer3 :text="obj.child" /> </template>
<script> import { reactive, computed, inject } from 'vue' // 第三層子組件引入後註冊 import layer3 from '@/components/Child_inject_layer3.vue' export default { name: 'ChildInjectLayer2', props: { text: String, }, components: { layer3 }, setup() { // 同樣從父組件inject我們要的資料 const rootObj = inject('rootObj') const rootText = inject('rootText') const obj = computed(() => { return rootObj }) const defaultText = computed(() => { return rootText }) return { obj, defaultText, } } } </script>

Child_inject_layer3.vue

<template> <div class="row vertical" data-inset="1rem"> <div>{{ 'layer 3: ' + text }}</div> <div>{{ 'layer 3 URL: ' + obj.baseUrl }}</div> </div> </template>
<script> <script> import { reactive, computed, inject } from 'vue' export default { name: 'ChildInjectLayer3', props: { text: String }, setup() { // 同樣從父組件inject我們要的資料 const rootObj = inject('rootObj') const rootText = inject('rootText') const obj = computed(() => { return rootObj }) const defaultText = computed(() => { return rootText }) return { obj, defaultText } } } </script>

切換到provide的路由應該會看到這個畫面,第一層的子組件預設文字是old text
第二層後的子組件2和子組件3預設沒有任何參數,會是undefined

按下Re Render按鈕後就會看到父組件的provide資料一路繼承給不同階層的後代組件去:

Vuex Next (4.0)

(待補)

tags: VueJS Vue3