双向绑定

v-model
更多表单知识,请访问 web - formform event
表单事件
表单常见事件类型
类型 说明
submit 表单数据提交;默认会跳转,通常需阻止该行为
reset 表单数据清除;各表单域值初始化
表单元素事件
表单输入元素常见事件类型
类型 说明
focus 获取焦点
blur 失去焦点
change 内容更改后触发,通常是更改后并失去焦点;如内容变化、选中状态变化等
input 内容输入过程中触发
[] 传统方式
结构复杂 - 需要指定 id 和 name
逻辑繁琐 - 需要大量事件参与
使用开发者视图查看表单和表单元素的事件响应情况
<form @submit="submit">
  <input type="text" required maxlength="16" id="msg" name="msg" @input="doInput" @change="doChange"
    @focus="doFocus" @blur="doBlur">
  <br>
  <label><input type="radio" id="male" name="gender" value="male" @change="doGenderMale">male</label>
  <label><input type="radio" id="female" name="gender" value="female" @change="doGenderFemale">female</label>
  <br>
  <label><input type="checkbox" id="code" name="hobby" value="code" @change="doHobbyCode">code</label>
  <label><input type="checkbox" id="sing" name="hobby" value="sing" @change="doHobbySing">sing</label>
  <br>
  <button type="submit">submit</button>
</form>
const submit = (event) => {
  event.preventDefault();
  console.log('Message:', msg.value);
  console.log('Gender:', gender.value);
  console.log('Hobbies:', hobbies.value);
};
const doInput = () => {
  console.log('input', msg.value);
}
const doChange = () => {
  console.log('change', msg.value);
}
const doFocus = () => {
  console.log('focus', msg.value);
}
const doBlur = () => {
  console.log('blur', msg.value);
}
const doGenderMale = () => {
  console.log('radio', male.value, male.checked);
}
const doGenderFemale = () => {
  console.log('radio', female.value, female.checked);
}
const doHobbyCode = () => {
  console.log('checkbox', code.value, code.checked);
}
const doHobbySing = () => {
  console.log('checkbox', sing.value, sing.checked);
}
双向绑定 v-model
v-bind:是单向数据绑定:逻辑层数据流向渲染层
v-model:表单数据的双向绑定;将表单元素的内容同步给 JavaScript 中的数据:数据可以影响到表单;表单也可以影响到数据
数据可以是数值类型 number,也可以是字符串类型 string,还可以是布尔类型 true/false
绑定后会忽略任何表单元素上初始的 value、checked 或 selected 属性
更多信息,请访问 表单输入绑定
元素 绑定属性 事件类型
<input> - text value 属性 input 事件
<textarea> value 属性 input 事件
<input> - radio checked 属性 change 事件
<input> - checkbox checked 属性 change 事件
<select> value 属性 change 事件
[] 双向绑定
结构简单 - 可以省略 id、checked 等属性
响应式数据支持
根据需要指定默认值
根据需要采用其它事件
<form @submit.prevent="submit">
  <input type="text" required maxlength="16" v-model="formData.msg">
  <br>
  <label><input type="radio" name="gender" v-model="formData.gender" value="male">male</label>
  <label><input type="radio" name="gender" v-model="formData.gender" value="female">female</label>
  <br>
  <label><input type="checkbox" name="hobby" v-model="formData.hobby" value="code">code</label>
  <label><input type="checkbox" name="hobby" v-model="formData.hobby" value="sing">sing</label>
  <br>
  <button type="submit">submit</button>
</form>
import { ref } from 'vue'
const formData = ref({
  msg: '',
  gender: 'male',
  hobby: []
})
const submit = () => {
  console.log('formData:', formData.value);
};
<input> - text
单行文本输入 - 可以省略 id 和 name
let msg = ref('')
<input type="text" v-model="msg" required>
<div>{{ msg }}</div>
传统方式 - 通过 input 事件和属性绑定实现双向绑定;这里采用内联事件|行内事件
<input type="text" @input="msg = $event.target.value" :value="msg" required>
<input type="text" @input="(e) => { msg = e.target.value }" :value="msg" required>
<div>{{ msg }}</div>
<input> - radio
单选;多选一
需要为每个 <radio> 指定值 value,并使用同一个名字 name 以确保它们是同一组
不指定 value 的话,只能获取单选框的选中状态 checked 是 true 还是 false,并不知道选择了谁
let gender = ref('')
双向绑定 - 选中都绑定 gender,同时指定值 value
<input type="radio" name="gender" value="male" v-model="gender">male
<input type="radio" name="gender" value="female" v-model="gender">female
<div>{{ gender }}</div>
传统方式 - 使用 change 事件获取
<input type="radio" name="gender" value="male" @change="(e) => gender = e.target.value">male
<input type="radio" name="gender" value="female" @change="(e) => gender = e.target.value">female
<div>{{ gender }}</div>
<input> - checkbox
多选
需要为每个 <input> 指定值 value,并使用同一个名字 name 以确保它们是同一组
如果不指定 value,只能获取复选框的选中状态 checked 是 true 还是 false,并不知道选择了谁 - 可用于状态判断,如是否同意、是否阅读等用户须知场合
let like = ref([])
双向绑定 - 选项都绑定数据 like,同时指定值 value
<input type="checkbox" name="like" value="code" v-model="like">code
<input type="checkbox" name="like" value="sing" v-model="like">sing
<input type="checkbox" name="like" value="game" v-model="like">game
<input type="checkbox" name="like" value="read" v-model="like">read
<div>{{ like }}</div>
传统方式 - 使用 change 事件获取
<input type="checkbox" name="like" value="code" @change="sel">code
<input type="checkbox" name="like" value="sing" @change="sel">sing
<input type="checkbox" name="like" value="game" @change="sel">game
<input type="checkbox" name="like" value="read" @change="sel">read
<div>{{ like }}</div>
const sel = (e) => {
    e.target.checked ? like.value.push(e.target.value) : like.value = like.value.filter(item => item !== e.target.value)
}
<input>的 radio 类型和 checkbox 类型,绑定的是 checked 属性 并侦听 change 事件
如果不指定 value,其默认值是 on - The default value for checkboxes and radio buttons is on
<textarea>
多行输入;留言、评论
let msg = ref('')
双向绑定
<textarea v-model="msg" required></textarea>
<div>{{msg}}</div>
不可以使用内容绑定的插值表达式 {{}}
<textarea v-model="msg" required>{{msg}}</textarea>
传统方式 - 通过 input 事件和属性绑定实现
<textarea @input="(e) => { msg = e.target.value }" :value="msg" required></textarea>
<div>{{msg}}</div>
<input> 的 text 类型和 <textarea>,绑定的是 value 属性 并侦听 input 事件
<select>
下拉列表;下拉框
可以使用 自动侦听 选项变化,加载不同的数据
let sel = ref('')
<select v-model = 'sel'>
  <option value='1'>1</option>
  <option value='2'>2</option>
  <option value='3'>3</option>
</select>
<div>{{sel}}</div>
<select> 绑定的是 value 属性 并侦听 change 事件
值的绑定
通常情况下,绑定时,当状态变化时,对应不同的的静态值,如男、女
使用 v-bind 绑定实现满足更多场景
同一份逻辑,既可以实现性别的选择,也可以实现其它两个状态的应用,如:是否在岗?是否婚配?是否成年?给什么数据,就绑定什么数据 - 响应式数据;
采用值绑定,可以拓展逻辑;如性别单选中,非男即女,是对立的两个状态;绑定后,响应式数据可以任意指定,甚至可以是完全不相关的场合
<input> - radio
<input type="radio" v-model="ugender" :value="maleVal">
<label for="male">{{maleVal}}</label>
<input type="radio" v-model="ugender" :value="femaleVal">
<label for="female">{{femaleVal}}</label>
为两个状态指定响应式数据,可以是任何值,而不是简单的静态 male 和 female
let ugender = ref('')
let maleVal = reactive({
    id: 10,
    name: 'gl'
})
let femaleVal = reactive({
    id: 20,
    name: 'gz'
})
<input> - checkbox
参照前例,修改静态值为动态绑定;内容自定
提示:列表渲染
<select>
参照前例,修改静态值为动态绑定;内容自定
提示:列表渲染
修饰符
.number
把 v-model 的值转换成数值类型
输入的第一个只能是数字或者小数点或者是正负号
如果输入的第一个字是字符串,那 number 这个修饰符就不会生效
既然需要转换为数字,那么就请输入数字吧
.lazy
默认情况下,v-model 在 input 事件中同步输入框的值与数据,实时渲染
指定 lazy,会把 oninput 事件改成 onchange 事件 - 节流
当失去焦点或按下回车时才触发更新
.trim
过滤用户输入时首尾的空格字符
更多数据过滤操作需要借助 JavaScript,甚至正则表达式实现
Summary & Homework
Summary
<input> 的 text 类型:绑定 value 属性;侦听 input 事件
<textarea>:绑定 value 属性;侦听 input 事件
<input> 的 radio 类型:绑定 checked 属性;侦听 change 事件
<input> 的 checkbox 类型:绑定 checked 属性;侦听 change 事件
<select>:绑定 value 属性;侦听 change 事件
数据应该是唯一的
实现双向绑定后,可以忽略 <form> 这个壳,直接在 <button>的事件上提交数据
实现绑定后,更多需求仍然需要事件的支持,如 change 后根据状态变化执行相应的逻辑
双向绑定引发的性能问题
建议通过组件封装实现
Homework
[] 留言板 - 按 Ctrl + Enter 提交留言
适合桌面端
方案1:使用双向数据绑定
不需要 <form> 这个壳
使用修饰符 lazy
<input type="text" v-model.lazy="msg">
import { ref } from 'vue'
const msg = ref('')
方案2:使用按键事件实现 - 查看并分析事件对象
不需要 <form> 这个壳
需要指定 id
<input type="text" @keyup.ctrl.enter="keySubmit" placeholder="your text" id="msg">
const keySubmit = (e) => {
  console.log('ctrl enter submit')
  console.log(e);
  console.log(e.key);
  console.log(e.keyCode);
  console.log(msg.value);
}
[] 登录页 LoginView.vue
1. 提示信息的显示与隐藏
方案1:使用 v-show
<div class="item">
  <input type="text" v-model.trim="user.name" required maxlength="11" 
    @focus="isFocus = true"
    @blur="isFocus = false">
  <span class="tips tips-name" v-show="isFocus && !user.name">请输入手机号</span>
</div>
.tips {
  position: absolute;
  top: 0;
  color: #666;
  line-height: 40px;
}

.tips-name {
  left: 20px;
}
方案2:使用动态类 - 一开始隐藏,激活时显示
<div class="item">
  <input type="text" v-model.trim="user.name" required maxlength="11" 
    @focus="isFocus = true"
    @blur="isFocus = false">
  <span class="tips tips-name" :class="{ 'active': isFocus && !user.name }">请输入手机号</span>
</div>
.tips {
  position: absolute;
  top: 0;
  color: #666;
  line-height: 40px;
}

.tips-name {
  left: 20px;
  display: none;
}

.tips-name.active {
  display: block;
}
2. 使用须知、记住密码
使用单个复选框实现;不需要指定 value,需要的只是是否选中
let isAgree = ref(false)
<label>
    <input type="checkbox" v-model="isAgree"> 已阅读并同意服务条款
</label>
[] 注册
[] 商品详情页 DetailsView.vue
产品规格 specification
甜点推荐 RecoDessert.vue