vue3组件化开发页面之渲染函数实现

文章目录


前言

组件化开发是目前开发的常态

本文记录页面拆分多个不同组件模块,然后再基于渲染函数实现页面的开发过程


一、渲染机制

由于渲染函数是基于虚拟DOM实现的,咱先简单了解渲染函数的背景

虚拟 DOM

虚拟 DOM (Virtual DOM,简称 VDOM) 是一种编程概念,意为将目标所需的 UI 通过数据结构"虚拟"地表示出来,保存在内存中,然后将真实的 DOM 与之保持同步。这个概念是由 React 率先开拓,随后被许多不同的框架采用,当然也包括 Vue。

与其说虚拟 DOM 是一种具体的技术,不如说是一种模式,所以并没有一个标准的实现。例如以下代码为纯 JavaScript 的对象 (一个"虚拟节点"),它代表着一个 <div> 元素

ts 复制代码
const vnode = {
  type: 'div',
  props: {
    id: 'hello'
  },
  children: [
    /* 更多 vnode */
  ]
}

渲染管线

  • 编译:Vue 模板被编译为渲染函数:即用来返回虚拟 DOM 树的函数。这一步骤可以通过构建步骤提前完成,也可以通过使用运行时编译器即时完成。

  • 挂载:运行时渲染器调用渲染函数,遍历返回的虚拟 DOM 树,并基于它创建实际的 DOM 节点。这一步会作为响应式副作用执行,因此它会追踪其中所用到的所有响应式依赖。

  • 更新:当一个依赖发生变化后,副作用会重新运行,这时候会创建一个更新后的虚拟 DOM 树。运行时渲染器遍历这棵新树,将它与旧树进行比较,然后将必要的更新应用到真实 DOM 上去。

结合上文可以看出,如果使用渲染函数会比 使用Vue模版渲染更快,因为少了一个编译的步骤

二、渲染函数

在绝大多数情况下,Vue 推荐使用模板语法来创建应用。然而在某些使用场景下,我们真的需要用到 JavaScript 完全的编程能力。这时渲染函数就派上用场了。

基本用法

h() 是 hyperscript 的简称------意思是"能生成 HTML (超文本标记语言) 的 JavaScript"。这个名字来源于许多虚拟 DOM 实现默认形成的约定。一个更准确的名称应该是 createVnode(),但当你需要多次使用渲染函数时,一个简短的名字会更省力。

  • 创建 Vnodes
  • Vue 提供了一个 h() 函数用于创建 vnodes:
ts 复制代码
import { h } from 'vue'

const vnode = h(
  'div', // type
  { id: 'foo', class: 'bar' }, // props
  [
    /* children */
  ]
)
// 除了类型必填以外,其他的参数都是可选的
h('div')
h('div', { id: 'foo' })

// attribute 和 property 都能在 prop 中书写
// Vue 会自动将它们分配到正确的位置
h('div', { class: 'bar', innerHTML: 'hello' })

// 像 `.prop` 和 `.attr` 这样的的属性修饰符
// 可以分别通过 `.` 和 `^` 前缀来添加
h('div', { '.name': 'some-name', '^width': '100' })

// 类与样式可以像在模板中一样
// 用数组或对象的形式书写
h('div', { class: [foo, { bar }], style: { color: 'red' } })

// 事件监听器应以 onXxx 的形式书写
h('div', { onClick: () => {} })

// children 可以是一个字符串
h('div', { id: 'foo' }, 'hello')

// 没有 props 时可以省略不写
h('div', 'hello')
h('div', [h('span', 'hello')])

// children 数组可以同时包含 vnodes 与字符串
h('div', ['hello', h('span', 'hello')])

声明渲染函数

可以使用 render 选项来声明渲染函数:

ts 复制代码
import { h } from 'vue'

export default {
  data() {
    return {
      msg: 'hello'
    }
  },
  render() {
    return h('div', this.msg)
  }
}

除了返回一个单独的 vnode 之外,你还可以返回字符串或是数组:

ts 复制代码
export default {
  render() {
    return 'hello world!'
  }
}
ts 复制代码
import { h } from 'vue'

export default {
  render() {
    // 用数组来返回多个根节点
    return [
      h('div'),
      h('div'),
      h('div')
    ]
  }
}

Vnodes 必须唯一

组件树中的 vnodes 必须是唯一的。

如果你真的非常想在页面上渲染多个重复的元素或者组件,你可以使用一个工厂函数来做这件事。比如下面的这个渲染函数就可以完美渲染出 20 个相同的段落:

ts 复制代码
function render() {
  return h(
    'div',
    Array.from({ length: 20 }).map(() => {
      return h('p', 'hi')
    })
  )
}

三、页面使用渲染函数及组件配置

把一个页面拆分多个组件模块,以组装形式依次生成

可以使用Wrapper 组件包装下,处理多个路由通用逻辑

  • wrapper/index.vue
html 复制代码
<template>
  <slot></slot>
</template>

<script lang="ts" src="./index.ts" />

<style lang="less" scoped></style>
  • user-sreach/index.vue
html 复制代码
<template>
  <div>
    user sreach
  </div>
</template>

<style scoped lang="less"></style>
  • user-list/index.vue
html 复制代码
<template>
  <div>
    user list
  </div>
</template>

<style scoped lang="less"></style>
  • /user/index.ts
ts 复制代码
import { ROUTER_CONFIG_MAP } from '@/const/router';
import { defineComponent, h, defineAsyncComponent, type Component } from 'vue';

const Wrapper = defineAsyncComponent(() => import('@/components-business/wrapper/index.vue'));
const UserSreach = defineAsyncComponent(() => import('@/components-business/form/user-sreach/index.vue'));
const UserList = defineAsyncComponent(() => import('@/components-business/table/user-list/index.vue'));

const componentsMap: Types.KeyMapAny<Component> = {
  UserSreach,
  UserList
};

const pageComponent = [
  'UserSreach',
  'UserList'
]
export default defineComponent({
  // 配置后可在 动态获取路由配置中设置对应值
  name: ROUTER_CONFIG_MAP.system.user.name,
  title: ROUTER_CONFIG_MAP.system.user.title,
  path: ROUTER_CONFIG_MAP.system.user.path,
  setup() {
	// return () => pageComponent.map((item: string) => componentsMap[item] ? h(componentsMap[item]) : null);
    return () => h(
      Wrapper,
      null,
      () => pageComponent.map((item: string) => componentsMap[item] ? h(componentsMap[item]) : null)
    );
  },
});
  • router/index.ts
ts 复制代码
import { createRouter, createWebHistory } from "vue-router";
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: "/",
      name: "Home",
      component: () => import('@/views/contents-pages/system/user/index'),
    },
  ],
});

export default router;

总结

如有启发,可点赞收藏哟~

相关推荐
程序员大金5 分钟前
基于SpringBoot+Vue+MySQL的在线学习交流平台
java·vue.js·spring boot·后端·学习·mysql·intellij-idea
Easonmax8 分钟前
【CSS3】css开篇基础(1)
前端·css
qq_25183645713 分钟前
基于SpringBoot vue 医院病房信息管理系统设计与实现
vue.js·spring boot·后端
大鱼前端27 分钟前
未来前端发展方向:深度探索与技术前瞻
前端
昨天;明天。今天。32 分钟前
案例-博客页面简单实现
前端·javascript·css
天上掉下来个程小白33 分钟前
请求响应-08.响应-案例
java·服务器·前端·springboot
萧鼎37 分钟前
JavaScript可视化
javascript
周太密1 小时前
使用 Vue 3 和 Element Plus 构建动态酒店日历组件
前端
安冬的码畜日常1 小时前
【玩转 JS 函数式编程_008】3.1.2 JavaScript 函数式编程筑基之:箭头函数——一种更流行的写法
开发语言·javascript·ecmascript·es6·this·箭头函数
时清云1 小时前
【算法】合并两个有序链表
前端·算法·面试