初始工程结构
这里我们使用script标签从cdn获取vue.js, 而不是使用脚手架vue-cli, 因为cdn比较方便一点, 也不用配置node之类的比较麻烦
index.html
<!DOCTYPE html>
<html>
<head>
<title>VueJS Course</title>
<link rel="stylesheet" type="text/css" href="main.css" />
</head>
<body>
<div id="app">
<p>Hey</p>
</div>
<!-- vue cdn -->
<script src="https://unpkg.com/vue@next"></script>
<script src="app.js"></script>
</body>
</html>
app.js
Vue.createApp({})
// 挂载vue到id=app的元素 .mount('#app')
main.css
body {
font-size: 22px;
}
input[type="text"] {
margin: 10px 0;
display: block;
}
vue devtools 调试工具
在谷歌插件商店安装, 没有也没关系
为了我们打开html(cdn引入了vue)也能调试vue
在浏览器打开文件的方式(我们使用的是vscode)
#1 - 创建并挂载vue实例
createApp会创建一个vue实例, 它有一个mount方法可以将该实例挂载到指定的dom元素上, 该实例会解析这个dom元素的vue语法, 进行处理
// 创建实例 Vue.createApp({})
// 挂载vue到id=app的元素 .mount('#app')
#2 - data: 定义数据 | 插值表达式
// 定义在vue实例外面的不会被vue实例识别并渲染 let lastName = 'abc'
Vue.createApp({
// 定义数据 data() {
return {
firstName:'John'
}
}
})
// 挂载vue到id=app的元素 .mount('#app')
在html里使用
<!DOCTYPE html>
<html>
<head>
<title>VueJS Course</title>
<link rel="stylesheet" type="text/css" href="main.css" />
</head>
<body>
<!-- {{firstName}} -->
<div id="app">
<!-- 插值表达式,vue会运行里面的js表达式,并将结果渲染到页面 -->
{{2+2}}
<br />
{{firstName}}
<br />
<!-- 没有定义在vue对象里 -->
<!-- operty "lastName" was accessed during render but is not defined on instance. at <App> -->
{{lastName}}
<!-- error: avoid using JavaScript keyword as property name: "const" -->
<!-- {{const a = 'foo'}} -->
</div>
<!-- vue cdn -->
<script src="https://unpkg.com/vue@next"></script>
<script src="app.js"></script>
</body>
</html>
#3 多个vue实例
同一个vue实例不能同时挂载到两个dom元素, 但是可以有多个vue实例, 分别挂载到不同的dom上
<!DOCTYPE html>
<html>
<head>
<title>VueJS Course</title>
<link rel="stylesheet" type="text/css" href="main.css" />
</head>
<body>
<!-- {{firstName}} -->
<div id="app">
<!-- 插值表达式,vue会运行里面的js表达式,并将结果渲染到页面 -->
{{2+2}}
<br />
{{firstName}}
<br />
<!-- 没有定义在vue对象里 -->
<!-- operty "lastName" was accessed during render but is not defined on instance. at <App> -->
{{lastName}}
<!-- error: avoid using JavaScript keyword as property name: "const" -->
<!-- {{const a = 'foo'}} -->
</div>
<!-- 无法同时挂载到多个对象 -->
<div id="app">
{{firstName}}
<br />
</div>
<!-- 可以有多个vue实例,分别挂载不同对象 -->
<div id="app2">
{{firstName}}
<br />
</div>
<!-- vue cdn -->
<script src="https://unpkg.com/vue@next"></script>
<script src="app.js"></script>
</body>
</html>
app.js
// let lastName = 'abc' Vue.createApp({
// 定义数据 data() {
return {
firstName: 'John'
}
}
})
// 挂载vue到id=app的元素 .mount('#app')
Vue.createApp({
// 定义数据 data() {
return {
firstName: 'Bob'
}
}
})
// 挂载vue到id=app2的元素 .mount('#app2')
#4 methods: 定义方法
<!DOCTYPE html>
<html>
<head>
<title>VueJS Course</title>
<link rel="stylesheet" type="text/css" href="main.css" />
</head>
<body>
<div id="app">
{{`${firstName} ${lastName.toUpperCase()}`}}
</div>
<!-- vue cdn -->
<script src="https://unpkg.com/vue@next"></script>
<script src="app.js"></script>
</body>
</html>
但是我们希望视图和逻辑分开, js写方法的具体逻辑, html里是样式和标签
我们定义一个函数, 放到vue实例的methods里, 然后在html里使用
const vm = Vue.createApp({
// 定义数据 data() {
return {
firstName: 'John',
lastName: 'Bob'
}
},
methods: {
fullName() {
return `${this.firstName} ${this.lastName.toUpperCase()}`
}
},
})
// 挂载vue到id=app的元素 .mount('#app')
在插值表达式里使用方法
<!DOCTYPE html>
<html>
<head>
<title>VueJS Course</title>
<link rel="stylesheet" type="text/css" href="main.css" />
</head>
<body>
<div id="app">
{{ fullName() }}
</div>
<!-- vue cdn -->
<script src="https://unpkg.com/vue@next"></script>
<script src="app.js"></script>
</body>
</html>
#5 v-cloak 实现渲染完再显示
先调整网速至低俗3G(按f12打开该界面)
此时刷新页面会发现, 插值表达式还在, 数据还没被渲染, 过了一会加载完成, 才渲染上数据
我们使用v-cloak来让加载时不出现这些vue语法
这个cloak在加载的时候会存在, 加载完成后会消失, 我们可以css选择clock, 让加载时cloak不显示页面元素, 等加载完移除cloak, 然后自动恢复元素显示
/* 加载时存在,加载并处理完成后变成data-v-app */
[v-cloak] {
/* 实现加载时不出现插值表达式等模板语法,加载完直接显示结果 */
display: none;
}
<!-- v-cloak,元素加载完成后会消失 -->
<div id="app" v-cloak>
{{ fullName() }}
</div>
#6 v-model: 数据绑定
v-model 可以将input框的value和data里的数据绑定, 改变input的value, 也能同时改变data里对应的变量
<!DOCTYPE html>
<html>
<head>
<title>VueJS Course</title>
<link rel="stylesheet" type="text/css" href="main.css" />
</head>
<body>
<div id="app" v-cloak>
<p>{{ fullName() }}</p>
<hr>
<label>First Name</label>
<!-- 响应式Reactivity -->
<input type="text" v-model="firstName">
<label>Last Name</label>
<input type="text" v-model="lastName">
</div>
<!-- vue cdn -->
<script src="https://unpkg.com/vue@next"></script>
<script src="app.js"></script>
</body>
</html>
其实v-model是v-bind和@input的语法糖
#7 v-bind 绑定data里的变量到dom元素的属性上
<p><a v-bind:href="url" target="_blank">bing</a></p>
<!-- 简写 -->
<p><a :href="url" target="_blank">bing</a></p>
const vm = Vue.createApp({
// 定义数据 data() {
return {
url: 'https://bing.com'
}
},
})
// 挂载vue到id=app的元素 .mount('#app')
#8 v-html: 渲染富文本
<p v-html="raw_url"></p>
const vm = Vue.createApp({
// 定义数据 data() {
return {
raw_url: '<p><a href="https://bing.com" target="_blank">bing</a></p>'
}
},
})
// 挂载vue到id=app的元素 .mount('#app')
#9 事件监听
我们用 v-on 或简写 @事件名 来监听事件
<label>Last Name</label>
<!-- <input type="text" v-model="lastName"> -->
<!-- 监听input事件 -->
<input type="text" :value="lastName" @input="updateLastName">
<hr>
<p>{{age}}</p>
<!-- 监听事件 -->
<button v-on:click="increment">Increment</button>
<button @click="age--">Decrement</button>
const vm = Vue.createApp({
// 定义数据 data() {
return {
lastName: 'Bob',
age: 20
}
},
methods: {
increment() {
this.age++
},
updateLastName(e) {
this.lastName = e.target.value
}
},
})
// 挂载vue到id=app的元素 .mount('#app')
#10 (调用函数时)传递事件对象 event
向方法传递参数时,如果想传递自定义参数,又想获取事件对象e,需要手动传递$event
<input type="text" :value="lastName" @input="updateLastName('update lastname',$event)">
const vm = Vue.createApp({
// 定义数据 data() {
return {
lastName: 'Bob',
}
},
methods: {
updateLastName(msg, e) {
e.preventDefault()
console.log(msg);
this.lastName = e.target.value
}
},
})
// 挂载vue到id=app的元素 .mount('#app')
#11 事件修饰符
事件修饰符,简化像e.preventDefault这种操作, 被加上了.xxx的方法, 会执行对应的js
<input type="text" :value="lastName" @input.prevent="updateLastName('update lastname',$event)">
const vm = Vue.createApp({
// 定义数据 data() {
return {
lastName: 'Bob',
}
},
methods: {
updateLastName(msg, e) {
// 加了.prevent, 不需要再加这句 // e.preventDefault() console.log(msg);
this.lastName = e.target.value
}
},
})
// 挂载vue到id=app的元素 .mount('#app')
#12 keyboard 键盘修饰符
<label>Middle Name</label>
<!-- 按下enter触发 -->
<input type="text" @keyup.enter="updateMiddleName">
<!-- 点击按钮+ctrl键才能触发 -->
<button @click.ctrl="...">Decrement</button>
const vm = Vue.createApp({
// 定义数据 data() {
return {
middleName: '',
}
},
methods: {
updateMiddleName(e) {
this.middleName = e.target.value
}
},
})
// 挂载vue到id=app的元素 .mount('#app')
#13 v-model的事件修饰符
<!-- 在输入框中,v-model 默认是同步数据,使用 .lazy 会转变为在 change 事件中同步 也就是在失去焦点 或者 按下回车键时才更新** -->
<input type="text" v-model.lazy.trim="firstName">
<!-- input框输入数字时,转为number类型,如果没有.number,则依然是string -->
<input type="text" v-model.number="age">
#14 Computed Properties 计算属性
如果在method里计算fullname,则当其他data的数据更新时,fullname也会被重新计算,因为每次更新数据,vue都会重新渲染数据到页面上,则会调用所有method方法(由事件触发的方法不会),所以,我们需要计算属性来让方法在需要时才被调用
const vm = Vue.createApp({
// 定义数据 data() {
return {
firstName: 'John',
middleName: '',
lastName: 'Bob',
}
},
methods: {
fullName() {
console.log(1);
return `${this.firstName} ${this.middleName} ${this.lastName.toUpperCase()}`
},
},
})
// 挂载vue到id=app的元素 .mount('#app')
<!DOCTYPE html>
<html>
<head>
<title>VueJS Course</title>
<link rel="stylesheet" type="text/css" href="main.css" />
</head>
<body>
<div id="app" v-cloak>
{{fullName()}}
<div @click="age++">
{{age}}
</div>
</div>
<!-- vue cdn -->
<script src="https://unpkg.com/vue@next"></script>
<script src="app.js"></script>
</body>
</html>
一直点击div, 更新age, 触发视图更新, 此时可以看到触发了很多次的fullname方法
只有和计算属性关联的值的更新,才会触发计算属性的更新
const vm = Vue.createApp({
// 定义数据 data() {
return {
firstName: 'John',
middleName: '',
lastName: 'Bob',
age:20
}
},
// 计算属性 computed: {
fullName() {
console.log(1);
return `${this.firstName} ${this.middleName} ${this.lastName.toUpperCase()}`
},
},
})
// 挂载vue到id=app的元素 .mount('#app')
#15 watch 监听
监听值的更新,用得比较少
const vm = Vue.createApp({
// 定义数据 data() {
return {
age: 20
}
},
// watch可以执行异步任务 watch: {
age(newval, oldval) {
setTimeout(() => {
this.age = 100
}, 2000);
}
}
})
// 挂载vue到id=app的元素 .mount('#app')
#16 动态绑定class
body{
font-size: 20px;
font-family: sans-serif;
}
label{
margin-bottom: 20px;
font-size: 20px;
display: block;
}
select{
font-size: 20px;
margin-bottom: 20px;
}
input[type=number]{
display: block;
font-size: 20px;
margin-bottom: 20px;
}
.circle{
width: 150px;
height: 150px;
border-radius: 100%;
background-color: #45D619;
text-align: center;
color: #fff;
line-height: 150px;
font-size: 32px;
font-weight: bold;
}
.purple{
background-color: #767DEA;
}
.text-black{
color: #424242;
}
.text-orange{
color: #FFC26F;
}
<!DOCTYPE>
<html>
<head>
<title>VueJS Course</title>
<link rel="stylesheet" type="text/css" href="main.css" />
</head>
<body>
<div id="app">
<label>
<input type="checkbox" v-model="isPurple" /> Purple
</label>
<div class="circle" :class="{purple: isPurple}">
Hi!
</div>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script src="app.js"></script>
</body>
</html>
let vm = Vue.createApp({
data() {
return { isPurple: false }
}
}).mount('#app')
当需要多个class时,这种写法会很乱,所以我们把它移到计算属性去
<!DOCTYPE>
<html>
<head>
<title>VueJS Course</title>
<link rel="stylesheet" type="text/css" href="main.css" />
</head>
<body>
<div id="app">
<label>
<input type="checkbox" v-model="isPurple" /> Purple
</label>
<select v-model="selectedColor">
<option value="">white</option>
<option value="text-black">black</option>
<option value="text-orange">orange</option>
</select>
<!-- 添加多个class: 传递数组 -->
<div class="circle" :class="[circle_classes,selectedColor]">
Hi!
</div>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script src="app.js"></script>
</body>
</html>
let vm = Vue.createApp({
data() {
return { isPurple: false, selectedColor: '' }
},
computed: {
// 计算多个class circle_classes() {
return { purple: this.isPurple }
}
}
}).mount('#app')
#17 绑定style
<input type="number" v-model="size" />
<div class="circle" :class="[circle_classes,selectedColor]"
:style="[{width:size+'px',height:size+'px',lineHeight: size+'px'},{transform: 'rotate(30deg)'}]">
Hi!
</div>
<!-- 可以多个style对象 -->
<div class="circle" :class="[circle_classes,selectedColor]"
:style="{width:size+'px',height:size+'px',lineHeight: size+'px'}">
Hi!
</div>
let vm = Vue.createApp({
data() {
return { size: 150 }
},
}).mount('#app')
#18 条件渲染
<!DOCTYPE>
<html>
<head>
<title>VueJS Course</title>
<link rel="stylesheet" type="text/css" href="main.css" />
</head>
<body>
<div id="app">
<!-- 这些v-if的元素必须在同一个层级 -->
<p v-if="mode==1">showing v-if directive content</p>
<!-- template是vue提供的标签,类似<></>,不会渲染为html元素 -->
<template v-else-if="mode==2">
<p>v-else-if</p>
<div>hello</div>
</template>
<p v-else>v-else</p>
<!-- 这个要放在vue实例挂载的元素里v-model才能生效 -->
<select v-model="mode">
<option value="1">v-if</option>
<option value="2">v-else-if</option>
<option value="3">v-else</option>
</select>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script src="app.js"></script>
</body>
</html>
const vm = Vue.createApp({
// 定义数据 data() {
return {
moed:1
}
},
})
// 挂载vue到id=app的元素 .mount('#app')
#19 v-show
<!-- v-if是替换,v-show是设置display -->
<!-- v-show不能使用v-else和template标签 -->
<!-- v-show的性能更好 -->
<i v-show="mode==1">v-show</i>
#20 循环渲染
let vm = Vue.createApp({
data() {
return {
birds: ['Pigeons', 'Eagles', 'Doves', 'Parrots'],
people: [
{ name: 'John', age: 20 },
{ name: 'Rick', age: 18 },
{ name: 'Amy', age: 33 }
]
}
}
}).mount('#app');
<!DOCTYPE>
<html>
<head>
<title>VueJS Course</title>
<link rel="stylesheet" type="text/css" href="main.css" />
</head>
<body>
<div id="app">
<!-- 遍历数组 -->
<ul>
<!-- 第二个参数是索引下标 -->
<li v-for="(bird,index) in birds" :key="bird">
{{bird}}-{{index}}
</li>
</ul>
<hr>
<!-- 遍历对象数组 -->
<ul>
<!-- <li v-for="person in people"> <div>{{person.age}}</div> <div>{{person.name}}</div> </li> -->
<li v-for="person in people">
<!-- 遍历对象 -->
<div v-for="(value,key,index) in person">
index:{{index}} - {{key}} : {{ value }}
</div>
</li>
</ul>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script src="app.js"></script>
</body>
</html>
#21 key的作用
let vm = Vue.createApp({
data() {
return {
people: [
{
name: 'John',
message: 'Hello world!'
},
{
name: 'Rick',
message: 'I like pie.'
},
{
name: 'Amy',
message: 'Skydiving is fun!'
}
]
}
},
methods: {
move() {
const first = this.people.shift()
this.people.push(first)
}
}
}).mount('#app')
<!DOCTYPE>
<html>
<head>
<title>VueJS Course</title>
<link rel="stylesheet" type="text/css" href="main.css" />
</head>
<body>
<div id="app">
<button type="button" class="move" @click="move">
Move to Bottom
</button>
<!-- 不加key,则input不动 -->
<!-- 加key,强制元素移动,但是性能开销大 -->
<div class="card" v-for="person in people" :key="person.name">
<h3>{{ person.name }}</h3>
<p>{{ person.message }}</p>
<input type="text" />
</div>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script src="app.js"></script>
</body>
</html>