vue3中$attrs的變化與inheritAttrs的使用

2022-10-22 15:00:31

在vue3中的$attrs的變化

$listeners已被刪除合併到$attrs中。
$attrs現在包括class和style屬性。

也就是說在vue3中$listeners不存在了。vue2中$listeners是單獨存在的。
在vue3 $attrs包括class和style屬性, vue2中 $attrs 不包含class和style屬性。

在vue2中的$attrs

在Vue 2中,attrs裡面包含著上層元件傳遞的所有資料(除style和class)
當一個元件宣告了prop時候,attrs裡面包含除去prop裡面的資料剩下的資料。
結合inheritAttrs:false,可以將傳遞下來的資料應用於其他元素,而不是根元素:

父元件的屬性直接渲染在根節點上

父頁面.vue

<template>
    <div>
        <TestCom title="父元件給的標題" aa="我是aa" bb="我是bb"></TestCom>
    </div>
</template>
<script setup lang="ts">
import TestCom from "../../components/TestCom.vue"
</script>

子元件.vue

<template>
    <div class="root-son">
       <p>我是p標籤</p>
       <span>我是span</span>
    </div>
</template>

我們發現父元件中的屬性直接是渲染在了 <div class="root-son"></div>這個節點上。
變為了 <div class="root-son" title="父元件給的標題" aa="我是aa" bb="我是bb"></div>。
因為在預設情況下,父元件的屬性會直接渲染在子元件的根節點上。【重點】
然後有些情況我們希望是渲染在指定的節點上。那怎麼處理這問題呢?
我們的 $attrs 和 inheritAttrs: false 這一對 」好基友「  閃亮登場

如何讓父元件的屬性渲染在指定的節點上

我們可以使用 $attrs 配合 inheritAttrs: false 可以將屬性渲染在指定的節點上
子元件的程式碼中新增 inheritAttrs: false
//子元件
<template>
    <div class="root-son">
        <!--所有的屬性都將被這個元素p接收  -->
        <p v-bind="$attrs">我是p標籤</p>
        <span>我是span</span>
    </div>
</template>
<script lang="ts" setup>
// 不讓子元件的根節點渲染屬性
inheritAttrs: false
</script>

發現問題-根節點和指定節點都別渲染了屬性

好傢伙,你不是說  $attrs 配合 inheritAttrs: false可以將屬性渲染在指定的節點上。
現在雖然渲染在指定節點上。但是根節點也有。這樣不好吧。切。走了。走了。菜雞。
這,這,這, 你別走呀。 等一會。趕緊偷偷人偷偷去官網看一下出怎麼說的。

原來是這樣的:
<script setup> 可以和普通的 <script> 一起使用。
普通的 <script> 在有這些情況下或許會被使用到。
比如:無法在 <script setup> 中的宣告選項中去使用 inheritAttrs 或外掛的自定義選項。

我們需要將程式碼變為如下:

<template>
    <div class="root-son">
        <!--所有的屬性都將被這個元素p接收  -->
        <p v-bind="$attrs">我是p標籤</p>
        <span>我是span</span>
    </div>
</template>

<script>
//無法在 <script setup> 中的宣告選項中去使用 inheritAttrs。
//所有隻有在整一個<script>
export default {
    inheritAttrs: false,
    customOptions: {}
}
</script>
<script lang="ts" setup>
 你的程式碼
</script>

TMD 又又發現問題了

在瀏覽器中提示:[plugin:vite:vue] [@vue/compiler-sfc] <script> and <script setup> must have the same language type.
TMD 又又發現問題了。穩住,我可以的。在心裡一直告訴自己,冷靜點,我可以解決這個問題的。
先看看它提示  must have the same language type. 必須具有相同的語言型別
小問題就是說必須要有一個型別。我將  script 上新增一個 lang="ts"就解決了。 我感覺自己又行了。

<template>
    <div class="root-son">
        <p v-bind="$attrs">我是p標籤</p>
        <span>我是span</span>
    </div>
</template>
<script lang="ts">
export default {
    inheritAttrs: false,
    customOptions: {}
}
</script>
<script lang="ts" setup>
</script>

簡單的介紹 $listeners

$listeners包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器。
它可以通過 v-on="$listeners" 傳入內部元件——在建立更高層次的元件時非常有用。

我的理解:因為$listeners 可以接收父級元件中(不含.native修飾器的) v-on 事件監聽器.
所以在進行元件事件傳遞的時候非常有用。
很多時候我們對元件進行二次封裝的時候不可能將元件中的內建事件都丟擲來。
這個時候 $listeners 就派上用場了。
在vue2中有 vue2中$listeners是單獨存在的。vue3 被合併到$attrs中了。

vue2中 v-bind="$attrs" 和 $listeners

//子元件.vue
<template>
    <div>
        <el-button type="primary" @click="dialogVisible = true">點選開啟 </el-button>
        <el-dialog  v-bind="$attrs"  v-on="$listeners" :visible.sync="dialogVisible" 
            width="30%" :before-close="handleClose">
            <span>這是一段資訊</span>
            <span slot="footer" class="dialog-footer">
                <el-button @click="dialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="dialogVisible = false">確 定</el-button>
            </span>
        </el-dialog>
    </div>
</template>

<script>
export default {
    inheritAttrs: false, //不讓屬性直接渲染在根節點上
    data() {
        return {
            dialogVisible: false
        };
    },
    methods: {
        handleClose(done) {
            this.$confirm('確認關閉?').then(() => {
                done();
            }).catch(() => { });
        }
    }
};
</script>

//父元件.vue
<template>
  <div>
    <LianCom title="父元件給的標題" @open="openHandler"></LianCom>
  </div>
</template>

<script>
import LianCom from "../components/LianCom.vue"
export default {
  components: {
    LianCom
  },
  methods:  {
    openHandler() { 
      console.log('可以直接註冊事件,因為 v-on="$listeners"會接收除了.native的原生事件')
    }
  }
}
</script>

vue3 v-bind="$attrs" 會接收屬性和事件

//子元件
<template>
    <el-button text @click="dialogTableVisible = true"> 開啟 </el-button>
    <el-dialog width="600px" v-bind="$attrs" v-model="dialogTableVisible" title="我是標題">
       <div>我值彈窗中的內容</div>
    </el-dialog>
</template>
<script lang="ts">
export default {
    inheritAttrs: false,
}
</script>
<script lang="ts" setup>
import {  ref } from 'vue'
const dialogTableVisible = ref(false)
</script>

ps:我們沒有向上丟擲任何事件。
但是父元件可以呼叫 Element Plus 中對話方塊中的內建方法。
但是父頁面中可以 註冊 Element Plus 中對話方塊中的內建方法。
// 父元件
<template>
    <div class="father">
        <TestCom @close="closeHandler" :before-close="beforeclose" title="父元件給的標題" aa="我是aa" bb="我是bb"></TestCom>
    </div>
</template>
<script setup lang="ts">
import { ElMessageBox } from 'element-plus'
import TestCom from "../../components/TestCom.vue"
// Dialog 關閉的回撥
const closeHandler = () => { 
    console.log('Dialog 關閉的回撥')
}
/* 
before - close 只會在使用者點選關閉按鈕或者對話方塊的遮罩區域時被呼叫。
如果你在 footer 具名插槽裡新增了用於關閉 Dialog 的按鈕,那麼可以在按鈕的點選回撥函數里加入 before - close 的相關邏輯。
關閉前的回撥,會暫停 Dialog 的關閉. 回撥函數內執行 done 引數方法的時候才是真正關閉對話方塊的時候.
*/
const beforeclose = (done: () => void) => {
    ElMessageBox.confirm('Are you sure to close this dialog?')
        .then(() => {
            console.log('使用者點選了確定')
            done()
        })
        .catch(() => {
            console.log('使用者點選了取消')
        })
}
</script>