Vue3-高级特性
一、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: undefined
value: "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>