Skip to content

组件意义

没有组件时,一般以一个html作为一个开发单元,随着现代前端的需求,这样的开发单元过大,不便管理,当有重复使用的功能时,开发会更加的低效。使用组件能够:

  • 开发粒度、难度降低
  • 减少重复代码,避免重轮子
  • 利于团队协作开发
  • 抽离为组件库

组件概念

每一个vue组件本质是一个js对象,通过SFC(Single File Component)单文件组件.vue的形式表示

vue.createApp()中传入的根组件,本质就是一个包含了很多子组件的组件(对象)

html
<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="UTF-8">
   
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>functional component</title>
  </head>
  <body>
    <div id="dano">
      <!--
      这之中的内容可以在template中编辑
      <div>{{ double }}</div>
      <button @click="printff">click me</button>
	  -->
    </div>
    <script type="module">
    import { createApp } from 'vue'
      createApp({
        methods:{
          printff(){
            this.count++
            console.log(this.count);
          }
        },
        data() {
          return {
            count: 3,
          }
        },
        computed: {
          double() {
            return this.count * 2
          },
        },
        template:/*html*/`
          <div>{{ double }}</div>
          <button @click="printff">click me</button>
        `
      }).mount('#dano')
    </script>
  </body>
</html>

在这种情况下,可以将传入的对象以js模块的形式导出

javascript
// main.js
import { createApp } from 'vue'
import com from '/src/component.js'
createApp(com).mount('#dano')

在将component.js视为component.vue的情况下,上面的写法就与Vue的SFC形式无异。这种写法和选项式API很像。学了Vue2就能够很方便的编写内联结构(或者称作js渲染方式、Non-Single File Component)的组件。需要特别注意的是在使用这些变量的时候要加==this==,因为这是在js中

javascript
// component.js
export default {
    methods: {
        printff() {
            this.count++
            console.log(this.count)
        },
    },
    data() {
        return {
            count: 3,
        }
    },
    computed: {
        double() {
            return this.count * 2
        },
    },
    template: /*html*/ `
          <div>{{ double }}</div>
          <button @click="printff">click me</button>
        `,
}

无论是通过 createApp 中的对象,还是通过 .vue 文件定义组件,它们最终都在 Vue 内部被转换成了 组件选项对象(Component Options Object) Vue 组件的核心。在 .vue 文件中,Vue 的构建工具(比如 Vue CLIVite)会自动将模板、脚本和样式解析并组合成一个 组件选项对象,然后 Vue 内部会处理这些内容并渲染:

  1. template 部分的处理:编译为 render 函数

    • 编译过程:在 .vue 文件中,template 部分会被 Vue 编译成一个 render 函数。这是因为 Vue 需要将 template 转换为 虚拟 DOM(VNode) 的描述,而 render 函数正是 Vue 渲染虚拟 DOM 的核心部分。
    • 工作原理:Vue 使用 模板编译器template 转换为渲染函数。这个渲染函数将描述如何根据组件的状态(datapropscomputed 等)渲染组件的视图。
  2. script 部分的处理:解析成组件选项script 部分是用于定义 Vue 组件逻辑的地方,其中包含组件的 datamethodscomputedprops 等内容。Vue 会在构建过程中将 script 部分提取出来并解析成组件选项对象

  3. style 部分的处理:样式预处理和作用域style 部分包含组件的样式,它可以有不同的处理方式:

    • 常规样式:如果没有任何作用域相关的指令(如 scoped),这些样式将会被处理成普通的 CSS,并被加载到页面中。通常,Webpack 会处理它们,将 CSS 提取到单独的 CSS 文件中,或者将其作为内联样式嵌入到 HTML 中。
    • scoped 样式:如果样式部分包含 scoped 属性(<style scoped>),Vue 会使用一种特殊的机制,将样式的作用域限制在当前组件中,防止样式影响到其他组件。

render函数

Vue 会在内部自动将 template 编译成 render 函数。render 函数可以在 Vue 组件的选项对象中定义,通常是作为 template 的替代品。无论是通过 .vue 文件还是通过 createApp 中的对象方式,render 函数都属于 Vue 组件的渲染逻辑部分响应式原理

  1. 灵活性和精确控制

    • 渲染函数render)提供了更高的灵活性,让你可以精确地控制组件的渲染过程。在某些情况下,使用 template 语法会让你受到限制,特别是在你需要动态地生成组件、动态插入子组件或根据复杂的条件渲染元素时。render 函数可以让你完全控制渲染流程。
    • 在 TypeScript 环境下,render 函数允许你更加细致地定义组件的类型和行为,避免一些模板编译时的类型推断问题。
  2. 类型推导与类型安全

    • TypeScript 中,render 函数能够让你充分利用 TypeScript 提供的类型推导和类型安全。通过 render 函数,你可以显式地控制每个元素的类型,避免模板中的一些类型不一致问题。
    • 对于复杂的项目,render 函数可以确保你可以直接使用类型检查工具来帮助你发现问题,而这在模板中可能会丧失一些类型检查的能力,尤其是在模板复杂的情况下。
  3. 性能优化

    • 在组件库中,性能通常是一个重要的考虑因素。使用 render 函数可以减少 Vue 的模板编译工作,从而提升性能。Vue 的模板需要经过编译阶段生成渲染函数,但使用 render 函数时,直接写代码进行渲染可以减少编译的负担。
    • 函数式组件,即没有实例的组件,使用 render 函数时通常会有更高的性能,尤其在需要频繁渲染且不依赖实例生命周期的情况下。
  4. 构建组件库时的需求

    • 在构建 Vue 组件库时,开发者通常倾向于使用 render 函数来实现组件,因为它使得组件库可以脱离 Vue 单文件组件(.vue 文件)的模板语法,而以纯 JavaScript 或 TypeScript 代码的形式提供更高的控制性和可复用性。
    • 使用 render 函数可以避免一些与模板相关的潜在问题,例如模板编译的复杂性或 Vue 编译器对模板中复杂表达式的限制。通过 render 函数,可以更方便地实现更加复杂的渲染逻辑,比如动态创建节点、条件渲染、递归渲染等。
  5. 函数式组件和 render 函数的协作

    • 函数式组件(Functional Components)是 Vue 的一种特殊组件,它们没有实例化,因此没有 data, computed, methods, 生命周期等。在函数式组件中,渲染逻辑完全依赖 render 函数,它允许你通过传入 props 渲染视图,而不会生成不必要的 Vue 实例。
    • 使用 TypeScript 时,函数式组件配合 render 函数能更好地进行类型推导,使得传递的 props 和返回的虚拟 DOM 元素具有明确的类型。

在 Vue 3 中,render 函数通常用 h(即 createElement)来生成虚拟 DOM,并且可以使用 setup 函数和 Composition API 更好地与 TypeScript 配合。

typescript
import { defineComponent, h } from 'vue';

const MyComponent = defineComponent({
  name: 'MyComponent',
  props: {
    msg: {
      type: String,
      required: true
    }
  },
  render() {
    return h('div', this.msg);  // 使用 `h` 函数来创建虚拟 DOM
  }
});

export default MyComponent;

在这个例子中:

  • 使用了 defineComponent 来定义一个组件。
  • render 函数使用了 h(即 createElement)来生成虚拟 DOM。
  • this.msg 通过 props 获取组件的属性值。

命令式开发与声明式开发的区别

  • 声明式开发:在 Vue 中,通常你会声明 UI 元素如何根据数据变化(如 propsdatacomputed)来渲染。当数据发生变化时,Vue 会自动更新 UI。
html
<MyButton :label="buttonLabel" :disabled="isDisabled" />
  • 命令式开发:你直接控制组件的行为和状态,而不依赖 Vue 的响应式机制。你可能会在组件外部调用方法来控制组件,例如通过 refs 或通过调用暴露出来的方法来改变按钮的状态。
html
<MyButton ref="myButton" />
<button @click="handleClick">Change Button State</button>