表单输入绑定:
你可以用 v-model 指令在表单 <input>、<textarea> 及 <select> 元素上创建双向数据绑定。
它负责监听用户的输入事件来更新数据,并在某种极端场景下进行一些特殊处理。
它会根据控件类型自动选取正确的方法来更新元素。
v-model 会忽略所有表单元素的 value、checked、selected attribute 的初始值而总是将当前活动实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。
<template>
<div class="home">
<div>用户名:<input type="text" v-model.lazy="form_data.username"></div>
<div>密码:<input type="password" v-model.trim="form_data.password"></div>
<div>年龄:<input type="text" v-model.number="form_data.age"></div>
<div>简介:<textarea v-model="form_data.text" ></textarea><br>
</div>
<div>VIP:<input type="checkbox" v-model="form_data.vip" true-value="yes" false-value="no" ></div>
<div>爱好:
<input type="checkbox" v-model="form_data.like" value="0">
<input type="checkbox" v-model="form_data.like" value="1">
<input type="checkbox" v-model="form_data.like" value="2">
</div>
<div>性别:
<input type="radio" v-model="form_data.sex" value="0">
<input type="radio" v-model="form_data.sex" value="1">
</div>
<div>籍贯:
<select v-model="form_data.jg" multiple>
<option value="" disabled>请选择籍贯</option>
<option>0</option>
<option value="8" >1</option>
<option value="2">2</option>
</select>
</div>
{{form_data}}
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
export default {
data(){
return{
form_data:{
like:[],
jg:'',
}
}
},
name:"Home",
components:{
}
}
</script>
复选框
单个
<input type="checkbox" id="checkbox" v-model="checked" />
多个
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
<input type="checkbox" id="john" value="John" v-model="checkedNames" />
data() {
return {
checkedNames: []//如果以对象的方式接收值,那么数组一定要放到对象中
}
}
另一种写法
<input type="checkbox" v-model="toggle" true-value="yes" false-value="no" />
<!--修改数据-->
单选框
如果需要默认选中,我们可以把属性的值,设置为与某个value值相同。
<input type="radio" id="one" value="One" v-model="picked" />
<input type="radio" id="two" value="Two" v-model="picked" />
data() {
return {
picked: 'One'//与value相同时,那个控件默认选中
}
}
选择框
单选
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
多选
<select v-model="selected" multiple>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
修饰符
.lazy 你可以添加 lazy 修饰符,从而转为在 change 事件
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" />
.number 自动将用户的输入值转为数值类型
<input v-model.number="age" type="number" />
.trim 如果要自动过滤用户输入的首尾空白字符
<input v-model.trim ="age" type="number" />
计算属性
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。
对于任何包含响应式数据的复杂逻辑,你都应该使用计算属性。computed
computed: {
publishedBooksMessage() {
// `this` 指向 vm 实例
return this.author.books.length > 0 ? 'Yes' : 'No'
}
}
计算属性缓存 vs 方法
计算属性 computed:只在相关响应式依赖发生改变时它们才会重新求值,计算属性是基于它们的响应依赖关系缓存的
方法 methods:每调用一次都重新求值
侦听器
当需要在数据变化时执行异步或开销较大的操作时,提供了一个更通用的watch侦听属性,来响应数据的变化
普通属性的侦听
watch: {
question(newQuestion, oldQuestion) {
}
}
//新值和旧值
表单对象数据的侦听
//监测对象数据的变化
watch: {
obj:{
handler: function (val) {
console.log(val)//数据对象
},
deep:true//对象内部的属性,需要开启deep
}
}
<template>
<div class="home">
{{getName()}}
{{getSex}}
{{getTime}}
<div>用户名:<input type="text" v-model="form_data.username"></div>
<div>密码:<input type="password" v-model="form_data.password"></div>
</div>
{{form_data}}
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
export default {
data(){
return{
name:"zhangsan",
sex:1,
time:new Date().getTime(),
form_data:{},
// username:"",
}
},
watch:{
// username(new_v,old_v){
// console.log(new_v,old_v);
// }
form_data:{
handler(v){
console.log(v)
},
deep:true,//深度监听
},
name(){
}
},
name:"Home",
methods:{
getName(){
return this.name.split('').reverse().join('')//反转
},
},
computed:{
getSex(){
return this.sex==1?'男':'女'
},
getTime(){
let time = new Date(this.time)
let year = time.getFullYear()
let month = time.getMonth() + 1
let date = time.getDate()
let hours = time.getHours()
let minute = time.getMinutes()
let second = time.getSeconds()
if (month < 10) { month = '0' + month }
if (date < 10) { date = '0' + date }
if (hours < 10) { hours = '0' + hours }
if (minute < 10) { minute = '0' + minute }
if (second < 10) { second = '0' + second }
return year + '-' + month + '-' + date + ' ' + hours + ':' + minute + ':' + second
}
},
// filters:{
// getTime(){
// }
// }vue3中没有过滤器
}
</script>
组件
在项目中,很多时候我们需要在不同的模块使用相同的一个块,而这个块的代码是相同的,这时候会造成代码冗余,我们可以通过封装组件的方式,实现代码的复用。
//计数器组件
<template>
<p>{{count}}</p>
<button @click="count++">增加</button>
</template>
<script>
export default {
data:function(){
return {
count:0
}
}
}
组件的复用
<template>
<my-count />
<my-count />
<my-count />
</template>
组件的引入
组件在导入时,import语句后面的名称我们可以自定义,它会作为标签名进行使用。当我们需要把它放入模板时:
<template>
<MyCount />
<my-count /><!--没插槽-->
<my-count></my-count>
<MyCount></MyCount>
</template>
import MyCount from './component/MyCount.vue'
export default {
components:{
MyCount
}
}
通过 Prop 向子组件传递数据
我们使用组件时,如果模板中的内容都固定不变,那么每个模块引入该组件的内容都将一致;其实我们也可以不这样,比如我们可以将组件的某些内容变为动态的,内容由父元素的传值决定,vue给我们提供了props属性,让我们的组件可以对子组件传值。
//子组件
export default {
props:['title'],
}
//父组件 动态传值
<my-count :title="title"/>
export default {
data:function(){
return {
title:'计数器'
}
},
}
//父组件 静态传值
<my-count title="title"/>
<my-count :title="'title'"/>
有时我们也可以提供一个数组,遍历多个相同组件传递不同数据:
<my-count :title="t" v-for="t in title" :key="t"/>
export default {
data:function(){
return {
title:['计数器1','计数器2','计数器3']
}
},
}
但是,通常你希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop,这些 property 的名称和值分别是 prop 各自的名称和类型:
props: {
title: String,
likes: Number,
}//该类型约束只会报警告信息
原生提供的数据类型有:
String
Number
Boolean
Array
Object
Date
Function
Symbol
当我们开启了对prop的验证,那么就需要注意传值的方式
数值:
<blog-post :likes="42"></blog-post>
布尔:
<blog-post is-published></blog-post> //默认为true
<blog-post :is-published="false"></blog-post>
数组:
<blog-post :comment-ids="[234, 266, 273]"></blog-post>
对象:
<blog-post :author="{
name: 'Veronica',
company: 'Veridian Dynamics'}"></blog-post>
一次传入多个prop:
post: {
id: 1,
title: 'My Journey with Vue'
}
<blog-post v-bind="post"></blog-post>
单向数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
另外,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
//子组件
prop:['name']
this.name = "李四"//报错
注意:
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态
//子组件
prop:['obj']//对象比较特殊,引用数据类型
this.obj.name="zhaoliu"//不会报错
poro的验证
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true//必填项
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default() {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator(value) {
// 这个值必须匹配下列字符串中的一个
验证是发生在实例创建前,所以这里用不了实例的data数据
return ['success', 'warning', 'danger'].includes(value)
}
},
// 具有默认值的函数
propG: {
type: Function,
// 与对象或数组默认值不同,这不是一个工厂函数 —— 这是一个用作默认值的函数
default() {
return 'Default function'
}
}
}
当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
注意:
注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如 data、computed 等) 在 default 或 validator 函数中是不可用的。
属性继承
一个非 prop 的 attribute 是指传向一个组件,但是该组件并没有相应 props 或 emits 定义的 attribute。常见的示例包括 class、style 和 id 属性
如果我们需要通过 data status property 定义 <date-picker> 组件的状态,它将应用于根节点 (即 div.date-picker)。
//父组件
<my-count class="a1" data-status="activated"></my-count>
<style>.a1{}</style>
//子组件
<template><div ></div><template>
如果你不希望组件的根元素继承 attribute,你可以在组件的选项中设置 inheritAttrs: false。例如:
//子组件
inheritAttrs:false,
<p v-bind="$attrs">1111111</p>
//父组件
<my-count class="a1" data-status="activated">
与单个根节点组件不同,具有多个根节点的组件不具有自动 attribute 回退行为。如果未显式绑定 $attrs,将发出运行时警告。
自定义事件
有时我们的子组件中会有一些事件和数据需要传递到父组件,例如我们点击了子组件中的按钮,那么这个按钮可能会触发某个方法,甚至传递了一些参数。这时我们的父组件就需要对事件进行处理或验证。
我们来看一个点击子组件按钮放大文字的例子:
//子组件
//这里我们给子组件绑定了一个事件,并通过调用内建的 $emit 方法并传入事件名称让父组件可以触发postfont
<button @click="$emit('postfont')">放大</button>
//父组件
//我们可以通过@+$emit 方法并传入事件名称去触发子组件的方法进行文字的放大
<div :style="{fontSize:fontSize+ 'em'}">
<my-count :title="title" @postfont="fontSize += 0.1"/>
</div>
//在$emit 方法中我们可以提供第二个参数来告诉父组件我们需要放大多少
//子组件
<button @click="$emit('postfont',0.2)">放大</button>
//父组件 传值的值会保存在$event中
<template>
<div :style="{fontSize:fontSize+ 'em'}">
<my-count :title="title" @postfont="fontSize += $event"/>
</div>
</template>
可以通过 emits 选项在组件上定义已发出的事件。
emits: ['postfont'],
验证抛出事件:
要添加验证,将为事件分配一个函数,该函数接收传递给 $emit 调用的参数,并返回一个布尔值以指示事件是否有效。
emits: {
// 没有验证
click: null,
// 验证submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
},
emit语法糖
//如果我们只是想要把值传输到父组件,那么可以通过update的方式进行传值
$emit('update:v',newv);
//父组件
<post-size v-model:v="size" />
//传递过来的v会覆盖size
组件的双向数据绑定
//子组件 子组件将需要一个 title prop 并发出 update:title 要同步的事件
<template>
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)">
<p>{{title}}</p>
</template>
export default {
props: ['title'],
emits: ['update:title'],
}
//父组件
<my-count v-model:title="bookTitle"></my-count>
export default {
data:function(){
return {
bookTitle:'默认值',
}
}
}
允许为组件绑定多个v-model
<my-count
v-model:title="bookTitle"
v-model:name="bookTitle"></my-count>
插槽
Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将 <slot> 元素作为承载分发内容的出口。
普通插槽
//父组件
<my-count>这是一个计数器组件</mycount>
//子组件
<template>
<slot></slot>
</template>
插槽还可以包含任何模板代码,包括 HTML
//父组件
<my-count><p>这是一个计数器组件</p></mycount>
插槽中可以使用实例的数据
//父组件
<my-count><p>这是一个计数器组件{{name}}</p></mycount>
如果 template 中没有包含一个 <slot> 元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃
备用内容
我们可能希望这个 <button> 内绝大多数情况下都渲染文本“Submit”。为了将“Submit”作为备用内容,我们可以将它放在 <slot> 标签内:
<button type="submit">
<slot>Submit</slot>
</button>
这时,如果你有使用插槽就覆盖该内容,如果未使用,则使用内用内容。
具名插槽
有时我们需要多个插槽。例如对于一个带有如下模板的 <base-layout> 组件:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
一个不带 name 的 <slot> 出口会带有隐含的名字“default”。
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
缩写(#)
跟 v-on(@) 和 v-bind(:) 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header:
<base-layout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
作用局插槽
有时让插槽内容能够访问子组件中才有的数据是很有用的。
比如我们的子组件中有一个对象数据:
user:{
name:'zhangsan',
age:10
}
我们希望在使用组件时,插槽可以灵活的控制显示name or age。
<slot v-bind:user="user">{{user.name}}</slot>
父组件:
<my-count >
<template v-slot:name="data">{{data.user.age}}</template>
</my-count>
默认插槽
在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上:
<my-count
v-slot:default="slotProps">{{slotProps.user.age}}</my-count>
注意默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确
解构插槽
v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。你也可以使用 ES2015 解构来传入具体的插槽 prop,如下:
<my-count >
<template v-slot:default="{user}">{{user.age}}</template>
</my-count>
动态插槽
动态指令参数也可以用在 v-slot 上,来定义动态的插槽名:
<base-layout>
<template v-slot:[name]> ... </template>
</base-layout>
动态组件
不同组件之间进行动态切换,在一个多标签的界面里:
<component :is="currentTabComponent"></component>
provide/inject
通常,当我们需要从父组件向子组件传递数据时,我们使用 props。
使用一对 provide 和 inject。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。
//父组件
provide: {
msg: 'is app msg'
},
//子组件
inject: ['msg'],
异步组件
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 有一个 defineAsyncComponent 方法:
import {defineAsyncComponent } from 'vue'
components:{
MyCount : defineAsyncComponent(() =>
import('./component/MyCount.vue')
)
}
为什么要使用异步组件
-
异步组件在创建时什么也不做,在组件第一次渲染时加载并缓存。
-
异步组件的性能更高,加载的速度更快。
-
异步组件在打包时,体积更小。