--- title: 'VueJS 3.0 教學筆記: 從2.0銜接到3.0' disqus: hackmd --- VueJS 3.0 教學筆記: 從2.0銜接到3.0 === 綱要 [TOC] 前言 --- Vue3.0在去年2020年9月的時候正式發布,但這次的大改版是向下兼容的,Vue.js 開發團隊在盡量不變動 API 的前提下,對 Vue.js 進行了底層核心的重構,針對過去2.0中幾個明顯缺點進行了重大的改進,並在此基礎上添加了一些更好用的新功能。 所以你同樣可以在Vue3.0中寫2.0的語法,初次接觸的人也不必煩惱「現在應該學習 Vue 2 或者 Vue 3」,直接從3.0起手是沒有問題的! 生命週期的更新與擴充 --- **Lifecycle Hooks的變動示意圖:** ![](https://i.imgur.com/HhffEFE.jpg) ```javascript= import { ref, reactive, onMounted // 生命週期改以import引入的方式調用 } export default { name: 'Home', components: {}, setup() { ... return { ... } } } ``` Router的設置 --- 原則上和2.x版的差別不大,但使用了`createWebHashHistory`取代以往的`mode: hash` ```javascript= 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的方式 --- ```javascript= 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** --- ![](https://i.imgur.com/92mMp2h.png) 由上頭的範例22行中我們可以看到,定義v-model時,**ref** 回傳的會是一個reference。 也就是說你必須指定底下的`value`才取到正確的值。 但 **reactive** 卻是完完整整的將定義後的值顯示出來,乍看下好像 **reactive** 比較方便,不必寫成`refVariable.value`才能取值,但實際上這兩種方法有各自不同的適用狀況。 假如你有一個 **Watch** 偵聽事件,可以試試看這個例子: ```javascript= 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** 正好相反== ![](https://i.imgur.com/6PT0mEH.png) **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** ```json= { "name": "Provide", "child": "Inject", "baseUrl": "localhost:8080/Provide" } ``` 接下來需要import父組件中使用的provide **views/Provide.vue** ```htmlembedded= <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> ``` ```javascript= 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** ```htmlembedded= <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> ``` ```javascript= <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** ```htmlembedded= <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> ``` ```javascript= <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** ```htmlembedded= <template> <div class="row vertical" data-inset="1rem"> <div>{{ 'layer 3: ' + text }}</div> <div>{{ 'layer 3 URL: ' + obj.baseUrl }}</div> </div> </template> ``` ```javascript= <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 ![](https://i.imgur.com/8xgbjzT.png) 按下Re Render按鈕後就會看到父組件的provide資料一路繼承給不同階層的後代組件去: ![](https://i.imgur.com/oYD0ABf.png) Vuex Next (4.0) --- (待補) ###### tags: `VueJS` `Vue3`