在vue3中定义组件的5种方式

在vue3中定义组件的5种方式

Vue 正在不断发展,目前在 Vue3 中定义组件的方法有多种。从选项式到组合式再到类API,情况截然不同。本文将会定义一个简单的组件并使用所有可用的方法重构它。

选项式

这是在 Vue 中声明组件的最常见方法。从 Vue1 就开始存在了,我们很可能已经熟悉它了。一切都在对象内部声明,并且数据在Vue中会定义成响应式。这种方式不是那么灵活,因为它使用 mixins 来共享行为。

html 复制代码
<script>
import TheComponent from './components/TheComponent.vue'
import componentMixin from './mixins/componentMixin.js'

export default {
  name: 'OptionsAPI',
  components: {
    TheComponent,
    AsyncComponent: () => import('./components/AsyncComponent.vue'),
  },
  mixins: [componentMixin],
  props: {
    elements: {
      type: Array,
    },
    counter: {
      type: Number,
      default: 0,
    },
  },
  data() {
    return {
      object: {
        variable: true,
      },
    }
  },
  computed: {
    isEmpty() {
      return this.counter === 0
    },
  },
  watch: {
    counter() {
      console.log('Counter value changed')
    },
  },
  created() {
    console.log('Created hook called')
  },
  mounted() {
    console.log('Mounted hook called')
  },
  methods: {
    getParam(param) {
      return param
    },
    emitEvent() {
      this.$emit('event-name')
    },
  },
}
</script>
<template>
  <div class="wrapper">
    <TheComponent />
    <AsyncComponent v-if="object.variable" />
    <div class="static-class-name" :class="{ 'dynamic-class-name': object.variable }">
      动态数据
    </div>
    <button @click="emitEvent">触发事件</button>
  </div>
</template>

<style lang="scss" scoped>
.wrapper {
  font-size: 20px;
}
</style>

使用这种混合方法,需要大量样板代码,并且设置的功能会随着项目越来越大越难以维护。

组合式

Vue3 中引入了 Composition API 。目的自然是提供更灵活的 API 和更好的 TypeScript 支持。这种方法在很大程度上依赖于安装生命周期挂钩(hooks)。

html 复制代码
<script>
import {
  ref,
  reactive,
  defineComponent,
  computed,
  watch,
} from 'vue'

import useMixin from './mixins/componentMixin.js'
import TheComponent from './components/TheComponent.vue'

export default defineComponent({
  name: 'CompositionAPI',
  components: {
    TheComponent,
    AsyncComponent: () => import('./components/AsyncComponent.vue'),
  },
  props: {
    elements: Array,
    counter: {
      type: Number,
      default: 0,
    },
  },
  setup(props, { emit }) {
    console.log('Equivalent to created hook')

    const enabled = ref(true)
    const object = reactive({ variable: false })

    const { mixinData, mixinMethod } = useMixin()

    const isEmpty = computed(() => {
      return props.counter === 0
    })

    watch(
      () => props.counter,
      () => {
        console.log('Counter value changed')
      }
    )

    function emitEvent() {
      emit('event-name')
    }
    function getParam(param) {
      return param
    }

    return {
      object,
      getParam,
      emitEvent,
      isEmpty
    }
  },
  mounted() {
    console.log('Mounted hook called')
  },
})
</script>

<template>
  <div class="wrapper">
    <TheComponent />
    <AsyncComponent v-if="object.variable" />
    <div class="static-class-name" :class="{ 'dynamic-class-name': object.variable }">
      动态数据
    </div>
    <button @click="emitEvent">触发事件</button>
  </div>
</template>

<style scoped>
.wrapper {
  font-size: 20px;
}
</style>

使用组合式的方式可以项目逻辑更加清晰。

script setup

Vue 3.2 中引入了更简洁的语法。通过在 script 标签中添加 setup 属性,脚本部分中的所有内容都会自动暴露给模板。通过这种方式同样可以删除很多样板文件。

html 复制代码
<script setup>
import {
  ref,
  reactive,
  defineAsyncComponent,
  computed,
  watch,
  onMounted,
} from "vue";

import useMixin from "./mixins/componentMixin.js";
import TheComponent from "./components/TheComponent.vue";
const AsyncComponent = defineAsyncComponent(() =>
  import("./components/AsyncComponent.vue")
);

console.log("Equivalent to created hook");
onMounted(() => {
  console.log("Mounted hook called");
});

const enabled = ref(true);
const object = reactive({ variable: false });

const props = defineProps({
  elements: Array,
  counter: {
    type: Number,
    default: 0,
  },
});

const { mixinData, mixinMethod } = useMixin();

const isEmpty = computed(() => {
  return props.counter === 0;
});

watch(() => props.counter, () => {
  console.log("Counter value changed");
});

const emit = defineEmits(["event-name"]);
function emitEvent() {
  emit("event-name");
}
function getParam(param) {
  return param;
}
</script>

<script>
export default {
  name: "ComponentVue3",
};
</script>

<template>
  <div class="wrapper">
    <TheComponent />
    <AsyncComponent v-if="object.variable" />
    <div class="static-class-name" :class="{ 'dynamic-class-name': object.variable }">
      动态数据
    </div>
    <button @click="emitEvent">触发事件</button>
  </div>
</template>

<style scoped>
.wrapper {
  font-size: 20px;
}
</style>

响应性语法糖

2023 年 1 月 26 日更新:这是非常有争议的,因此被删除!不过我们也可以稍微了解一下

以下使用script setup代码片段中演示的内容存在问题:

html 复制代码
<script setup>
import { ref, computed } from 'vue'

const counter = ref(0)
counter.value++

function increase() {
  counter.value++
}

const double = computed(() => {
  return counter.value * 2
})
</script>


<template>
  <div class="wrapper">
    <button @click="increase">Increase</button>
    {{ counter }}
    {{ double }}
  </div>
</template>

使用.value访问反应式计数器感觉不自然,并且是造成混乱和错误输入的常见原因。有一个实验性解决方案利用编译时转换来解决此问题。反应性转换是一个可选的内置步骤,它会自动添加此后缀并使代码看起来更干净。

html 复制代码
<script setup>
import { computed } from 'vue'

let counter = $ref(0)
counter++

function increase() {
  counter++
}

const double = computed(() => {
  return counter * 2
})
</script>


<template>
  <div class="wrapper">
    <button @click="increase">Increase</button>
    {{ counter }}
    {{ double }}
  </div>
</template>

$ref.value需要构建步骤,但消除了访问变量时的必要性。启用后,它在全局范围内可用。

class api

Class API 已经存在很长时间了。通常与 Typescript 搭配使用。并且被认真考虑过作为默认的 Vue 3 语法。但经过多次长时间的讨论后,它被放弃了,取而代之的是 Composition API。它在 Vue 3 中可用,但工具明显缺乏,官方建议放弃它。

html 复制代码
<script lang="ts">
import { Options, Vue } from 'vue-class-component';
  
import AnotherComponent from './components/AnotherComponent.vue'  
  
@Options({
  components: {
    AnotherComponent
  }
})
export default class Counter extends Vue {
  counter = 0;
  
  get double(): number {
    return this.counter * 2;
  }
  increase(): void {
    this.quantity++;
  }
}
</script>


<template>
  <div class="wrapper">
    <button @click="increase">Increase</button>
    {{ counter }}
    {{ double }}
  </div>
</template>
相关推荐
小奶包他干奶奶21 小时前
Webpack学习——Loader(文件转换器)
前端·学习·webpack
zy happy1 天前
若依 vue3 报错:找不到模块“@/api/xxxx/xxxxx”或其相应的类型声明。。Vue 3 can not find mod
前端·javascript·vue.js
潘小安1 天前
Git Worktree + Claude Code:让你的开发效率翻倍的秘密武器
前端
meichaoWen1 天前
【Vue3】vue3的全面学习(一)
前端·javascript·学习
小猪努力学前端1 天前
在 React + React Router v7 SSR 项目里做多端适配,我踩的两个坑
前端·react.js
q***d1731 天前
React桌面应用开发
前端·react.js·前端框架
8***29311 天前
解决 Tomcat 跨域问题 - Tomcat 配置静态文件和 Java Web 服务(Spring MVC Springboot)同时允许跨域
java·前端·spring
0***141 天前
React计算机视觉应用
前端·react.js·计算机视觉
Q***K551 天前
React高级
前端·react.js·前端框架
c***97981 天前
React语音识别案例
前端·react.js·语音识别