在官网学习Vue3的过程中感到有些晕头转向,学到后面又发现自己前面的知识点有些已经忘了,于是索性再重新学一遍,并且按照自己的理解稍微调换了一下目录顺序。笔记中有很多内容直接引用了官网上的示例或解释,所以篇幅有点长。另外有一些比较杂乱的知识点没有包含在其中,打算以后整理一下再专门写一篇文章。之前没怎么记过笔记,而且本人水平有限,如果有发现问题或有好的建议,欢迎提出!
1. 安装
安装Vue.js主要有以下五种方式。
1.1 CDN
直接引入对应的js文件。
<script src="https://unpkg.com/vue@next"></script>
对于生产环境,我们推荐链接到一个明确的版本号和构建文件,以避免新版本造成的不可预期的破坏。
1.2. 下载js文件并自行托管
从CDN下载js文件并自行托管在你的服务器上,使用方法与CDN一样。
1.3 npm
在用 Vue 构建大型应用时推荐使用 npm 安装 。npm 能很好地和诸如webpack 和 Rollup 模块打包器配合使用。
npm install vue@next
Vue
还提供了编写单文件组件的配套工具。如果你想使用单文件组件,那么你还需要安装@vue/compiler-sfc:
npm install -D @vue/compiler-sfc
1.4 命令行工具(CLI)
yarn global add @vue/cli
# 或
npm install -g @vue/cli
# 然后运行
vue upgrade --next
1.5 Vite
Vite 是一个 web 开发构建工具,由于其原生 ES模块导入方式,可以实现闪电般的冷服务器启动。
通过在终端中运行以下命令,可以使用 Vite 快速构建 Vue 项目。
接下来的笔记基于这个方法。
# npm 6.x
npm init vite@latest <project-name> --template vue
# npm 7+,需要加上额外的双短横线
npm init vite@latest <project-name> -- --template vue
cd <project-name>
npm install
npm run dev
2. 应用&组件实例
2.1 基本示例
cosnt RootComponent = {
// 选项
data() {
return {
count: 4
}
},
methods() {
},
// 还有props, computed, inject, setup ... 这些是组件实例property,还有内置property: $attrs和$emit
created() {
},
mounted() {
},
// 这些(created, mounted)是生命周期钩子函数,在实例生命周期的不同阶段被调用,还有updated,unmounted ...
template: `
<button @click="count++">
You clicked me {{ count }} times.
</button>`
}
const app = Vue.createApp({})
.component('root-component', RootComponent) // 创建一个名为root-component的全局组件
.directive('focus', FocusDirective)
.use(LocalePlugin)
const vm = app.mount("#app")
我们可以把这个组件作为一个根实例的自定义元素来使用:
<div id="components-demo">
<button-counter></button-counter>
</div>
每个 Vue 应用都是通过用 createApp
函数创建一个新的应用实例开始的,传递给 createApp的选项用于配置根组件。当我们挂载应用时,该组件被用作渲染的起点。
不要在选项 property 或回调上使用箭头函数,
因为this都是指向当前实例的,而箭头函数并没有this。
点击此处去官网详细了解生命周期图示
2.2 通过 Prop 向子组件传递数据
如下,父组件blog-post
通过设置title向子组件内传递数据
<!-- js文件 -->
const app = Vue.createApp({})
app.component('blog-post', {
props: ['title'],
template: `<h4>{{ title }}</h4>`
})
app.mount('#blog-post-demo')
<!-- html文件 -->
<div id="blog-post-demo" class="demo">
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>
</div>
2.3 监听子组件事件
<!-- 在emits列出子组件抛出的事件,注意在父组件和子组件中不同的命名规则 -->
app.component('blog-post', {
props: ['title'],
emits: ['enlargeText']
})
<!-- 父组件监听事件,通过$event访问被子组件抛出的值 -->
<blog-post ... @enlarge-text="postFontSize += $event"></blog-post>
<!-- 子组件被点击时触发事件 -->
<button @click="$emit('enlargeText', 0.1)">
Enlarge text
</button>
2.4 通过插槽分发内容
app.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
当我们在定义一个父组件时:<alert-box>Hello World</alert-box>
,其中的"Hello World"便被传到了slot
占位符内。这只是简单的用法,之后会有更复杂的用法。
2.5 动态组件
Vue提供了一个<component>
元素,配合一个特殊的is
attribute可以实现动态切换组件。
<!-- component可以是已注册的组件的名字或者一个组件选项对象 -->
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component :is="currentTabComponent"></component>
3. 模板语法
<!-- 插入文本 -->
<span>Message: {{ msg }}</span>
<!-- 一次性插值 -->
<span v-once>这个将不会改变: {{ msg }}</span>
<!-- 插入原始HTML -->
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
<!-- 使用v-bind指令将id与dynamicId动态绑定,当dynamicId在data(){}中变化时,id也跟着变化(指令后面会详细介绍) -->
<div v-bind:id="dynamicId"></div>
<!-- 使用JS表达式, 只能是单个表达式 -->
{{message.split('').reverse().join('')}}
在你的站点上动态渲染任意的 HTML 是非常危险的,因为它很容易导致 XSS
攻击。请只对可信内容使用 HTML 插值,绝不要将用户提供的内容作为插值。
模板表达式都被放在沙盒中,只能访问一个受限的全局变量列表,如 Math
和Date
。你不应该在模板表达式中试图访问用户定义的全局变量。
除此之外,需要注意的是:
4. 指令
指令 (Directives) 是带有 v-
前缀的特殊 attribute。指令 attribute的值预期是单个 JavaScript 表达式 ( v-for
和 v-on
是例外情况,稍后我们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于DOM。
4.1 v-if
<!-- seen为假时,元素所占内存会被释放 -->
<p v-if="seen">现在你看到我了</p>
<!-- v-if v-else-if v-else 的用法就像别的编程语言一样 -->
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
4.2 v-show
<!-- v-show用法和v-if最基础的用法一样-->
<!-- 但是该元素始终会被渲染并保留在DOM中,只是简单的切换display CSS property -->
<h1 v-show="ok">Hello!</h1>
4.3 v-bind
<!-- 绑定的完整语法,当url变化时,href也会跟着变 -->
<a v-bind:href="url"> ... </a>
<!-- 缩写 -->
<a :href="url"> ... </a>
<!-- 动态参数的缩写,连key也是变量 -->
<a :[key]="url"> ... </a>
<!-- 在将v-bind用于class和style时,Vue做了专门的增强 -->
<!-- active这个class是否存在将取决于data property中的isActive的truthiness -->
<div :class="{ active: isActive }"></div>
<!-- :class指令可以与普通的class共存 -->
<div class="static" :class="{ active: isActive, 'text-danger': hasError }"></div>
<!-- 绑定的数据对象不必内联定义在模板里,我们可以在计算属性中返回一个class对象-->
<div :class="classObject"></div>
<!-- 我们可以把一个数组传给:class -->
<div :class="[activeClass, errorClass]"></div>
<!-- 我们可以用三元表达式根据条件切换列表中的class -->
<div :class="[isActive ? activeClass : '', errorClass]"></div>
<!-- 数组语法和对象语法是可以共存的 -->
<div :class="[{ active: isActive }, errorClass]"></div>
<!-- style语法和class大部分相同,如果浏览器不支持某个CSS Property,Vue会多次测试自动添加不同的浏览器前缀以找到支持它的前缀 -->
<!-- 可以为 style 绑定中的 property 提供一个包含多个值的数组,常用于提供多个带前缀的值,这样写只会渲染数组中最后一个被浏览器支持的值 -->
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
4.4 v-on
4.4.1 基础语法
<!-- 完整语法,click事件触发时调用doSomething -->
<a v-on:click="doSomething"> ... </a>
<!-- 缩写 -->
<a @click="doSomething"> ... </a>
<!-- 动态参数的缩写,连event也能是变量 -->
<a @[event]="doSomething"> ... </a><a v-on:click="doSomething"> ... </a>
<!-- 访问原始DOM事件 -->
<button @click="warn('Form cannot be submitted yet.', $event)"> </button>
<!-- 事件处理程序中可以有多个方法,这些方法由逗号运算符分隔 -->
<button @click="one($event), two($event)"> </button>
对动态参数值约定
动态参数预期会求出一个字符串,null
例外。这个特殊的 null
值可以用于显式地移除绑定。任何其它非字符串类型的值都将会触发一个警告。
对动态参数表达式约定
动态参数表达式有一些语法约束,因为某些字符,如空格和引号,放在 HTML attribute 名里是无效的。例如:
<!-- 这会触发一个编译警告 -->
<a v-bind:['foo' + bar]="value"> ... </a>
在 DOM 中使用模板时 (直接在一个 HTML文件里撰写模板),还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写:
<!--
在 DOM 中使用模板时这段代码会被转换为 `v-bind:[someattr]`。
除非在实例中有一个名为“someattr”的 property,否则代码不会工作。
-->
<a v-bind:[someAttr]="value"> ... </a>
4.4.2 修饰符
事件修饰符
-
.stop
-
.prevent
-
.capture
-
.self
-
.once
-
.passive
示例:
<!-- 阻止单击事件继续冒泡 -->
<a @click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a @click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form @submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div @click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div @click.self="doThat">...</div>
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发, -->
<!-- 而不会等待 `onScroll` 完成, -->
<!-- 以防止其中包含 `event.preventDefault()` 的情况 -->
<div @scroll.passive="onScroll">...</div>
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用
@click.prevent.self
会阻止元素本身及其子元素的点击的默认行为,而
@click.self.prevent
只会阻止对元素自身的点击的默认行为。
按键修饰符
-
.enter
-
.tab
-
.delete (捕获"删除"和"退格"键)
-
.esc
-
.space
-
.up
-
.down
-
.left
-
.right
-
.ctrl
-
.alt
-
.shift
-
.meta
-
.exact (允许你控制由精确的系统修饰符组合触发的事件)
鼠标按钮修饰符
-
.left
-
.right
-
.middle
示例:
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input @keyup.enter="submit" />
<!-- 你可以直接将 KeyboardEvent.key 暴露的任意有效按键名转换为 kebab-case 来作为修饰符。-->
<input @keyup.page-down="onPageDown" />
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
4.5 v-for
// v_for_demo.js
Vue.createApp({
data() {
return {
items: [{ message: 'Foo' }, { message: 'Bar' }],
itemObj: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
}
}).mount('#array-rendering')
4.5.1 渲染数组
如下段代码,我们可以用v-for
指令渲染一个列表,index是当前项的索引,item是被迭代的数组元素的别名,这两个参数的位置不能调换,但可以只使用item。in
也可以换成 of
。
<!-- v_for_demo.html -->
<ul id="array-rendering">
<li v-for="(item, index) in items">
{{index}} - {{ item.message }}
</li>
</ul>
4.5.2 渲染对象
对于渲染对象,v-for支持三个参数(value, key,index)。
<!-- v_for_demo.html -->
<li v-for="(value, name, index) in myObject">
{{ index }}. {{ name }}: {{ value }}
</li>
在遍历对象时,会按 Object.keys()
的结果遍历,但是不能保证它在不同
JavaScript 引擎下的结果都一致。
在 v-for 块中,我们可以访问所有父作用域的 property。
4.5.3 维护状态
当 Vue 正在更新使用 v-for渲染的元素列表时,它默认使用"就地更新"的策略。如果数据项的顺序被改变,Vue将不会移动 DOM元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。
这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态(例如:表单输入值) 的列表渲染输出。
为了给 Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一的key
attribute:
<!-- 建议在使用v-for时提供key attribute, 除非DOM内容简单或可以依赖默认行为提升性能-->
<div v-for="item in items" :key="item.id">
<!-- 内容 -->
</div>
4.5.4 数组更新检测
数组的原地操作以及非原地操作方法都会触发视图更新,对于非原地操作,你可能会认为这将导致Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue为了使得 DOM元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
4.5.5 使用值的范围
v-for 也可以接受整数。在这种情况下,它会把模板重复对应次数。
<div id="range" class="demo">
<span v-for="n in 10" :key="n">{{ n }} </span>
</div>
注意我们不推荐在同一元素上使用 v-if
和v-for
。当它们处于同一节点,v-if
的优先级比 v-for
更高,这意味着v-if
将没有权限访问 v-for
里的变量。
4.6 v-model
v-model
指令可以实现<input>
、<textarea>
、<select>
的双向数据绑定,如当data
选项中的数据变化时会影响input,反过来input变化也会影响input。
v-model
会忽略所有表单元素的 value
、checked
、selected
attribute 的初始值。它将始终将当前活动实例的数据作为数据来源。你应该通过JavaScript 在组件的 data
选项中声明初始值。
- text 和 textarea 元素使用
value
property 和input
事件 - checkbox 和 radio`使用
checked
property 和change
事件 - select 字段将
value
作为 prop 并将change
作为事件
4.6.1 text/textarea
<input v-model="message" placeholder="edit me" />
<p>Message is: {{ message }}</p>
<!-- 插值在 textarea 中不起作用,请使用 v-model 来代替 -->
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br />
<textarea v-model="message" placeholder="add multiple lines"></textarea>
4.6.2 checkbox
<!-- 单个复选框,绑定到布尔值 -->
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>
<!-- 多个复选框,绑定到同一个数组,选中后会将value添加到数组中 -->
<div id="v-model-multiple-checkboxes">
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames" />
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
<label for="mike">Mike</label>
<br />
<span>Checked names: {{ checkedNames }}</span>
</div>
Vue.createAp
4.6.3 radio
<!-- 选中的项的value会被复制到picked -->
<div id="v-model-radiobutton">
<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>
<br />
<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>
<br />
<span>Picked: {{ picked }}</span>
</div>
4.6.4 select
<!-- 选中的项的value会被复制到selected -->
<div id="v-model-select" class="demo">
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
<!-- 多选时 (绑定到一个数组),value会被添加进数组 -->
<select v-model="selected" multiple>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<br />
<span>Selected: {{ selected }}</span>
4.6.5 修饰符
<!-- 在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组织文字时)。你可以添加 lazy 修饰符,从而转为在 change 事件之后进行同步 -->
<input v-model.lazy="msg" />
<!-- .number自动将输入值转为数值类型; .trim自动过滤首尾空白字符-->
5. 组件实例Property
5.1 data
// data_demo.js
const app = Vue.createApp({
data() {
return { count: 4 }
}
})
const vm = app.mount('#app')
console.log(vm.$data.count) // => 4
console.log(vm.count) // => 4
// 修改 vm.count 的值也会更新 $data.count
vm.count = 5
console.log(vm.$data.count) // => 5
// 反之亦然
vm.$data.count = 6
console.log(vm.count) // => 6
这些实例 property 仅在实例首次创建时被添加,所以你需要确保它们都在data函数返回的对象中。必要时,要对尚未提供所需值的 property 使用
null
、undefined
或其他占位的值。
直接将不包含在 data
中的新 property 添加到组件实例是可行的。但由于该
property 不在背后的响应式 $data 对象内,所以 Vue的响应性系统不会自动跟踪它。
Vue 使用 $ 前缀通过组件实例暴露自己的内置 API。它还为内部 property 保留_ 前缀。你应该避免使用这两个字符开头的顶级 data property 名称。
5.2 methods(方法)
const app = Vue.createApp({
data() {
return { count: 4 }
},
methods: {
increment() {
// `this` 指向该组件实例
this.count++
}
add(num) {
this.count += num
}
}
})
const vm = app.mount('#app')
console.log(vm.count) // => 4
vm.increment()
console.log(vm.count) // => 5
在模板中,我们可以这样调用定义在methods里的函数:
<button @click="increment">+1</button>
<button @click="add(count+3)">count * 3 + 2</button>
5.3 computed(计算属性)
// computed_demo.js
Vue.createApp({
data() {
return {
author: {
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
}
}
},
computed: {
// 计算属性的 getter
publishedBooksMessage() {
// `this` 指向 vm 实例
return this.author.books.length > 0 ? 'Yes' : 'No'
}
},
methods: {
method_publishedBooksMessage() {
return this.author.books.length > 0 ? 'Yes' : 'No'
}
}
}).mount('#computed-basics')
<!-- -->
<div id="computed-basics">
<p>Has published books:</p>
<!-- 使用方法时要加上(),与计算属性还有其他区别 -->
<span>{{ method_publishedBooksMessage }}</span>
<!-- 直接用js表达式也能达到相同的效果,但也有区别 -->
<span>{{ author.books.length > 0 ? 'yes' : 'no'}}</span>
<!-- 计算属性不需要加() -->
<span>{{ publishedBooksMessage }}</span>
</div>
computed VS 模板语法
直接使用模板语法,模板不再是简单的和声明性的,我们更难迅速理解代码意思。
computed VS methods
计算属性将基于它们的响应依赖关系缓存,只会在相关响应式依赖发生改变时重新求值。这就意味着只要author.books
还没有发生改变,多次访问publishedBookMessage
时计算属性会立即返回之前的计算结果,而不必再次执行函数。
这也同样意味着下面的计算属性将永远不会更新,因为Date.now()
不是响应式依赖:
computed: {
// 调用一次Date.now()之后,没有什么东西可以更新它之前的返回值了
now() {
return Date.now()
}
}
计算属性默认只有getter,不过我们也可以提供一个setter:
// ...
computed: {
fullName: {
// getter
get() {
return this.firstName + ' ' + this.lastName
},
// setter
set(newValue) {
const names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
// ...
现在再运行 vm.fullName = ‘John Doe’ 时,setter 会被调用,vm.firstName
和 vm.lastName 也会相应地被更新。
5.4 watch(侦听器)
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么Vue通过watch
选项提供了一个更通用的方法来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello World!',
},
watch: {
msg(newValue: string, oldValue: string) {
if ((newValue.length - oldValue.length) > 2) {
console.log(newValue, oldValue)
}
}
}
})
// 更改msg的值
vm.msg = 'Hello Worl!'; // 此时并不会输出,因为字符串长度变化小于2
watch
选项允许我们执行异步操作或者设置一个执行该操作的条件,这些都是计算属性无法做到的。