一、Vue中自定義指令
1.認識自定義指令
在Vue的模板語法中我們學習過各種各樣的指令:v-show
、v-for
、v-model
等等,除了使用這些指令之外,Vue也允許我們來 自定義自己的指令。
- 注意:在Vue中,代碼的復用和抽象主要還是通過組件;
- 通常在某些情況下,你需要對DOM元素進行底層操作,這個時候就會用到
自定義指令
;
自定義指令分為兩種:
-
自定義
局部
指令:組件中通過directives
選項,只能在當前組件中使用; -
自定義
全局
指令:app的 directive 方法,可以在任意組件中被使用;
比如我們來做一個非常簡單的案例:當某個元素掛載完成后可以自動獲取焦點
-
實現方式一:如果我們使用默認的實現方式;
-
實現方式二:自定義一個 v-focus 的局部指令;
-
實現方式三:自定義一個 v-focus 的全局指令;
1.1默認實現
<script setup>
import { onMounted, useTemplateRef } from "vue";const inputRef = useTemplateRef('inputRef');onMounted(() => {if (inputRef.value) {inputRef.value.focus();}console.log(inputRef.value);
})
</script><template><div class="app"><input type="text" ref="inputRef"></div>
</template><style scoped></style>
1.2自定義局部指令
-
這個自定義指令實現非常簡單,我們只需要在組件選項中使用
directives
即可; -
它是一個對象,在對象中編寫我們自定義指令的名稱(注意:這里不需要加v-);
-
自定義指令有一個生命周期,是在組件掛載后調用的 mounted,我們可以在其中完成操作;
<script setup>
// 任何以 v 開頭的駝峰式命名的變量都可以當作自定義指令使用
const vFocus = (el) => {el.focus();
};
</script><template><div class="app"><input type="text" v-focus /></div>
</template><style scoped></style>
1.3自定義全局指令
自定義一個全局的v-focus指令可以讓我們在任何地方直接使用
import { createApp } from 'vue'
import App from './App.vue'const app = createApp(App);
app.directive('focus', {mounted(el) {el.focus()}
})
app.mount('#app');
2.指令的生命周期
一個指令定義的對象,Vue提供了如下的幾個鉤子函數(都是可選的):
? created:在綁定元素的 attribute 或事件監聽器被應用之前調用;
? beforeMount:當指令第一次綁定到元素并且在掛載父組件之前調用;
? mounted:在綁定元素的父組件被掛載后調用;
? beforeUpdate:在更新包含組件的 VNode 之前調用;
? updated:在包含組件的 VNode 及其子組件的 VNode 更新后調用;
? beforeUnmount:在卸載綁定元素的父組件之前調用;
? unmounted:當指令與元素解除綁定且父組件已卸載時,只調用一次;
3.指令的參數和修飾符
如果我們指令需要接受一些參數或者修飾符應該如何操作呢?
-
foo是參數的名稱;
-
bar是修飾符的名稱;
-
后面是傳入的具體的值;
在我們的生命周期中,我們可以通過 bindings
獲取到對應的內容
<script setup>const vFocus = {mounted(el, binding) {console.log('binding:', binding)/*{arg: "foo"dir: {mounted: ?}instance: Proxy(Object) {__v_skip: true}modifiers: {bar: true}oldValue: undefinedvalue: "admin"}*/el.focus()}
}
</script><template><div class="app"><input type="text" v-focus:foo.bar="'admin'" /></div>
</template>
4.自定義指令練習
自定義指令案例:時間戳的顯示需求:
-
在開發中,大多數情況下從服務器獲取到的都是
時間戳
; -
我們需要將時間戳轉換成具體格式化的時間來展示;
-
在Vue2中我們可以通過過濾器來完成;
-
在Vue3中我們可以通過
計算屬性
(computed) 或者自定義一個方法
(methods) 來完成; -
其實我們還可以通過一個
自定義的指令
來完成;
我們來實現一個可以自動對時間格式化的指令v-format-time
:
- 這里我封裝了一個函數,在首頁中我們只需要調用這個函數并且傳入app即可;
directives/v-format-time.js
import dayjs from "dayjs";export default function (app) {app.directive("format-time", {mounted(el, binding) {let format = 'YYYY-MM-DD';if (binding.value) {format = binding.value;}let timestamp = parseInt(el.textContent);if (!timestamp) return;if (timestamp.length === 10) {timestamp = timestamp * 1000;}// 對時間戳格式化el.textContent = dayjs(timestamp).format(format);}});
}
directives/index.js
import vFormatTime from './v-format-time.js';export default function (app) {vFormatTime(app);
}
main.js
import { createApp } from 'vue'
import App from './App.vue'
import directives from '@/directives'const app = createApp(App);
// 全局指令
directives(app);app.mount('#app');
二、Vue內置組件Teleport
1.認識Teleport
在組件化開發中,我們封裝一個組件A,在另外一個組件B中使用:
-
那么組件A中template的元素,會被掛載到組件B中template的某個位置;
-
最終我們的應用程序會形成一顆DOM樹結構;
但是某些情況下,我們希望組件不是掛載在這個組件樹上的,可能是移動到Vue app之外的其他位置:
-
比如移動到body元素上,或者我們有其他的div#app之外的元素上;
-
這個時候我們就可以通過
teleport
來完成;
Teleport是什么呢?
它是一個Vue提供的內置組件,類似于react的Portals
;
teleport翻譯過來是心靈傳輸、遠距離運輸的意思; 它有兩個屬性:
- to:指定將其中的內容移動到的目標元素,可以使用選擇器;
- disabled:是否禁用 teleport 的功能;
<template>
<div class="hello"><teleport to="body"><div class="hello"><h2>Hello World</h2></div></teleport>
</div>
</template>
2.和組件結合使用
當然,teleport也可以和組件結合一起來使用:
我們可以在 teleport 中使用組件,并且也可以給他傳入一些數據;
<template>
<div class="app"><h2>app</h2><teleport to="body"><HelloWorld message="Hello World" /></teleport>
</div>
</template>
3.多個Teleport一起使用
如果我們將多個teleport應用到同一個目標上(to的值相同),那么這些目標會進行合并:
<template>
<div class="app"><h2>app</h2><div class="content"><h3>content</h3></div><teleport to=".content"><HelloWorld message="Hello World" /></teleport><teleport to=".content"><HelloWorld message="abc" /></teleport>
</div>
</template>
三、Vue內置組件Suspense
注意:目前(2024-08-01)Suspense顯示的是一個實驗性的特性,API隨時可能會修改。
Suspense是一個內置的全局組件,該組件有兩個插槽:
-
default
:如果default可以顯示,那么顯示default的內容; -
fallback
:如果default無法顯示,那么會顯示fallback插槽的內容;
<script setup>
import { defineAsyncComponent } from "vue";const AsyncHome = defineAsyncComponent(() => import("./AsyncHome.vue"))
</script><template>
<div class="app"><h2>app</h2><suspense><template #default><AsyncHome /></template><template #fallback><div>loading...</div></template></suspense>
</div>
</template><style scoped></style>
suspense用于加載異步組件
四、Vue中安裝插件的方式
1.認識Vue插件
通常我們向Vue全局添加一些功能時,會采用插件的模式,它有兩種編寫方式
-
對象類型:一個對象,但是必須包含一個
install
的函數,該函數會在安裝插件時執行; -
函數類型:一個function,這個函數會在安裝插件時自動執行;
插件可以完成的功能沒有限制,比如下面的幾種都是可以的:
- 添加全局方法或者 property,通過把它們添加到
config.globalProperties
上實現; - 添加全局資源:指令/過濾器/過渡等;
- 通過全局 mixin 來添加一些組件選項;
- 一個庫,提供自己的 API,同時提供上面提到的一個或多個功能;
2.插件的編寫方式
對象類型寫法
export default {name: 'plugin_01',install(app) {console.log('plugin_01 install');}
}
函數類型寫法
export default function (app, options) {console.log('plugin_02 install', app, options);
}
五、Vue中渲染函數的使用
1.認識h函數
Vue推薦在絕大數情況下使用模板來創建你的HTML,然后一些特殊的場景,你真的需要JavaScript的完全編程的能力,這個時候你可以使用 渲染函數 ,它比模板更接近編譯器;
前面我們講解過VNode和VDOM的概念:
-
Vue在生成真實的DOM之前,會將我們的節點轉換成VNode,而VNode組合在一起形成一顆樹結構,就是虛擬DOM (
VDOM
); -
事實上,我們之前編寫的 template 中的HTML 最終也是使用渲染函數生成對應的VNode;
-
那么,如果你想充分的利用JavaScript的編程能力,我們可以自己來編寫
createVNode
函數,生成對應的VNode;
那么我們應該怎么來做呢?使用 h()函數:
-
h() 函數是一個用于創建 vnode 的一個函數;
-
其實更準確的命名是 createVNode() 函數,但是為了簡便在Vue將之簡化為 h() 函數;
2.h函數的使用
h()函數 如何使用呢?它接受三個參數:
{String | Object | Function} tag
一個 HTML 標簽名、一個組件、一個異步組件、或一個函數式組件,必需的
eg: ‘div’
{Object} props
與 attribute、prop 和時間相對應的對象,我們會在模板中使用,可選的
eg: {}
{String | Array | Object} children
子 VNodes,使用 h()
構建,或使用字符串獲取 文本 Vnode
或者有插槽的對象
, 可選的
eg:
['Some text comes first',h('h1', 'A headline'),h(MyComponent, {someprop: 'foobar'}) ]
注意:
-
如果沒有props,那么通常可以將children作為第二個參數傳入;
-
如果會產生歧義,可以將null作為第二個參數傳入,將children作為第三個參數傳入;
3.使用示例
h函數可以在兩個地方使用:
- render函數選項中;
- setup函數選項中(setup本身需要是一個函數類型,函數再返回h函數創建的VNode);
render函數中
import { h } from 'vue'export default {data() {return {msg: 'Hello Vue 3.0 + Vite'}},render() {return h("div", { className: "app" }, [h("h2", { className: "title" }, "我是標題123"),h("p", { className: "content" }, "我是內容, 哈哈哈"),])}
}
setup函數
<script>
import { h } from "vue";export default {data() {return {msg: 'Hello Vue 3.0 + Vite'}},setup() {return () => h("div", { className: "app" }, [h("h2", { className: "title" }, "我是標題123"),h("p", { className: "content" }, "我是內容, 哈哈哈"),])}
}
</script>
setup script寫法
<script setup>
import { h } from 'vue'const render = () => h("div", { className: "app" }, [h("h2", { className: "title" }, "我是標題123"),h("p", { className: "content" }, "我是內容, 哈哈哈"),
])
</script><template><render></render>
</template>
4.h函數計數器案例
<script setup>
import { h, ref } from 'vue'const count = ref(0);const render = () => h("div", { className: "app" }, [h("h2", { className: "title" }, count.value),h("button", { className: "btn", onClick: () => count.value++ }, "+"),h("button", { className: "btn", onClick: () => count.value-- }, "-"),
])
</script><template><render></render>
</template>
六、Vue中編寫jsx的語法
如果我們希望在項目中使用jsx,那么我們需要添加對jsx的支持:
- jsx我們通常會通過Babel來進行轉換(React編寫的jsx就是通過babel轉換的);
- 對于Vue來說,我們只需要在Babel中配置對應的插件即可;
安裝Babel支持Vue的jsx插件:
npm install @vue/babel-plugin-jsx -D
如果是Vite環境,需要安裝插件:
npm install @vitejs/plugin-vue-jsx -D
在babel.config.js配置文件中配置插件:
module.export = {"presets": ["@vue/cli-plugin-ba"]"plugins": ["@vue/babel-plugin-jsx"]
}
jsx計數器案例
<script lang="jsx">
export default {data() {return {count: 0}},render() {return (<div><h1>{ this.count }</h1><button onClick={() => this.count++}>+</button><button onClick={() => this.count--}>-</button></div>)}
}
</script>