文章目录
- 一、模版语法
- 1.1 插值表达式和文本渲染
- 1.1.1 插值表达式 语法
- 1.1.2 文本渲染 语法
- 1.2 Attribute属性渲染
- 1.3 事件的绑定
- 二、响应式基础
- 2.1 响应式需求案例
- 2.2 响应式实现关键字ref
- 2.3 响应式实现关键字reactive
- 2.4 扩展响应式关键字toRefs 和 toRef
- 三、条件和列表渲染
- 3.1 条件渲染
- 3.2 列表渲染
- 四、双向绑定
- 五、数据监听器
一、模版语法
Vue
使用一种基于HTML
的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。- 所有的
Vue
模板都是语法层面合法的HTML
,可以被符合规范的浏览器和 HTML 解析器解析。- 在底层机制中,
Vue
会将模板编译成高度优化的JavaScript
代码。- 结合响应式系统,当应用状态变更时,Vue 能够智能地推导出需要重新渲染的组件的最少数量,并应用最少的 DOM 操作。
1.1 插值表达式和文本渲染
插值表达式:最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 ,即双大括号
{{}}
- 插值表达式是将数据渲染到元素的指定位置的手段之一
- 插值表达式不绝对依赖标签,其位置相对自由
- 插值表达式中支持
javascript
的运算表达式 - 插值表达式中也支持函数的调用
1.1.1 插值表达式 语法
{{数据名字/函数/对象调用API}}
<script setup>
let msg = "hello";
let getMsg = () => {
return "hello vue3 message";
};
let age = 25;
let nickname = "道格";
// 对象调用API
// 模拟购物车:
const carts = [
{ name: "可乐", price: 3, number: 10 },
{ name: "百事", price: 2, number: 20 }
];
//购物车计算: 总价 = 单价*个数
function compute() {
let count = 0;
for (const index in carts) {
count += carts[index].price * carts[index].number;
}
return count;
}
</script>
<template>
<div>
msg: {{msg}}
<br />
getMsg : {{getMsg()}}
<br />
age : {{age}}
<br />
nickname : {{nickname}}
<br />
购物总价:{{compute()}}
<br />
表达式写计算购物总价: {{carts[0].price*carts[0].number + carts[1].price * carts[1].number}}
</div>
</template>
<style scoped>
</style>
1.1.2 文本渲染 语法
为了渲染双标中的文本,我们也可以选择使用
v-text
和v-html
命令
v-***
这种写法的方式使用的是vue
的命令v-***
的命令必须依赖元素,并且要写在元素的开始标签中v-***
指令支持ES6中的字符串模板- 插值表达式中支持javascript的运算表达式
- 插值表达式中也支持函数的调用
v-text
可以将数据渲染成双标签中间的文本,但是不识别html元素结构的文本v-html
可以将数据渲染成双标签中间的文本,识别html元素结构的文本
<script setup>
let msg = "hello";
let getMsg = () => {
return msg + " getMsg";
};
let refMsg = "<font color='red'>msg</font>";
let greenMsg = `<font color=\'green\'>${msg}</font>`;
</script>
<template>
<div>
<span v-text="msg"></span>
<br />
<span v-text="getMsg()"></span>
<br />
<span v-text="refMsg"></span>
<br />
<span v-html="refMsg"></span>
<br />
<span v-html="greenMsg"></span>
</div>
</template>
<style scoped>
</style>
1.2 Attribute属性渲染
想要渲染一个元素的
attribute
,应该使用v-bind
指令
- 由于插值表达式不能直接放在标签的属性中,所有要渲染元素的属性就应该使用
v-bind
v-bind
可以用于渲染任何元素的属性,语法为v-bind:属性名='数据名'
, 可以简写为:属性名='数据名'
<script setup>
const data = {
name: "道格维克",
url: "https://blog.csdn.net/GavinGroves",
image: "https://www.runoob.com/wp-content/uploads/2017/01/vue.png"
};
</script>
<template>
<div>
<!--target="_blan" 跳转页面 _self当前页面变化 -->
<a v-bind:href="data.url" target="_self">
<img :src="data.image" :title="data.name" />
<br />
<input type="button" :value="`点击访问${data.name}`" />
</a>
</div>
</template>
<style scoped>
</style>
1.3 事件的绑定
v-on
来监听 DOM 事件,并在事件触发时执行对应 Vue的JavaScript代码
- 用法:
v-on:click="handler"
或简写为@click="handler"
- vue中的事件名=原生事件名去掉
on
前缀 如:onClick --> click
- handler的值可以是方法事件处理器,也可以是内联事件处理器
- 绑定事件时,可以通过一些绑定的修饰符,常见的事件修饰符如下
.once:只触发一次事件。
.prevent:阻止默认事件。
- .stop:阻止事件冒泡。
- .capture:使用事件捕获模式而不是冒泡模式。
- .self:只在事件发送者自身触发时才触发事件。
<script setup>
import { ref } from "vue";
function fun1() {
alert("你好");
}
// 计数器:
let count = ref(0);
function counter() {
count.value++;
}
function jumpDeterPrevent() {
alert("不许访问!");
}
function jumpDeter(event) {
let isFlag = confirm("确定要访问吗?");
if (!isFlag) {
event.preventDefault();
}
}
</script>
<template>
<div>
<!-- 事件的绑定函数 脸两种写法 -->
<button v-on:click="fun1">按钮1</button>
<br />
<button @click="fun1">按钮2</button>
<br />
<!-- 内联事件处理器 -->
<button @click="counter">{{count}}</button>
<!-- 事件修饰符 once 只能执行一次-->
<button @click.once="counter">加一次</button>
{{count}}
<br />
<!-- 事件修饰符 prevent 阻止组件的默认行为 -->
<a
href="https://blog.csdn.net/GavinGroves"
target="_blank"
@click.prevent="jumpDeterPrevent()"
>prevent阻止跳转页面</a>
<br />
<!-- 原生js方式阻止组件默认行为 (推荐) -->
<a
href="https://blog.csdn.net/GavinGroves"
target="_blank"
@click="jumpDeter($event)"
>JS原生阻止跳转页面</a>
</div>
</template>
<style scoped>
</style>
二、响应式基础
此处的响应式是指 :
- 数据模型发生变化时,自动更新DOM树内容,页面上显示的内容会进行同步变化,
vue3
的数据模型不是自动响应式的,需要我们做一些特殊的处理
2.1 响应式需求案例
需求:实现 + - 按钮,实现数字加一减一
这个案例中 数值是会变的 但是页面上不会实时更新
Vue3中 需要用vue
提供的ref
reactive
关键字
案例参考:
【Vue3 + Vite】Vite搭建 项目解构 Vue快速学习 第一期
2.2 响应式实现关键字ref
ref
可以将一个基本类型的数据(如字符串,数字等)转换为一个响应式对象。
ref
只能包裹单一元素
/* 从vue中引入ref方法 */
import {ref} from 'vue'
let counter = ref(0);
数据变化,视图也会跟着动态更新。
需要注意的是,由于使用了ref
,因此需要在访问该对象时使用.value
来获取其实际值。
2.3 响应式实现关键字reactive
reactive() 函数创建一个响应式对象或数组:
<script setup>
/* 从vue中引入reactive方法 */
import { ref, reactive } from "vue";
let person = reactive({
name: "道格",
age: "22"
});
/* 函数中要操作reactive处理过的数据,需要通过 对象名.属性名的方式 */
function changeAge() {
person.age++;
}
function showAge() {
alert(person.age);
}
</script>
<template>
<div>
<button @click="changeAge()">{{person.age}}</button>
<button @click="showAge()">showAge</button>
</div>
</template>
<style scoped>
</style>
对比ref和reactive :
-
ref
函数 适用场景:- 更适合单个变量
- 需要通过
.value
访问其实际值
-
reactive
函数 适用场景:- 更适合对象
reactive
可以将一个普通对象转化为响应式对象,这样在数据变化时会自动更新界面,特别适用于处理复杂对象或者数据结构。- 使用
reactive
可以递归追踪所有响应式对象内部的变化,从而保证界面的自动更新
综上所述:
ref
适用与简单情形下的数据双向绑定,对于只有一个字符等基本类型数据或自定义组件等情况,建议可以使用ref
;- 而对于对象、函数等较为复杂的数据结构,以及需要递归监听的属性变化,建议使用
reactive
。
2.4 扩展响应式关键字toRefs 和 toRef
toRef
函数
- 将一个
reactive
响应式对象中的某个属性转换成一个ref
响应式对象- 每个单独的 ref 都是使用 toRef() 创建的。
toRefs
函数:
- 将一个reactive
响应式对象中的多个属性转换成多个ref
响应式对象
案例:响应显示reactive对象属性
<script type="module" setup>
/* 从vue中引入reactive方法 */
import {ref,reactive,toRef,toRefs} from 'vue'
let data = reactive({
counter:0,
name:"test"
})
// 将一个reactive响应式对象中的某个属性转换成一个ref响应式对象
let ct =toRef(data,'counter');
// 将一个reactive响应式对象中的多个属性转换成多个ref响应式对象
let {counter,name} = toRefs(data)
function show(){
alert(data.counter);
// 获取ref的响应对象,需要通过.value属性
alert(counter.value);
alert(name.value)
}
/* 函数中要操作ref处理过的数据,需要通过.value形式 */
let decr = () =>{
data.counter--;
}
let incr = () =>{
/* ref响应式数据,要通过.value属性访问 */
counter.value++;
}
</script>
<template>
<div>
<button @click="data.counter--">-</button>
<button @click="decr()">-</button>
{{ data.counter }}
&
{{ ct }}
<button @click="data.counter++">+</button>
<button @click="incr()">+</button>
<hr>
<button @click="show()">显示counter值</button>
</div>
</template>
<style scoped>
</style>
三、条件和列表渲染
3.1 条件渲染
v-if
条件渲染
v-if='表达式'
只会在指令的表达式返回真值时才被渲染- 也可以使用
v-else
为v-if
添加一个“else 区块”。 - 一个
v-else
元素必须跟在一个v-if
元素后面,否则它将不会被识别。
<script setup>
/* 从vue中引入reactive方法 */
import { ref, reactive } from "vue";
let awesome = ref(true);
</script>
<template>
<div>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no !</h1>
<button @click="awesome = !awesome">Toggle</button>
</div>
</template>
<style scoped>
</style>
v-show
条件渲染扩展:
-
另一个可以用来按条件显示一个元素的指令是
v-show
。其用法基本一样. -
不同之处在于
v-show
会在 DOM 渲染中保留该元素;v-show
仅切换了该元素上名为display
的 CSS 属性。 -
v-show
不支持在<template>
元素上使用,也不能和v-else
搭配使用。
<script setup>
/* 从vue中引入reactive方法 */
import { ref, reactive } from "vue";
let awesome = ref(true);
</script>
<template>
<div>
<h1 id="ha" v-show="awesome">Vue is awesome!</h1>
<button @click="awesome = !awesome">Toggle</button>
</div>
</template>
<style scoped>
</style>
v-show
就是通过style
隐藏样式
只是需要隐藏/显示 频繁切换可以用 v-show
v-if
vsv-show
-
v-if
是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。 -
v-if
也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。 -
相比之下,
v-show
简单许多,元素无论初始条件如何,始终会被渲染,只有 CSSdisplay
属性会被切换。 -
总的来说,
v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此,如果需要频繁切换,则使用v-show
较好;如果在运行时绑定条件很少改变,则v-if
会更合适。
3.2 列表渲染
使用
v-for
指令基于一个数组来渲染一个列表。
-
v-for
指令的值需要使用item in items
形式的特殊语法,其中items
是源数据的数组,而item
是迭代项的别名: -
在
v-for
块中可以完整地访问父作用域内的属性和变量。v-for
也支持使用可选的第二个参数表示当前项的位置索引。
<script setup>
/* 从vue中引入reactive方法 */
import { ref, reactive } from "vue";
let parentMessage = ref("产品");
let items = reactive([
{ id: "item1", message: "可乐" },
{ id: "item2", message: "百事" }
]);
</script>
<template>
<div>
<ul>
<li v-for="item in items" :key="item.id">{{item.message}}</li>
</ul>
<ul>
<!-- index表示索引 -->
<li
v-for="(item, index) in items"
:key="index"
>{{ parentMessage }} - {{ index }} - {{ item.message }}</li>
</ul>
</div>
</template>
<style scoped>
</style>
- 案例:实现购物车显示和删除购物项
<script type="module" setup>
//引入模块
import { reactive } from "vue";
//准备购物车数据,设置成响应数据
const carts = reactive([
{ id: "item1", name: "可乐", price: 3, number: 5 },
{ id: "item2", name: "百事", price: 2, number: 10 }
]);
//计算购物车总金额
function counter() {
let count = 0;
for (let index in carts) {
count += carts[index].price * carts[index].number;
}
return count;
}
//删除购物项方法
function removeThing(index) {
carts.splice(index, 1);
}
</script>
<template>
<div>
<table>
<thead>
<th>序号</th>
<th>商品名</th>
<th>价格</th>
<th>数量</th>
<th>小计</th>
<th>操作</th>
</thead>
<tbody v-if="carts.length > 0">
<tr v-for="(item,index) in carts" :key="index">
<td>{{index+1}}</td>
<td>{{item.name}}</td>
<td>{{item.price}}</td>
<td>{{item.number}}</td>
<td>{{item.price * item.number + '元'}}</td>
<td>
<button @click="removeThing(index)">删除</button>
</td>
</tr>
</tbody>
<tbody v-else>
<!-- 没有数据显示-->
<tr>
<td colspan="6">购物车没有数据!</td>
</tr>
</tbody>
</table>
购物车总金额:{{counter()}} 元
</div>
</template>
<style scoped>
</style>
四、双向绑定
单项绑定和双向绑定
- 单向绑定: 响应式数据 会影响 页面显示DOM的数据,反过来用户在页面上的操作不会影响响应式数据。
- (script中设置数值,HTML显示,HTML里改变数据不影响script中数值)
- 双向绑定: 响应式数据 会 影响页面 ,反过来 页面数据的变化 也会影响响应式数据
- (script设置数据,HTML显示的同时也能反向改变script里的数值)
- 用户通过表单标签才能够输入数据,所以双向绑定都是应用到表单标签上的,其他标签不行
- v-model专门用于双向绑定表单标签的value属性,语法为
v-model:value=''
,可以简写为v-model=''
- v-model还可以用于各种不同类型的输入,
<textarea>
、<select>
元素。
代码演示:
<script type="module" setup>
//引入模块
import { ref, reactive } from "vue";
let hobbys = ref([]);
let user = reactive({
username: null,
pwd: null,
introduce: null,
address: null
});
// 清空全部已填数据
function clearAll() {
user.username = "";
user.pwd = "";
user.introduce = "";
user.address = "";
//ref要用value
hobbys.value.splice(0, hobbys.value.length);
}
</script>
<template>
<div>
账号:
<input type="text" placeholder="请输入账号!" v-model="user.username" />
<br />密码:
<input type="password" placeholder="请输入密码!" v-model="user.pwd" />
<br />
<textarea v-model="user.introduce"></textarea>
<br />地址:
<select v-model="user.address">
<option value="1">闽</option>
<option value="2">粤</option>
<option value="3">京</option>
</select>
<br />爱好:
<input type="checkbox" name="hbs" v-model="hobbys" value="Java" />
<input type="checkbox" name="hbs" v-model="hobbys" value="C#" />
<input type="checkbox" name="hbs" v-model="hobbys" value="Python" />
<input type="checkbox" name="hbs" v-model="hobbys" value="C++" />
{{hobbys}}
<br />
显示user数据: {{user}}
<br />
<button @click="clearAll()">清空已填数据</button>
</div>
</template>
<style scoped>
</style>
数据显示:
数据清空:
五、数据监听器
使用 watch 函数在每次响应式状态发生变化时触发回调函数:
watch
主要用于以下场景:
- 当数据发生变化时需要执行相应的操作
- 监听数据变化,当满足一定条件时触发相应操作
- 在异步操作前或操作后需要执行相应的操作
监控响应式数据(watch):
<script type="module" setup>
//引入模块
import { ref,reactive,watch} from 'vue'
let firstname=ref('')
let lastname=reactive({name:''})
let fullname=ref('')
//监听一个ref响应式数据
watch(firstname,(newValue,oldValue)=>{
console.log(`${oldValue}变为${newValue}`)
fullname.value=firstname.value+lastname.name
})
//监听reactive响应式数据的指定属性
watch(()=>lastname.name,(newValue,oldValue)=>{
console.log(`${oldValue}变为${newValue}`)
fullname.value=firstname.value+lastname.name
})
//监听reactive响应式数据的所有属性(深度监视,一般不推荐)
//deep:true 深度监视
//immediate:true 深度监视在进入页面时立即执行一次
watch(()=>lastname,(newValue,oldValue)=>{
// 此时的newValue和oldValue一样,都是lastname
console.log(newValue)
console.log(oldValue)
fullname.value=firstname.value+lastname.name
},{deep:true,immediate:false})
</script>
<template>
<div>
全名:{{fullname}} <br>
姓氏:<input type="text" v-model="firstname"> <br>
名字:<input type="text" v-model="lastname.name" > <br>
</div>
</template>
<style scoped>
</style>
监控响应式数据(watchEffect):
watchEffect
默认监听所有的响应式数据
<script type="module" setup>
//引入模块
import { ref,reactive,watch, watchEffect} from 'vue'
let firstname=ref('')
let lastname=reactive({name:''})
let fullname=ref('')
//监听所有响应式数据
watchEffect(()=>{
//直接在内部使用监听属性即可!不用外部声明
//也不需要,即时回调设置!默认初始化就加载!
console.log(firstname.value)
console.log(lastname.name)
fullname.value=`${firstname.value}${lastname.name}`
})
</script>
<template>
<div>
全名:{{fullname}} <br>
姓氏:<input type="text" v-model="firstname"> <br>
名字:<input type="text" v-model="lastname.name" > <br>
</div>
</template>
<style scoped>
</style>
watch
vs.watchEffect
watch
和 watchEffect
都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:
watch
只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch
会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。watchEffect
,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。