Vue3/Nuxt3中的双向绑定和翻页组件的封装与使用

1,142 阅读10分钟

image.png

文章摘要

本文全面探讨了 Vue 3 中 v-model 的使用方法及其高级应用,涵盖了以下关键技术点:

  • 基本用法:介绍了 v-model 在表单输入和应用状态管理中的基础应用,如何通过它实现数据的双向绑定。
  • 自定义组件中的应用:深入讲解了在自定义组件中实现单个或多个 v-model 绑定的方法,以及如何通过这些技巧提升组件的灵活性和复用性。
  • Element-plus 分页组件封装:展示了基于 Element-plus 进一步封装支持多 v-model 绑定的分页组件的过程,通过实例代码演示了封装复用组件的具体步骤和方法。
  • Nuxt3 中的应用:探讨了在 Nuxt3 项目中有效利用 v-model 的策略和最佳实践,以及如何结合 Nuxt3 的特性优化 Vue 应用。

通过本文的介绍,读者将能够深入理解和掌握 v-model 在 Vue 3 中的高效使用方法,以及如何在不同场景下灵活应用,从而为开发更加动态、可复用且性能优异的 Vue 应用打下坚实的基础。

基本用法

在 Vue 3 中,v-model 用于在表单输入和应用状态之间创建双向绑定。这意味着可以很容易地将输入字段(如文本输入、复选框等)与应用程序数据绑定在一起。

例如,创建一个文本输入框,其值与组件的 data 属性绑定:

<template>
  <input v-model="message" placeholder="输入一些文本">
</template>

<script setup>
import { ref } from 'vue'

const message = ref('')
</script>

在这个例子中,message 是一个响应式引用(通过 ref 创建),它与输入框的值双向绑定。当用户在输入框中输入文本时,message 的值将更新,反之亦然。

组件中的 v-model

在自定义组件中使用 v-model 时,Vue 3 提供了更多的灵活性。可以在一个组件上使用多个 v-model 绑定,每个绑定对应组件的不同属性。

单个 v-model

<template>
  <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">
</template>

<script setup>
import { defineProps, defineEmits } from 'vue'

const props = defineProps({
  modelValue: String
})

const emit = defineEmits(['update:modelValue'])
</script>

在这个例子中,组件接受一个名为 modelValue 的 prop,这是 v-model 的默认命名约定。当输入框的值变化时,组件发出一个事件来更新父组件的状态。

多个 v-model

Vue 3 允许在同一个组件上使用多个 v-model。可以通过给 v-model 指定不同的名称来实现:

<template>
  <ChildComponent v-model:title="bookTitle" v-model:author="bookAuthor" />
</template>

<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const bookTitle = ref('未命名书籍')
const bookAuthor = ref('未知作者')
</script>

在子组件中:

<template>
  <input :value="title" @input="$emit('update:title', $event.target.value)">
  <input :value="author" @input="$emit('update:author', $event.target.value)">
</template>

<script setup>
import { defineProps, defineEmits } from 'vue'

const props = defineProps({
  title: String,
  author: String
})

const emit = defineEmits(['update:title', 'update:author'])
</script>

在这个例子中,子组件接受两个 prop(titleauthor),并对每个 prop 使用 v-model 进行双向绑定。这为在单个组件中管理多个双向绑定提供了一种非常灵活的方法。

Vue 3 的 v-model 提供了比 Vue 2 更加灵活和强大的双向数据绑定功能,特别是在自定义组件的使用上。通过利用这些技术,可以创建更加动态和可复用的 Vue 应用程序。

基于Element-plus进一步封装分页组件(ts+setup)

要封装一个支持多个 v-model 绑定的 Vue 3 组件,可以遵循 Vue 3 的自定义组件 v-model 绑定规则。在el官方提供的例子中,el-pagination 组件使用了两个 v-model 绑定:v-model:current-pagev-model:page-size。我们将创建一个封装了这些功能的自定义分页组件。

步骤 1: 创建自定义分页组件

首先,创建一个新的 Vue 组件,比如命名为 BasePagination.vue。在这个组件中,需要定义接受的 props 和发出的事件,以实现与父组件的双向绑定。

nuxt中可以在如下路径新建组件components/base/pagination/index.vue

nuxt会自动帮你导入并直接可以在页面、其他组件、layout等地方使用<BasePagination />

<template>
  <div class="base-pagination">
    <el-pagination
      v-model:current-page="current.page"
      v-model:page-size="current.size"
      :page-sizes="sizes"
      :small="small"
      :disabled="disabled"
      :background="background"
      layout="total, prev, pager, next, jumper,sizes"
      :total="total"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
  </div>
</template>

<script lang="ts" setup>
const props = withDefaults(
  defineProps<{
    current?: { page: number; size: number };
    sizes?: number[];
    small?: boolean;
    disabled?: boolean;
    background?: boolean;
    total: number;
  }>(),
  {
    current: () => ({ page: 1, size: 20 }),
    sizes: () => [10, 20, 30, 50, 100],
  }
);

const emit = defineEmits(["update:current"]);

const handleSizeChange = (newPageSize: number) => {
  // 当页面大小变化时,同时保持当前页不变
  const updatedCurrent = { ...props.current, size: newPageSize };
  emit("update:current", updatedCurrent);
};

const handleCurrentChange = (newCurrentPage: number) => {
  // 当当前页变化时,保持页面大小不变
  const updatedCurrent = { ...props.current, page: newCurrentPage };
  emit("update:current", updatedCurrent);
};
</script>

<style scoped>
.base-pagination {
  width: 100%;
  text-align: center;
  background-color: #fff;
  padding: 5px 15px;
}
.demo-pagination-block + .demo-pagination-block {
  margin-top: 10px;
}
.demo-pagination-block .demonstration {
  margin-bottom: 16px;
}
</style>
  • <template> 部分:定义了组件的 HTML 结构,使用了 Element Plus 的 <el-pagination> 组件来实现分页功能。通过 v-model:current-pagev-model:page-size 实现了与父组件的双向绑定。
  • <script lang="ts" setup> 部分
    • 使用 defineProps 定义接收的 props,包括当前页码和页面大小的对象 current,页面大小选项 sizes,以及其他几个布尔值属性来控制分页组件的显示。
    • withDefaults 是用来为这些 props 提供默认值的函数。这意味着,如果父组件没有为这些 props 提供值,那么将使用 withDefaults 中指定的默认值。
    • defineEmits 定义了组件可以发出的事件,这里定义了一个 update:current 事件,用来在页面大小或当前页变化时通知父组件。
    • handleSizeChangehandleCurrentChange 是事件处理函数,分别在页面大小变化和当前页变化时被调用,它们通过发出 update:current 事件来更新父组件的状态。

关于withDefaults

withDefaults 是 Vue 3 Composition API 中的一个工具函数,用于为 defineProps 函数定义的组件属性(props)提供默认值。这在创建可复用组件时特别有用,因为它允许组件开发者为某些可能不会由父组件传递的属性指定一个回退值。

当你使用 defineProps 定义组件的属性时,你可以链式调用 withDefaults 来为这些属性指定默认值。这样,如果父组件没有为这些属性提供值,组件就会使用 withDefaults 指定的默认值

setup 在 Vue 和 Nuxt 3 中的区别

setup 函数是 Vue 3 引入的一个新的组件选项,用于使用 Composition API 创建组件的逻辑和状态。在 Nuxt 3 中,setup 函数也被采用,并在此基础上进行了扩展,以更好地支持服务端渲染(SSR)和静态站点生成(SSG)等特性。下面我们来详细梳理 setup 在 Vue 3 和 Nuxt 3 中的使用和区别。

Vue 3 中的 setup
  • 执行时机:在组件创建之前执行,这是所有组合式 API 逻辑开始的地方,用于定义响应式状态、计算属性、方法等。
  • 执行环境:仅在客户端执行。在服务端渲染(SSR)的情况下,setup 会在服务端执行一次以进行初始渲染,然后在客户端执行以激活组件。
  • 功能:通过 setup,可以访问到组件的 props 和 context(包括 attrs、slots 等),并返回一个对象,其中包含了可以在模板中使用的所有响应式状态和方法。
  • 限制:无法直接访问 this 上下文,因为 setup 是在组件选项处理之前被调用的。
Nuxt 3 中的 setup
  • 执行时机和环境:与 Vue 3 类似,但 Nuxt 3 为 setup 提供了更多的服务端和客户端特定的 API,以支持更复杂的 SSR 和 SSG 场景。Nuxt 3 的 setup 函数可以在服务端执行以进行初始渲染,也可以在客户端执行以处理客户端逻辑或在路由导航时重新获取数据。
  • Nuxt 3 特有的 API:Nuxt 3 在 setup 函数中提供了许多特有的 Composition API,如 useNuxtAppuseAsyncDatauseFetch 等,这些 API 旨在简化数据获取、状态管理和与 Nuxt 应用生命周期相关的操作。
  • 页面和路由:在 Nuxt 3 中,setup 函数还可以用于定义页面级别的逻辑,比如通过 useAsyncDatauseFetch 在服务端获取数据,并将这些数据预先渲染到页面中,以优化性能和SEO。
  • 服务端特有的操作:Nuxt 3 提供了一系列的 API 和钩子,使得在 setup 中执行服务端特有的操作(比如访问请求头信息)变得更加容易。这些操作通常需要通过服务端特有的上下文来实现,而 Nuxt 3 提供的 Composition API 已经封装了这部分逻辑,使得开发者可以更专注于业务逻辑。

总结来说,虽然 Nuxt 3 在 setup 函数的基本概念上与 Vue 3 保持一致,但它通过提供额外的 API 和钩子,扩展了 setup 的能力,以更好地支持服务端渲染、数据获取和应用状态管理。这些扩展使得在 Nuxt 3 应用中使用 setup 时,能够更灵活地处理客户端和服务端逻辑,优化应用性能和用户体验。

步骤 2: 在父组件中使用自定义分页组件

在父组件中,可以像使用任何其他组件一样使用 BasePagination 组件,并利用 v-model 绑定来同步当前页和页面大小。

<template>
  <!-- 其他组件巴拉巴拉 -->
  <!-- 分页 -->
  <BasePagination
    v-model:current="pagination.page"
    :total="pagination.total"
    @update:current="updatePagination"
  />
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import BasePagination from './BasePagination.vue'; // Nuxt3中是自动引入的,这行不用写
    
const route = useRoute();
const router = useRouter();
    
const pagination = reactive({
  total: 100,
  page: {
    page: Number(route.query.page) || 1,
    size: Number(Number(route.query.size)) || 20,
  },
});
const updatePagination = (newCurrent: { page: number; size: number }) => {
  console.log(newCurrent);
  pagination.page = newCurrent;
  // 更新 URL,但不刷新页面
  router.push({
    query: {
      ...router.currentRoute.value.query,
      page: newCurrent.page.toString(),
      size: newCurrent.size.toString(),
    },
  });
};

</script>

这样,就创建了一个支持多个 v-model 绑定的自定义分页组件。通过这种方式,可以将 el-pagination 组件的功能封装在 BasePagination 组件内,同时保持双向绑定的灵活性和简洁性。这种封装方法不仅使得组件易于管理和复用,而且还保持了 Vue 组件开发的最佳实践。

总结

详细介绍了 Vue 3 中 v-model 的使用方法,包括其在基本用法和自定义组件中的应用。Vue 3 通过引入 Composition API,不仅提高了代码的组织和复用性,还保留了 Vue 2 中流行的双向绑定特性,并在此基础上进行了增强。文章首先解释了 v-model 在基本用法中的应用,如何通过它实现表单输入和应用状态之间的双向绑定。随后,文章深入探讨了在自定义组件中使用 v-model 的高级用法,包括单个和多个 v-model 的使用场景,并通过示例代码展示了如何实现。

此外,文章还介绍了如何基于 Element-plus 进一步封装分页组件,展示了创建支持多个 v-model 绑定的 Vue 3 组件的步骤。通过详细的代码示例,文章展示了如何定义接受的 props、发出的事件以及如何在父组件中使用自定义分页组件,实现与父组件的双向绑定。

总结而言,文章通过详细的介绍和示例代码,全面展示了 Vue 3 中 v-model 的强大功能和灵活性,特别是在自定义组件和封装复用组件方面的应用,为开发者提供了创建更加动态和可复用的 Vue 应用程序的有效指导。