Introduce
使用
使用 vue 的两种方式:
- 引入 vue 的 js 文件
- 脚手架
- 官方脚手架 vue-cli
- 民间脚手架 如 webpack-simple
- 手动搭建
Hello!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 界面模板 -->
<!-- mustache: {{js 表达式}} -->
<h1>This is title: {{title}}</h1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
// vm: Vue 实例
let vm = new Vue({// 配置
// el(element): css 选择器
el: '#app',
// data: 和界面相关的数据
data: {
message: 'Hello Vue.js!',
title: 'Hello!'
}
})
</script>
</body>
</html>
在开发者控制台中可通过变量访问并修改内容。
数据变动,VUE感知到数据变化,重渲染(数据响应式)。
MVVM(Model,View,View Model)模式
creeper!
<div id="app">
<!-- 界面模板 -->
<!-- mustache: {{js 表达式}} -->
<h1>{{message}}</h1>
<ul>
<li v-for="(creep, i) in lists">
{{creep.role}}:
<span v-if="creep.num > 0">{{creep.num}}</span>
<span v-else>none</span>
<button @click="spawn(i)">spawn</button>
<button @click="kill(i)">kill</button>
</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
// vm: Vue 实例
let vm = new Vue({// 配置
// el(element): css 选择器
el: '#app',
// data: 和界面相关的数据
data: {
message: 'Hello Vue.js!',
lists: [
{role: 'harvester', num: 2},
{role: 'builder', num: 3},
{role: 'upgrader', num: 4}
]
},
methods: {
// 方法
spawn(i) {
// this 指代 vm 对象
this.lists[i].num++;
},
kill(i) {
this.lists[i].num--;
if (this.lists[i].num < 0) {
this.lists[i].num = 0;
};
}
}
})
</script>
效果如下
基础概念
ES6 一些特性
速写属性 和 速写方法
let role = harvest;
let creep = {
role,
logic() {
}
}
/*
let creep = {
role: role,
logic: function() {
}
}
*/
模板字符串
let num = 1;
let str = `第一行
第二行
第三行${num}`;
// let str = '第一行\n第二行\n第三行' + num;
箭头函数
匿名函数都可写为箭头函数...
箭头函数没有自己的this,使用箭头函数定义位置的this
模块化
常见标准:CommonJS、ES6 Module、AMD、CMD、UMD
注入
之前的代码如下:
// vm: Vue 实例
let vm = new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
lists: [
{role: 'harvester', num: 2},
{role: 'builder', num: 3},
{role: 'upgrader', num: 4}
]
},
methods: {
// 方法
spawn(i) {
// this 指代 vm 对象
this.lists[i].num++;
},
kill(i) {
this.lists[i].num--;
if (this.lists[i].num < 0) {
this.lists[i].num = 0;
};
}
}
})
我们可以直接通过vm.message或vm.spawn(NUM)访问data或methods中的属性。
因为data和methods中的内容会被直接提取到vue实例中。
该过程称之为注入。
目的如下:
- 完成数据响应式
将使用Object.defineProperty
或class Proxy
添加对数据的监听。
区别-> - 绑定this
我们在methods中的函数中使用了this,方便函数修改读取数据。
虚拟 DOM 树
为了提高渲染效率,vue将模板编译成为虚拟DOM树,然后再生成真实DOM树。
虚拟DOM树只是js对象,并未展示到页面上,真实DOM树也是js对象,但和页面上的信息相关。
如何提高渲染效率的:
- 减少直接操作真实DOM: 真实DOM操作是相对昂贵的,因为每次更改都会引起浏览器的重排或重绘。虚拟DOM充当了一个中间层,将对DOM的更改聚集在一起,并使用高效的算法来最小化实际DOM的操作次数,减少性能开销。
- 批量更新: 虚拟DOM将多个DOM更改批量处理,这样可以避免多次重新渲染。在更新过程中,虚拟DOM会记录所有需要更新的更改,然后一次性进行实际DOM的操作,优化整体性能。
- Diff算法: 虚拟DOM使用Diff算法比较先前虚拟DOM树和当前虚拟DOM树的差异。Diff算法能够高效地找出哪些部分发生了变化,然后只更新发生变化的部分到真实DOM,而不会重新渲染整个DOM树。
- 内存中的操作: 虚拟DOM存在于JavaScript内存中,操作更加高效。相比之下,实际DOM操作涉及到与浏览器的交互,需要消耗更多的资源。
- 跨平台优势: 虚拟DOM可以在不同平台上运行,例如浏览器和服务器端(例如React使用的React Native和React Server)。这种一致性让开发者可以在不同环境下共享代码,从而提高开发效率和性能。
可通过vm._vnode访问虚拟DOM树 pe(Pseudo Element)
let v1 = vm._vnode;
vm.title = "123";
let v2 = vm._vnode;
v1 === v2;
// false
v1.elm === v2.elm;
// true
因此,对于提升vue效率重点在以下两个方面:
- 减少新的虚拟DOM树生成。
- 保证对比之后,只有必要的节点变化。
vue提供了以下方式修改虚拟DOM树: - 直接修改挂载的元素内部,使用元素的outerHTML作为模板。
- 在template配置中书写。
- 在render配置中使用函数直接创建虚拟节点树,完全脱离模板,省略编译步骤。
从上到下优先级递增。
上面的三个方法其实本质相同:
<div id="app">
<<h1>{{message}}</h1>
<ul>
<li v-for="(creep, i) in lists">
{{creep.role}}
</li>
</ul>
</div>
// vm: Vue 实例
let vm = new Vue({
el: '#app',
template: `
<div>
<h1>{{message}}</h1>
<ul>
<li v-for="(creep, i) in lists">
{{creep.role}}
</li>
</ul>
</div>
`,
render(h) {
return h("div", [
h("h1", this.message),
h("ul", this.lists.map(creep => h("li", creep.role)))
])
},
data: {
...
},
methods: {
...
}
})
本质都是通过render函数。数据变化便可重新调用render函数将页面更新。
虚拟节点树必须是单根的
“模板只能由一个根节点”
挂载
将虚拟DOM树生成的真实DOM树,放置到某个元素位置(替换),称为挂载。
挂载的方式:
- el配置
- 通过调用vm.$mount("css选择器")
完整流程
首次渲染:实例创建 -> 注入 -> 编译生成虚拟DOM树 -> 挂载 -> 已挂载
重新渲染:数据变动 -> 重新生成新DOM树 -> 对比新旧树的差异 -> 将差异应用到真实DOM -> 完成渲染
模板语法 与 计算属性
模板语法
内容
元素内容位置
元素内容中使用mustache模板引擎进行解析
vue将vue实例绑定到模板字符串上,{{title}} 相当于 vm.title。
模板引擎
将一个模板字符串,加上一个对象,进行编译得到一个完整字符串。
指令
元素属性位置
指令影响元素的渲染行为,始终以v-开头
基础指令:
v-for
: 循环渲染元素v-html
: 设置元素的innerHTML,会导致元素的模板内容失效v-on
: 注册事件- 十分常用可简写为
@
- 支持一些指令修饰符,如
prevent
阻止默认行为,stop
阻止冒泡 - 自动传递参数
- 十分常用可简写为
v-bind
: 绑定动态属性- 简写为
:
- 简写为
v-show
: 控制元素可见度v-if v-else-if v-else
: 控制元素生成v-model
: 双向数据绑定- 是
v-on
和v-bind
的复合版
- 是
<li v-for="(creep, i) in lists">{{creep}}</li>
// html: "<p style='color:red'> Hello! </p>"
<h1 v-html="html"></h1>
<button v-on:click="spawn(i)">spawn</button>
<button @click="spawn(i)">spawn</button>
// 阻止默认行为
<a herf="" @click.prevent="console.log"></a>
// 点击时运行console.log(e), 参数自动传递
// 手动传递
<a herf="" @click.prevent="console.log($event)"></a>
// imgUrl: "https://xxx"
<img v-bind:src="imgUrl"/>
<img :src="imgUrl"/>
// isShow: true
<img v-show="isShow" :src="imgUrl"/>
<button @click="isShow = !isShow"></button>
<img v-if="isShow" :src="imgUrl"/>
<h1>{{text}}<h1>
<input type="text" :value="text" @input="text=$event.target.value"></input>
<h1>{{text}}<h1>
<input type="text" v-model:value="text"></input>
v-on
中传递的可以是函数名也可以是函数调用
v-show
和v-if ...
的区别
v-show
只设置display:none
,元素依旧存在
v-if ...
不存在就真的不存在,不会出现在DOM中
若需要频繁切换显示,使用v-show
不会影响元素结构
其他指令
- v-slot
- v-text
- v-pre
- v-cloak
- v-once
- 自定义指令
特殊属性
最重要的特殊属性 key
考虑以下代码:
<div id="app">
<div v-if="loginMethod==='phone'">
<label>手机号</label>
<input type="text" />
</div>
<div v-else>
<label>邮箱</label>
<input type="text" />
</div>
<button @click="loginMethod === 'phone' ? loginMethod = 'email' : loginMethod = 'phone'">切换登录方式</button>
</div>
如果我们尝试在input中输入内容后切换登录方式,会发现输入框的内容依旧保留,但观察我们的代码两个并不是同一个输入框。
diff算法通过比较前两个DOM发现只有label发生变化,便只改动了label,导致input并没有发生任何变化。
key属性可以干预diff算法,若设置了key值,则会只对key相同的节点进行比对,若key值不同,便不再比对元素,直接修改(旧节点移除,新节点添加)。
<div id="app">
<div v-if="loginMethod==='phone'">
<label>手机号</label>
<input type="text" key="1"/>
</div>
<div v-else>
<label>邮箱</label>
<input type="text" key="2"/>
</div>
<button @click="loginMethod === 'phone' ? loginMethod = 'email' : loginMethod = 'phone'">切换登录方式</button>
</div>
这样就不会出现上面的情况了。
再考虑以下情况:)
<div id="app">
<p v-for="(v, i) in lists">
{{v}}
<button @click="lists.splice(i, 1)">delete</button>
</p>
</div>
通过开发者控制台可以发现,当我们删除其中一个元素时,发现其他没被删除的元素也被更新了,若列表较长,会非常影响效率。
假设原来有1、2、3、4四个节点,若删除第二个1、3、4,比较时,1和1比较,2和3比较,3和4比较...发现后面的节点都变化了
针对这种情况,vue强烈建议给每个在循环生成的节点上添加一个唯一且稳定的key值。
添加后,只会比较key相同的节点。
<div id="app">
<p v-bind:key="v.id" v-for="(v, i) in lists">
{{v.value}}
<button @click="lists.splice(i, 1)">delete</button>
</p>
</div>
key属性不会出现在实际dom中
其他特殊属性
- ref
- is
- slot
- slot-scope
- scope
计算属性
类似 getter setter
computed: {
// 仅访问器
prop() {
return ...
}
// 访问器 + 设置器
fullProp: {
get() {
return ...
},
set(val) {
...
}
}
}
例:
<div id="app">
<p>firstname: {{firstName}}</p>
<p>lastname: {{lastName}}</p>
<p>fullname: {{fullName}}</p>
<input type="text" v-model="fullName"/>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
firstName: "Lorem",
lastName: "ipsum"
},
computed: {
fullName: {
get() {
return this.firstName + " " + this.lastName;
},
set(val) {
console.log("1");
[this.firstName, this.lastName] = val.split(" ");
}
}
}
})
</script>
计算属性(computed)和方法(method)的区别
- 计算属性可以赋值,方法不行
- 计算属性会进行缓存,依赖不变,不会重新计算
- 根据已有数据得到新数据的无参函数,都应该写成计算属性
组件
组件概念
直接将完整网页作为整体开发,会遇到以下困难
- 代码凌乱臃肿
- 不易协作
- 难以复用
推荐组件化开发,组件化就是把页面中的区域功能细分,每个区域成为一个组件,每个组件包含: - 功能:JS
- 内容:模板
- 样式:CSS
CSS代码需要构建工具的支撑
组件开发
创建组件
组件根据普通的配置对象创建,只需写一个配置对象即可开发一个组件
配置对象和vue实例几乎一样
// 组件配置对象
let com = {
data() {
return {
// ...
}
},
methods: {
// ...
},
computed: {
// ...
},
template: ...
}
组件配置对象和vue实例有以下差异:
- 无el
- data必须是一个函数,函数返回的对象作为数据
- 没有el配置,组件的虚拟DOM树必须定义在template或render中
注册组件
两种注册方式,全局注册,局部注册
全局注册
整个应用中任何地方都可以使用该组件
全局注册方式:
// 参数1:组件名称,使用组件时的名称
// 参数2:组件配置对象
// 运行后便可在模板中使用组件
Vue.component("MyComp", myComp)
// 使用
<MyComp />
<MyComp></MyComp>
// 或
<my-comp />
<my-comp></my-comp>
大多数时候不需要全局注册,一般不建议
局部注册
在哪用在哪注册
let vm = new Vue({
el:...
...
components {
// 属性名为组件名,属性值为组件配置对象
MyComp: myComp,
},
template: `<div>
<my-comp></my-comp>
</div>`
})
应用组件
将组件名当作HTML元素名使用即可
- 组件必须有结束
可以自结束,也可使用结束标记 - 组件的命名
可以使用kebab-case短横线命名法,也可使用PascalCase大驼峰命名法
若使用大驼峰命名法,使用时可以使用两种组件名
实际上,使用小驼峰命名法camelCase也可以识别,不过不符合官方要求的规范
组件树
一个组件创建好后,可能被多次使用,便会形成组件树
向组件传递数据
let ChildComp = {
props: ["msg"],
template: `
<h2>{{ msg || 'No props passed yet' }}</h2>
`
}
let vm = new Vue({
el: ...
components: {
ChildComp,
}
template: `<div>
<child-comp msg="Hello child" />
</div>`
})
:msg 和 msg
若使用 :msg 即 v-bind:msg 时,引号内部被当作js代码传递
不使用 v-bind 则当作字符串传递
单向数据流
在组件中,属性只读,绝不可以更改
工程化
TODOList
├─ index.html
├─ lib
│ └─ vue.js
└─ src
├─ App.js
├─ components
│ └─ TodoLists.js
└─ main.js