Learning Vue(7 component高级渲染)

chapter 7(高级渲染,动态组件和插件)

render()

在 Vue 3 中,render() 函数 是一种通过 JavaScript 代码直接描述组件虚拟 DOM 结构的方式,属于 Vue 的底层渲染机制。它提供了比模板语法更高的灵活性,允许开发者通过编程方式动态生成和操作组件的渲染结构。以下是详细解释:


1. 基本概念

  • render() 函数:返回一个虚拟 DOM 节点(VNode),描述组件最终渲染的结构。
  • h 函数:全称 hcreateElementVNode),用于创建虚拟 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
  • 事件:通过 onClickonInput 等命名约定(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 结合:可以与 refreactive 等响应式数据无缝协作。

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. 注意事项

  1. 响应式数据绑定
  • 使用 refreactive 定义数据,并通过 modelValue 和事件(如 onUpdate:modelValue)实现双向绑定。
  • 示例: h(ElInput, { modelValue: formData.value.name, 'onUpdate:modelValue': (val) => (formData.value.name = val) })
  1. 组件引用(ref)
  • 通过 ref 获取组件实例: const formRef = ref(null); h(ElForm, { ref: formRef }, /* ... */);
  1. 事件处理
  • 直接绑定事件处理函数: h(ElButton, { onClick: () => alert('Clicked!') }, '按钮');
  1. 插槽(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 插件提供了一种将功能全局扩展到整个应用的标准化方式,尤其适合添加全局方法、组件、指令或初始化第三方库。以下是具体解释和示例:


核心概念

  1. 全局功能
    通过插件,可以将工具函数、配置、组件等挂载到 Vue 实例(Vue 2)或 app 上下文(Vue 3),使其在任何组件中直接可用。
  2. 插件定义结构
    一个 Vue 插件通常是一个对象或函数,需包含 install 方法(或直接导出一个函数作为安装逻辑):
   // 插件定义(Vue 3)
   export default {
     install(app: VueApp, options: any) {
       // 扩展逻辑:添加方法、组件、指令等
     }
   };

   // 或直接导出函数
   export default (app: VueApp, options: any) => {
     // 安装逻辑
   };

典型使用场景

  1. 添加全局方法
    将工具函数挂载到 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());
  1. 注册全局组件
    在插件中批量注册组件,避免每个页面单独导入:
   import MyButton from '@/components/MyButton.vue';

   export default (app: VueApp) => {
     app.component('MyButton', MyButton);
   };
  1. 添加全局指令
    定义可复用的指令(如自动聚焦):
   export default (app: VueApp) => {
     app.directive('auto-focus', {
       mounted(el: HTMLElement) {
         el.focus();
       }
     });
   };
  1. 初始化第三方库
    集成如 axioslodash 等库,统一配置:
   import axios from 'axios';

   export default (app: VueApp, config: any) => {
     app.config.globalProperties.$http = axios.create({
       baseURL: config.baseURL
     });
   };

使用步骤

  1. 创建插件文件
    在项目中新建一个插件文件(如 my-plugin.ts),定义 install 方法。
  2. 在入口文件注册插件
    main.ts 中通过 app.use() 安装插件:
   import { createApp } from 'vue';
   import MyPlugin from '@/plugins/my-plugin';

   const app = createApp(App);
   app.use(MyPlugin, { /* 传递配置选项 */ });
   app.mount('#app');

注意事项

  1. Vue 2 与 Vue 3 的差异
  • Vue 2 使用 Vue.use(Plugin)Vue.prototype 挂载方法。
  • Vue 3 推荐通过 app 上下文管理全局属性(app.config.globalProperties)。
  1. 避免滥用全局状态
    全局功能虽方便,但需谨慎使用,避免过度耦合。对于状态管理,建议使用 PiniaVuex
  2. 插件解耦
    每个插件应专注于单一功能(如仅注册组件或仅添加工具方法),便于复用和维护。

示例:完整插件实现

// 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 APIthis.$truncatethis.$truncate(text, 10)
Composition APIinject('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. 关键属性

属性名类型说明
includeString/Array缓存包含的组件名称(需注册为组件名字符串)。
excludeString/Array排除不缓存的组件名称。
maxNumber最大缓存组件数量,超过时按 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 属性。

文末附加内容

评论

  1. 路人甲
    Windows Edge
    2 天前
    2025-4-17 23:43:25

    路过

    来自安徽

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇