---
# System prepended metadata

title: 'VueJS 3.0 教學筆記: 從2.0銜接到3.0'
tags: [Vue3, VueJS]

---

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