---
# System prepended metadata

title: vue3 basic
tags: [vue筆記]

---

# vue3 basic
{%hackmd BJrTq20hE %}
###### tags: `vue筆記`
- 載入法一：
    ```jsx=
    <script src="https://unpkg.com/vue@3/dist/vue.global.js" integrity="sha384-8CdW77YPqMZ3v22pThUIR22Qp1FB5oisZG2WE3OpE0l1fTHAIsdIwjQZFf/rmQ/B" crossorigin="anonymous"></script>
    ```
    起手式：
    ```jsx=
    <div id="app">...</div>

    <script>
    const app = {
        data () {
            return {
                text: 0,
            }
        }
    }

    Vue.createApp(app).mount('#app')
    </script>
    ```
    or
    ```jsx=
    <script>
    const { createApp, ref } = Vue;

    const App = {
        setup() {
            const message = ref("<data:blog.title/>");
            return {
                message,
            };
        },
    };

    const app = createApp(App);
    app.mount("#app")

    app.component("home", {
        template: `<teleport to='title'>小明</teleport> //傳送到class對應位置
        `,
    });
    </script>
    ```
- 載入法二：直接寫在 js 裡面 [link](https://vuejs.org/guide/quick-start.html#using-the-es-module-build)
    ```javascript=
    import { createApp, ref } from 'https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.9/vue.esm-browser.js';
    ```
- CDN 版本解釋：[link](https://juejin.cn/post/7043991342166310942)、[各版本下載](https://unpkg.com/vue@3/dist/)
	- prd：是压缩版，体积小    
	- runtime版：可以理解为 **阉割版，体积小，不带动态编译模板的能力**
        - **需要编译模板的能力的话，比如vue.extend，那么要用不带runtime的完整版，体积会稍微大一些**

- HOOK：
![](https://i.imgur.com/ZUQUTvU.png)
```javascript=
<script type="module" src="js/products.js"></script> 
//每一個type="module"的作用域都是獨立的，無法讀取其他<script>區域，使用import元件時必加，因为Module 的加载实现的是es6语法，所以在浏览器加载html文件时，需要在script 标签中加入type="module"属性。
new Date().getTime(); //取得不重複timestamp
```
## css深度选择器：[link](https://juejin.cn/post/6978781674070884366)
- deep四種寫法：
    ```scss=
    // 写法1 使用 ::v-deep /* sass 能用 */
    <style lang="scss" scoped>
      ::v-deep .ant-card-head-title{
        background: yellowgreen;
      }
    </style>

    // 写法2 使用 >>> 操作符 /* sass 不能用 */
    <style scoped>
    >>>.ant-card-head-title{
      background: yellowgreen;
    }
    </style>

    // 写法3 使用 /deep/
    <style scoped>
     /deep/.ant-card-head-title{
      background: yellowgreen;
    }
    </style>

    // 写法4 使用 :deep(<inner-selector>)
    <style lang="scss" scoped>
      :deep(.ant-card-head-title){
        background: yellowgreen;
      }
    </style>
    ```
    PS：使用寫法四兼容性較好
    其他：
    ```scss=
    /* 可以用在 slot 裡面的內容 */
    :slotted(.slot-class) {
        background-color: #000;
    }

    /* 設置為全域 css */
    :global(.my-class) {
        background-color: #000;
    }
    ```
- [看更多應用](https://zh-hk.vuejs.org/api/sfc-css-features)
## v-model 表單元件綁定的方法：
data()：存放資料

- `v-model`：資料雙向綁定，綁定input
    ```javascript=  
    //Vue 3 前 v-model 是以下的簡寫
    <MyInput :value="name" @input="newValue => this.name = name" />
    
    //Vue 3 後 v-model 是以下的簡寫
    <MyInput :modelValue="name" @update:modelValue="newValue => this.name = name" />
    ```
    - checkbox單選框(法一，綁定data為bool)：預設值為true、false，可以用三元運算子來放文字
        ```htmlembedded=
        {{ checkAnswer ? '吃飽了' : '還沒'}}
        <input
            type="checkbox"
            class="form-check-input"
            id="check1"
            v-model="checkAnswer"
        />

        checkAnswer: false
        ```
    - checkbox單選框(法二)：
        ```javascript=
        {{ selectAnswer2 }}
        <input
            type="checkbox"
            class="form-check-input"
            id="check2"
            v-model="checkAnswer2"
            true-value="吃飽了" //"吃飽了"
            false-value="還沒"
        />

        checkAnswer2: "" //單選格式會用字串
        ```
        例二：

        有时候我们希望切换checkbox时，让它绑定的值不是默认的false和true

        而是我们自己期望的一些值，比如0和1

        只要在checkbox上用v-bind绑定相应的变量就行了
        ```htmlembedded=
        <input
            class="form-check-input"
            type="checkbox"
            v-model="tempProduct.is_enabled"
            :true-value="1" //1，假設沒有v-bind綁定，就會變成字串"1"
            :false-value="0"
            id="is_enabled"
        />

        <label class="form-check-label" for="is_enabled">
            是否啟用
        </label>
        ```
    - checkbox複選框：須代上value
        ```htmlembedded=
        {{ checkAnswer3.join(' ') }} //join是把存到data的陣列轉換成字串
        <input
            type="checkbox"
            class="form-check-input"
            id="check3"
            v-model="checkAnswer3"
            value="蛋餅"
        />…*3

        checkAnswer3: [] //複選格式會用陣列來儲存多筆資料
        ```
    - radio 單選框：
        ```htmlembedded=
        {{ radioAnswer }}
        <input
            type="radio"
            class="form-check-input"
            id="radio1"
            value="蛋餅"
            v-model="radioAnswer"
        />

        radioAnswer: "蛋餅" //此蛋餅為radio的預設值，不需要可以留空
        ```
    - select 單選：
        ```htmlembedded=
        {{ selectAnswer }}

        <select class="form-select" v-model="selectAnswer">
            <option value="">說吧，你要吃什麼？</option> //如果要預設值需加入空value
            <option
                v-for="item in products"
                :key="item.name"
                :value="item.name"
            >{{item.name}} / {{item.price}} 元
            </option>
        </select>

        selectAnswer: ""
        ```
    - select 多選：
        ```htmlembedded=
        {{ selectAnswer2 }}

        <select class="form-select" multiple v-model="selectAnswer2">
            <option selected disabled value="">說吧，你要吃什麼？</option>
            <option
                :value="item.name"
                v-for="item in products"
                :key="item.name"
            >
            {{item.name}} / {{item.price}} 元</option
            >
        </select>

        selectAnswer2: []
        ```
- `v-model-modifiers`(修飾符)：除了資料綁定外的小方法，在model後面加.
    - 延遲 Lazy：
        ```htmlembedded=
        {{ lazyMsg }}
        <input type="text" class="form-control" v-model.lazy="lazyMsg">
        ```
    - 純數值 Number：前面type要確定有加上number
        將輸入的字串自動轉型成數字
        ```htmlembedded=
        {{ numberMsg }}{{ typeof numberMsg }}
        <input type="number" class="form-control" v-model.number="numberMsg" />
        ```
    - 修剪 Trim：
        ```htmlembedded=
        這是一段{{ trimMsg }}緊黏的文字
        <input type="text" class="form-control" v-model.trim="trimMsg" />
        ```
    - v-model [自定義修飾符](https://cn.vuejs.org/guide/components/v-model.html#handling-v-model-modifiers)
    ```jsx=
    <MyCounter v-model.number.alex="count" />

    <script setup>
    const props = defineProps({
                modalValue: Number,
                modelModifiers: {
                  default: () => ({})
                }
            })
    const emit = defineEmits(['update:modelValue'])

    // 使用computed來簡化在templete的設置 (參考官方範例)
    const value = computed({
            get(){
                return props.modelValue
            },
            set(value){
                // 如果有設定.alex修飾符的話，value*=10
                if(props.modelModifiers.alex) value *= 10
                emit('update:modelValue', value)
            }
        })
    </script>

    <template>
        {{ props.modelModifiers }} // 有設定屬性就會給true：{ alex: true }
      <input v-model="value" />
      <button @click="value++">ADD</button>
    </template>
    ```
### 多個 v-model [link](https://cloud.tencent.com/developer/article/1741250)
- 在 vue3 中可以给v-model的属性起名字，并且你可以拥有任意数量的v-model。
    ```jsx=
    <InviteeForm
      v-model:name="inviteeName"
      v-model:email="inviteeEmail"
    />
    ```
- `v-focus`：自動聚焦
    - 全域註冊：
        ```javascript=
        const app = Vue.createApp({})
        // 注册一个全局自定义指令 `v-focus`
        app.directive('focus', {
          // 当被绑定的元素挂载到 DOM 中时……
          mounted(el) {
            // 聚焦元素
            el.focus()
          }
        })
        ```
    - 局部註冊：
        ```javascript=
        const vFocus = {
            mounted(el) {
                el.focus()
            }
        }
        ```
    - 使用：
        ```htmlembedded=
        <input v-focus />
        ```
- `v-once`：單次綁定，只會初次渲染，之後編輯資料時不會立即變動
- `v-pre`：暫停轉譯花括弧，會原始呈現`{{ name }}`在`{{ position }}`吃早餐
    ```jsx=
    <v-pre>{{ message }}</v-pre>
    ```
    跳过这个元素和它的子元素的编译过程可以加快编譯
    ![](https://img-blog.csdnimg.cn/20200731153955110.png)
- `pre`：能夠優質顯示json在網頁上，而不會擠成一坨
    ```jsx=
    <pre>{{ jsonData }}</pre>

    const jsonData = {
        "key": "名字",
        "value": "张三丰"
    }
    ```
    - 未使用 ![](https://img-blog.csdnimg.cn/20200731154435370.png)
    - 使用 ![](https://img-blog.csdnimg.cn/20200731154609655.png)
## methods()：function方法

- `v-on:click="funName"`：觸發事件到method(不用())
    - 帶入參數：
        ```htmlembedded=
        <button class="btn btn-outline-primary" v-on:click="change('123')">選轉物件</button>
        ```
    - form submit 事件：綁在form可以觸發submit
        ```htmlembedded=
        <form @submit.prevent="submitForm">
            <input type="text" v-model="name" /> //輸入完按enter即可觸發submit
            <button>送出表單</button>
        </form>
        ```
    - 動態事件 []：(少用)
        ```htmlembedded=
        <input type="text" @[event]="dynamicEvent" />
        <input type="text" v-model="event" />

        data() {
            return {
                event: "click",
            };
        },
        method：{
            dynamicEvent() {
            console.log("這是一個動態事件", this.event);
        }}
        ```
    - 動態物件方法 {}：在一個物件上面加入多個事件，但注意：此方法無法傳入參數
        ```htmlembedded=
        <button class="box" @="{mousedown: down, mouseup: up}"></button>

        method：{
            down() { console.log("按下");},
            up() { console.log("放開");}
        }
        ```
    - 新特性：多事件監聽者
        ```javascript=
        <button @click="one($event), two($event)">
          Submit
        </button>
        ```
- v-on修飾符 [link](https://zh-hk.vuejs.org/guide/essentials/event-handling.html#key-modifiers)
    - 按鍵修飾符：keyAlias - 只當事件是從特定鍵觸發時才觸發。
        - 別名修飾：
            ```htmlembedded=
            <input type="text" class="form-control" v-model="text" @keyup.enter="trigger('enter')">
            ```
        - 相應(組合)按鍵時才觸發的監聽器：
            ```htmlembedded=
            <input type="text" class="form-control" v-model="text" @keyup.shift.enter="trigger('shift + Enter')">
            ```
        - 特定鍵：
            ```htmlembedded=
            <input type="text" class="form-control" v-model="text" @keyup.h="trigger('h')">
            ```
    建立 keyboard 事件觸發器：
    ```jsx=
    function trigger(keyEvent) {
        // 創建一個新的 KeyboardEvent
        const event = new KeyboardEvent('keyup', {
            key: keyEvent,
            bubbles: true, // 事件是否冒泡
            cancelable: true // 事件是否可以取消
        });
        window.dispatchEvent(event);
    }
    ```
    - 常用：
		- `.enter`
		- `.tab`
		- `.delete` (captures both "Delete" and "Backspace" keys)
		- `.esc`
		- `.space`
		- `.up`
		- `.down`
		- `.left`
		- `.right`

    - 滑鼠修飾符：
        `.left` 只當點擊鼠標左鍵時觸發。
        `.right` 只當點擊鼠標右鍵時觸發。
        `.middle` 只當點擊鼠標中鍵時觸發。
        ```htmlembedded=
        <span class="box" @click.right="trigger('right button')"> </span>
        ```
- 事件修飾符：
    `.stop` - 調用 event.stopPropagation()。   //阻止事件傳導( 冒泡 )
    `.prevent` - 調用 event.preventDefault()。   //常用於超連結，阻止預設行為
    上面兩者的差異：先用prevent阻止預設行為，如果外層還有行為則可再加上stop阻止傳遞 [https://dotblogs.com.tw/harry/2016/09/10/131956](https://dotblogs.com.tw/harry/2016/09/10/131956)
    `.capture` - 添加事件偵聽器時使用 capture 模式。改變事件為捕獲方向，變成由外而內
     -  ```jsx=
        // 點擊clicktwo會先觸發one後才觸發twoz
        <div id="app">
            <div id="div1" @click.capture="handleOnClickOne">
                <div id="div2" @click="handleOnClickTwo">
                </div>
            </div>
        </div>
        ```
    `.self` - 只當事件是從偵聽器綁定的元素本身觸發時才觸發回調。
    `.once` - 只觸發一次回調。
## 渲染方法：
- `{{變數}}`：Mustache，效果等同於在屬性加上 `v-text="變數"`

    進階技巧：表達式(在花括弧裡使用語法)
    - 樣板字面值：{{`${name}在${position}吃早餐`}} //使用樣板語法就可以只用一個花括弧
    - 反轉字串：`{{text.split('').reverse().join('') }}`
    - 綁定methods：`{{ say('杰倫') }}` //可以帶入函式
    - JS運算：`{{ 1 + 1 }} or {{ a + b }}`
- v-for說明：有相同父元素的子元素必須有獨特的 key。重複的 key 會造成渲染錯誤。
    `v-for="item in 陣列or物件"`   //v-for一定要帶入key值，否則子元素如果沒變就不會重新渲染
    兩個v-for：
    ```htmlembedded=
    <h3>年紀大於 25 歲的同事</h3>
    <ul>
        <template v-for="all in collegueList" :key="all.name"> //取出多個物件
            <li v-if="all.age >= 25">
                <p v-for="(value, key) in all">屬性: {{key}}，值: {{value}}</p> //取出物件裡的多個值
            </li>
        </template>
    </ul>
    ```
    `:key="item.id"`：唯一值
    ```htmlembedded=
    <ul>
        <li v-for="(item, key) in products" v-bind:key="item.name">
            {{ key }} - {{ item.name}} / {{ item.price }} 元
            <input type="text">
        </li>
    </ul>
    ```
    進階技巧：
    在 template 標籤使用 v-for (用template包住要重複的區塊(如有複數的node要重複，假設有兩個li要迴圈就是代表兩個節點))
- v-show：把DOM隱藏或不隱藏起來(display: none)
    ```htmlembedded=
    <p v-show="true">小明 飽了</p>
    ```
- v-if：把DOM用判斷式隱藏或不隱藏(visibility: hidden)-較常用
    ```htmlembedded=
    <div v-if="link === '小明'">小明吃早餐</div>
    <div v-else-if="link === '小美'">小美去百貨公司</div>
    <div v-else="link === '杰倫'">杰倫去幫助人</div>
    ```
    如果要保留v-if的生命週期，可以用`<keep-alive></keep-alive>`包住
    注意事項：v-for 與 v-if [不要](https://vue3js.cn/docs/zh/guide/conditional.html#v-if-%E4%B8%8E-v-for-%E4%B8%80%E8%B5%B7%E4%BD%BF%E7%94%A8)寫在同一個標籤，如需要混用，請搭配`<template>`標籤
- v-bind：綁定元素屬性
切換class的四種寫法：
    1. 物件寫法：
        如果要為屬性加上判斷，須把值轉成物件{key : value}，其中因為key不支援符號，所以須當成 '字串' 處裡
        ```htmlembedded=
        v-bind:class="table-success"
        v-bind:class="{'className':判斷式}"
        v-bind:class="{'table-success':true}"
        v-bind:class="{'table-success':true, rotate: true}" //多個屬性可用陣列方式插入
        v-bind:class="{'table-success rotate': true}" //同層級可以合併控制的寫法
        ```
        不一定只能用在class，切換屬性也可使用。例如：
        ```htmlembedded=
        <input type="text" :disabled="disabled" :placeholder="placeholder" v-model="value">
        //or
        <audio id="audio-don" src="/music/don.mp3" :muted="!ringbellStatus"></audio>
        ```
    2. 物件寫法2：如上，但是直接寫好在data再整個帶入
        ```htmlmixed=
        <div class="box" :class='objectClass'></div>
        data() {
            return {
            objectClass:{
                rotate: true,
                'bg-danger': false
            }
        ```
    3. 陣列寫法：(不需要判斷的寫法)
        ```htmlmixed=
        <button class="btn" :class="['btn-primary','disabled']">請操作本元件</button>
        ```
    4. 綁定行內樣式：一樣為{key:value}，key為style的屬性，value為style相對應的值，這邊的key可為字串or小駝峰的形式
        ```htmlmixed=
        <div class="box" :style="{backgroundColor:'red'}"></div>
        <div class="box" :style="styleObject"></div>
        <div class="box" :style="[styleObject, styleObject2]"></div>   //此為[{},{}]形式
        data() {
            return {
            styleObject: {
                backgroundColor: 'red',
                borderWidth: '5px'
            },
            styleObject2: {
                boxShadow: '3px 3px 5px rgba(0, 0, 0, 0.16)'
            }
        ```
- 動態屬性綁定：使用[]包入變數
    ```htmlembedded=
    <button type="button" v-on:click="dynamic = dynamic === 'disabled' ? 'readonly':'disabled'">切換為 {{ dynamic }}</button>
    <input type="text" :[dynamic] :value="name">

    v-bind:title="breakfastShop.name" //放在圖片標籤裡有替代文字效果
    ```
- 動態本地圖片:src https://codertw.com/%E5%89%8D%E7%AB%AF%E9%96%8B%E7%99%BC/232846/
    1. 如果直接給img的src繫結一個字串
        ```htmlembedded=
        <img :src=nowIcon />
        ```
        ```javascript=
         data () {
            return {
              nowIcon: ''
            }
          },
        this.nowIcon = '../assets/64/' + 圖片名 + '.png'
        ```
        vue會將這個路徑當成字串，不會給這個圖片路徑編譯，圖片顯示不出來
        此時的路徑是未經過編譯的
    2. 解決辦法(將圖片作為模組載入)
        ```jsx=
        // 寫法一：
        <img :src="avatar" />
        import avatar from '@/assets/logo.png'

        // 寫法二：
        <img :src=require('@/assets/logo.png') />
        ```
        ```javascript=
        this.nowIcon = '../assets/64/' + 圖片名 + '.png'
        改為
        this.nowIcon = require('../assets/64/' + 圖片名 + '.png')
        ```
        此時的程式碼是正常編譯後的路徑，圖片正常顯示
        ```htmlembedded=
        <img src="/img/101.ce5f2cfc.png">
        ```
- 動態元件管理( 切換模組 )：https://book.vue.tw/CH2/2-3-async-dynamic-components.html
    `v-bind:is`

- `v-html`="含html語法資料" //盡量避免用，以免XSS攻擊
    ```jsx=
    <v-card v-html="htmlContent" class="pa-3"></v-card>
    ```
- vue語法糖簡寫
    ```jsx=
    // vue綁定簡寫：
    @：v-on
    ":"：v-bind
    #：v-slot
    ```
- Computed：主要是處理完資料回傳到畫面上 https://hackmd.io/v5US5x1zQtK4BC_BeiBzDw?view by Alysa Chan
使用時機：如果你的某個變數是依賴其他變數⽽來，這時候就適合使用computed。如果該目標變數不變，則不會執行。

1. 法一：將data裡的資料，運算處裡過後再渲染出來(無法主動呼叫，所以不可把參數帶入computed)，例如總計or搜尋
    ```javascript=
    {{total}} {{filterProducts}}

    computed: {
        total(){
            let total = 0;
            this.carts.forEach( item => { //當carts有變化會自動觸發
            total += item.price;
            });
            return total;
        },
        filterProducts(){
            reutrn this.products.filter( item => {
                console.log(item);
                return item.name.match(this.search); //回傳搜尋結果
            }
        }
    }
    ```
2. 法二：變成物件型式，使用getter、setter方法(defineProperty)(如果需要用v-model綁定computed資料的話)
    ```javascript=
    <button type="button" @click="total= num">更新</button>

    computed: {
        total:{
            get() {
                let total = 0;
                this.carts.forEach( item => {
                    total += item.price;
                });
                return this.sum || total;
            },
            set(val){
                this.sum = val;
            }
        },
    }
    ```
3. 法三：監聽
    - 監聽多個變數觸發事件
    - 會產生一個值
    ```javascript=
    computed: {
        result2() {
            retrun `嬤嬤買了 ${this.productName}，共花費 ${this.productPrice}元，
            另外這 ${this.productVegan ? "是" : "不是"} 素食的` 
            //此範例同時監聽了三個變數${}
        }
    }
    ```
## watch：監聽，可以透過方法修改資料
- 監聽單一"變數"觸發事件
- 該函式可同時操作多個變數
```javascript=
<input type="text" id="name" v-model="tempName">

watch: {
    tempName(n, o) { //將要監聽的值當成函式的名稱，n為new(新的值)，o為old(舊的值)}
}
```
深層監聽(監聽多個變數)：如要監聽多個變數，該多筆資料須包在一個物件裡面
```javascript=
data() {
    product: {
        name: '蛋餅',
        price: 30,
        vegan: false,
    }
},
watch: {
    product: {
        handler(n, o) { //handler控制器
        this.result4 = `嬤嬤買了 ${this.product.name}，共花費 ${this.product.price}元，另外這 ${this.product.vegan ? "是" : "不是"} 素食的
        },
        deep: true,
    },
}
```
- computed與methods的分別：
    | **computed**| **methods**|
    |-----------------------|-----------------------|
    | 如果computed的響應式==依賴(監視的值)沒有改動，就不會觸發。==(無法呼叫，被動執行)| 每次觸發methods，methods裏的函式一定會執行(可以呼叫&主動執行) |
    | 有緩存資料的功能| 沒有緩存資料的功能|
    | 不可帶入參數| 可帶入參數|
    | 可在HTML裏直接使用該computed函式所回傳的值，因為computed函式本身是有get函式，最後一定會回傳一個值 | methods本身沒有規定一定要回傳一個值|
    
    關於最後一點，打開Vue dev tool看就知道，computed本身就會回傳一個值，但methods就不會。
        示範：[https://codepen.io/alysachan/pen/WNpgxeW?editors=1111](https://codepen.io/alysachan/pen/WNpgxeW?editors=1111)
    以上例子可見，如果要把computed得出的值寫在畫面上，我們可直接把該computed函式寫在畫面裏，即是折扣價` {{ computedPrice }}` 元。但methods就要加上括號折扣價 `{{ methodsPrice() }}` 元
- computed與watch的分別：
    | **computed**                           | **watch**               |
    |----------------------------------------|-------------------------|
    | 監聽資料變動產生資料，資料衍生（算出新值）           | 監聽資料變動產生行為，副作用處理（觸發行為）         |
    | 能監聽多個值的變動(只要該資料是在該computed函式裏和data屬性裏) | 只能監聽一個值                 |
    | 不能帶入參數                                 | 能帶入參數，第一個參數是新值，第二個參數是舊值 |
    - 有緩存的功能，只要該computed所綁定的值沒有改變，computed函式就不會執行
    - ~~不可對computed寫入值，因為computed只有getter屬性，沒有setter~~
    
    緩存資料的意思：
    computed本身有緩存資料的功能，如果緩存資料沒更動，該computed的函式就不會被執行。相反，methods沒有緩存功能，因此只要methods的函式被觸發，它就會執行。
    
    ++**computed緩存例子：**++
    當按按鈕後num變成1，即使你之後再按多少次按鈕，add都不會被執行
    [https://codepen.io/alysachan/pen/LYWmOLq](https://codepen.io/alysachan/pen/LYWmOLq)
    
    ++**methods沒有緩存的例子：**++
    即使num變成1，之後你再按按鈕，add都會執行
    [https://codepen.io/alysachan/pen/BaWxmYz](https://codepen.io/alysachan/pen/BaWxmYz)
    
    好處：**緩存資料**的好處就是省略執行不必要的計算，尤其是在處理大量資料時，緩存資料的功能++有助提高效能++

    ==methods、computed、watch使用時機：==
    1. methods: 這是需要主動觸發，且可以多次重複觸發
    2. Computed: 當資料需要複雜運算時
    3. Watch: 監控特定資料變化的 function 就放這裡

## Vue基本結構：
- option API風格：對於程式架構有分門別類
    ```javascript=
    <div id="app">
        {{ counter }} {{ text }} <br />
        <input type="text" v-model="text" />
        <button type="button" @click="clickMe(1)">按我</button>
    </div>

    <script>
    Vue.createApp({
        data() {
            return {
                counter: 0,
                text: "這裡有一段文字",
            };
        },
    }).mount("#app");

    //上面可改寫為：
    const App = {
        // 資料(函式)
        data() {
            return { //一律使用return
                counter: 0,
                text: '這裡有一段文字'
            }
        },
        // 方法(物件)
        methods: {
            clickMe(num) {
                this.counter = this.counter + num;
            }
        },
        // 生命週期(函式)
        created() {
            this.counter = 10;
        },
        computed: {},
        mounted(){},
    }

    Vue.createApp(App).mount('#app'); //.mount("")：綁定DOM
    </script>
    ```
- composition API：程式架構沒有順序之分，全部寫在裡面
- 元件：可以把一個頁面分割成多個組件，給多個工程師一起撰寫

元件使用的基本要點
- 元件需要在 createApp 後，mount 前進行定義
- 元件需指定一個名稱(元件名稱不可重複)
- 元件結構與最外層的根元件結構無異（除了增加 Template 的片段）
- 元件另有 prop, emits 等資料傳遞及事件傳遞
    props：父傳子
    emit：子傳父
## 單向數據流：
另外一點要注意 Props 是單向數據流，不能改變外部傳進來的值，所以當我們於內層元件要編輯傳進來的資料時，會提醒錯誤並不能更改，`[Vue warn]: Attempting to mutate prop "money". Props are readonly.` 。這時候可以在內層元件的 data 中新增一個新變數賦予外層傳進來的值，就可以利用新變數進行編輯摟～
```javascript=
app.component('editMoney', {
    props: ['money'],
    data(){
        return{
            newMoney:this.money,
        }
    },
    template: `
    <input type="text" v-model="newMoney"></input>
    <div>value: {{newMoney}}</div>
    `
});
```
### :star:技巧：前內、後外

## Props：使用屬性的方式傳入參數(PS.此參數只能讀不能寫，無法使用v-model方法)
```javascript=
const app = Vue.createApp({
    data() {
        return {
            imgUrl:
            "https://images.unsplash.com/photo-1605784401368-5af1d9d6c4dc?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=600&q=80",
            msgObj: ['也是', '可以', '傳遞陣列資料'],
        };
    },
});

app.component("photo", {
    props: ["url","arraymsg"], //屬性名稱命名
    //令src屬性等於url
    template: `<img :src="url" class="img-thumbnail" alt> 
    <p>請正確呈現訊息：<span v-for="item in arraymsg">{{ item }}</span></p>`, //可以傳送陣列
});

<photo :url="imgUrl" :arraymsg="msgObj"></photo> //標籤綁定屬性名稱為props，( 把data參數傳入元件 )
```
- :star: 命名限制：因為網頁屬性只能呈現小寫，所以屬性命名如果用小駝峰可以用另一種方式呈現
    ```htmlembedded=
    <photo3 :superUrl="imgUrl"></photo3>
    <!--改為-->
    <photo3 :super-url="imgUrl"></photo3>
    ```
- Props 型別驗證
    ```javascript=
    <props-type money="300"></props-type> //屬性沒綁定，值一律為字串
    <props-type :money="money"></props-type>

    app.component('props-type', {
        props: ['money'],
        template: `<div>value: {{money}}, typeof:{{ typeof money }}</div>`
    });
    ```
    驗證檢查：不會出錯，只會提示
    ```javascript=
    <props-validation
        :prop-a="fun"
        prop-c="required"
        :prop-f="10000"
    >
    </props-validation>

    app.component('props-validation', {
    props: {
        // 單一型別檢查，可接受的型別 String, Number, Object, Boolean, Function(在 Vue 中可使用 Function 驗證型別)
        // null, undefined 會直接通過驗證
        propA: Function,
        // fun sample
        callback: {
            type: Function,
            default: (item) => {
                return item;
            },
        },

        // 多個型別檢查
        propB: [String, Number],

        // 必要值
        propC: {
            type: String,
            required: true,
        },

        // 預設值
        propD: {
            type: Number,
            default: 300
        },

        // 自訂函式
        propE: {
            type: Object,
            default() {
                return {
                money: 300
                }
            }
        },

        // 自訂驗證
        propF: {
            validator(value) {
            return value > 1000
            }
        },
    },

    template: `{{propA}},{{propC}},{{propD}}<br>{{propE}},{{propF}}`
    })
    ```
### 將非同步資料傳入 Props：
- [如何將非同步資料傳入 Prop ?](https://old-oomusou.goodjack.tw/vue/async-prop/)
    解法重點：使用watch監視props，當props資料傳進來後再執行需要的操作
    ```jsx=
    watch(
        () => props.itemProperty,
        () => {
            getRule();
        }
    );
    ```
## Emit 觸發外部事件：從子元件傳送值or觸發事件，一律傳送至父元件函式
1. 先定義外層接收的方法(使用函式參數傳值)
2. 定義內層的 $emit 觸發方法
3. 使用 v-on 的方式觸發外層方法（口訣：前內、後外），在元件標籤裡撰寫
    1：父元件
    ```javascript=
    const app = Vue.createApp({
        data() {
            return {
                num: 0,
                text: "",
            };
        },
        methods: {
            getData(text) {
                console.log("getData");
                this.text = text;
            },
        },
    });
    ```
    2：子元件
    ```javascript=
    app.component("button-text", {
        data() {
            return {
                text: "內部資料",
            };
        },
        methods: {
            emit() {
                // this.$emit('屬性名稱', 要傳遞的參數);
                console.log("emit");
                this.$emit("emit-text", this.text);
            },
        },

        template: `<button type="button" @click="emit">emit data</button>`,
    });
    ```
    3：外層與內層間的橋梁
    ```htmlembedded=
    內部傳來的文字：{{ text }}<br />

    <button-text @emit-text="getData"></button-text>
    ```
    emit消除警告：如果子元件修改到原始資料，就會出現警告
    ```javascript=
    <script type="module">
        const app = Vue.createApp({
            data() {
                return {
                    num: 0,
                };
            },
            methods: {
                addNum(num) {
                    this.num = this.num + num;
                },
            },
        });

        app.component("button-counter", {
            data() {
                return {
                    num: 1,
                };
            },
            emits: ["add"], //有警告訊息建議加上emit選項，複數[","]
            // 出現黃色警告訊息
            // 主要會出現在該值是由 data 定義，但難以追蹤他的變化時會出現
            template: `
            <button type="button" @click="num++">調整 num 的值</button>
            <button type="button" @click="$emit('add', num)">add</button>`, //子元件送出事件add呼叫父層addNum方法，並帶入num值
        });

        app.mount("#app");
    <script>

    {{ num }}
    <button-counter @add="addNum"></button-counter>
    ```
    emit驗證資料：使用物件，可放多個驗證
    ```javascript=
    <h3>驗證資料內容</h3>
    <button-counter2 @add="addNum"></button-counter2>

    app.component("button-counter2", {
        emits: {
            add: (num) => {
                if (typeof num !== "number") {
                    console.warn("add事件參數型別須為number");
                }
                return typeof num !== "number";
            },
        },

        template: `<button type="button" @click="$emit('add', '1')">Emit 驗證是否為數值</button>`,
    });
    ```
## 元件基本結構：  // 注意，這段起手式與先前不同
- 全域註冊：直接掛載到app底下
    此 createApp 下，任何子元件都可運用，在中小型專案、一般頁面開發很方便
- 區域註冊：掛載在app的根元件裡面( `Vue.createApp()`裡面 )
    限制在特定元件下才可使用，在 Vue Cli 中很常使用此方法（便於管理）
- 模組化：可以把元件從外部匯進來

全域法一：
```javascript=
<alert></alert>
<script type="module">
    //區域元件建立：
    const alert3 = {
        data() {
            return {
                text: "區域元件3",
            };
        },
        template: `<div class="alert alert-primary" role="alert">{{ text }}</div>`,
    };

    const app = Vue.createApp({
        data() {
            return {
                text: "外部元件文字",
            };
        },
        components: { //區域註冊法一：(在根元件註冊)，結尾需加s
            alert3,
        },
    }).component("alert", { //(全域)註冊一個名叫alert的元件，對應到畫面的標籤名稱
        data() {
            return {
                text: "內部文字",
            };
        },
        template: `<div class="alert alert-primary" role="alert">{{ text }}</div>`,
    });

    app.mount("#app");
</script>
```
全域法二：註冊在app之後，amount之前
```javascript=
app.component("alert2", { //alert2為標籤名稱
    data() {
        return {
            text: "內部文字",
        };
    },
    template: `<div class="alert alert-primary" role="alert">{{ text }}</div>`,
    //template也可為: '#alert2'，
});
```
- 模組化(區域註冊法二)：
// in alert-component.js：
    - 外部匯出：
    ```javascript=
    export default {
        data() {
            return {
                text: '這是模組化元件'
            };
        },
        template: `<div class="alert alert-primary" role="alert">
            {{ text }}
        </div>`
    }
    ```
    - 匯入：
    `import alert4 from "./alert-component.js";`
    - 註冊：
    ```javascript=
    components: {
        alert3,
    },
    ```
元件樣板及綁定方式：
- 樣板建立方式：
    - template：如上面範例，使用反引號
    - x-template：把樣板寫在元件外面(需再app外面)
    ```javascript=
    <script type="text/x-template" id="alert-template"> //id一律一樣(template、component、bootstrapModal)
        <div class="alert alert-primary" role="alert id="alert-template"">
            x-template 所建立的元件
        </div>
    </script>

    <div id="app">
        <alert2></alert2>
    </div>

    app.component("alert2", {
        template: "#alert-template",
    });
    ```
- 單文件元件（單一檔案包含 HTML, JS, CSS）

綁定方式：
- 直接使用 標籤 名稱
- 搭配 v-for 也是沒問題的
- 使用 v-is 綁定(可使用原HTML標籤)，ps:裡面須加單引號，確保為字串格式
    `<div v-is="'元件名稱'"></div>`
- 動態屬性
    ```htmlembedded=
    <input type="text" v-model="componentName">
    <div v-is="componentName"></div> //綁定資料的名稱

    const app = Vue.createApp({
        data() {
            return {
                array: [1, 2, 3],
                componentName: "alert",
            };
        },
    });
    ```
## 元件動態屬性：
可以同時綁定多個 v-model
```jsx=
<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />

<!-- would be shorthand for: -->

<ChildComponent
  :title="pageTitle"
  @update:title="pageTitle = $event"
  :content="pageContent"
  @update:content="pageContent = $event"
/>
```
## ref 取得標籤
在標籤上加上ref屬性：該屬性即為此標籤的DOM元素名稱，可以用來呼叫(只能在自己元件使用，無法跨元件，跨元件需用mitt)
```javascript=
<product-modal ref="productModal" @update="getData"></product-modal>

//in父元件：
this.$refs.DOM元素名稱.要呼叫子元件的函式;
this.$refs.productModal.clearFile();

//in子元件的方法：
clearFile() {
    this.$refs.file.value = null; //呼叫自己標籤的DOM元素，file為DOM名稱
}
```
使用Vue的ref屬性操作Modal：
```javascript=
<input type="text" ref="inputDom">

const app = Vue.createApp({
    data() {
        return {
            bsModal: "",
        };
    },
    methods: {
        openModal() {
            this.Modal.show();
        },
    },
    mounted() {
        this.$refs.inputDom.focus(); //在網頁載入時會自動focus進去input，ref只能在mounted看的到
        //變數 = new bootstrap.Modal(DOM實體);
        this.bsModal = new bootstrap.Modal(this.$refs.modal); //註冊Modal元件實體
    },
});
```
動態ref取值： [https://blog.csdn.net/qq_26834399/article/details/119992536](https://blog.csdn.net/qq_26834399/article/details/119992536)

- 分頁組件範例：
```javascript=
const app = Vue.createApp({
    data() {
        return {
            pagination: {},
        };
    },
});

app.component("pagination", {
    template: "#pagination",
    props: ["pages"],
    methods: {
        emitPage(page) {
            this.$emit("emit-page", page); //傳送要點擊的頁數
        },
    },
});

<script type="text/x-template" id="pagination">
    <nav aria-label="Page navigation example">
        <ul class="pagination">
            <li class="page-item" :class="{'disabled': pages.current_page == 1}">
                <a class="page-link" href="#" aria-label="Previous" @click="emitPage(pages.current_page - 1)">
                <span aria-hidden="true">&laquo;</span>
                </a>
            </li>
            <li class="page-item" v-for="(item, index) in pages.total_pages" :key="index" :class="{'active':item == pages.current_page}">
                <a class="page-link" href="#" @click.prevent="emitPage(item)">{{item}}</a>
            </li>
            <li class="page-item" :class="{'disabled': pages.current_page == pages.total_pages}">
                <a class="page-link" href="#" aria-label="Next" @click.prevent="emitPage(pages.current_page + 1)">
                <span aria-hidden="true">&raquo;</span>
                </a>
            </li>
        </ul>
    </nav>
</script>
```
## 插槽：在內層開一個插槽，可以由外層操作內層插槽的HTML結構
Slot 插巢可以在外層元件，直接操作內層元件的某一區塊（例如插入 HTML 結構）。在內元件放入 `<slot>` 標籤，然後即可在 HTML 中的元件區塊放入要放的 HTML 結構。
https://hackmd.io/DEjj54NKQhOOjPuTmFZTWg?view
https://www.youtube.com/watch?v=_Dp-MlSSB_U&ab_channel=Alex%E5%AE%85%E5%B9%B9%E5%98%9B
- sample：
    ```javascript=
    <card>
        <p>這是由外層定義的文字</p> //直接在子元件插入HTML，如果沒插入就顯示子層的預設HTML
    </card>

    app.component("card", {
        template: `
        <div class="card" style="width: 18rem;">
            <div class="card-header">
                元件 Header
            </div>
            <div class="card-body">
                <slot>
                <p>這段是預設的文字</p>
                </slot>
            </div>
            <div class="card-footer">
                元件 Footer
            </div>
        </div>`,
    });
    ```
- 具名插槽 (插入多個插槽)：需使用==template==標籤來接
    ```htmlembedded=
    <h3>具名插巢縮寫</h3> <!--v-slot縮寫為：#-->
    <card2>
        <template v-slot:header>我喜歡這張卡片</template> // 將此DOM插入到header插槽
        <!--預設請加入 default-->
        <template v-slot:default>這是卡片 2 號</template> //綁定預設是綁定沒給名稱的slot
        <template v-slot:footer>這是卡片腳</template>
    </card2>

    app.component("card2", {
    template: `
    <div class="card" style="width: 18rem;">
        <div class="card-header">
            <slot name="header">元件 Header</slot> //接收插槽位置，使用name屬性取名
        </div>
        <div class="card-body">
            <slot>這段是預設的文字</slot>
        </div>
        <div class="card-footer">
            <slot name="footer">元件 Footer</slot>
        </div>
    </div>`
    });
    ```
- 動態切換具名插槽：`v-slot:[]`
    用法和用is切換component差不多，在這邊的寫法改為`v-slot:[]`，可以自己選擇位置放入更新內容。
    ```jsx=
    // 主要頁面
    <template>
      <div class="">
        <label v-for="(opt, i) in options" :key="i">
          <!-- 選擇插入哪裡 -->
          <input type="radio" :value="opt" v-model="dynamic_slot_name" />{{ opt }}
        </label>
        <div class="ma-5" />
        <light-box>
          <!-- 動態指定要插入的位置名稱(插在header、footer or center) -->
          <template v-slot:[dynamic_slot_name]>
            <h1>更新後內容</h1>
          </template>
        </light-box>
      </div>
    </template>

    <script setup>
    import { ref } from "vue";
    import LightBox from "./LightBox.vue";

    const options = ref(["header", "footer", "default"]); // 插入選項
    const dynamic_slot_name = ref("header");
    </script>
    ```
    ```jsx=
    // LightBox模板頁面
    <template>
      <div class="lightbox">
        <div class="modal-body">
          <header>
            <slot name="header">Default header</slot>
          </header>
          <hr />
          <main>
            <slot>Default body</slot>
          </main>
          <hr />
          <footer>
            <slot name="footer">Default footer</slot>
          </footer>
        </div>
      </div>
    </template>
    ```
### ==插槽props：== (作用域插槽)
slot資料都是由父層提供，若希望內層的data可以給外層使用，可以透過插巢的 props 則可把內元件的資料透過 props 丟給外層使用。

==作用域插槽==意指，當內層資料丟給外層使用時，該資料會限定子元件綁定的父層讀取
- 作用域範例：
    ```jsx=
    // 從插槽取值後再show出來，因為有兩個插槽所以會出現兩次：
    // Sample：【 OutSide {'abc'} OutSide {} 】
    <MyButton v-slot="slotProps">
        OutSide {{ slotProps }} 
    </MyButton>
    ```
    ```jsx=
    // .\MyButton.vue
    // 開兩個default插槽，一個有給父層值，一個沒有。
    // 而資料只會顯示在插槽1本身的作用域，而插槽2不會因為也是default而取得插槽1的資料。
    <script setup>
        const myText = 'abc'
    </script>

    <template>
        <button>
            // 插槽1
            <slot :text="myText"></slot> // 綁定的資料只會顯示在此插槽

            // 插槽2
            <slot></slot>
        </button>
    </template>
    ```
網頁寫法
- 一個(直接帶變數)：
    1. 使用 v-bind 將內層資料傳出去
    ```jsx=
    <div id="app">
        <card>
            <template v-slot:default="slotProps">
                <!-- 3) slotProps (自定義) 代表以物件包覆的，內元件傳入的資料-->
                {{ slotProps }}
                {{ slotProps.productNew.name }}
                <!-- 4) 傳入的物件在屬性名 productNew 下可找到 name 屬性及資料-->
            </template>
        </card>
    </div>

    app.component('card',{
        data(){
            return{
                product:{
                    name:'蛋餅',
                    price: 30,
                    vegan: false,
                }
            };
        },
        template:`
        <div class='card'>
            <div class='card-body'>
                <slot :productNew='product'></slot>
                <!-- 1) 把整個內元件的 product 放進「物件」傳出去-->
                <!-- 2) 該「物件」的屬性名稱為「productNew」(自定義)-->
            </div>
        </div>`
    })
    ```
- 多個（解構賦值）：
    ```htmlembedded=
    <card2 :product="product">
        <template #default="{product, veganName}">
            {{product}} {{veganName}}
            <!--{ "name": "蛋餅", "price": 30, "vegan": false } 非素食-->
        </template>
    </card2>

    app.component("card2", {
        props: ["product"],
        data() {
            return {
                veganName: "",
            };
        },
        created() {
            console.log();
            this.veganName = this.product.vegan ? "素食" : "非素食";
        },
        template: `
        <div class="card" style="width: 18rem;">
            <div class="card-body" >
                <slot :product="product" :veganName="veganName"></slot>   //把整個內元件的 product 放進「物件」傳出去
            </div>
        </div>`,
    });
    ```
## Mitt跨元件溝通：子元件to子元件
Or 使用 [vueUse 的 EventBus](https://vueuse.org/core/useEventBus/)。
```javascript=
<card-on></card-on>
<card-emit></card-emit> //把emit資料傳送到on
```
載入方式： 
1. ```javascript=
    import "https://unpkg.com/mitt/dist/mitt.umd.js"; //載入mitt套件
    ```
2. 安裝 `npm install --save mitt` https://juejin.cn/post/6973106775755063333
3. 
    ```javascript=
    //vue3 optional
    import mitt from 'mitt'
    const emitter = mitt();

    //main.js (vite)
    import mitt from "mitt"
    app.config.globalProperties.$emitter = mitt()
4. 使用： 
    ```javascript=
    import { getCurrentInstance } from 'vue'
    const { proxy } = getCurrentInstance()
    
    proxy.$emitter.emit('chartUpdate')
    ```
- 傳送端：
    ```javascript=
    app.component("card-emit", {
        data() {
            return {
                product: {
                    name: "蛋餅",
                    price: 30,
                    vegan: false,
                },
            };
        },
        methods: {
            sendData() {
                console.log("sendData");
                //this.$emit
                emitter.emit("sendProduct", this.product);
                //自訂義名稱跨元件方法('傳值名稱', 要傳送的值)
            },
        },
        template: `
        <div class="card" style="width: 18rem;">
            <div class="card-body" >
                <button @click="sendData">送出</button>
            </div>
        </div>`,
    });
    ```
- 接收端：接收端要作監聽
    ```javascript=
    app.component("card-on", {
        data() {
            return {
                item: {},
            };
        },
        //接收資料建議放在created
        created() {
            emitter.on("sendProduct", (data) => { //使用.on對傳送過來的sendProduct作監聽，sendProduct名稱自訂
                console.log(data);
                this.item = data;
            });
        },
        template: `
        <div class="card" style="width: 18rem;">
            <div class="card-body">
            {{ item }}
            </div>
        </div>`,
    });
    ```
## Teleport：
可以用id or class or HTML標籤的方式重用組件內的某段HTML，或是把HTML傳送到指定位置
使用限制：要傳送的位置只限自己script標籤包住的範圍，無法跨script傳送。
可傳送多個
```htmlembedded=
app.component("home", {
    template: `<teleport to='.minHome'>小明</teleport> //傳送到class對應位置
    <teleport to='#huaHome'>小華</teleport> //傳送到id對應位置
    <teleport to='span'>小王</teleport> //傳送到標籤位置
    `
});

<div id="app">
    <div class="minHome">小明家:</div>
    <div id="huaHome">小華家:</div>
    <span>小王家:</span>
    <home></home>
</div>

//結果：
小明家:小明
小華家:小華
小王家:小王
```
### SSR
請避免在 SSR 的同時把 Teleport 的目標設為 body——通常 `<body>`
會包含其他服務端渲染出來的內容，這會使得 Teleport 無法確定激活的正確起始位置。

推薦用一個獨立的只包含 teleport 的內容的容器，例如：`<div id="teleported"></div>`
## Provide：跨元件(巢狀元件)資訊傳遞
- 在外層加入 provide，可分為物件與函式方式(響應式)
    物件：如果最底層有對資料作處理，只會影響本身的資料
    函式：如果最底層有對資料作處理，會連帶影響中間層(常用)
- 內層元件補上 inject(物件格式)
    ```javascript=
    //第三層
    const userComponent = {
        template: `<div>
            {{ user.name }}
            {{ user.uuid }}
        </div>`,
        inject: ["user"], //inject: ['要取得的變數']
        created() {
            console.log(this.user);
            // 如果根元件沒有使用 function return 則不能使用響應式(資料連動)
            this.user.name = "杰倫";
        },
    };

    const app = Vue.createApp({
        data() {
            return {
                user: {
                    name: "小明",
                    uuid: 78163,
                },
            };
        },
        // provide: { //物件結構
        //    user: {
        //        name: "小明",
        //        uuid: 78163,
        //    },
        // },
        provide() { //提供(provide)給這個元件都可以用的方法
            return {
                user: this.user, //可以使用this
            };
        },
    });

    //第二層
    app.component("card", {
        data() {
            return {
                title: "文件標題",
                content: "文件內文",
                toggle: false,
            };
        },
        components: {
            userComponent,
        },
        inject: ["user"], //中間層要取得也要加上inject，如果不需要就不用加 or 用props取得
        template: `
        <div class="card" style="width: 18rem;">
            <div class="card-body">
                <userComponent></userComponent>
                {{[user.name](http://user.name)}}
            </div>
        </div>
        `,
    });
    ```
    [provide與inject響應式綁定](https://juejin.cn/post/7018102866292244488)
    ```javascript=
    //祖先组件----------------------------------
    provide() {
        return {
          foo: this
        }
    },

    //子孙组件----------------------------------
      inject: ["foo"],
      mounted: {
        console.log(this.foo.someval)//===>> 获取data中的数据
        console.log(this.foo.getVal())//===>>调用method中的方法
        console.log(this.foo.calculation())//===>>调用computed中的方法
     },
    ```
## 子元件資料雙向綁定父元件：
```javascript=
{{ text }} {{ text2 }}

<custom-input2
v-model:t1="text"
v-model:t2="text2">
</custom-input2> //屬性本為props，然後再加上v-model綁定父層

const app = Vue.createApp({
    data() {
        return {
            text: "這是文字片段 1",
            text2: "這是文字片段 2",
        };
    },
});

// $emit('update:text', $event.target.value) 搭配 props，可以將更新後的值寫回 v-model 內
app.component("custom-input2", {
    props: ["t1", "t2"],
    template: `
    <input type="text" :value="t1" @input="$emit('update:t1', $event.target.value)"
    class="form-control">
    
    <input type="text" :value="t2" @input="$emit('update:t2', $event.target.value)"
    class="form-control">
    `,
});
```
## Mixin：混和元件，讓元件共用的方法(類似function可以不斷拿來用)
- 可以重複混合
- 生命週期可以各自觸發
- 同名的變數、方法則會被後者覆蓋
    ```javascript=
    //元件一
    const mixComponent1 = {
        data() {
            return {
                name: "混合的元件",
            };
        },
        created() {
            console.log("混合的元件生命週期");
        },
    };

    //元件二
    const mixComponent2 = {
        data() {
            return {
                name: "混合的元件 2",
            };
        },
        created() {
            console.log("混合的元件生命週期 2");
        },
    };

    const app = Vue.createApp({});

    app.component("card", {
        template: `<div class="card">
            <div class="card-body">{{ name }}</div>
        </div>`,
        mixins: [mixComponent1, mixComponent2], //混和元件的值會透過mixin帶進來，name會被後者覆蓋，只顯示"混合的元件 2"
        created() {
            console.log("card 的元件生命週期");
        },
    });
    ```
- 共用方法：
    ```javascript=
    const filterMix = { //建立一個共用方法
        created(){
            this.cash = this.dollarSign(this.cash)
        },
        methods:{
            dollarSign(dollar) {
                return `${dollar}`;
            }
        },
    };
    ```
    在需要插入共用的方法新增mixins
    ```javascript=
    import XXX form '路徑' //如果在不同檔案需要import

    app.component('nike', {
        data() {
            return {
                cash:3000,
                title:'nike 球鞋',
                img:'https://i.imgur.com/fiUT2Sx.jpg'
            }
        },
        mixins: [filterMix],
        template:
        `<div class="card" style="width: 18rem;">
            <img :src="img" class="card-img-top">
            <div class="card-body">
                <h5 class="card-title">{{title}}</h5>
                <div>售價: <span>{{cash}}</span></div>
            </div>
        </div>`,
    });
    ```
## Extend：擴展，沿用元件資料
- 擴展為==單一擴展==
- 生命週期可以與 mixins 重複觸發
- 權重：元件屬性 \> mixins > extend
- 執行順序：與權重相反
- 同名的變數、方法則會依據權重決定
    ```javascript=
    app.component('card2', {
        template: `<div class="card">
        <div class="card-body">{{ name }}</div>
        </div>`,
        mixins: [mixinComponent],
        extends: extendComponent1, //只能一個
        created() {
            console.log('card 的元件生命週期')
        }
    });
    ```
## Directive (Vue模板)
- https://ithelp.ithome.com.tw/articles/10266580
    ==只有当所需功能只能通过直接的 DOM 操作来实现时，才应该使用自定义指令，否則皆應使用資料來驅動畫面或程式。==
    ```javascript=
    import { createApp } from "vue";
    import dayjs from "dayjs";
    import App from "./App.vue";
    const app = createApp(App);

    // 先註冊一個 timeformat 的語法 (全域註冊)
    app.directive("timeformat", {
      mounted(el, binding) { // 當元件綁定時會觸發
        const time = dayjs(binding.value).format("YYYY年MM月DD日");
        el.innerText = time;
      }
    });

    app.mount("#app");
    ```
    使用：
    ```htmlembedded=
    <p v-timeformat="card.post_date"></p>
    ```
## Renderless Component (無渲染組件)
- https://medium.com/%E4%BA%BA%E7%94%9F%E7%9A%84%E5%90%84%E7%A8%AE%E5%8F%AF%E8%83%BD/vue%E8%A8%AD%E8%A8%88%E6%A8%A1%E5%BC%8F-%E7%84%A1%E6%B8%B2%E6%9F%93%E7%B5%84%E4%BB%B6-renderless-component-182f1f1bb4a3
    使用情境：
    1. 如果你有很多個元件他的功能都很類似但卻希望有不一樣的外貌
    1. 如果你希望讓User自定義UI的樣式的時候