---
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的變動示意圖:**

```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**
---

由上頭的範例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** 正好相反==

**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

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

Vuex Next (4.0)
---
(待補)
###### tags: `VueJS` `Vue3`