Vue2学习内容(上)

什么是vue

一套用于构建用户界面的渐进式JavaScript框架

渐进式:Vue可以自底向上逐层的应用

vue的特点

  1. 采用组件化模式,提高代码复用率,且让代码更好维护
  2. 声明式编码,让编码人员无需操作DOM,提高开发效率
  3. 使用虚拟DOM+优秀的Diff算法,尽量复用DOM节点

初识Vue

  1. 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
  2. root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法
  3. root容器里的代码被称为Vue模板
  4. Vue实例和容器是一一对应的
  5. 真实开发中只有一个Vue实例,并且会配合着组件一起使用
  6. {{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性
  7. 一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新
html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
    <!-- 准备好一个容器 -->
    <div id="root">
        <h1>Hello,{{name}},{{Date.now()}}</h1>
    </div>
    
    <script type="text/javascript">
        Vue.config.productionTip = false

        //创建vue实例
        const x = new Vue({
            el: '#root',//el用于指定为那个容器服务,值通常为css选择器字符串
            data: {//data用于存储数据,数据供el所指定的容器去使用,值暂时写成一个对象
                name: 'Vue'
            }

        })

    </script>
</body>

</html>

注意区分js表达式和js代码:

  1. 表达式:一个表达式会生成一个值,可以放在任何一个需要值的地方
  2. js代码(语句)
    (1) if(){}
    (2) for(){}

模板语法

  1. 插值语法

功能:用于解析标签体内容

写法:{{xxx}},xxx是js代码,且可以直接读取到data中的所有属性

  1. 指令语法

功能:用于解析标签(包括:标签属性、标签体内容、绑定事件)

举例:v-bind:href="xxx"或 简写为 :href="xxx",xxx同样要写js表达式,且可以直接读取到data中的所有属性

备注:Vue中有很多的指令,且形式都是:v-???,比如v-bind

数据绑定

Vue中有两种数据绑定的方式:

  1. 单向绑定:(v-bind)数据只能从data流向页面

  2. 双向绑定:(v-model)数据不仅能从data流向页面,还可以从页面流向data

    备注:

    1. 双向绑定一般都应在表单类元素上
    2. v-model:value 可以简写为v-model
  3. 常见指令

    指令 作用 示例

    v-if 条件渲染 v-if="isShow"

    v-else 否则分支 v-else

    v-show 控制显示与隐藏 v-show="isShow"

    v-for 遍历列表 v-for="item in list"

    v-bind 绑定属性 :title="title"

    v-on 绑定事件 @click="handleClick"

    v-model 双向绑定 v-model="username"

    v-html 渲染HTML v-html="html"

    v-text 设置纯文本内容

    v-cloak 编译前隐藏模板

    v-pre 跳过Vue编译

el与data的两种写法

  1. el的两种写法
  • new Vue时候配置el属性
  • 先创建Vue实例,随后再通过vm.$mount('#root')指定el的值
  1. data的两种写法
  • 对象式
  • 函数式
    如何选择:目前哪种写法都可以,学到组件时,data必须使用函数式,否则会报错
  1. 一个重要的原则:
    由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了

object.defineProperty方法

  1. enumerable: true,//控制属性是否可以枚举(遍历),默认值false
  2. writable: true,//控制属性是否可以被修改,默认值为false
  3. configurable: true,//控制属性是否可以被删除,默认值为false
  4. get函数:当有人读取person的age属性时,get函数(getter)就会被调用,其返回值就是get的值
  5. set函数://当有人修改person的属性时,set函数(setter)就会被调用

数据代理

通过一个对象代理对另一个对象中属性的操作(读/写)

  1. Vue中的数据代理:
    通过vm对象来代理data对象中属性的操作(读/写)
  2. Vue中数据代理的好处:
    更加方便的操作data中的数据
  3. 基本原理:
    通过Object.defineProperty()把data对象中所有属性添加到vm上
    为每一个添加到vm上的属性,都指定一个getter/setter
    在getter/setter内部去操作(读/写)data中对应的属性

事件处理

事件的基本使用:

  1. 使用v-on:xxx 或@xxx绑定事件,其中xxx是事件名
  2. 事件的回调需要配置在methods对象中,最终会在vm上
  3. methods中配置的函数,不要用箭头函数,否则this就不是vm而是windows
  4. methods中配置的函数,都是被VUE所管理的函数,this的指向是vm或组件实例对象
  5. @click="demo" 和 @click = "demo($event)"效果一致,但后者可以传参

Vue中的事件修饰符

  1. prevent:阻止默认事件
  2. stop:阻止事件冒泡
  3. once:事件只触发一次
  4. capture:使用事件的捕获模式
  5. self:只有event.target是当前操作的元素是才触发事件
  6. passive:事件的默认行为立即执行,无需等待

计算属性computed与监听属性watch

computed:根据已有数据计算出一个结果

watch:监听某个数据的变化,并在变化时执行相应的操作

computed适合计算并返回结果,methods适合执行操作,watch适合在数据变化时执行逻辑

响应式属性--Vue.set()

js 复制代码
Vue.set(object, propertyName, value)

Vue2对对象后来新增的属性,不能自动建立响应式监测

数组下标更新

在 Vue 2 中,直接通过数组下标修改元素,Vue 无法可靠地检测到这种变化,通常使用两种方法解决:

  1. splice(0,1,'小赵')

    先删除原位置的,再插入到原位置

  2. $set(this.students,0,'小赵')

    直接设置元素

Vue.set() 与 this.$set() 的使用

  1. 区别
  • Vue.set(object,key,value):通过Vue构造函数调用
  • this.$set(object,key,value):通过当前Vue实例调用
  1. this.$set() 对象新增属性,通过数组下标修改元素

过滤器filters

通常用于格式化显示内容,不负责修改原始数据

html 复制代码
<p>{{message | hello}}</p>
  1. 局部过滤
js 复制代码
new Vue({
  filters:{
    hello(value) {
      return value + '!'
    }
  }
})
  1. 全局过滤
js 复制代码
Vue.filter('hello',function(value)  {
  return value + '!'
})
  • 局部过滤器写在 filters 中
  • 全局过滤器使用 Vue.filter('名称', 函数) 注册
  • value 是传入过滤器的第一个参数
  • 可以通过 {{ message | format('参数') }} 传递额外参数
    Vue 3 已移除过滤器功能,通常改用方法或计算属性

自定义指令

  1. 自定义指令用于封装直接操作 DOM 的行为。例如,让输入框在元素插入页面后自动获得焦点
  2. Vue 2 中可以使用 Vue.directive() 注册全局指令,也可以在组件中通directives 注册局部指令

学生管理页面知识总结

列表渲染与数据管理

  1. v-for:遍历数组
    v-for 可以把数组中的数据逐条显示在页面上
js 复制代码
<li v-for="(student, index) in userList" :key="student.id">
  {{ index + 1 }}. {{ student.name }}
</li>
  • student:当前遍历到的学生对象
  • index:当前元素的下标,从 0 开始
  • student.id:每个学生的唯一标识
  • index + 1:让页面序号从 1 开始显示
  1. 为什么要使用 key?
js 复制代码
:key="student.id"

key 用来帮助 Vue 识别列表中的每一项

重点:当列表会添加、删除或重新排序时,优先使用稳定且唯一的 ID,不要随意使用 index 作为 key

  1. 学生数据从字符串升级为对象
    最初可能是:
js 复制代码
userList: ['张三', '李四']

后来升级为:

js 复制代码
userList: [
  { id: 1, name: '张三' },
  { id: 2, name: '李四' }
]

对象可以同时保存学生的多个属性,比如 id、name、age 等

添加学生时,可以用 nextId 生成唯一 ID:

js 复制代码
this.userList.push({
  id: this.nextId++,
  name: this.username.trim()
})

其中:

  • push():向数组末尾添加元素
  • trim():去掉字符串首尾的空格
  • this.nextId++:取出当前 ID,再将 ID 加一

添加、删除与搜索

  1. 添加学生:v-model + @click
js 复制代码
<input v-model="username" placeholder="请输入学生姓名">
<button @click="add">添加</button>
  • v-model:实现表单输入与 Vue 数据的双向绑定
  • @click="add":点击按钮时调用 add 方法
    添加前先检查输入是否为空:
js 复制代码
if (this.username.trim() === '') {
  alert('姓名为空,请重新输入')
  return
}

这里的 return 用来提前结束函数,避免添加空姓名

添加完成后:

js 复制代码
this.username = ''

清空输入框

  1. 删除学生:splice() 与 findIndex()
    在没有搜索过滤时,可以根据下标删除:
js 复制代码
this.userList.splice(index, 1)

但当列表经过搜索过滤后,显示列表的下标不一定等于原数组的下标

所以我们现在采用 ID 删除:

js 复制代码
del: function (id) {
  var index = this.userList.findIndex(function (student) {
    return student.id === id
  })

  if (index !== -1) {
    this.userList.splice(index, 1)
  }
}

记住这两个方法:

  • findIndex():找到满足条件的元素下标;找不到返回 -1
  • splice(index, 1):从数组的指定下标删除一个元素
    核心原则: 搜索后的列表可以用于展示,但删除时要准确定位原数组中的学生
  1. 搜索学生:computed + filter()
    搜索框:
js 复制代码
<input v-model="search" placeholder="请输入学生姓名">

计算属性:

js 复制代码
computed: {
  filterstudents: function () {
    if (this.search === '') {
      return this.userList
    }

    var keyword = this.search

    return this.userList.filter(function (student) {
      return student.name.includes(keyword)
    })
  }
}

这里有三个关键点:

  • computed:根据已有数据计算出需要的结果
  • filter():筛选出符合条件的数组元素,返回一个新数组
  • includes():判断字符串中是否包含指定内容
    例如,搜索关键词为"张",那么"张三""张小明"都能被筛选出来

小优化:为了让只输入空格时也显示全部学生,可以把搜索关键词处理为:

js 复制代码
var keyword = this.search.trim()

然后判断 keyword === ''

  1. computed 和 methods 有什么区别?

对比 computed methods

主要用途 计算并返回结果 执行操作或处理事件

典型例子 搜索过滤后的学生列表 添加学生、删除学生

缓存 有缓存,依赖数据不变时可复用结果 调用时通常重新执行

简单记忆:计算结果用 computed,点击后执行操作用 methods

条件渲染与统计

  1. v-if 和 v-else-if
    可以根据不同条件显示不同提示:
js 复制代码
<p v-if="userList.length === 0">
  暂无学生,请先添加
</p>

<p v-else-if="filterstudents.length === 0">
  没有找到匹配学生
</p>

<p v-else>
  当前学生人数:{{ filterstudents.length }}
  学生总人数:{{ userList.length }}
</p>

这三个条件按顺序判断:

  • 原始数组为空:还没有学生
  • 原始数组不为空,但搜索结果为空:没有匹配项
  • 其他情况:显示人数统计
  1. 区分两种人数
  • filterstudents.length:当前搜索结果中的人数。
  • userList.length:所有学生的总人数。
    注意:如果想统计所有学生,即使正在搜索,也应该使用 userList.length

组件化:把学生列表拆出来

当一个页面越来越复杂时,可以把列表部分拆成独立组件

Vue 2 中可以使用 Vue.component() 注册全局组件:

js 复制代码
Vue.component('student-list', {
  props: ['students'],
  template: '<ul>...</ul>'
})
  1. 父组件向子组件传数据:props
    父组件:
js 复制代码
<student-list :students="filterstudents"></student-list>

子组件:

js 复制代码
props: ['students']

含义:

  • 父组件把 filterstudents 传给子组件
  • 子组件通过 students 接收数据
  • 子组件使用 v-for 遍历 students
  1. 子组件向父组件传递事件:$emit
    子组件中的删除按钮:
js 复制代码
<button @click="$emit('delete-student', student.id)">
  删除
</button>

父组件监听事件:

js 复制代码
<student-list
  :students="filterstudents"
  @delete-student="del">
</student-list>

这表示子组件触发 delete-student 事件,并把学生 ID 传给父组件;父组件收到事件后调用 del 方法

  1. 父子组件通信总结
    父组件

持有 userList、add、del、filterstudents

props:父传子

传递 filterstudents

子组件 student-list

接收 students,展示列表

$emit:子通知父

发送 delete-student 和学生 ID

父组件执行删除

调用 del(id),更新原数组

重点

  • 父传子:props
  • 子通知父:$emit
  • 父组件监听:@事件名="方法名"

生命周期

什么是生命周期?

Vue 实例从创建、初始化数据、编译模板、挂载 DOM,到数据更新以及最终销毁,会经历一系列特定的阶段

这些阶段被称为 Vue 的生命周期

Vue 在不同生命周期阶段提供了对应的生命周期钩子函数(Lifecycle Hooks),开发者可以在这些阶段执行相应的操作

例如:

  • 实例创建时初始化数据
  • 页面挂载完成后操作 DOM
  • 数据更新后执行相关操作
  • 实例销毁前清理定时器、事件监听等资源

Vue 2 生命周期的完整执行顺序

Vue 2 常见的生命周期钩子按照执行顺序分为以下八个阶段:

| 执行顺序 | 生命周期钩子 |

| -------- | ------------- | -------------------------------------------------------- |

| 1 | beforeCreate | 实例初始化之后,数据观测和事件配置之前 |

| 2 | created | 实例创建完成,数据观测、计算属性、方法和事件等初始化完成 |

| 3 | beforeMount | 模板编译完成,即将挂载到页面 |

| 4 | mounted | 实例挂载完成,可以访问真实 DOM |

| 5 | beforeUpdate | 响应式数据发生变化,DOM 更新之前 |

| 6 | updated | DOM 更新完成之后 |

| 7 | beforeDestroy | 实例销毁之前 |

| 8 | destroyed | 实例销毁之后 |

可以把生命周期分为四个主要阶段:

  1. 创建阶段:beforeCreate、created
  2. 挂载阶段:beforeMount、mounted
  3. 更新阶段:beforeUpdate、updated
  4. 销毁阶段:beforeDestroy、destroyed

生命周期执行流程图

text 复制代码
创建 Vue 实例
      |
      v
  beforeCreate
      |
      v
  初始化数据、事件等
      |
      v
    created
      |
      v
  编译模板、生成渲染函数
      |
      v
   beforeMount
      |
      v
  创建并挂载真实 DOM
      |
      v
    mounted
      |
      v
  -------------------
  |                 |
数据发生变化       数据未变化
  |
  v
beforeUpdate
  |
  v
更新虚拟 DOM 和真实 DOM
  |
  v
updated
  |
  v
实例准备销毁
  |
  v
beforeDestroy
  |
  v
销毁实例、清理相关资源
  |
  v
destroyed

各生命周期钩子的具体作用

  1. beforeCreate
    执行时机:Vue 实例刚刚初始化完成,此时数据观测和事件配置尚未完成

特点:

  • data 尚未完成响应式处理
  • methods 等实例选项尚未完成初始化
  • 不能正常通过实例访问已经初始化的 data 数据

适用场景:很少直接使用,通常用于极早期的初始化逻辑

  1. created

执行时机:Vue 实例创建完成,数据观测、计算属性、方法和事件等初始化已经完成

特点:

  • 可以访问 data 中的数据
  • 可以调用 methods 中的方法
  • 可以使用计算属性
  • 此时还没有完成 DOM 挂载,不能通过 $el 获取已经挂载完成的页面 DOM

适用场景:

  • 请求初始化数据
  • 初始化非 DOM 相关的数据
  • 执行一些初始计算

示例:

javascript 复制代码
new Vue({
  data() {
    return {
      username: '小明'
    }
  },

  created() {
    console.log(this.username)
  }
})
  1. beforeMount

执行时机:模板编译完成,即将把 Vue 实例挂载到页面

特点:

  • 数据已经完成初始化
  • 模板已经准备好
  • 真实 DOM 尚未完成挂载

适用场景:一般不需要在这里进行大量操作

  1. mounted

执行时机:Vue 实例已经完成挂载,真实 DOM 已经生成

特点:

  • 可以通过 this.$el 访问当前组件的根 DOM
  • 可以操作已经挂载的 DOM
  • 子组件的挂载通常先于父组件完成

适用场景:

  • 初始化依赖 DOM 的第三方插件
  • 获取 DOM 元素
  • 初始化图表
  • 执行需要页面元素存在才能进行的操作

示例:

javascript 复制代码
new Vue({
  el: '#app',

  mounted() {
    console.log(this.$el)
  }
})

注意:mounted 只保证当前实例的 DOM 已挂载,不代表所有异步组件或所有异步更新都已经完成

  1. beforeUpdate

执行时机:响应式数据发生变化,Vue 准备重新渲染 DOM,但 DOM 还没有更新

特点:

  • 数据已经发生变化
  • 此时访问 DOM,得到的通常还是更新前的 DOM
  • 可以在这里获取更新前的 DOM 状态

适用场景:需要在 DOM 更新之前获取旧状态时使用

  1. updated

执行时机:由于响应式数据变化,DOM 更新完成之后

特点:

  • 数据已经更新
  • DOM 已经更新
  • 可以获取更新后的 DOM

适用场景:需要在数据更新后执行与 DOM 有关的操作

注意:

不要在 updated 中无条件修改响应式数据,否则可能引起重复更新,甚至无限更新

  1. beforeDestroy

执行时机:Vue 实例即将销毁,但此时实例仍然可以正常使用

特点:

  • 实例仍然存在
  • 数据和方法仍然可以访问
  • 适合在销毁前清理资源

适用场景

  • 清除定时器
  • 移除手动添加的事件监听
  • 取消不再需要的订阅
  • 清理第三方插件

示例:

javascript 复制代码
beforeDestroy() {
  clearInterval(this.timer)

  window.removeEventListener(
    'resize',
    this.handleResize
  )
}
  1. destroyed

执行时机:Vue 实例已经销毁

特点:

  • Vue 实例的响应式系统已经停止
  • 实例的事件监听器已经被移除
  • 子组件和相关指令也会被销毁

适用场景:执行销毁完成后的通知或清理工作

生命周期中的数据与 DOM

数据初始化完成,不代表 DOM 已经生成。

生命周期 能否访问 data 是否完成 DOM 挂载
beforeCreate 尚未完成初始化 否
created 可以 否
beforeMount 可以 否
mounted 可以 是
beforeUpdate 可以 已有旧 DOM
updated 可以 已完成本次 DOM 更新
beforeDestroy 可以 尚未完成销毁
destroyed 不应再依赖实例正常运行 已销毁

重点记忆:

  • created:适合请求数据,不适合直接操作已挂载 DOM
  • mounted:适合操作已经挂载的 DOM
  • beforeUpdate:获取更新前的 DOM 状态
  • updated:获取更新后的 DOM 状态
  • beforeDestroy:清理资源

$nextTick:等待 DOM 更新完成

Vue 采用异步更新策略

当修改响应式数据时,Vue 不一定立即更新真实 DOM,而是会把更新任务放入异步更新队列,在合适的时机批量执行

因此:

javascript 复制代码
this.message = '你好'
console.log(this.$el.innerText)

这里不一定能立即获取到更新后的 DOM 内容

如果希望在 DOM 更新完成之后执行操作,可以使用:

javascript 复制代码
this.message = '你好'

this.$nextTick(() => {
  console.log(this.$el.innerText)
})

$nextTick() 的作用:

等待 Vue 完成本轮 DOM 更新后,再执行传入的回调函数

常见使用场景:

  • 修改数据后获取更新后的 DOM。
  • 修改列表数据后获取新生成的元素。
  • 数据更新后设置输入框焦点。

重点区别:

  • 修改 data:数据变化。
  • DOM 更新:Vue 根据变化重新渲染页面。
  • $nextTick():等待本轮 DOM 更新完成后执行回调。

v-if 和 v-show 的区别

这两个指令经常与生命周期和组件销毁联系在一起。

对比 v-if v-show
实现方式 条件性创建和销毁元素 修改 CSS display 属性
初始条件为 false 不创建对应元素 元素仍然创建
切换成本 较高 较低
初始渲染成本 条件为 false 时较低 无论条件如何都会渲染
适用场景 不经常切换 频繁切换

示例:

html 复制代码
<div v-if="isVisible">
  使用 v-if
</div>

<div v-show="isVisible">
  使用 v-show
</div>

区别:

当 isVisible 为 false 时:

  • v-if:对应元素不会被渲染到 DOM 中
  • v-show:元素仍然存在,只是通过 display: none 隐藏

如果 v-if 控制的是组件,条件切换为 false 时,组件实例会被销毁;再次变为 true 时,会重新创建实例

如果 v-show 控制的是组件,组件实例通常仍然存在,只是显示或隐藏

注意:v-if 和 v-show 的区别不只是显示效果,还会影响组件的创建、销毁和生命周期执行

Vue 2 组件化开发

什么是组件?

组件(Component)是 Vue 中非常重要的概念

可以把组件理解为一个独立、可复用的页面功能模块

组件化开发的优点:

  1. 提高代码复用性
  2. 降低代码耦合度
  3. 便于维护和管理
  4. 让页面结构更加清晰
  5. 方便多人协作开发

组件的注册方式

Vue2 中主要有两种组件注册方式:

  • 全局组件
  • 局部组件

全局组件

通过 Vue.component() 注册。

语法:

javascript 复制代码
Vue.component('组件名称', {
  template: '<div>组件内容</div>'
})

示例:

javascript 复制代码
Vue.component('my-box', {
  template: '<div>我是一个全局组件</div>'
})

使用:

html 复制代码
<div id="app">
  <my-box></my-box>
</div>

特点:

  • 全局注册后,可以在当前 Vue 应用的组件范围内使用
  • 不需要在每个使用它的父组件中重复注册
  • 适合多个地方都需要使用的公共组件

局部组件

通过 Vue 实例或其他组件的 components 选项注册

示例:

javascript 复制代码
var MyBox = {
  template: '<div>我是一个局部组件</div>'
}

new Vue({
  el: '#app',

  components: {
    'my-box': MyBox
  }
})

特点:

  • 只在注册它的组件及其后代组件的模板范围内可用
  • 其他互不相关的组件不能直接使用
  • 适合只在特定区域使用的组件

如果 app1 和 app2 都需要使用同一个局部组件,可以分别注册:

javascript 复制代码
new Vue({
  el: '#app1',

  components: {
    'my-box': MyBox
  }
})

new Vue({
  el: '#app2',
  components:{
    'my-box':MyBox
  }
})

这里不需要重新定义 MyBox,只需要分别注册

全局组件和局部组件的区别

组件模板的根元素要求

Vue 2 的组件模板通常只能有一个根元素

错误示例:

js 复制代码
Vue.component('my-box', {
  template: `
    <h3>商品名称</h3>
    <p>商品价格:100元</p>
  `
})

这里存在两个并列的根元素

正确示例:

js 复制代码
Vue.component('my-box', {
  template: `
    <div>
      <h3>商品名称</h3>
      <p>商品价格:100元</p>
    </div>
  `
})

使用一个 div 将多个元素包裹起来,就只有一个根元素了

注意:这是 Vue 2 的要求,不要与 Vue 3 支持多根节点的特性混淆

组件复用与组件实例

一个组件定义可以重复使用多次

例如:

js 复制代码
Vue.component('my-box', {
  template: '<div>我是一个盒子</div>'
})

页面中:

html 复制代码
<div id="app">
  <my-box></my-box>
  <my-box></my-box>
  <my-box></my-box>
</div>

页面会显示三个盒子

重点理解两个概念:

组件定义: 相当于一个模具,只需要定义一次

组件实例: 每使用一次组件,Vue 就会创建一个对应的组件实例

例如:

js 复制代码
<my-box></my-box>
<my-box></my-box>

这里使用了两次组件,因此会创建两个独立的组件实例

组件中的 data 为什么必须是函数?

普通 Vue 实例:

js 复制代码
new Vue({
  data: {
    count: 0
  }
})

组件中的 data:

js 复制代码
Vue.component('my-counter', {
  data() {
    return {
      count: 0
    }
  },

  template: `
    <button @click="count++">
      {{ count }}
    </button>
  `
})

为什么组件中的 data 必须是函数?

因为组件可能被重复使用多次

如果多个组件实例共享同一个 data 对象,那么修改其中一个实例的数据,其他实例也可能受到影响

例如:

js 复制代码
var obj = {
  count: 0
}

Vue.component('my-counter', {
  data() {
    return obj
  }
})

这里虽然 data 是函数,但每次都返回同一个 obj 对象,因此多个组件实例仍然会共享数据

正确的做法是:

js 复制代码
Vue.component('my-counter', {
  data() {
    return {
      count: 0
    }
  }
})

每次创建组件实例时,调用 data 函数都会返回一个新的对象

这样每个实例就拥有独立的数据

核心记忆:Vue 2 组件的 data 必须是函数,并且每次调用都应该返回一个新的对象

父组件向子组件传递数据:props

组件之间经常需要传递数据

父组件向子组件传递数据,可以使用 props

父组件:

js 复制代码
new Vue({
  el: '#app',

  data() {
    return {
      student: {
        no: 1001,
        name: '小明'
      }
    }
  }
})

父组件模板:

html 复制代码
<div id="app">
  <student-info :student="student"></student-info>
</div>

子组件:

js 复制代码
Vue.component('student-info', {
  props: ['student'],

  template: `
    <div>
      <p>学号:{{ student.no }}</p>
      <p>姓名:{{ student.name }}</p>
    </div>
  `
})

理解:

父组件通过 :student="student" 传递数据

子组件通过 props 接收数据

子组件通过 student 访问传入的数据

注意:

student 是子组件接收的 prop 名称,右侧的 student 是父组件中的数据

props 是父组件向子组件传递数据的主要方式

props 的基本类型校验

props 不仅可以接收数据,还可以指定数据类型

示例:

js 复制代码
Vue.component('student-info', {
  props: {
    name: String,
    age: Number,
    hobbies: Array,
    user: Object,
    isStudent: Boolean
  }
})

常见数据类型:

props 的默认值 default

当父组件没有传递某个 prop 时,可以使用 default 设置默认值

示例:

js 复制代码
props: {
  name: {
    type: String,
    default: '匿名用户'
  },

  age: {
    type: Number,
    default: 18
  }
}

如果父组件没有传递 name 和 age,组件就会使用默认值

特别注意:

对于 Array 和 Object 类型,默认值应该使用工厂函数返回

数组:

js 复制代码
props: {
  list: {
    type: Array,
    default: () => []
  }
}

对象:

js 复制代码
props: {
  user: {
    type: Object,
    default: () => ({})
  }
}

原因:

避免多个组件实例共享同一个数组或对象引用

props 的必填校验 required

可以使用 required 设置某个 prop 是否为必填项

示例:

js 复制代码
props: {
  title: {
    type: String,
    required: true
  }
}

含义:

父组件必须传递 title 属性

如果没有传递,Vue 会在控制台给出警告

注意:

required 和 default 的作用不同

required:规定是否必须传递

default:规定没有传递时使用什么默认值

required 使用布尔值:

required: true

不要写成:

required: 'true'

props 自定义校验 validator

除了基本的数据类型校验,还可以使用 validator 自定义校验规则

示例:

js 复制代码
props: {
  price: {
    type: Number,

    validator(value) {
      return value >= 0
    }
  }
}

含义:

商品价格必须大于或等于 0

如果传入负数,就不符合校验规则

validator 的返回值:

return true:校验通过

return false:校验不通过,Vue 给出警告

再看一个年龄校验:

js 复制代码
props: {
  age: {
    type: Number,

    validator(value) {
      return value >= 1 && value <= 120
    }
  }
}

注意:

validator 只是开发阶段的校验机制,不会自动修改错误数据,也不会阻止组件接收这个值。实际业务中如果必须保证数据合法,还需要在业务逻辑中进行处理

props 的单向数据流

这是组件通信中非常重要的原则

Vue 2 中,props 是单向数据流:

父组件的数据传递给子组件,子组件不应该直接修改父组件传递过来的 prop

例如:

props: 'student'

子组件中:

this.student.name = '小红'

如果 student 是一个对象,这种写法可能会修改父组件共享对象中的属性

虽然 JavaScript 对象是引用类型,修改嵌套属性可能影响父组件中的数据,但这并不意味着应该这样做

Vue 2 会对直接修改 prop 本身给出警告,例如:

this.student = {}

推荐做法:

父组件负责管理数据

子组件负责展示数据

子组件需要修改数据时,通过 $emit 通知父组件

父组件接收事件后修改自己的数据

这就是父子组件通信的基本原则

相关推荐
shangjian0072 小时前
K8S学习-基础组件
学习·容器·kubernetes
梦曦i2 小时前
v0.2.0 架构升级:class + reactive 重塑状态管理
前端·uni-app x
布丁爱干饭2 小时前
寄给午后
前端
lifallen2 小时前
ub-mode skill:软件工程原则
人工智能·学习·ai·软件工程·ai编程
java1234_小锋2 小时前
【技术专题】CSS3 网页技术 - CSS3 布局进阶
前端·css·css3
zhippt_10143 小时前
只剩截图没有源文件?图转PPT把图片还原成可编辑幻灯片
前端·人工智能·powerpoint
shmily麻瓜小菜鸡3 小时前
TypeScript 知识点补充(第三部分:工程化与高级类型)
前端·javascript·typescript
10岁的博客4 小时前
Chrome 扩展安全攻防实录:从权限模型到真实漏洞复盘
前端·chrome·安全
凉、介4 小时前
深入理解 ARMv7-A | Generic Timer
笔记·学习·嵌入式·arm