chapter 7(高级渲染,动态组件和插件)
render()
在 Vue 3 中,render() 函数 是一种通过 JavaScript 代码直接描述组件虚拟 DOM 结构的方式,属于 Vue 的底层渲染机制。它提供了比模板语法更高的灵活性,允许开发者通过编程方式动态生成和操作组件的渲染结构。以下是详细解释:
1. 基本概念
- render() 函数:返回一个虚拟 DOM 节点(VNode),描述组件最终渲染的结构。
- h 函数:全称
h
(createElementVNode
),用于创建虚拟 DOM 节点。它是 Vue 3 渲染函数的核心工具。 - 虚拟 DOM:Vue 通过虚拟节点(VNode)描述 UI,
render()
函数生成的 VNode 会被 Vue 内部转换为真实 DOM。
2. 基本语法
在 Vue 3 中,render()
函数通常与 h
函数结合使用,语法结构如下:
import { h } from 'vue';
export default {
render() {
return h('div', { class: 'example' }, [
h('h1', 'Hello Vue 3!'),
h('p', 'This is a dynamically generated component.')
]);
}
};
参数说明
- h 的第一个参数:要创建的 DOM 标签名或组件对象(如
'div'
、MyComponent
)。 h
的第二个参数 :属性对象(props、事件、样式等),支持以下类型:- 普通属性:如
class
,style
。 - 组件 prop:如
:modelValue
。 - 事件:通过
onClick
、onInput
等命名约定(on + 事件名
)。 - h 的第三个参数:子节点(可以是字符串、其他 VNode 或数组)。
3. 在 script setup
中使用 render()
在 Vue 3 的组合式 API 中,可以通过 setup()
返回渲染函数,但更推荐直接在 <script setup>
中定义 render()
函数:
<script setup>
import { h, ref } from 'vue';
import MyComponent from './MyComponent.vue';
const message = ref('Hello from render function!');
const render = () => {
return h('div', [
h('p', message.value),
h(MyComponent, { prop: 'value' })
]);
};
</script>
4. 渲染函数的优势
- 动态结构 :可以基于条件或循环动态生成 DOM 结构。
render() {
return h('div', [
this.showHeader && h('h1', '动态标题'),
h('ul', this.items.map(item => h('li', item)))
]);
}
- 高性能场景:适合需要频繁更新或复杂渲染逻辑的场景。
- 与组合式 API 结合:可以与
ref
、reactive
等响应式数据无缝协作。
5. 与模板的对比
特性 | 模板语法 | 渲染函数 |
---|---|---|
语法 | HTML 风格,声明式描述结构 | JavaScript 函数,程序化描述结构 |
动态性 | 通过指令(如 v-if , v-for ) | 通过 JavaScript 条件和循环 |
复杂逻辑 | 有限,需配合计算属性/方法 | 无限,直接使用 JS 逻辑 |
可读性 | 更易读(接近 HTML) | 较复杂(纯代码)JSX语法 |
6. 典型应用场景
场景 1:动态生成组件
render() {
const Component = this.isForm ? 'form' : 'div';
return h(Component, { key: this.isForm });
}
场景 2:条件渲染
render() {
return h('div', [
this.isAuthenticated
? h('p', '欢迎回来!')
: h('button', { onClick: this.login }, '登录')
]);
}
场景 3:复用逻辑
const createListItem = (item) => {
return h('li', { key: item.id }, item.name);
};
render() {
return h('ul', this.items.map(createListItem));
}
7. 注意事项
- 响应式数据绑定:
- 使用
ref
或reactive
定义数据,并通过modelValue
和事件(如onUpdate:modelValue
)实现双向绑定。 - 示例:
h(ElInput, { modelValue: formData.value.name, 'onUpdate:modelValue': (val) => (formData.value.name = val) })
- 组件引用(ref):
- 通过
ref
获取组件实例:const formRef = ref(null); h(ElForm, { ref: formRef }, /* ... */);
- 事件处理:
- 直接绑定事件处理函数:
h(ElButton, { onClick: () => alert('Clicked!') }, '按钮');
- 插槽(Slots):
- 通过
slots
对象访问插槽内容render() { return h('div', [ this.$slots.default && this.$slots.default(), h('footer', this.$slots.footer && this.$slots.footer()) ]); }
8. 使用 JSX 的简化写法
Vue 3.0支持使用JSX开箱即用。JSX的语法与Vue模板不同。为了绑定一个动态数据,我们使用单花括号{}
Vue 3 支持通过 Babel 插件将 JSX 转换为 h
函数调用,代码更接近模板语法:
render() {
return (
<div>
<ElForm model={formData.value}>
<ElFormItem label="项目名称" prop="name">
<ElInput v-model={formData.value.name} />
</ElFormItem>
</ElForm>
<ElButton onClick={handleSubmit}>提交</ElButton>
</div>
);
}
9. 与组合式 API 的结合
在 <script setup>
中,render()
函数需显式导出:
<script setup>
import { h, ref } from 'vue';
const count = ref(0);
const render = () => {
return h('div', [
h('p', `Count: ${count.value}`),
h('button', { onClick: () => count.value++ }, '增加')
]);
};
// 显式导出 render 函数
defineExpose({ render });
</script>
10. 总结
- render() 是 Vue 3 的底层渲染机制,通过
h
函数构建虚拟 DOM。 - 适用场景:动态结构、复杂逻辑、需要直接操作 VNode 的场景。
- 与组合式 API 结合:通过
ref
和响应式数据实现状态管理。 - JSX 简化写法:推荐使用 JSX 语法提升可读性。
通过 render()
函数,你可以完全用 JavaScript 控制组件的渲染逻辑,但需权衡代码的可维护性和模板的简洁性。对于大多数场景,模板语法仍是首选,而 render()
更适合需要高级控制的复杂需求。
Plugins插件
Vue 插件提供了一种将功能全局扩展到整个应用的标准化方式,尤其适合添加全局方法、组件、指令或初始化第三方库。以下是具体解释和示例:
核心概念
- 全局功能
通过插件,可以将工具函数、配置、组件等挂载到 Vue 实例(Vue 2)或app
上下文(Vue 3),使其在任何组件中直接可用。 - 插件定义结构
一个 Vue 插件通常是一个对象或函数,需包含install
方法(或直接导出一个函数作为安装逻辑):
// 插件定义(Vue 3)
export default {
install(app: VueApp, options: any) {
// 扩展逻辑:添加方法、组件、指令等
}
};
// 或直接导出函数
export default (app: VueApp, options: any) => {
// 安装逻辑
};
典型使用场景
- 添加全局方法
将工具函数挂载到app.config.globalProperties
(Vue 3)或Vue.prototype
(Vue 2):
// Vue 3 插件示例
export default (app: VueApp) => {
app.config.globalProperties.$utils = {
formatTime(date: Date) {
return date.toISOString();
}
};
};
// 在组件中使用:
this.$utils.formatTime(new Date());
- 注册全局组件
在插件中批量注册组件,避免每个页面单独导入:
import MyButton from '@/components/MyButton.vue';
export default (app: VueApp) => {
app.component('MyButton', MyButton);
};
- 添加全局指令
定义可复用的指令(如自动聚焦):
export default (app: VueApp) => {
app.directive('auto-focus', {
mounted(el: HTMLElement) {
el.focus();
}
});
};
- 初始化第三方库
集成如axios
、lodash
等库,统一配置:
import axios from 'axios';
export default (app: VueApp, config: any) => {
app.config.globalProperties.$http = axios.create({
baseURL: config.baseURL
});
};
使用步骤
- 创建插件文件
在项目中新建一个插件文件(如my-plugin.ts
),定义install
方法。 - 在入口文件注册插件
在main.ts
中通过app.use()
安装插件:
import { createApp } from 'vue';
import MyPlugin from '@/plugins/my-plugin';
const app = createApp(App);
app.use(MyPlugin, { /* 传递配置选项 */ });
app.mount('#app');
注意事项
- Vue 2 与 Vue 3 的差异
- Vue 2 使用
Vue.use(Plugin)
和Vue.prototype
挂载方法。 - Vue 3 推荐通过
app
上下文管理全局属性(app.config.globalProperties
)。
- 避免滥用全局状态
全局功能虽方便,但需谨慎使用,避免过度耦合。对于状态管理,建议使用Pinia
或Vuex
。 - 插件解耦
每个插件应专注于单一功能(如仅注册组件或仅添加工具方法),便于复用和维护。
示例:完整插件实现
// plugins/global-utils.ts
export default (app: VueApp) => {
// 1. 添加全局方法
app.config.globalProperties.$utils = {
formatTime(date: Date) {
return date.toISOString();
}
};
// 2. 注册全局指令
app.directive('auto-focus', {
mounted(el: HTMLElement) {
el.focus();
}
});
// 3. 配置第三方库(如 axios)
import axios from 'axios';
app.config.globalProperties.$http = axios.create({
baseURL: '/api'
});
};
在组件中使用:
<template>
<input v-auto-focus />
</template>
<script setup lang="ts">
const time = $utils.formatTime(new Date());
const response = await $http.get('/endpoint');
</script>
通过插件机制,可以高效地将功能全局化,同时保持代码的模块化和可维护性。
在 Vue 3 中,通过 app.config.globalProperties
添加的全局属性(如 $truncate
)无法直接在 <script setup>
或 setup() 组合式函数中访问。这是因为:
<script setup>
的上下文与this
绑定的 Options API 不同,无法通过this
访问全局属性。- 全局属性仅在组件实例(如
this
)或模板中可用。
组合式API解决方案:使用 provide/inject
通过在插件的 install
函数中使用 app.provide
将 $truncate
提供给整个应用,然后在组件中通过 inject
获取。
步骤说明
1. 修改插件代码(plugins/truncate.ts)
在插件的 install
函数中,除了设置全局属性,还需通过 app.provide
注入 $truncate
:
// plugins/truncate.ts
import { App } from 'vue';
// 假设这是你的 truncate 函数
const truncate = (text: string, length: number) => {
return text.length > length ? `${text.slice(0, length)}...` : text;
};
export default {
install(app: App) {
// 1. 将 truncate 挂载到全局属性(兼容 Options API)
app.config.globalProperties.$truncate = truncate;
// 2. 通过 provide 提供给整个应用(支持 Composition API)
app.provide('truncate', truncate);
//app.provide('truncate', {truncate});
}
};
2. 在组件中通过 inject 使用(<script setup>
)
在需要使用的组件中,通过 inject
获取 $truncate
:
<template>
<div>{{ truncatedText }}</div>
</template>
<script setup>
import { inject } from 'vue';
// 1. 注入 truncate 函数
const truncate = inject('truncate');
//const {truncate} = inject('truncate');
// 2. 使用 truncate 处理文本
const text = '这是一个需要截断的长文本';
const truncatedText = truncate(text, 10); // 输出:"这是..."
// 若需要类型提示,可指定类型:
// const truncate = inject<Function>('truncate');
</script>
3. 入口文件注册插件
确保在入口文件(如 main.ts
)中正确安装插件:
// main.ts
import { createApp } from 'vue';
import TruncatePlugin from '@/plugins/truncate';
const app = createApp(App);
app.use(TruncatePlugin);
app.mount('#app');
关键点总结
场景 | 访问方式 | 示例代码 |
---|---|---|
Options API | this.$truncate | this.$truncate(text, 10) |
Composition API | inject('truncate') | truncate(text, 10) |
通过此方案,可以无缝在 <script setup>
和 setup()
中使用 $truncate
,同时保持与 Options API 的兼容性。
<component>
组件
在 Vue.js 中,动态组件(Dynamic Components) 是通过 <component>
标签和 is
属性实现的,允许你在运行时根据条件动态切换或渲染不同的组件。这种机制提供了极大的灵活性,适用于需要根据状态或数据动态切换视图的场景。以下是详细解释和示例:
1. 基本语法
使用 <component>
标签结合 is
属性来动态渲染组件:
<template>
<component :is="currentComponent" />
</template>
<script setup>
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'
const currentComponent = ref(ComponentA)
</script>
<component>
:Vue 的内置组件,用于动态渲染。- :is=”currentComponent”:通过
is
属性绑定一个变量,该变量可以是组件对象或组件名称。
2. 动态组件的常见用法
场景 1:根据状态切换组件
<template>
<button @click="switchToA">显示组件A</button>
<button @click="switchToB">显示组件B</button>
<component :is="currentComponent" />
</template>
<script setup>
import { ref } from 'vue'
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'
const currentComponent = ref(ComponentA)
const switchToA = () => {
currentComponent.value = ComponentA
}
const switchToB = () => {
currentComponent.value = ComponentB
}
</script>
场景 2:使用组件名称字符串
如果组件已全局注册,可以直接用字符串名称:
<script setup>
const currentComponentName = ref('ComponentA')
</script>
<template>
<component :is="currentComponentName" />
</template>
3. 传递 Props 和事件
动态组件可以像普通组件一样接收 props
和事件:
<template>
<component
:is="currentComponent"
:message="dynamicMessage"
@custom-event="handleEvent"
/>
</template>
<script setup>
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
const currentComponent = ref(MyComponent)
const dynamicMessage = ref('Hello Dynamic Component!')
const handleEvent = (payload) => {
console.log('事件触发:', payload)
}
</script>
4. 与 <keep-alive>
结合使用
通过 <keep-alive>
缓存动态组件的状态:
<template>
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
</template>
- 作用:保持组件实例状态,避免重复渲染。
- 示例
<template>
<button @click="switchComponent">切换组件</button>
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
</template>
<script setup>
import { ref } from 'vue'
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'
const currentComponent = ref(ComponentA)
const switchComponent = () => {
currentComponent.value = currentComponent.value === ComponentA ? ComponentB : ComponentA
}
</script>
5. 总结
<component :is="...">
是 Vue 动态渲染组件的核心语法。- 灵活性:通过绑定变量动态切换组件。
- 状态管理:结合
<keep-alive>
缓存组件状态。 - 性能优化:按需加载异步组件,避免一次性加载所有组件。
通过动态组件,你可以实现更灵活和动态的 UI,例如表单步骤、路由视图切换、条件渲染等场景。合理使用这一特性,可以显著提升代码的复用性和可维护性。
<keep-alive>
组件
1. 核心作用
Vue 的 <keep-alive>
是一个内置组件,用于缓存组件实例,避免组件在切换时被销毁和重新创建,从而保留组件状态和避免重复渲染。
2. 基础用法
将动态组件包裹在 <keep-alive>
中:
<template>
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
</template>
3. 核心特性
特性 | 说明 |
---|---|
缓存机制 | 被 <keep-alive> 包裹的组件实例会被缓存,状态(如输入框内容、变量)保留。 |
生命周期钩子 | 组件激活时触发 activated ,停用时触发 deactivated 。 |
include/exclude | 通过名称控制哪些组件被缓存或排除。 |
max | 限制最大缓存组件数量,避免内存溢出。 |
4. 关键属性
属性名 | 类型 | 说明 |
---|---|---|
include | String/Array | 缓存包含的组件名称(需注册为组件名字符串)。 |
exclude | String/Array | 排除不缓存的组件名称。 |
max | Number | 最大缓存组件数量,超过时按 LRU 算法移除最旧的组件。 |
5. 生命周期钩子
在被缓存的组件中,可使用以下选项:
<script setup>
import { onActivated, onDeactivated } from 'vue';
// 组件被激活时(从缓存恢复)
onActivated(() => {
console.log('组件激活,可重新获取数据');
});
// 组件被停用时(进入缓存)
onDeactivated(() => {
console.log('组件停用,可清理资源');
});
</script>
6. 示例:动态组件 + 缓存
<template>
<button @click="toggleComponent">切换组件</button>
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
</template>
<script setup>
import { ref } from 'vue';
import FormA from './FormA.vue';
import FormB from './FormB.vue';
const currentComponent = ref(FormA);
const toggleComponent = () => {
currentComponent.value = currentComponent.value === FormA ? FormB : FormA;
};
</script>
7. 高级用法
1. 按名称缓存组件
<keep-alive include="FormA,FormB">
<component :is="currentComponent" />
</keep-alive>
2. 限制最大缓存数
<keep-alive :max="2">
<component :is="currentComponent" />
</keep-alive>
3. 动态控制缓存
<keep-alive :include="cachedComponents">
<component :is="currentComponent" />
</keep-alive>
<script setup>
const cachedComponents = ref(['FormA']);
</script>
8. 注意事项
- 组件需注册为组件名:若使用字符串绑定
<component :is="name"
,需确保组件已注册为名称。 - 不支持
<keep-alive>
的组件:如<async-component>
或Functional
组件无法被缓存。 - 状态管理:缓存可能导致内存占用增加,需合理设置
max
属性。
路过