vue 从入门到入土---复习 组件基础下

115 阅读7分钟

vue 从入门到入土---复习 组件基础下

目录

[TOC]

props 验证

1. 什么是 props 验证

指的是 在封装组件时对外界传递过来的 props 数据进行合法性的校验 从而防止数据不合法的问题

使用数组类型的 props 节点的缺点:无法为每个 prop 指定具体的数据类型

2. 对象类型 的 props 节点

使用对象类型的 props 节点,可以对每个 prop 进行数据类型的校验,示意图如下

3. props 验证

对象类型的 props 节点提供了多种数据验证方案,例如

  • ① 基础的类型检查
  • ② 多个可能的类型
  • ③ 必填项校验
  • ④ 属性默认值
  • ⑤ 自定义验证函数

3.1 基础的类型检查

可以直接为组件的 prop 属性指定基础的校验类型从而防止组件的使用者为其绑定错误类型的数据

3.2 多个可能的类型

如果某个 prop 属性值的类型不唯一,此时可以通过数组的形式,为其指定多个可能的类型

3.3 必填项校验

如果组件的某个 prop 属性是必填项,必须让组件的使用者为其传递属性的值。此时,可以通过如下的方式将 其设置为必填项:

3.4 属性默认值

在封装组件时,可以为某个 prop 属性指定默认值。示例代码如下:

3.5 自定义验证函数

在封装组件时 可以为 prop 属性指定自定义的验证函数从而对 prop 属性的值进行更加精确的控制

计算属性

1. 什么是计算属性

计算属性本质上就是一个function 函数,它可以实时监听data 中数据的变化,并return 一个计算后的新值, 供组件渲染 DOM 时使用

2. 如何声明计算属性 计算属性需要以function 函数的形式声明到组件的computed 选项中,示例代码如下:

注意:计算属性侧重于得到一个计算的结果,因此计算属性中必须有 return 返回值

3. 计算属性的 使用注意点

  • ① 计算属性必须定义在 computed 节点中
  • ② 计算属性必须是一个 function 函数
  • ③ 计算属性必须有返回值
  • ④ 计算属性必须当做普通属性使用

4. 计算属性 vs 方法

相对于方法来说,计算属性会缓存计算的结果,只有计算属性的依赖项发生变化时,才会重新进行运算。因此 计算属性的性能更好:

5. 计算属性案例

案例需求,使用计算属性动态计算:

  • ① 已勾选的商品总个数
  • ② 已勾选的商品总价
  • ③ 结算按钮的禁用状态

自定义事件

1.什么是自定义事件

在封装组件时 为了让组件的使用者可以监听到组件内状态的变化此时需要用到组件的自定义事件

**2. 自定义事件的 3 个使用步骤**

在封装组件时:

  • 声明自定义事件
  • 触发自定义事件

在使用组件时:

监听自定义事件

2.1 声明 自定义事件 开发者为自定义组件封装的自定义事件,必须事先在emits节点中声明,示例代码如下:

2.2 触发 自定义事件

emits节点下声明的自定义事件,可以通过this.$emit('自定义事件的名称')方法进行触发

2.3 监听 自定义事件

在调用this.$emit()方法触发自定义事件时,可以通过第 2 个参数为自定义事件传参

组件上的 v-model

1.为什么需要在组件上使用 v-model

v-model 是双向数据绑定指令 当需要维护组件内外数据的同步时 可以在组件上使用 v-model 指令

  • 外界数据的变化自动同步到 counter 组件中
  • counter 组件中数据的变化,也会自动同步到外界

2. 在组件上使用 v-model 的步骤

任务列表案例

1. 案例效果

2. 用到的知识点

  • ① vite 创建项目
  • ② 组件的封装与注册
  • ③ props
  • ④ 样式绑定
  • ⑤ 计算属性
  • ⑥ 自定义事件
  • ⑦ 组件上的 v-model

3. 整体实现步骤

1. 初始化项目

  • 1. 在终端运行以下的命令,初始化 vite项目:

  • 2. 使用 vscode打开项目,并安装依赖项:

  • 3. 安装 less语法相关的依赖项:

npm init vite-app todos
npm install
npm i less -D

2. 梳理项目结构 1. 重置 index.css中的全局样式如下:

:root {
  font-size: 12px;
}
 
body {
  padding: 8px;
}

2. 重置App.vue组件的代码结构如下:

<template>
  <h1>app 根组件</h1>
</template>
 
<script>
export default {
  name: "MyApp",
  data() {
    return {
      //任务列表数据
      todolist: [
        { id: 1, task: "周一早晨9点开会", done: false },
        { id: 2, task: "周一晚上8点聚餐", done: false },
        { id: 3, task: "准备周三上午的演讲稿", done: true },
      ],
    };
  },
};
</script>
 
<style lang="less" scoped>
</style>

. 3.删除components目录下的HelloWorld.vue组件 4.在终端运行以下的命令,npmrun dev

3. 封装 todo-list 组件 1. 在src/components/todo-list/目录下新建TodoList.vue组件

<template>
  <div></div>
</template>
 
<script>
export default {
  name: "TodoList",
};
</script>
 
<style lang="less" scoped>
</style>

2.在App.vue组件中导入并注册TodoList.vue组件:

// 导入 TodoList 组件
import TodoList from './components/todo-list/TodoList.vue'
export default {
 name: 'MyApp',
 // 注册私有组件
 components: {
 TodoList,
 },
}

3.App.vuetemplate模板结构中使用注册的TodoList组件:

<template>
 <div>
 <h1>App 根组件</h1>
 <hr />
 
 <!-- 使用 todo-list 组件 -->
 <todo-list></todo-list>
 </div>
</template>

3.2 基于 bootstrap 渲染列表组件 1. 将资料目录下的css文件夹拷贝到src/assets/静态资源目录中。 2. 在main.js入口文件中,导入src/assets/css/bootstrap.css样式表:

import { createApp } from 'vue'
import App from './App.vue'
// 导入 bootstrap.css 样式表
import './assets/css/bootstrap.css'
import './index.css'
createApp(App).mount('#app')

3. 根据 bootstrap 提供的渲染列表组件的基本效果: 列表组 v4.bootcss.com/docs/compon… 复选框 v4.bootcss.com/docs/compon…

<template>
 <ul class="list-group">
 <li class="list-group-item d-flex justify-content-between
align-items-center">
 <!-- 复选框 -->
 <div class="custom-control custom-checkbox">
 <input type="checkbox" class="custom-control-input"
id="customCheck1" />
 <label class="custom-control-label"
for="customCheck1">Check this custom checkbox</label>
 </div>
 <!-- badge 效果 -->
 <span class="badge badge-success badge-pill">完成</span>
 <span class="badge badge-warning badge-pill">未完成</span>
 </li>
 </ul>
</template> 

3.3 TodoList 声明 props 属性

1. 为了接受外界传递过来的列表数据,需要在TodoList组件中声明如下的props属性:

export default {
 name: 'TodoList',
   props: {
    list: {
      type: Array,
      required: true,
      default: [],
    },
  },
};

2.在App组件中通过list属性,将数据传递到TodoList组件之中:

<todo-list :list="todolist"></todo-list>

3.4渲染列表的DOM结构 1. 通过 v-for指令,循环渲染列表的DOM结构:

<template>
 <ul class="list-group">
 <li class="list-group-item d-flex justify-content-between
align-items-center" v-for="item in list" :key="item.id">
 <!-- 复选框 -->
 <div class="custom-control custom-checkbox">
 <input type="checkbox" class="custom-control-input"
:id="item.id" />
 <label class="custom-control-label" :for="item.id">{{
item.task }}</label>
 </div>
 <!-- badge 效果 -->
 <span class="badge badge-success badge-pill">完成</span>
 <span class="badge badge-warning badge-pill">未完成</span>
 </li>
 </ul>
</template> 

2. 通过 v-ifv-else指令,按需渲染badge效果:

<!-- badge 效果 -->
<span class="badge badge-success badge-pill" v-if="item.done">完成
</span> <span class="badge badge-warning badge-pill" v-else>未完成</span>

3. 通过 v-model指令,双向绑定任务的完成状态:

<!-- 复选框 -->
<input type="checkbox" class="custom-control-input" :id="item.id"
v-model="item.done" />
<!-- 注意:App 父组件通过 props 传递过来的 list 是“引用类型”的数
据, -->
<!-- 这里 v-model 双向绑定的结果是:用户的操作修改的是 App 组件中数
据的状态 -->

4. 通过 v-bind属性绑定,动态切换元素的class类名:

<label class="custom-control-label" :class="item.done ? 'delete' :
''" :for="item.id">{{ item.task }}</label>

TodoList组件中声明如下的样式,美化当前组件的UI结构:

// 为列表设置固定宽度
.list-group {
 width: 400px; }
// 删除效果
.delete {
 text-decoration: line-through; }

4. 封装 todo-input 组件

4.1 创建并注册 TodoInput 组件 1. 在src/components/todo-input/目录下新建TodoInput.vue组件:

<template>
 <div>TodoInput 组件</div>
</template> <script>
export default {
 name: 'TodoInput', }
</script> <style lang="less" scoped></style> 

2. 在App.vue组件中导入并注册TodoInput.vue组件:

// 导入 TodoList 组件
import TodoList from './components/todo-list/TodoList.vue'
// 导入 TodoInput 组件
import TodoInput from './components/todo-input/TodoInput.vue'
export default {
 name: 'MyApp',
 // 注册私有组件
 components: {
 TodoList,
 TodoInput,
 },
} 

3.App.vuetemplate模板结构中使用注册的TodoInput组件:

<template>
 <div>
 <h1>App 根组件</h1>
 <hr />
 <!-- 使用 TodoInput 组件 -->
 <todo-input></todo-input>
 <!-- 使用 TodoList 组件 -->
 <todo-list :list="todolist" class="mt-2"></todo-list>
 </div>
</template> 

4.2 基于 bootstrap 渲染组件结构 1.根据bootstrap提供的 inline-forms渲染TodoInput组件的基本结构。 v4.bootcss.com/docs/compon… line-forms

<template>
  <!-- form 表单 -->
  <form class="form-inline">
    <div class="input-group mb-2 mr-sm-2">
      <!-- 输入框的前缀 -->
      <div class="input-group-prepend">
        <div class="input-group-text">任务</div>
      </div>
      <!-- 文本输入框 -->
      <input
        type="text"
        class="form-control"
        placeholder="请填写任
务信息"
        style="width: 356px"
      />
    </div>
 
    <!-- 添加按钮 -->
    <button type="submit" class="btn btn-primary mb-2">添加新任务</button>
  </form>
</template> 

4.3 通过自定义事件向外传递数据 1. 在TodoList组件的data中声明如下的数据:

data() {
 return {
 // 新任务的名称
 taskname: '',
 }
} 

2. 为input输入框进行v-model的双向数据绑定:

<input type="text" class="form-control" placeholder="请填写任务信
息" style="width: 356px" v-model.trim="taskname" />

3. 监听form表单的submit事件,阻止默认提交行为并指定事件处理函数:

<form class="form-inline" @submit.prevent="onFormSubmit"></form>

4. 在methods中声明onFormSubmit事件处理函数如下:

methods: {
 // 表单提交的事件处理函数
 onFormSubmit() {
 // 1. 判断任务名称是否为空
 if (!this.taskname) return alert('任务名称不能为空!')
 // 2. 触发自定义的 add 事件,并向外界传递数据
 // 3. 清空文本框
 },
}

5. 声明自定义事件如下:

export default {
 name: 'TodoInput',
 // 声明自定义事件
 emits: ['add'],
}

6. 进一步完善onFormSubmit事件处理函数如下:

methods: {
 // 表单提交的事件处理函数
 onFormSubmit() {
 // 1. 判断任务名称是否为空
 if (!this.taskname) return alert('任务名称不能为空!')
 // 2. 触发自定义的 add 事件,并向外界传递数据
 this.$emit('add', this.taskname)
 // 3. 清空文本框
 this.taskname = ''
 },
},

4.4 实现添加任务的功能 1.App.vue组件中监听TodoInput组件自定义的add事件

<!-- 使用 TodoInput 组件 -->
<!-- 监听 TodoInput 的 add 自定义事件 -->
<todo-input @add="onAddNewTask"></todo-input>

2. 在App.vue组件的data中声明nextId来模拟id自增+1的操作:

data() {
 return {
 // 任务列表的数据
 todolist: [
 { id: 1, task: '周一早晨9点开会', done: false },
 { id: 2, task: '周一晚上8点聚餐', done: false },
 { id: 3, task: '准备周三上午的演讲稿', done: true },
 ],
 // 下一个可用的 Id 值
 nextId: 4,
 }
},

3. 在App.vue组件的methods中声明onAddNewTask事件处理函数如下:

methods: {
 // TodoInput 组件 add 事件的处理函数
 onAddNewTask(taskname) {
 // 1. 向任务列表中新增任务信息
 this.todolist.push({
 id: this.nextId,
 task: taskname,
 done: false, // 完成状态默认为 false
 })
 
 // 2. 让 nextId 自增+1
 this.nextId++
 },
},

5. 封装 todo-button 组件 5.1 创建并注册 TodoButton 组件 1. 在src/components/todo-button/目录下新建TodoButton.vue组件:

<template>
 <div>TodoButton 组件</div>
</template> <script>
export default {
 name: 'TodoButton', }
</script> <style lang="less" scoped></style>

2.在App.vue组件中导入并注册TodoButton.vue组件:

// 导入 TodoList 组件
import TodoList from './components/todo-list/TodoList.vue'
// 导入 TodoInput 组件
import TodoInput from './components/todo-input/TodoInput.vue'
// 导入 TodoButton 组件
import TodoButton from './components/todo-button/TodoButton.vue'
export default {
 name: 'MyApp',
 // 注册私有组件
 components: {
 TodoList,
 TodoInput,
 TodoButton
 },
}

5.2 基于 bootstrap 渲染组件结构 1.根据bootstrap提供的 Button group渲染Todobutton组件的基本结构。 v4.bootcss.com/docs/compon… 2.TodoButton组件中渲染如下的DOM结构

<template>
  <div class="button-container mt-3">
    <div class="btn-group">
      <button type="button" class="btn btn-primary">全部</button>
      <button type="button" class="btn btn-secondary">已完成</button>
      <button type="button" class="btn btn-secondary">未完成</button>
    </div>
  </div>
</template>

3.并通过button-container类名美化组件的样式:

.button-container {
 // 添加固定宽度
 width: 400px;
 // 文本居中效果
 text-align: center; }

5.3 通过 props 指定默认激活的按钮 TodoButton组件中声明如下的props,用来指定默认激活的按钮的索引:

 name: 'TodoButton',
 props: {
 // 激活项的索引值
 active: {
 type: Number,
 required: true,
 // 默认激活索引值为 0 的按钮(全部:0,已完成:1,未完成:2)
 default: 0,
 },
 },
}

2. 通过 动态绑定 class 类名 的方式控制按钮的激活状态:

<template>
 <div class="button-container mt-3">
 <div class="btn-group">
 <button type="button" class="btn" :class="active === 0 ?
'btn-primary' : 'btn-secondary'">全部</button>
 <button type="button" class="btn" :class="active === 1 ?
'btn-primary' : 'btn-secondary'">已完成</button>
 <button type="button" class="btn" :class="active === 2 ?
'btn-primary' : 'btn-secondary'">未完成</button>
 </div>
 </div>
</template>

3.App组件中声明 默认激活项的索引 ,并通过 属性绑定 的方式传递给TodoButton组 件

data() {
 return {
 // 激活的按钮的索引
 activeBtnIndex: 0
 }
}
<!-- 使用 TodoButton 组件 -->
<todo-button :active="activeBtnIndex"></todo-button> 1

5.4 通过 v-model 更新激活项的索引

需求分析: -> 通过props传递了激活项的索引(active -> 需要更新父组件中激活项的索引 这种场景下适合 在组件上使用 v-model 指令 ,维护 组件内外数据的同步

1.TodoButton组件中的三个按钮分别绑定click事件处理函数如下:

<button type="button" class="btn" :class="active === 0 ? 'btnprimary' : 'btn-secondary'" @click="onBtnClick(0)">
 全部
</button> <button type="button" class="btn" :class="active === 1 ? 'btnprimary' : 'btn-secondary'" @click="onBtnClick(1)">
 已完成
</button> <button type="button" class="btn" :class="active === 2 ? 'btnprimary' : 'btn-secondary'" @click="onBtnClick(2)">
 未完成
</button> 

2.TodoButton组件中声明如下的自定义事件,用来更新父组件通过v-model指令传递过来 props数据:

export default {
 name: 'TodoButton',
 // 声明和 v-model 相关的自定义事件
 emits: ['update:active'],
 props: {
 // 激活项的索引值
 active: {
 type: Number,
 required: true,
 default: 0,
 },
 },
}

3.在TodoButton组件的methods节点中声明onBtnClick事件处理函数如下:

methods: {
 // 按钮的点击事件处理函数
 onBtnClick(index) {
 // 1. 如果当前点击的按钮的索引值,等于 props 传递过来的索引值,
则没必要触发 update:active 自定义事件
 if (index === this.active) return
 // 2. 通过 this.$emit() 方法触发自定义事件
 this.$emit('update:active', index)
 },
},
12345678

5.5 通过计算属性动态切换列表的数据

需求分析: 点击不同的按钮,切换显示不同的列表数据。此时可以根据当前激活按钮的索引,动态计算出要显 示的列表数据并返回即可!

1. 在App根组件中声明如下的计算属性:

computed: {
 // 根据激活按钮的索引值,动态计算要展示的列表数据
 tasklist() {
 // 对“源数据”进行 switch...case 的匹配,并返回“计算之后的结果”
 switch (this.activeBtnIndex) {
 case 0: // 全部
 return this.todolist
 case 1: // 已完成
 return this.todolist.filter(x => x.done)
 case 2: // 未完成
 return this.todolist.filter(x => !x.done)
 }
 },
},

2. 在App根组件的DOM结构中,将TodoList组件的:list="todolist"修改为:

<!-- 使用 TodoList 组件 -->
<todo-list :list="tasklist" class="mt-2"></todo-list> 1

总结

① 能够知道如何对 props 进行验证

数组格式、对象格式

typedefaultrequired、validator

② 能够知道如何使用计算属性

computed 节点、必须 return 一个结果、缓存计算结果

③ 能够知道如何为组件绑定自定义事件

v-on 绑定自定义事件、emits$emit()

④ 能够知道如何在组件上使用 v-model

应用场景:实现组件内外的数据同步

v-model:props名称、emits、$emit('update:props名称')