Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
使用了 vue 的页面中,vue 会监听数据的变化,从而自动重新渲染页面的结构。如下图:
好处:当页面数据发生变化时,页面会自动重新渲染!
注意:数据驱动视图是单向的数据绑定。
在填写表单时,双向数据绑定可以辅助开发者在不操作 DOM 的前提下,自动把用户填写的内容同步到数据源中。
好处:开发者不再需要手动操作 DOM 元素,来获取表单最新的值!
<body>
<!--
1. 想让 vue 工作,就必须创建一个 vue 实例,且要传入一个配置对象;
2. app 容器里的代码依然符合 html 规范,只不过混入了一些特殊的 vue 语法;
3. app 容器里的代码被称为 vue 模板;
4. vue 实例和容器是一一对应的;
5. 真实开发中只会有一个 vue 实例,并且会配合着组件一起使用;
6. {{xxx}} 中的 xxx 要写 js 表达式,且 xxx 可以自动读取到 data 中的所有属性;
7. 一旦 data 中的数据发生改变,那么页面中用到该数据的地方也会自动更新。
-->
<!-- 1.声明要被 vue 所控制的 DOM 区域(容器) -->
<div id="app">
hello,{{name}}
</div>
<!-- 2.导入 vue.js 的 script 脚本文件 -->
<script src="./js/vue.js"></script>
<script>
Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示
// 3.创建 vue 实例对象
const vm = new Vue({
el: '#app',
data: {
name: '张三'
}
})
</script>
</body>
作用:用于解析标签体内容。
语法:{{xxx}}
,xxx 是 js 表达式,且可以直接读取到 data 中的所有属性。
作用:用于解析标签(包括:标签属性、标签体内容、绑定事件…)。
举例:v-bind:href="xxx"
或简写为:herf="xxx"
,xxx同样要写 js 表达式,且可以直接读取到 data 中的所有属性。
vue 中的其他指令都是以
v-
开头。
数据只能从 data 流向页面。
数据不仅能从 data 流向页面,还可以从页面流向 data。
- 双向绑定一般都应用在表单类元素上(如:input、select等);
v-model:value
可以简写为 v-model,因为 v-model 默认收集的就是 value 值。
Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示
// 3.创建 vue 实例对象
const vm = new Vue({
// el: '#app', // 第一种
data: {
name: '张三'
}
})
vm.$mount('#app') // 第二种
// 对象方式
data: {
name: '张三'
}
// 函数方式
data() {
return {
name: '张三'
}
}
其中:
- M:模型(Model):对应 data 中的数据;
- V:视图(View):模板;
- VM:视图模型(ViewModel):Vue 实例对象。
发现:
- data 中所有的属性,最后都出现在了 vm(vue 实例化对象)身上。
- vm 身上所有的属性以及 Vue 原型上的所有属性,在 Vue 模板中都可以直接使用。
通过 vm 对象来代理 data 对象中属性的操作(读/写)。
更加方便的操作 data 中的数据。
通过 object.defineProperty()
把 data 对象中所有属性添加到 vm 上。
为每一个添加到 vm 上的属性,都指定一个 getter/setter。
在 getter/setter 内部去操作(读/写)data 中对应的属性。
v-on:xxx
或@xxx
绑定事件,其中xxx是事件名;@click="fun"
和@click="fun($event)"
效果一致,但后者可以传参。事件修饰符 | 描述 |
---|---|
prevent | 阻止默认事件。 |
stop | 阻止事件冒泡。 |
once | 事件只触发一次。 |
capture | 使用事件的捕获模式。 |
self | 只有 event.target 是当前操作的元素时才触发事件。 |
passive | 事件的默认行为立即执行,无需等待事件回调执行完毕。 |
别名 | 按键 |
---|---|
.delete | delete(删除)/BackSpace(退格) |
.tab | Tab(配合 keydown 使用) |
.enter | Enter(回车) |
.esc | Esc(退出) |
.space | Space(空格键) |
.left | Left(左箭头) |
.up | Up(上箭头) |
.right | Right(右箭头) |
.down | Down(下箭头) |
别名 | 按键 |
---|---|
.ctrl | Ctrl |
.alt | Alt |
.shift | Shift |
.meta | windows 中为 window 键,mac 中为 command 键 |
- 配合 keyup 使用时:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
- 配合 keydown 使用时:正常触发事件。
Vue.config.keyCodes.自定义键名 = 键码
computed: {
// 完整写法
demo: {
get() {
// 执行操作
},
set(value) {
// 执行操作
}
},
// 简写(注意:在不考虑 set 的情况下,才可以用简写形式)
demo2() {
// 执行操作
}
}
当被监视的属性变化时,回调函数自动调用,进行相关操作。
监视的属性必须存在,才能进行监视。
监视的两种写法:
深度监视
补充
语法示例
watch: {
// 完整写法
name: {
immediate: true, // 页面初始化时就执行一次
deep: true, // 深度监视
handler(newVal, oldVal) {
// 代码逻辑
}
},
// 简写(注意:简写不能有配置项,如 immediate、deep 等。
name2(newVal, oldVal) {
// 代码逻辑
}
}
两个原则:
- 所被 vue 管理的函数,最好写成普通函数,这样 this 的指向才是 vm 或组件实例对象。
- 所有不被 vue 所管理的函数(定时器的回调函数、Ajax 的回调函数等),最好写成箭头函数。这样 this 的指向才是 vm 或组件实例对象。
// 字符串写法适用于:类名不确定、需要动态获取。
:class="xxx" // xxx可以是字符串、对象、数组。
// 对象写法适用于:要绑定多个样式,个数确定,名字也确定,不确定用不用。
:class="{ active: isActive }"
// 数组写法适用于:要绑定多个样式,个数不确定,名字也不确定。
:class="[activeClass, errorClass]"
:style="{fontSize:xxx}" // 其中 xxx 是动态的值,如 18px。
:style="[a,b]" // 其中 a,b 是样式对象
// 写法:
v-if="表达式"
v-else-if="表达式"
v-else="表达式"
// 适用于:切换频率较低的场景。
// 特点:不展示的 DOM 元素直接被移除。
// 注意:v-if 可以和 v-else-if、v-else 一起使用,但要求结构不能被“打断”。
// 写法:
v-show="表达式"
// 适用于:切换频率较高的场景。
// 特点:不展示的 DOM 元素未被移除,仅仅是使用样式隐藏掉。
使用 v-if 时,元素可能无法获取到,但是用 v-show 元素一定能获取到。
v-for="item in items" :key="item.message"
1. 虚拟 DOM 中 key 的作用
key 是虚拟 DOM 对象的标识,当状态中的数据发生变化时,Vue 会根据【新数据】生成【新虚拟 DOM】,随后 Vue 进行【新虚拟 DOM】与【旧虚拟 DOM】的差异进行对比。
2. 对比规则
(1) 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:
若虚拟 DOM 中内容没有改变,直接使用之前的真实 DOM;
若虚拟 DOM 中内容变了,则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM。
(2) 旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key:
创建新的真实 DOM,随后渲染到也页面上。
3. 用 index 作为 key 可能会引发的问题
(1) 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实 DOM 更新 ==> 界面效果没有问题,但效率低。
(2) 如果结构中还包含输入类的 DOM:
会产生错误 DOM 更新 ==> 界面有问题。
4. 开发中如何选择 key?
(1) 最好使用每条数据的唯一标识作为 key,比如 id、学号等唯一值。
(2) 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用 index 作为 key 是没有问题的。
1. vue 会监视 data 中所有层次的数据。
2. 如何测对象中的数据?
通过 setter 实现监测,且要在 new Vue 时就传入要监测的数据。
(1) 对象中后追加的属性,Vue 默认不做响应式处理。
(2) 如需给后添加的属性做响应式,使用如下 API:
Vue.set(target, propertyName/index, value)或
vm.$set(target, propertyName/index, value)
3. 如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1) 调用原生对应的方法对数组进行更新。
(2) 重新解析模板,进而更新页面。
4. 在 Vue 修改数组中的某个元素一定要用如下方法:
(1) 使用这些 push(), pop(), shift(), splice(), sort(), reverse()
(2) Vue.set() 或 vm.$set()
(3) Vue.delete() 或 vm.$delete()
注意:Vue.set() 和 vm.$set() 不能给 vm 或 vm 的根数据对象添加属性!!!
,则 v-model 收集的是 value 值,用户输入的就是 value 值。
,则 v-model 收集的是 value 值,并且要给标签配置 value 值。
:
定义:对要显示的数据进行特定格式化后再显示(使用一些简单的逻辑处理)。
语法:
// 注册过滤器
Vue.filter(name, callback) 或 new Vue(filters:{})
// 使用过滤器
{{xxx | 过滤器名} 或 v-bind:属性="xxx | 过滤器名"
注意:
vue 3 中会移除过滤器(filter)。
作用:更新元素的textContent
。如果要更新部分的 textContent
,Mustache
插值。与插值语法的区别是 v-text 会替换节点中的内容,而插值不会。
示例:
<span v-text="msg">span>
<span>{{msg}}span>
注意:在网站上动态渲染任意 HTML 是非常危险的,因为容易导致 XSS 攻击。只在可信内容上使用
v-html
,永不用在用户提交的内容上。
示例:
<div v-html="'Hello World
'">div>
作用:
这个指令保持在元素上直到关联组件实例结束编译。和 CSS 规则如 [v-cloak] { display: none }
一起用时,这个指令可以隐藏未编译的 Mustache 标签直到组件实例准备完毕。
示例:
[v-cloak] {
display: none;
}
<div v-cloak>
{{ message }}
div>
作用:
只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
<span v-once>This will never change: {{msg}}span>
<div v-once>
<h1>commenth1>
<p>{{msg}}p>
div>
<my-component v-once :comment="msg">my-component>
<ul>
<li v-for="i in list" v-once>{{i}}li>
ul>
作用:
跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。
示例:
<span v-pre>{{ this will not be compiled }}span>
// 局部定义
// 对象式
new Vue({
directives:{指令名:配置对象}
})
// 函数式
new Vue({
directives:{指令名:回调函数}
})
// 全局定义
// 对象式
Vue.directives(指令名, 配置对象)
// 函数式
Vue.directives(指令名, 回调函数)
配置对象常用的 3 个回调
补充
v-
,但使用时要加v-
。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ScQvzAXj-1658564648097)(…/typora-user-images/生命周期.png)]
当应用中的 js 都以模块来编写的,那这个应用就是一个模块化的应用。
当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用。
使用 Vue.extend(options) 创建,与 new Vue(options) 的区别:
使用 template 可以配置页面结构。
new Vue({
components: {
// '组件名': 组件
}
})
Vue.component('组件名', 组件)
// 把组件名作为标签使用
<组件名>组件名>
<body>
<!-- 声明要被 vue 所控制的 DOM 区域(容器) -->
<div id="app">
// 3. 使用组件
<headers></headers>
<contents></contents>
</div>
<!-- 导入 vue.js 的 script 脚本文件 -->
<script src="./js/vue.js"></script>
<script>
Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示
// 1. 定义组件
const headers = Vue.extend({
template: `
{{title}}
`,
data() {
return {
title: '这里是头部'
}
}
})
const contents = Vue.extend({
template: `
{{title}}
`,
data() {
return {
title: '这里是内容部分'
}
}
})
// 创建 vue 实例对象
const vm = new Vue({
el: '#app',
// 2. 局部注册
components: {
headers, // 这里是 headers: headers 的简写
contents
}
})
</script>
</body>
const school = Vue.extend(options)
// 简写为
const school = options
Vue.extend = function (extendOptions) {
// 略
var Sub = function VueComponent (options) {
this._init(options);
};
// 略
return Sub
};
关于 this 指向
组件配置中:
data 函数、methods 中的函数、watch 中的函数、computed 中的函数,它们的 this 指向均是【VueComponent 实例对象】
new Vue(options) 配置中:
data 函数、methods 中的函数、watch 中的函数、computed 中的函数,它们的 this 指向均是【Vue 实例对象】
VueComponent 实例对象可以简称 vc,而 Vue 实例对象可以简称 vm。
VueComponent.prototype.__proto__ === Vue.prototype
组件实例对象可以访问到 Vue 原型上的属性和方法。
// 文件以 .vue 扩展名结尾,例如 Student.vue
<template>
</template>
<script>
export default {
}
</script>
<style>
</style>
<template>
<div id="school">
<h1>{{msg}}</h1>
</div>
</template>
<script>
export default {
name: 'School',
data() {
return {
msg: 'hello!'
}
}
}
</script>
<style>
#school {
color: red;
}
</style>
Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)。
① 安装脚手架依赖 nodejs,安装 nodejs 参考node.js 安装教程 (Windows zip 版)
② 在终端输入如下命令,全局安装@vue/cli。
npm install -g @vue/cli
③ 切换到你要创建项目的目录,然后使用命令创建项目。
vue create xxx
④ 在当前项目目录下启动项目。
npm run serve
├─node_modules
├─public
| ├─favicon.ico: 页签图标
| └index.html: 主页面
├─src
| ├─App.vue: 汇总所有组件
| ├─main.js: 入口文件
| ├─components: 存放组件
| ├─assets: 存放静态资源
| | └logo.png
├─.browserslistrc
├─.editorconfig
├─.eslintrc.js
├─.gitignore: git 版本管制忽略的配置
├─babel.config.js: babel 的配置文件
├─jsconfig.json
├─package-lock.json:包版本控制文件
├─package.json: 应用包配置文件
├─README.md: 应用描述文件
├─vue.config.js
Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpack 配置, 请执行:
vue inspect > output.js
如果想要对脚手架进行修改定制,可配置 vue.config.js,具体配置信息详见Vue CLI (vuejs.org)。
// 元素
<h1 ref="title">welcome</h1>
// 组件
<Hello ref="hello"></Hello>
// 获取
this.$refs.xxx
<Hello :msg="123"></Hello>
// 第一种方式(只接收)
props: ['msg']
// 第二种方式(限制类型)
props: {
msg: String
}
// 第三种方式(限制类型、限制必要性、指定默认值)
props: {
msg: {
type: String,
required: true,
default: 'hello'
}
}
// 定义混合
{
data() {return {...}},
methods: {...}
}
// 使用混合
// 1. 全局混入
Vue.mixin(xxx)
// 2. 局部混入
mixins: ['xxx']
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或 property
Vue.myGlobalMethod = function () {
// 逻辑...
}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})
// 3. 注入组件选项
Vue.mixin({
created: function () {
// 逻辑...
}
...
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}
Vue.use(MyPlugin)
<style scoped>
</style>
// 1. 第一种方式,在父组件中
<Demo @test="test"/>
// 或者
<Demo v-on:test="test"/>
// 2. 第二种方式,在父组件中
<Demo ref="demo"/>
//.......
mounted() {
this.$refs.demo.$on('test', this.test)
}
// 3.若想让自定义事件只触发一次,可以使用 once 修饰符,或 $once 方法。
this.$emit('test', data)
this.$off('test')
native
修饰符。this.$refs.xxx.$on('test', 回调)
绑定自定义事件时,回调要么配置在methods
中,要么用箭头函数,否则 this 指向会出问题。new Vue({
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this
}
}).$mount('#app')
// 1. 接收数据:A 组件想接收数据,则在 A 中给 $bus 绑定自定义事件,时间的回调留在 A 组件身上
// 组件 A
methods: {
demo(data) {......}
},
mounted: {
this.$bus.$on('xxx', this.demo)
}
// 2. 提供数据
// 组件 B
this.$bus.$emit('xxx', data)
一种组件间通信的方式,适用于任意组件间通信。
使用步骤:
// 1. 安装 pubsub
npm i pubsub-js
// 2. 引入
import pubsub from 'pubsub-js'
methods: {
demo(data) {......}
},
mounted() {
this.demoPub = pubsub.subscribe('xxx', this.demo)
}
pubsub.publish('xxx', data)
pubsub.unsubscribe(this.demoPub)
取消订阅。this.$nextTick(回调函数)
过渡的类名:
元素进入的样式:
v-enter
:定义进入过渡的开始状态。
v-enter-active
:定义进入过渡生效时的状态。
v-enter-to
:2.1.8 版及以上定义进入过渡的结束状态。
元素离开的样式:
v-leave
:定义离开过渡的开始状态。
v-leave-active
:定义离开过渡生效时的状态。
v-leave-to
:2.1.8 版及以上定义离开过渡的结束状态。
写法:
// 使用 <transition> 包裹要过渡的元素,并配置 name 属性
<transition name="slide-fade">
<p v-if="show">hellop>
transition>
// 在 vue.config.js 中添加如下配置
devServer: {
proxy: 'http://localhost:4000'
}
// 1. 优点:配置简单,请求资源时直接发送给前端(8080)即可
// 2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理
// 3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器(有限匹配前端资源)
// 在 vue.config.js 中配置具体代理规则
devServer: {
proxy: {
'/api': {
target: '' ,
ws: true,
changeOrigin: true // 是否改变请求路径端口号为服务器端口号
},
'/foo': {
target: ''
}
}
}
// 1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理
// 2. 缺点:配置略微繁琐,请求资源时必须加qin
作用:让父组件可以向子组件指定位置插入 html 结构,也是一种组件通信的方式,适用于父组件 => 子组件。
分类:默认插槽、具名插槽、作用域插槽。
使用方式:
// 父组件中:
<navigation-link>
Your Profile
</navigation-link>
// 子组件 navigation-link 中:
<a>
<slot></slot>
</a>
// 父组件中:
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
// 子组件 base-layout 中:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
// 一个不带 name 的 出口会带有隐含的名字“default”。
// 具名插槽的缩写形式:
// 把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header:
<template #header>
<h1>Here might be a page title</h1>
</template>
// 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。
// 注意:这里将包含所有插槽 prop 的对象命名为 slotProps,但你也可以使用任意你喜欢的名字。
// 父组件中:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
// 子组件 current-user 中:
<span>
<slot :user="user">
{{ user.lastName }}
</slot>
</span>
// 作用域插槽的内部工作原理是将你的插槽内容包裹在一个拥有单个参数的函数里:
function (slotProps) {
// 插槽内容
}
// 这意味着 v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。所以在支持的环境下 (单文件组件或现代浏览器),你也可以使用 ES2015 解构来传入具体的插槽 prop,如下:
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 vue 应 用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方 式,且适用于任意组件间通信。
创建文件:src/store/index.js
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
//准备actions对象——响应组件中用户的动作
const actions = {}
//准备mutations对象——修改state中的数据
const mutations = {}
//准备state对象——保存具体的数据
const state = {}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state
})
在main.js
中创建vm时传入store
配置项
......
//引入store
import store from './store'
......
//创建vm
new Vue({
el:'#app',
render: h => h(App),
store
})
初始化数据、配置actions
、配置mutations
,操作文件store.js
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//引用Vuex
Vue.use(Vuex)
const actions = {
//响应组件中加的动作
jia(context,value){
// console.log('actions中的jia被调用了',miniStore,value)
context.commit('JIA',value)
},
}
const mutations = {
//执行加
JIA(state,value){
// console.log('mutations中的JIA被调用了',state,value)
state.sum += value
}
}
//初始化数据
const state = {
sum:0
}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
})
组件中读取vuex中的数据:$store.state.sum
组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)
或 $store.commit('mutations中的方法名',数据)
备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写
dispatch
,直接编写commit
概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。
在store.js
中追加getters
配置
......
const getters = {
bigSum(state){
return state.sum * 10
}
}
//创建并暴露store
export default new Vuex.Store({
......
getters
})
组件中读取数据:$store.getters.bigSum
mapState方法:用于帮助我们映射state
中的数据为计算属性
computed: {
//借助mapState生成计算属性:sum(对象写法)
...mapState({sum:'sum'}),
//借助mapState生成计算属性:sum(数组写法)
...mapState(['sum']),
},
mapGetters方法:用于帮助我们映射getters
中的数据为计算属性
computed: {
//借助mapGetters生成计算属性:bigSum(对象写法)
...mapGetters({bigSum:'bigSum'}),
//借助mapGetters生成计算属性:bigSum(数组写法)
...mapGetters(['bigSum'])
},
mapActions方法:用于帮助我们生成与actions
对话的方法,即:包含$store.dispatch(xxx)
的函数
methods:{
//靠mapActions生成:incrementOdd、incrementWait(对象形式)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
//靠mapActions生成:incrementOdd、incrementWait(数组形式)
...mapActions(['jiaOdd','jiaWait'])
}
mapMutations方法:用于帮助我们生成与mutations
对话的方法,即:包含$store.commit(xxx)
的函数
methods:{
//靠mapActions生成:increment、decrement(对象形式)
...mapMutations({increment:'JIA',decrement:'JIAN'}),
//靠mapMutations生成:JIA、JIAN(对象形式)
...mapMutations(['JIA','JIAN']),
}
备注:mapActions 与 mapMutations 使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。
目的:让代码更好维护,让多种数据分类更加明确。
修改store.js
const countAbout = {
namespaced:true,//开启命名空间
state:{x:1},
mutations: { ... },
actions: { ... },
getters: {
bigSum(state){
return state.sum * 10
}
}
}
const personAbout = {
namespaced:true,//开启命名空间
state:{ ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
countAbout,
personAbout
}
})
开启命名空间后,组件中读取state数据
//方式一:自己直接读取
this.$store.state.personAbout.list
//方式二:借助mapState读取:
...mapState('countAbout',['sum','school','subject']),
开启命名空间后,组件中读取getters数据
//方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName']
//方式二:借助mapGetters读取:
...mapGetters('countAbout',['bigSum'])
开启命名空间后,组件中调用dispatch
//方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
//方式二:借助mapActions:
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
开启命名空间后,组件中调用commit
//方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',person)
//方式二:借助mapMutations:
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
安装vue-router,命令:npm i vue-router
应用插件:Vue.use(VueRouter)
编写router配置项:
//引入VueRouter
import VueRouter from 'vue-router'
//引入组件
import About from '../components/About'
import Home from '../components/Home'
//创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
})
//暴露router
export default router
实现切换(active-class可配置高亮样式)
About
指定展示位置
pages
文件夹,一般组件通常存放在components
文件夹。$route
属性,里面存储着自己的路由信息。$router
属性获取到。配置路由规则,使用children配置项:
routes:[
{
path:'/about',
component:About,
},
{
path:'/home',
component:Home,
children:[ //通过children配置子级路由
{
path:'news', //此处一定不要写:/news
component:News
},
{
path:'message',//此处一定不要写:/message
component:Message
}
]
}
]
跳转(要写完整路径):
News
传递参数
跳转
跳转
接收参数:
$route.query.id
$route.query.title
作用:可以简化路由的跳转。
如何使用?
给路由命名:
{
path:'/demo',
component:Demo,
children:[
{
path:'test',
component:Test,
children:[
{
name:'hello', //给路由命名
path:'welcome',
component:Hello,
}
]
}
]
}
简化跳转:
跳转
跳转
跳转
重定向也是通过 routes
配置来完成,下面例子是从 /home
重定向到 /
:
const routes = [{ path: '/home', redirect: '/' }]
重定向的目标也可以是一个命名的路由:
const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
甚至是一个方法,动态返回重定向目标:
const routes = [
{
// /search/screens -> /search?q=screens
path: '/search/:searchText',
redirect: to => {
// 方法接收目标路由作为参数
// return 重定向的字符串路径/路径对象
return { path: '/search', query: { q: to.params.searchText } }
},
},
{
path: '/search',
// ...
},
]
请注意,导航守卫并没有应用在跳转路由上,而仅仅应用在其目标上。在上面的例子中,在 /home
路由中添加 beforeEnter
守卫不会有任何效果。
在写 redirect
的时候,可以省略 component
配置,因为它从来没有被直接访问过,所以没有组件要渲染。唯一的例外是嵌套路由:如果一个路由记录有 children
和 redirect
属性,它也应该有 component
属性。
也可以重定向到相对位置:
const routes = [
{
// 将总是把/users/123/posts重定向到/users/123/profile。
path: '/users/:id/posts',
redirect: to => {
// 该函数接收目标路由作为参数
// 相对位置不以`/`开头
// 或 { path: 'profile'}
return 'profile'
},
},
]
重定向是指当用户访问 /home
时,URL 会被 /
替换,然后匹配成 /
。那么什么是别名呢?
将 /
别名为 /home
,意味着当用户访问 /home
时,URL 仍然是 /home
,但会被匹配为用户正在访问 /
。
上面对应的路由配置为:
const routes = [{ path: '/', component: Homepage, alias: '/home' }]
通过别名,你可以自由地将 UI 结构映射到一个任意的 URL,而不受配置的嵌套结构的限制。使别名以 /
开头,以使嵌套路径中的路径成为绝对路径。你甚至可以将两者结合起来,用一个数组提供多个别名:
const routes = [
{
path: '/users',
component: UsersLayout,
children: [
// 为这 3 个 URL 呈现 UserList
// - /users
// - /users/list
// - /people
{ path: '', component: UserList, alias: ['/people', 'list'] },
],
},
]
如果你的路由有参数,请确保在任何绝对别名中包含它们:
const routes = [
{
path: '/users/:id',
component: UsersByIdLayout,
children: [
// 为这 3 个 URL 呈现 UserDetails
// - /users/24
// - /users/24/profile
// - /24
{ path: 'profile', component: UserDetails, alias: ['/:id', ''] },
],
},
]
配置路由,声明接收 params 参数
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News
},
{
component:Message,
children:[
{
name:'xiangqing',
path:'detail/:id/:title', //使用占位符声明接收 params 参数
component:Detail
}
]
}
]
}
传递参数
跳转
跳转
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
接收参数:
$route.params.id
$route.params.title
作用:让路由组件更方便的收到参数
{
name:'xiangqing',
path:'detail/:id',
component:Detail,
//第一种写法:props 值为对象,该对象中所有的 key-value 的组合最终都会通过 props 传给 Detail 组件
// props:{a:900}
//第二种写法:props 值为布尔值,布尔值为 true,则把路由收到的所有params参数通过props传给 Detail 组件
// props:true
//第三种写法:props 值为函数,该函数返回的对象中每一组 key-value 都会通过 props 传给 Detail 组件
props(route){
return {
id:route.query.id,
title:route.query.title
}
}
}
push
和replace
,push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push。
replace
模式:News
。作用:不借助
实现路由跳转,让路由跳转更加灵活。
具体编码:
//$router的两个API
this.$router.push({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
this.$router.replace({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go() //可前进也可后退
作用:让不展示的路由组件保持挂载,不被销毁。
具体编码:
activated
路由组件被激活时触发。deactivated
路由组件失活时触发。作用:对路由进行权限控制
分类:全局守卫、独享守卫、组件内守卫
全局守卫:
//全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
console.log('beforeEach',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
next() //放行
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next() //放行
}
})
//全局后置守卫:初始化时执行、每次路由切换后执行
router.afterEach((to,from)=>{
console.log('afterEach',to,from)
if(to.meta.title){
document.title = to.meta.title //修改网页的title
}else{
document.title = 'vue_test'
}
})
独享守卫:
beforeEnter(to,from,next){
console.log('beforeEnter',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){
next()
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next()
}
}
组件内守卫:
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}
打包大小减少41%
初次渲染快55%, 更新渲染快133%
内存减少54%
…
使用Proxy代替defineProperty实现响应式
重写虚拟DOM的实现和Tree-Shaking
…
Composition API(组合API)
新的内置组件
其他改变
官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue_test
## 启动
cd vue_test
npm run serve
官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite
vite官网:https://vitejs.cn
const xxx = ref(initValue)
xxx.value
{{xxx}}
Object.defineProperty()
的get
与set
完成的。reactive
函数。ref
函数)const 代理对象= reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)实现原理:
对象类型:通过Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
存在问题:
实现原理:
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
通过Reflect(反射): 对源对象的属性进行操作。
MDN文档中描述的Proxy与Reflect:
Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
new Proxy(data, {
// 拦截读取属性值
get (target, prop) {
return Reflect.get(target, prop)
},
// 拦截设置属性值或添加新属性
set (target, prop, value) {
return Reflect.set(target, prop, value)
},
// 拦截删除属性
deleteProperty (target, prop) {
return Reflect.deleteProperty(target, prop)
}
})
proxy.name = 'tom'
reactive
转为代理对象。Object.defineProperty()
的get
与set
来实现响应式(数据劫持)。.value
,读取数据时模板中直接读取不需要.value
。.value
。this.$attrs
。this.$slots
。this.$emit
。与 Vue2.x 中 computed 配置功能一致
写法
import {computed} from 'vue'
setup(){
...
//计算属性——简写
let fullName = computed(()=>{
return person.firstName + '-' + person.lastName
})
//计算属性——完整
let fullName = computed({
get(){
return person.firstName + '-' + person.lastName
},
set(value){
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
}
与 Vue2.x 中 watch 配置功能一致
两个小“坑”:
//情况一:监视ref定义的响应式数据
watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
},{immediate:true})
//情况二:监视多个ref定义的响应式数据
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg变化了',newValue,oldValue)
})
/* 情况三:监视reactive定义的响应式数据
若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
*/
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不再奏效
//情况四:监视reactive定义的响应式数据中的某个属性
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
//情况五:监视reactive定义的响应式数据中的某些属性
watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
//特殊情况
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
watch 的套路是:既要指明监视的属性,也要指明监视的回调。
watchEffect 的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
watchEffect 有点像 computed:
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
watchEffect(()=>{
const x1 = sum.value
const x2 = person.age
console.log('watchEffect配置的回调执行了')
})
beforeDestroy
改名为 beforeUnmount
destroyed
改名为 unmounted
beforeCreate
===>setup()
created
=======>setup()
beforeMount
===>onBeforeMount
mounted
=======>onMounted
beforeUpdate
===>onBeforeUpdate
updated
=======>onUpdated
beforeUnmount
==>onBeforeUnmount
unmounted
=====>onUnmounted
什么是 hook?—— 本质是一个函数,把 setup 函数中使用的 Composition API 进行了封装。
类似于 vue2.x 中的 mixin。
自定义 hook 的优势: 复用代码, 让 setup 中的逻辑更清楚易懂。
作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
语法:const name = toRef(person,'name')
应用: 要将响应式对象中的某个属性单独提供给外部使用时。
扩展:toRefs
与toRef
功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
return{
...toRefs(person)
}
shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理。
什么时候使用?
reactive
生成的响应式对象转为普通对象。作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
实现防抖效果:
{{keyword}}
作用:实现祖与后代组件间通信
套路:父组件有一个 provide
选项来提供数据,后代组件有一个 inject
选项来开始使用这些数据
具体写法:
祖组件中:
setup(){
......
let car = reactive({name:'奔驰',price:'40万'})
provide('car',car)
......
}
后代组件中:
setup(props,context){
......
const car = inject('car')
return {car}
......
}
reactive
创建的响应式代理readonly
创建的只读代理reactive
或者 readonly
方法创建的代理使用传统 OptionsAPI 中,新增或者修改一个需求,就需要分别在 data,methods,computed 里修改 。
我们可以更加优雅的组织我们的代码、函数。让相关功能的代码更加有序的组织在一起。
什么是 Teleport?—— Teleport
是一种能够将我们的组件 html 结构移动到指定位置的技术。
我是一个弹窗
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
使用步骤:
异步引入组件
import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
使用Suspense
包裹组件,并配置好default
与 fallback
我是App组件
加载中.....
Vue 2.x 有许多全局 API 和配置。
例如:注册全局组件、注册全局指令等。
//注册全局组件
Vue.component('MyButton', {
data: () => ({
count: 0
}),
template: ''
})
//注册全局指令
Vue.directive('focus', {
inserted: el => el.focus()
}
Vue3.0 中对这些 API 做出了调整:
将全局的 API,即:Vue.xxx
调整到应用实例(app
)上
2.x 全局 API(Vue ) |
3.x 实例 API (app ) |
---|---|
Vue.config.xxxx | app.config.xxxx |
Vue.config.productionTip | 移除 |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
data 选项应始终被声明为一个函数。
过度类名的更改:
Vue2.x 写法
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-leave,
.v-enter-to {
opacity: 1;
}
Vue3.x 写法
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-leave-from,
.v-enter-to {
opacity: 1;
}
移除 keyCode 作为 v-on 的修饰符,同时也不再支持config.keyCodes
移除v-on.native
修饰符
父组件中绑定事件
子组件中声明自定义事件
移除过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。
fineAsyncComponent(()=>import(‘./components/Child.vue’))
- 使用```Suspense```包裹组件,并配置好```default```与 ```fallback```
```vue
我是App组件
加载中.....
Vue 2.x 有许多全局 API 和配置。
例如:注册全局组件、注册全局指令等。
//注册全局组件
Vue.component('MyButton', {
data: () => ({
count: 0
}),
template: ''
})
//注册全局指令
Vue.directive('focus', {
inserted: el => el.focus()
}
Vue3.0 中对这些 API 做出了调整:
将全局的 API,即:Vue.xxx
调整到应用实例(app
)上
2.x 全局 API(Vue ) |
3.x 实例 API (app ) |
---|---|
Vue.config.xxxx | app.config.xxxx |
Vue.config.productionTip | 移除 |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
data 选项应始终被声明为一个函数。
过度类名的更改:
Vue2.x 写法
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-leave,
.v-enter-to {
opacity: 1;
}
Vue3.x 写法
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-leave-from,
.v-enter-to {
opacity: 1;
}
移除 keyCode 作为 v-on 的修饰符,同时也不再支持config.keyCodes
移除v-on.native
修饰符
父组件中绑定事件
子组件中声明自定义事件
移除过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。