针对Vue 2版本。
约定:
vm
(ViewModel)变量名表示:Vue实例。
心得
针对不在vue视图内出现的变量:
组件的多个实例会共用模块内、组件外的变量(闭包),组件内部的数据则互相独立(类似于
new
实例)。
const
+ 大写和下划线组成。data
或computed
或不提前定义就直接this.新属性 = 值
)。注意内存泄漏(全局副作用):
new
的其他实例或DOM,应放在data
内进行掌控,当使用完毕后引导垃圾回收。addEventListener
)、计时器、http连接、以及任何需要手动关闭的内容,需要在beforeDestroy
手动清除(destroyed
仅自动清除Vue自己定义、绑定的内容)。请求异步数据的业务结构:
在view层触发异步请求(页面创建钩子触发、或用户交互触发)
使用vuex(建议方式):
dispatch
发起(模板的响应式数据使用store的state/getters
);actions
内请求异步数据,得到数据后commit
改变state
;返回的数据保存位置:
state
,供所有组件使用。actions
的方法return
数据给view层,保存在Vue组件实例的data
。不使用vuex、直接在view层引入API模块:
返回的数据保存在Vue组件实例的data
。
项目内文件的命名:
_
;(除了.vue)其他所有文件用:-
-
短横线隔开式(kebab-case)-
短横线隔开式(kebab-case)支持JS表达式(单个),不支持语句、流控制。
作用域在所属Vue实例范围内。
在模板中使用Vue实例的属性/方法时,省去
。this
父级模板里的所有内容都是在父级作用域中编译的;子级模板里的所有内容都是在子级作用域中编译的。
特例:父级通过
v-slot="临时变量"
去使用子级<slot>
给定的属性对应的值。
v-slot
和<slot>
用于父级(v-slot:某名字
)向子组件(<slot name="某名字">
)插入内容。
<template>
指令(directives)是带有v-
前缀的DOM的特殊属性。
JS表达式的值改变时,将响应式地改变DOM。
:
参数
用于添加指令后的参数。
某指令:[表达式]
动态参数
由表达式计算的结果为最终:
后跟的值。
e.g.
v-bind:[表达式]='xx'
、v-on:[表达式]='xx'
、v-slot:[表达式]
。
v-if
、v-else
、v-else-if
DOM或组件判定为false
,则完全销毁(组件会调用destroyed
);判定为true
,则新建。除非使用<keep-alive/>
包裹。
若切换的DOM或组件是相同的,则根据key
属性是否相同判断是否复用(未设置key
则也认为是相同)。
e.g.
```vue ```
v-for="值/(值, 键)/(值, 键, 索引) in/of JS表达式/整数"
处于同一节点时,
```htmlv-for
比v-if
优先级更高(v-if
将分别重复运行于每个v-for
循环中)- v-if在v-for循环的每一次都运行判断
``` 避免`v-if`和`v-for`同时用在同一个元素上。
v-for
时,必须添加key
属性尽可能在使用v-for
时提供key
属性(除非输出的DOM非常简单,或刻意重用DOM以获取性能提升)
e.g.
```html <div v-for="(item, key) in items" :key="item.id || key"> </div> ```
v-for
子组件时,最好都用props
传入参数,而不用(相同标签名的DOM或相同组件切换展示时,)没有提供
key
属性:若数据项的展示/顺序被改变,则Vue将不会销毁再新建DOM/移动DOM来匹配数据项的顺序,而是保持原DOM尽量不变化,尽量仅在原DOM上修改属性和内部内容,以确保渲染正确。
v-bind
(v-bind:xx
缩写::xx
)绑定DOM属性与JS表达式的结果
此DOM属性随表达式最终值改变而改变,直接修改此DOM属性值不改变表达式的值。
绑定修饰符:
.sync
.prop
(绑定到DOM的property
而不是HTML标签的 attribute
.camel
(小驼峰式camelCase转换为大驼峰式PascalCase)特殊的DOM属性:
绑定class
:class="'a b'"
:class="a b"
:class="{ 'a': true, 'b': false }"
:class="a"
:class="['a', 'b']"
:class="a b"
:class="[{ 'a': true, 'b': false }, { 'c': true }, 'd', ['e']]"
:class="a c d e"
绑定style
- 自动添加样式前缀。
- CSS属性名可以用小驼峰式(camelCase)或
-
短横线隔开式(kebab-case,需用单引号包裹)命名。
:style="{ '属性名1': '属性值1', '属性名2': '属性值2' }"
:style="[{ '属性名1': '属性值1' }, { '属性名2': '属性值2', '属性名3': '属性值3' }]"
多重值:渲染数组中最后一个被浏览器支持的值
e.g. :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"
传递给子组件DOM属性的值类型
若不带参数的v-bind="表达式"
,则绑定表达式的所有属性到DOM。
v-on
(v-on:xx
缩写:@xx
)事件监听
事件修饰符:
.stop
(阻止冒泡)、.prevent
(阻止默认行为)、.capture
(捕获事件流)、.self
(只当事件在该元素本身而不是子元素触发时才触发)、.once
(事件将只触发一次)、.passive
(滚动事件的默认滚动行为将立即触发,而不等待scroll事件完成)
特殊:
.stop/prevent
:不带方法名的修饰符也有效果。@click.stop
所有该节点的点击事件阻止冒泡,@click.prevent
所有该节点的点击事件阻止默认行为。
.enter
、.tab
、.delete
、.esc
、.space
、.up
、.down
、.left
、.right
、.数字键值
、KeyboardEvent.key的短横线形式、Vue.config.keyCodes
自定义的键位别名
键盘。
.left
、.right
、.middle
鼠标。
.native
监听组件根元素的原生事件,仅在父级引用子组件处添加。
.exact
精确匹配(有其他按键则失败)。
可同时使用,但改变顺序会产生不同效果。
e.g.
```html <input @keyup.alt.67="clear"> <div @click.ctrl="doSomething">...</div> <div @click.prevent.self="doThat">...</div> <div @click.self.prevent="doThat">...</div> ```
$event
原生DOM事件的变量,仅能由HTML内联传入
自定义事件
仅定义在父组件对子组件的引用上,只能由子组件内部vm.$emit
触发,然后触发父级方法,再通过改变父级属性去改变子组件的props
(或置换组件)。
支持不带参数绑定(值为事件-监听器的键-值的对象)
不支持修饰符。
e.g. <button v-on="{ mousedown: doThis, mouseup: doThat }"/>
v-slot
(v-slot:xx
缩写:#xx
)插槽
只允许添加在template
上(特例见下),且不能嵌套使用。子组件使用<slot>
在内部插入父级引入的内容。
只允许添加在
template
上的特例:被引用的子组件只使用默认插槽时,可以简写在引用的子组件上(若要使用多个插槽,则必须始终为所有的插槽使用完整的基于<template>
的语法)。e.g.
<子组件 v-slot:default>
、<子组件 v-slot>
、<子组件 #default>
若子组件没有包含
<slot name="某名字">
,则父组件引用子组件时的v-slot:某名字"
的DOM会被抛弃。
后备内容
子组件中没有name
属性或name="default"
的<slot>
,匹配父级中去除所有包含v-slot:某名字
的DOM的内容(即:匹配没有v-slot
或v-slot
值为空的DOM和内容)。
<slot>
默认name
为:default
。
子组件中<slot>
的DOM内容,当且仅当没有父级匹配时显示。
具名插槽
父级引用子组件,在元素内部添加的标签的DOM属性v-slot:某名字
;会匹配子组件模板的<slot name="某名字">
。
作用域插槽
父级引用子组件时,使用子组件<slot>
上的属性对应的值(除了name
属性)。
子组件的模板:
<slot 组件属性1="字符串" :组件属性2="表达式">
<slot>
内部是子组件的作用域,和在其上添加的属性内容无关。
父级使用子组件<slot>
上显性提供的属性对应值:
<template/子组件 v-slot="临时变量"></template/子组件>
(临时变量 === { 组件属性1: '字符串'. 组件属性2: 表达式 }
)
临时变量
支持解构。
v-model
表单的双向绑定
忽略表单元素上的
value
、checked
、selected
等初始值,而仅通过Vue实例赋值。
表单修饰符:
.lazy
(change
而不是input事件触发)、.number
(输入值转为Number
类型)、.trim
(过滤首尾空格)
<input>
、<textarea>
、<select>
、组件绑定的Vue实例的值:
value
属性的值;type="checkbox"
,则为true/false
;若要获得Vue实例的动态属性值,则:
:value="表达式"
;type="checkbox"
,则用:true-value="表达式" :false-value="表达式"
。
不能和表达式一起使用,仅能绑定属性名:错误:
。v-model="属性名 + '!'"
v-once
只渲染元素和组件一次
随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
v-text
等价于:``
模板对空格
、HTML的字符实体
的不同输入有所不同输出
e.g.
```vue1 2 1 2```
v-html
输入真正HTML
与其他插值(如:模板插值)的区别
1. `v-html`:`innerHTML`。 2. `v-text`、``及其他插值:`innerText(或textContent)`。 >`innerText`与`textContent`的区别:[MDN:textContent](https://developer.mozilla.org/zh-CN/docs/Web/API/Node/textContent#与innerText的区别)。
v-pre
不编译
e.g.
<p v-pre></p>
v-show
总是渲染出DOM,根据值切换display
值。
不支持
<template>
、不支持v-else
。
v-cloak
指令保持在元素上直到关联实例结束编译
与CSS一起使用时,在编译完成前使用样式,编译完成后去除样式。
.
修饰符
用于指出一个指令应该以特殊方式绑定。
使用在v-on
、v-bind
、v-model
后添加。
|
使用filters过滤器,参数带入函数运行出结果(支持过滤器串联)
仅可以在``和v-bind
中使用(其他如: 无效、无法在Vue实例中使用)。v-html
1
<p :title="num | a | b"/>自定义指令(在钩子函数中进行业务)
v-自定义指令名
(或v-自定义指令名="表达式"
、v-自定义指令名:参数
、v-自定义指令名.修饰符
)
同一个节点可以添加多个不同参数/修饰符的同一个自定义指令名
e.g. `<div v-observer:10="{ show: xx }" v-observer:20="{ show: yy }" />`:添加2个独立的自定义指令。
// 局部
new Vue({
// 在局部的模板内使用
directives: {
自定义指令名: 钩子对象,
}
})
// 全局
Vue.directive('自定义指令名', 钩子对象)
```javascript { // 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置 bind (el, binding, vnode) {}, // 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中) inserted (el, binding, vnode) {}, // 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 update (el, binding, vnode, oldVnode) {}, // 指令所在组件的 VNode 及其子 VNode 全部更新后调用 componentUpdated (el, binding, vnode, oldVnode) {}, // 只调用一次,指令与元素解绑时调用 unbind (el, binding, vnode) {} } // 可以把副作用的内容绑定到`el`,方便钩子函数之间传递信息 // 钩子对象的函数的第二个参数 binding: { name // 指令名,不包括`v-`前缀 value // 指令的绑定值,例如:`v-my-directive="1 + 1"`中,绑定值为`2` oldValue // 指令绑定的前一个值,仅在`update`和`componentUpdated`钩子中可用。无论值是否改变都可用 expression // 字符串形式的指令表达式。例如:`v-my-directive="1 + 1"`中,表达式为`"1 + 1"` arg // 传给指令的参数,可选。例如:`v-my-directive:foo`中,参数为`"foo"` modifiers // 一个包含修饰符的对象。例如:`v-my-directive.foo.bar`中,修饰符对象为`{ foo: true, bar: true }` } ```
钩子对象
特殊attribute
key
key
或key
相同:保持原DOM尽量不变化,尽量仅在原DOM上修改属性和内部内容;有key
且变化:销毁再新建DOM 或 移动DOM来匹配数据项的顺序)针对:有相同父元素的子元素必须有独特的
key
(重复的key
会造成渲染错误)。相同标签名的DOM切换展示、或相同组件间切换展示。v-for
、v-if
、<transition/>
、<transition-group/>
。
key
的取值:
v-for
生成的key
都各自等于重新渲染之后的每一个项的key
)。Math.random()
),除了小概率会碰撞之外,框架无法优化性能。每项数据需要一个唯一值作为key
(如:id),若原数据项没提供,则可在拉到数据时由前端额外加入。但是要对数据来源进行去重,避免渲染相同key
导致报错。
id + 键或索引
,能复用没有改变位置的DOM。ref
被用来给原生DOM元素或子组件注册引用信息。引用信息注册在vm.$refs
对象上。
- 初始渲染时无法访问。
vm.$refs
不是响应式的,因此不应该在template
或computed
做数据绑定。- 多个DOM或子组件命名为同一个ref值,最后的会覆盖之前的,ref值只能取到最后的一个。
添加在原生DOM:指向DOM元素
代替原生JS获取DOM,如:
。document.getElementById
v-for
一起使用时:指向包含 DOM元素或子组件Vue实例 的数组。is
有效标签或动态组件。
(已废弃,用slot
v-slot
代替)
父级向子组件引入内容。
(已废弃,用slot-scope
、scope
v-slot
代替)
父级使用子组件字符串的作用域名字。
-
定义(提供组件的选项)
is
列表渲染(创建多个变化的相同元素)
v-for
条件渲染(元素是否渲染/显示)
v-if
v-else-if
v-else
v-show
v-cloak
渲染方式(改变元素的渲染方式)
v-pre
v-once
全局感知(需要超越组件的知识)
id
唯一的特性(需要唯一值的特性)
ref
key
v-slot
slot
slot-scope
scope
双向绑定(把绑定和事件结合起来)
v-model
事件(组件事件监听器)
v-on
内容(覆写元素的内容)
v-html
v-text
</details>
new Vue(对象)
el
(字符串):选择器
限制:只在由
new
创建的Vue实例中。
data
(对象或方法):数据
限制:组件的
data
是方法且返回一个数据对象。
以_
或$
开头的属性不会被Vue实例代理,但可以使用vm.$data
访问(e.g. vm.$data._property
)。
Vue内置的属性、API方法会以
_
或$
开头,因此若看到不带这些前缀的Vue实例的属性时,则一般可认为是Vue实例代理的属性(props
、data
、computed
、methods
、provide/inject
的属性,或mixins
传入的属性)。
computed
(对象):依赖其他值(props
、data
、computed
)的改变而执行,最后return
值
get
(初始化时会调用一次);显式设置:set
(被赋值时执行)和get
若return
的内容不涉及Vue实例的响应式内容(非Vue实例的属性、或已经被Object.freeze
处理而非响应式的Vue实例的属性),则computed
的内容就不是响应式的(不进行Vue的响应式绑定处理)。
在(
props
、)data
、computed
先定义再使用,而不要对未使用过的变量进行this.新变量名 = 值
(不先定义就直接使用无法绑定到响应式系统,无法触发视图更新渲染)。
watch
(对象):被watch的值改变而执行函数(观察的值必须是props
或data
或computed
的属性)
immediate
参数(侦听开始后立即调用一次)deep
参数(监听的对象的属性修改时调用一次,无论嵌套多深的属性,属性的属性也触发)属性1.子属性2
,来观察嵌套的属性值还可以用
vm.$watch
来观察属性改变。
执行顺序是:(
props
-> )data
->computed
->watch
。
filters
(对象):过滤器方法
方法内部没有代理 到Vue实例。this
因为不会被Vue实例代理,所以可以和Vue实例代理的属性同名(
props
、data
、computed
、methods
、provide/inject
的属性,或mixins
传入的属性`)。
components
(对象):局部注册组件(仅在此Vue实例中可用)methods
(对象):可调用方法
new
methods里的方法,方法体内的this
指向这个实例,而非Vue实例。建议不要在methods中添加构造函数,而改用import
方式引入构造函数。template的每次改变,都会导致VNode重新渲染,也会导致methods重新调用(无论methods使用的值是否变化)。
若在
template
里调用methods
中的方法从而绑定了数据(因为template最终是成为render函数,且每一次的数据改变都会导致整个组件的VNode重新渲染(VNode在differ之后的nextTick才会真的在DOM中重新渲染),其他方式的数据都有缓存,而调用methods
的值没有缓存),则数据由调用methods
而绑定的值,会随着任意模板数据改变而重新执行求值(无论methods使用的值是否变化)。
e.g. ```javascript async mounted () { await this.login() console.log('等待上面await的Promise完成或失败后才往下继续执行') }, methods: { login () { return new Promise((resolve, reject) => { shouldLogin(err => { if (err) { reject(err) } resolve() }) }) }, async openQuestion () { await this.login() console.log('等待上面await的Promise完成或失败后才往下继续执行') } } ```
methods
、生命周期钩子都能使用async-await
生命周期钩子
async-await
不会阻止继续向下执行生命周期。e.g.beforeCreate(); created(); ...
beforeCreate
实例的(
props
、)data
、computed
、watch
等实例内容的创建。
created
渲染模板。
beforeMount
mounted
不判断子组件是否挂载完毕。若希望整个视图都渲染完毕,可以用
vm.$nextTick
。
页面加载后就要展现的数据,可以在
created
、beforeMount
、mounted
请求(vue-router
、nuxt
等封装了更方便的钩子,可控制更多执行时机,如:路由切换前后、数据加载完毕前后)。
beforeUpdate
updated
不判断子组件是否更新完毕。若希望整个视图都渲染完毕,可以用
vm.$nextTick
。
activated
<keep-alive/>
组件特有,内部组件激活时在内部组件调用。
deactivated
<keep-alive/>
组件特有,内部组件停用时在内部组件调用。
beforeDestroy
可将大部分内存清理工作放在
beforeDestroy
。
destroyed
调用后,Vue实例指示的所有内容都会解绑,所有Vue事件监听器会被移除,所有子实例也会被销毁。
errorCaptured
mixins
(数组,每一项为Vue实例的属性):混入
mixin数组每一项中的属性,都会合并到组件本身的选项(如:mixin的methods
合并到组件的methods
、mixin的data
合并到组件的data
)。
非钩子函数属性,若有同名内容,则合并之后,组件自身内容覆盖mixin:
methods
、components
、directives
等,合并为同一个对象;对象内部键名冲突时(如:methods
都有某同名方法),使用组件对象的内容、丢弃mixin的内容。
作用域:
Vue.mixin
全局注册,将会影响之后创建的(之前的不受影响)Vue实例,包括第三方模板。extends
(组件对象):扩展、继承一个组件
所有Vue组件同时也都是Vue实例。
组件属性:
template
(字符串):组件的字符串模板props
(数组或对象):接受父级传递内容
对象:{ 接受的DOM属性名: 验证方式, }
验证方式:
String
、Number
、Boolean
、Function
、Object
、Array
)、或Symbol
、BigInt
、或null
(允许任何类型)对象
type
:原生构造器、或null
、或原生构造器的数组required
:是否必须(默认:false
)default
:基本数据类型的值;对象或数组必须从工厂函数返回默认值(当且仅当没有传入时才使用或调用)
required
和default
二选一。
validator
:验证方法(对子组件的任何修改包括v-show
修改以及自身default
,都会触发所有prop的验证方法)props会在一个组件实例创建之前进行验证,所以实例的属性(如:
data
、computed
、methods
等)在default
或validator
函数中不可用。
data
(方法):return
数据对象
data () { // 组件多个实例间不共享数据对象
return {
a: 0,
b: ''
}
}
v-for
循环的每个实例都调用创建一份。- 仅执行一次,父组件传进来的props改变也不再触发执行。
model
(对象,包含prop
、event
):修改v-model
默认使用的属性和事件name
(字符串)
默认:undefined
。
- 其他与Vue实例的属性相同(除了一些根级特有的选项)
注册组件方式:
要确保在初始化Vue实例之前注册了组件。
// 局部
new Vue({
components: {
'组件元素名': 对象 // 仅在此Vue实例中可用
}
})
// 全局
Vue.component('组件元素名', 对象)
组件命名:
W3C规定的组件命名要求:小写,且包含一个
-
。
JS注册组件或props
:
-
短横线隔开式(kebab-case)、小驼峰式(camelCase)、大驼峰式(PascalCase)。
HTML中:
-
短横线隔开式(把大/小驼峰式用-
隔开并小写单词代替)在JS字符串模版、.vue
组件,可以使用额外方式
使用组件:
<组件名/>
不会导致渲染出错的方式:JS字符串模版、
.vue
组件。
<有效标签 is="组件名"/>
动态组件:<component :is="表达式"/>
也可执行,只不过写死了某个组件,没有了「动态」的意义。<component is="组件名字符串"/>
<div id="test">
<component :is="current1"/>
<component :is="current2"/>
</div>
<script>
const vm = new Vue({
el: '#test',
data: {
current1: { template: '<p>111</p>' }, // 对象,则当作组件对象渲染
current2: 'component2' // 字符串,则搜索已注册的components
},
components: {
component2: { template: '<p>222</p>' }
}
})
</script>
<keep-alive/>
包裹,保留它的状态或避免重新渲染。<keep-alive/>
<keep-alive>组件</keep-alive>
,会缓存不活动的组件实例,而不是销毁、重建。当组件在其内被切换时,组件的activated
、deactivated
被对应执行。
通信
组件(Vue实例)有自己独立的作用域,虽然可以访问到互相依赖关系(
vm.$parent
、vm.$children
、vm.$refs
),但是不推荐(不允许)通过依赖获取、修改数据。
父子组件间的通信
父(props
) -> 子:传入属性值;子(vm.$emit
) -> 父:触发外部环境事件,外部事件再改变传进组件的props
值。
父 -> 子:通过props
向下传递初始化数据给子组件实例(不出现在DOM中)
(当
inheritAttrs
默认true
时,)添加在DOM上而不在props
的声明,则仅添加到子组件最外层的DOM属性,不传入子组件。其中class
和style
属性会合并,其他属性会覆盖。
props
是单向传递的:当父级的属性变化时,将传导给子组件,不会反过来
每次父组件更新时,子组件的所有prop都会更新为最新值。
不应该 在子组件内部改变(只能props
vm.$emit
到父级再由父级传props
进子组件来改变)。
props
。data
。故不推荐):props
-> data
。props
-> computed
。props
-> watch
。注意:避免引用数据类型导致的子组件改变父级。
传递
```vueFunction
数据类型<Son :func-data="funcData" :func-methods="funcMethods" />```
子 -> 父:通过vm.$emit
向上传递事件、参数
vm.$listeners
能获得父级的所有能够从子级向上传递的事件。
在父级引用子组件处添加@自定义事件1="父方法"
监听;
若
自定义事件1
是原生事件,可以添加.native
修饰符,监听组件根元素的原生事件(不再接收子组件的)。vm.$emit
在子组件方法体内添加vm.$emit('自定义事件1', 参数)
向上传递。
(不推荐)可以传递组件的Vue实例,提供给父级或子级调用。
父 -> 子:触发子组件的方法
$refs
直接调用子组件的方法(vm.$refs.子组件引用名.子组件的methods方法()
)。props
,子组件watch
而触发子组件方法(或其他方式让子组件能够watch
而执行的方式,如:vuex)。非父子组件通信
通用
在简单的场景下,可以使用一个空的Vue实例作为中央事件总线。
const bus = new Vue() // vm.$emit只能向自己的Vue实例发送触发事件通知
// 触发组件 A 中的事件
bus.$emit('事件名', 传参)
// 在组件 B 创建的钩子中监听事件
bus.$on('事件名', function (para) {})
或专门状态管理模式,如:vuex。
祖孙组件间的通信
通过provide/inject
从祖辈向所有孙辈传递(不是响应式的,除非传递一个观察对象)数据
(不推荐)可以传递组件的Vue实例,提供给祖辈或孙辈调用。
props
) -> 孙:逐层往下传递孙(vm.$emit
) -> 祖:逐层往上传递
v-slot
和<slot>
:逐层插槽
<my-component :子属性="父属性" @父事件="父方法"><template v-slot:某名字="临时变量">插槽内容</template></my-component>
props
:允许外部环境传递数据给组件。events
:允许从组件内触发外部环境的副作用。slots
:允许外部环境将额外的内容组合在组件中。内置组件
<component/>
<transition/>
<transition-group/>
<keep-alive/>
<slot/>
ref="字符串"
,可以在Vue实例的$refs
中访问子组件。内联模板:
引用组件时,添加inline-template
DOM属性。组件的内容当作模板,而不是分发内容。
<script type=text/x-template id="id名">
Vue.component('组件名', {
template: '#id名'
})
v-once
将渲染结果在组件注册的template
字段里缓存起来。if-else
的展示逻辑,则尽量创建新的子组件,把每个子组件的逻辑放到子组件内部。样式引入
全局起作用(在任意地方引入样式,导致所有页面都使用样式)
在任意地方引入CSS文件(非 )scoped
在多个组件多次引入同一个CSS文件(允许同时使用JS和CSS两种方式混合引入),最终只会引入同一个CSS文件一次。
<script>import '@/assets/文件名.css'</script>
<style src="@/assets/文件名.css"/>
在任意地方引入没有 的scoped
<style>
:
<style>样式内容</style>
在public/index.html
中添加静态样式
局部样式
scoped
(vue-loader的Scoped CSS)
scoped
方式引用整个样式文件:
<style scoped src="@/assets/文件名.css"/>
全局起作用、无法识别:@
<style scoped>@import "../assets/文件名.css";</style>
。<style>
内只能用相对路径,不能用@/~
。
scoped
的<style>
:
<style scoped>样式内容</style>
使用scoped
的单文件组件内所有Element(包括其引用的子组件),都会添加自定义attributes
(如:data-v-2185bf6b
);非Vue内部生成的节点(手动添加)不会添加自定义attributes
。
若已添加scoped
的子组件也添加scoped
,则增加一份子组件的自定义attributes
。
深度作用选择器:若希望
scoped
样式中的一个选择器能够作用得「更深」(如:影响子组件),则可使用>>>
或/deep/
或::v-deep
操作符(不支持预处理器,如:scss)。e.g.
<style scoped> .a >>> .b { /* 等价于:`.a /deep/ .b`或`.a ::v-deep .b` */ } .c > .d {} // 编译为: .a[data-v-039c5b43] .b {} .c > .d[data-v-039c5b43] {} </style>
module
(vue-loader的CSS Modules)
在<style>
添加module
,即在Vue实例内加入$style
对象,访问单文件组件内<style module>
的选择器。
官方建议的单文件组件命名:
通用
大驼峰式(PascalCase)或-
短横线隔开式(kebab-case)
选择一种方式后,应该在项目中始终仅使用这种方式,不要在同一个项目中混用两种命名方式。
基础组件名(以Base
、App
、V
开头)
应用特定样式和约定的基础组件(展示类的、无逻辑的、无状态的组件,绝对不包括全局状态,如:vuex),以一个特定的前缀开头,如:Base
、App
、V
。
单例组件名(以The
开头)
只应该拥有单个活跃实例的组件应该以The
前缀命名,以示其唯一性。每个页面最多使用一次,不接收props。
紧密耦合的组件名(以完整父组件名开头)
和父组件紧密耦合的子组件应该以父组件名作为前缀命名。
此处描述的「帧」是
requestAnimationFrame
(在浏览器下一次重绘之前执行),而不是Vue的(在Vue控制的DOM下次更新循环结束之后执行)。nextTick
插入、更新、移除DOM时,提供过渡/动画的操作
<transition/>
(仅针对第一个子元素)
- 过渡/动画效果仅加在第一个子元素上(子元素内部不会触发)。
<transition/>
内仅能展示唯一一个子元素(多个元素用<transition-group/>
)。- 不添加真实的DOM,只有逻辑(
<transition-group/>
会添加真实的DOM)。- 组件最外层是
<transition/>
无法触发动画,要在外部嵌套一层DOM。
提供给包裹的第一个子元素(任何DOM或组件)进入/离开的过渡/动画效果。
触发条件:
①在<transition/>
内嵌套子元素,在子元素上进行以下操作;或②引用根节点是<transition/>
的组件,在父级引用上进行以下操作、或在此组件根节点<transition/>
内嵌套的子元素进行以下操作:
v-if
(v-else
、v-else-if
)v-show
<component :is="表达式"/>
触发机制:
<transition/>
添加了过渡钩子,则这些钩子函数在恰当的时机被调用。特殊的props(在<transition/>
组件引用上添加):
name
:用于自动生成CSS过渡/动画类名的前缀(默认:'v'
)
自定义过渡/动画的类名
优先级高于普通的类名,主要用于和第三方CSS动画库配合,如:animate.css。
enter-class
enter-active-class
enter-to-class
leave-class
leave-active-class
leave-to-class
type
:指定过渡事件类型('transition'
或'animation'
。默认:自动检测出持续时间长的)duration
:不根据过渡/动画的JS事件,而手动设置过渡/动画时间(毫秒)
e.g.
:duration="1000"
或:duration="{ enter: 500, leave: 800 }"
css
:是否使用CSS过渡/动画的class(默认:true
。若false
,则只触发过渡钩子)过渡钩子事件:
建议对于仅使用JS过渡的
<transition/>
添加:css="false"
。
进入过程
before-enter
enter
(与CSS结合使用。当只用JS过渡时,在enter
中必须调用函数的第二个参数done
)after-enter
enter-cancelled
离开过程
before-leave
leave
(与CSS结合使用。当只用JS过渡时,在leave
中必须调用函数的第二个参数done
)after-leave
leave-cancelled
appear
:是否在初始渲染时使用过渡/动画(默认:false
)
(可选)初始渲染时使用额外的类名、钩子事件(:appear="true"
)
初始渲染的自定义过渡/动画的类名:
appear-class
appear-active-class
appear-to-class
初始渲染的过渡钩子事件:
before-appear
appear
after-appear
appear-cancelled
mode
:(当有子元素切换时,)控制进入/离开的过渡顺序('out-in'
:先离开后进入;'in-out'
:先进入后离开。默认:同时进行)<transition/>
内嵌的子元素切换:
v-if
(v-else
、v-else-if
)作为切换,则必须在这些子元素上添加 key
属性。若是组件子元素,则不需要额外添加 属性。key
v-if
(v-else
、v-else-if
)作为切换。<component :is="表达式"/>
进行切换。选择:仅渲染一个节点:
<transition/>
;渲染多个节点:<transition-group/>
。
<transition-group/>
子元素必须添加
key
属性。
<transition/>
类似。特殊的props(在<transition-group/>
组件引用上添加):
tag
:子级外层嵌套的标签名(默认:'span'
)move-class
:用于自动改变定位时引用的类名(默认:name
属性的值 + '-move'
)mode
<transition/>
相同的props自制可复用的过渡组件:把
<transition/>
或<transition-group/>
作为根组件。
// 插件是.js文件,应当有一个公开的install方法
const MyPlugin = {}
MyPlugin.install = function (Vue, options) { // 第一个参数是Vue构造器,第二个参数是Vue.use时传入的可选参数对象
// 1. 添加全局方法或属性
Vue.myGlobalMethod = function () {
// 逻辑...
}
// 2. 添加全局资源(Vue.directive、Vue.filter、Vue.component)
// 3. 全局混入
Vue.mixin({
created: function () {
// 逻辑...
},
})
// 4. 添加实例方法(约定用`$`开头来命名原型自定义属性)
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}
// 安装插件处使用
Vue.use(MyPlugin, { /* 向MyPlugin传入的参数 */ }) // Vue.use会自动阻止多次注册相同插件,届时只会注册一次该插件。
制作插件供Vue项目使用:
- 导出包含
install
的对象,这样项目Vue.use(引入的包含install的对象)
就全局注册该插件。也可导出具体的内容(如:组件、自定义指令),由项目选择:
- 调用对应的全局API全局注册(如:
Vue.directive('自定义指令名', 导出的自定义指令对象)
)- 在项目本地插件内局部注册(如:
directives: { '自定义指令名': 导出的自定义指令对象 }
)。
可以合起来导出:
import 组件名字 from './路径/组件名字.vue' 组件名字.install = (Vue, options = {}) => { // 具体install注册组件的各种方式 } export default 组件名字
不推荐
```javascript import 组件名字 from './路径/组件名字.vue' const plugin = { install (Vue, options = {}) { // 具体install注册组件的各种方式 } } // 会导致一些打包模式下出问题 export { plugin as default, 组件名字 } ```
Vue实例代理的属性(props
、data
、computed
、methods
、provide/inject
的属性,或mixins
传入的属性),在内部vm.名字
访问,可以直接使用,所有属性名字都不能重复(filters不属于同一类);也有以$
开头的Vue实例属性(如:vm.$el
、vm.$props
、vm.$data
、vm.$watch
)。
只有已经被代理的内容是响应的(Vue实例被创建时传入的属性),值的改变(可能)会触发视图的重新渲染。
this
的指向无法按预期指向Vue实例。因为HTML不区分大小写,所以大/小驼峰式命名的JS内容,在HTML使用时要转换为相应的-
短横线隔开式。
不受限制、不需要转换:JS字符串模版、.vue
组件。
JS字符串模版:
<script type="text/x-template">
、JS内联模板字符串。
Vue
)或Vue组件实例(VueComponent
)挂载在DOM,会在这个DOM上增加__vue__
属性指向这个Vue实例或Vue组件实例(生产环境也行)。当把对象传给Vue实例的data
和vuex的store时,Vue将遍历此对象所有的属性,并使用Object.defineProperty
把这些属性全部转为getter/setter
(访问器属性)。
Object.defineProperty
类似的:
Object.defineProperties
。
数据劫持,直接修改数据触发、或事件监听触发。
针对不需要响应式变化的属性(但要展示到
template
),可以用Object.freeze
处理,就不会进行Object.defineProperty
响应式处理。e.g.
`x: Object.freeze({ y: 1 })`: 1. `x`是响应式的。 2. `x`指向的对象不是响应式的,无法通过`x.y = 2`去响应式修改。 3. 若`x = { y: 2 }`指向新的响应式对象,之后`x.y = 3`会响应式触发视图更新。- 针对不需要展示到
template
的属性,可以不把属性初始化在data
中,而是直接vm.属性 = 值
在JS中使用(这个属性值无法绑定到template
中显示,意味着给这个属性值赋值之后,``也总是为空)。- 针对所有这个组件的实例全部共用同一个值、不响应式更新、不能放在
template
内的值,可以放在组件外部(类似上面的那用用法)。
watcher
实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter
被调用时,会通知watcher
重新计算,从而致使它关联的组件得以更新(虚拟DOM系统)。响应式操作:Vue实例的data
的属性值、vuex的store的属性值
约定:原对象、原数组、原属性均为已存在的、响应式的数据。
导致视图更新:
push
、pop
、unshift
、shift
、reverse
、sort
、splice
原属性赋值给任意类型的新值(=
)
原属性的属性值是原对象/原数组:
原对象 = Object.assign({}, 原对象, { 键: 新值 })
或原对象 = { ...原对象, 键: 新值 }
原数组 = [ 新值, ...原数组, 新值 ]
或concat
等
= []
、= {}
)也是一次重新赋值操作Vue.set(原对象/原数组, 键/索引, 新值)
:原对象/原数组添加新属性或修改原属性Vue.delete(原对象/原数组, 键/索引)
:原对象/原数组删除原属性无法检测原对象/原数组变动:
Vue不能检测到原对象属性的新增或删除,因此添加
Vue.set/delete
。
原对象的新属性赋值(=
)。
替代方法:
Vue.set(原对象, 键, 新值)
或原对象 = Object.assign({}, 原对象, { 键: 新值 })
或原对象 = { ...原对象, 键: 新值 }
原数组
原数组索引赋值(=
),已存在索引或新增索引都不行。
替代方法:
Vue.set(原数组, 索引, 新值)
或原数组.splice(索引, 1, 新值)
直接修改原数组长度。
替代方法:
原数组.splice(新长度)
原数组的最新mutator方法:copyWithin
、fill
响应式原理对比:
- backbone.js:发布者-订阅者模式
- angular.js:脏值检查
- react:主动触发
- vue.js:数据劫持 + 发布者-订阅者模式
Vue.js参考:snabbdom的虚拟DOM实现。
约定
1. 虚拟节点 (virtual node,VNode):Vue通过建立一个VNode反映真实DOM结构。 2. 虚拟DOM(virtual DOM):由Vue组件树建立起来的整个**VNode树**。
一般算法实现步骤:
在底层的实现上,Vue将模板编译成虚拟DOM渲染函数。
结合响应式系统,Vue能够智能地计算出最少需要重新渲染多少组件,并把DOM操作次数减到最少。
虚拟DOM系统的辩证思考:Why Virtual DOM is slower
根据定义,**虚拟DOM** 比 **精细地直接更新DOM(`innerHTML`)** 更慢。虚拟DOM是执行DOM更新的一种折衷方式、一种权衡,尽管没有提升性能,但带来很多好处,可以提升开发人员的开发效率(提供了开发人员:「用数据驱动视图」代替「频繁操作DOM」的能力)。
(响应式系统和)虚拟DOM系统只能单向操作DOM结构。
若使用非Vue控制的方法直接修改DOM结构,无法反向对响应式系统和虚拟DOM系统起效果,将导致虚拟DOM系统与DOM结构不匹配。因此若使用了虚拟DOM系统,必须严格控制仅用此系统操作DOM结构。
若在mounted
之前改变DOM,会导致「客户端激活」(client-side hydration)时,客户端的虚拟DOM(从服务端渲染完毕传递来的JSON字符串,在客户端解析而成。如:nuxt的window.__NUXT__
)和服务端传来的DOM(服务端渲染完毕传输来的HTML)不同而出问题。
客户端激活时
1. 在开发模式下,Vue将判断(从服务端渲染完毕传递来的JSON字符串,在客户端解析而成的)虚拟DOM(virtual DOM),是否与从服务端渲染完毕传输来的DOM结构(DOM structure)匹配。若无法匹配,则将退出混合模式,丢弃现有的DOM并从头开始渲染。 2. 在生产模式下,此检测会被跳过,以避免性能损耗。 3. 重新渲染可能导致页面抖动、闪屏。
建议SSR的应用,不要在mounted
之前进行能导致模板变化的数据修改。
服务端默认禁用模板数据的响应式,因此响应式操作仅会在客户端进行。但是如:
watch
的immediate: true
的方法,在服务端也会执行,因此就需要process.client/process.server
来判断是否允许服务端执行。
注意SSR与
v-html
中的有可能修改html的功能(比如:lazysizes会尽可能快地处理标记图片),会导致重新渲染。
服务端也会调用的钩子:beforeCreate
、created
将全局副作用代码移动到服务端不会运行的生命周期钩子中(除了 、beforeCreate
之外的其他钩子)created
(nuxt)把直出内容放在
asyncData/fetch
;把客户端异步请求放在mounted
;不使用。beforeCreate/created/beforeMount
接口依赖
e.g.
function apiFetch () { // 所有接口都返回Promise。错误处理:每个接口、每个Promise.all/race后添加`.then/catch`
return Promise.all([ // 互不依赖的接口:apiA、apiB、apiC
apiA.then(() => {
return Promise.all([ // 依赖apiA的互不依赖的接口apiAA、apiAB、apiAC
apiAA,
apiAB,
apiAC
])
}),
apiB,
apiC
])
}
组件 -> h
函数 -> VNode -> HTML
h
函数
创建VNode的方法封装。
VNode
用对象来反映真实DOM的情况。
种类
渲染器(render)
将VNode渲染成特定平台下真实DOM的函数。
mount
(原本没有VNode)patch
(原本有VNode)
相同类型的VNode才需要比较,不同类型直接完全替换;相同HTML标签才需要比较,不同标签直接完全替换。
Diff
双向数据绑定原理不同
2
利用ES5的Object.defineProperty
对数据进行劫持,结合发布订阅模式的方式来实现。
3
使用了ES6的Proxy
对数据代理。
相比vue2.x的Object.defineProperty
,使用Proxy
的优势:
Object.defineProperty
只能监听某个属性,不能对全对象监听for-in
,闭包等内容来提升效率(直接绑定整个对象即可)Proxy
可以检测到数组内部数据的变化虚拟DOM的diff算法不同
2
递归遍历两个虚拟DOM树,并比较每个节点上的每个属性(有点暴力算法),来确定实际DOM的哪些部分需要更新。
3
采用block tree的做法。
是否支持碎片(Fragment)
2
不支持,必须:<template><div>多个节点</div></template>
。
3
支持,允许:<template>多个节点</template>
。
API类型不同
2
使用选项类型api(Options API),在代码里分割了不同的属性:data、computed、methods、等。声明选项的方式书写Vue组件。
3
使用合成型api(Composition API),使用方法来分割,相比于旧的api使用属性来分组,这样代码会更加简便和整洁。使用函数的方式书写组件。
定义数据变量和方法、生命周期钩子函数、父子通信 不同
2
定义数据变量要在data(){}
,创建的方法要在methods:{}
中,计算属性要在computed:{}
中,生命周期钩子要在组件属性中调用,父子通信用this.props/emit
。
e.g.
```javascript export default { props: { title: String }, data () { return { username: '', password: '' } }, computed: { lowerCaseUsername () { return this.username.toLowerCase() } }, methods: { login () { // this.props // this.emit } }, mounted () {} } ```
3
需要使用一个新的setup
属性方法,此方法在组件初始化构造的时候触发。
使用以下三个步骤来建立反应性数据:
reactive
;reactive()
方法来声明数据为响应性数据;setup()
方法来返回我们的响应性数据,从而template可以获取这些响应性数据。e.g.
```javascript import { reactive, computed, onMounted } from 'vue' export default { props: { title: String }, setup (props, content) { const state = reactive({ username: '', password: '', lowerCaseUsername: computed(() => state.username.toLowerCase()) }) const login = () => { // props // content.emit } onMounted(()=>{}) return { state, login } } } ```
this
不同
2
指向当前组件
3
undefined
指令与插槽不同
2
使用slot可以直接使用slot;v-for
优先级高于v-if
,而且不建议一起使用。
3
必须使用v-slot的形式;只会把当前v-if当做v-for中的一个判断语句,不会相互冲突;移除keyCode作为v-on的修饰符,当然也不支持config.keyCodes;移除v-on.native修饰符;移除过滤器filter。
main.js文件不同
2
可以使用prototype(原型)的形式去进行操作,引入的是构造函数。
3
需要使用结构的形式进行操作,引入的是工厂函数;vue3中app组件中可以没有根标签。
ts的支持不同
2
支持Flow。
3
支持ts。
初始化
new Vue({
router: new VueRouter({
mode: 'hash或history或abstract', // 默认:hash
base: '/...基路径/', // 仅在history模式下,设置应用的基路径
routes: [
// 普通路由
{
path: '路由参数', // 除了path,其他都是可选
component: 组件,
name: '路由名',
redirect: 路由参数, // 重定向(URL变化)
alias: 路由参数, // 别名(URL不变化)
props: 布尔或对象或函数, // 传参进组件(布尔值决定vm.$route.params是否被设置为组件属性;对象或函数则传入具体属性)。针对components要再嵌套一层对象
beforeEnter: 方法,
meta: '额外信息参数',
caseSensitive: 布尔值, // 匹配规则是否大小写敏感(默认:false)
pathToRegexpOptions: 对象, // 编译正则的选项
},
// 动态路由
{
path: ':动态路由',
component: 组件
},
// 命名视图
{
path: '路由参数',
components: {
'default': 组件,
'视图名1': 组件,
},
props: {
'default': 布尔或对象或函数,
'视图名1': 布尔或对象或函数
}
},
// 嵌套路由(可以嵌套所有类型的路由)
{
path: '路由参数',
component: 组件,
children: [
路由配置,
]
},
],
linkActiveClass: 'router-link-active', // 激活<router-link/>的CSS类名
linkExactActiveClass: 'router-link-exact-active', // 精确激活<router-link/>的CSS类名
scrollBehavior: 方法,
parseQuery/stringifyQuery: 方法,
fallback: 方法,
}),
})
内置组件
<router-link/>
:导航
to
:目标地址replace
:(默认:false
)是否使用replace
替换push
tag
:(默认:a
)生成其他标签名append
:(默认:false
)是否是相对路径exact
:(默认:false
):是否精确激活event
:(默认:'click'
):触发导航的事件active-class
:(默认:'router-link-active'
):链接激活时使用的CSS类名exact-active-class
:(默认:'router-link-exact-active'
):被精确匹配时应该激活的CSS类名<router-view/>
:渲染路由匹配的组件(可嵌套)
name
:(默认:'default'
)命名视图的名字
注入后在组件中增加
$router
:路由器实例对象
app
:Vue根实例mode
:路由模式currentRoute
:$route
beforeEach/beforeResolve/afterEach(方法)
:导航守卫go(数字)
back()
forward()
push(路由字符串或对象[, 完成回调函数[, 失败回调函数]])
replace(路由字符串或对象[, 完成回调函数[, 失败回调函数]])
若
push/replace
仅修改search
值(或history
模式的hash
值),则不会改变路由也不刷新页面。如:vm.$router.push/replace('?a=xx')
。
getMatchedComponents(location?)
resolve(location, current?, append?)
addRoutes(routes)
onReady(callback, [errorCallback])
onError(callback)
$route
:当前激活的路由的状态信息
path
:绝对路径params
:动态路由的路由参数对象query
:查询参数对象hash
:hash值matched
:匹配到的路由数组fullPath
:完整路径name
:路由名称redirectedFrom
:若存在重定向,则为重定向来源的路由名字watch
注入的$route
或使用额外的钩子(导航守卫)增加钩子:beforeRouteEnter
、beforeRouteUpdate
、beforeRouteLeave
导航守卫
守卫接收三个参数
1. `to`:即将进入的目标`$route` 2. `from`:正在离开的`$route` 3. `next`:`resolve`方法to, from, next
全局(通过router对象)
router.beforeEach((to, from, next) => {})
router.beforeResolve((to, from, next) => {})
router.afterEach((to, from) => {})
路由配置中
beforeEnter (to, from, next) {}
组件内新增钩子
beforeRouteEnter (to, from, next) {}
beforeRouteUpdate (to, from, next) {}
beforeRouteLeave (to, from, next) {}
完整的导航解析流程
beforeRouteLeave
守卫;beforeEach
守卫;beforeRouteUpdate
守卫;beforeEnter
;beforeRouteEnter
;beforeResolve
守卫;afterEach
钩子;beforeRouteEnter
中的next
回调函数。注意
store的概念:vuex提供的容器,state的集合。
一个专为Vue.js应用程序开发的状态管理模式。采用集中式存储管理应用的所有组件的状态(仅一个实例对象就能负责保存整个应用的状态,「唯一数据源」),并以相应的规则保证状态以一种可预测的方式发生变化。vuex的状态存储是响应式的,若store中的状态发生变化,则有读取状态的组件(computed
依赖状态或直接输出状态)也会相应地得到高效更新。
state
状态(仅能在mutation方法体内改变state)。
state.state名
(或state.module名.state名
)获取。响应规则(和Vue的响应式系统一致)
约定:原对象、原数组、原属性均为已存在的、响应式的数据。
Object.defineProperty
处理过,能够响应式更新)。push
、pop
、unshift
、shift
、reverse
、sort
、splice
=
)原对象/原数组添加新属性或修改原属性:
Vue.set(state或state.原对象/原数组, 键/索引, 新值)
state.原对象 = Object.assign({}, state.原对象, { 键: 新值 })
state.原对象 = { ...state.原对象, 键: 新值 }
state.原数组 = 经过处理的新数组
mapState
getters
由state或其他getter派生的值。
getters['getter名']
(或getters['module名/getter名']
)获取。computed
一样,)getter的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算;若getter返回一个函数,每次都会去进行调用,而不会缓存结果。mapGetters
mutations
改变state的唯一方式。
commit('mutation方法名'[, 参数])
或commit({ type: 'mutation方法名'[, 参数的项: 值] })
(或commit('module名/mutation方法名'[, 参数])
或commit({ type: 'module名/mutation方法名'[, 参数的项: 值] })
)触发。必须是同步函数。
返回的内容无效,使用时不会有返回值。
mapMutations
actions
仅触发mutations,而不直接改变state。
dispatch('action方法名'[, 参数])
或dispatch({ type: 'action方法名'[, 参数的项: 值] })
(或dispatch('module名/action方法名'[, 参数])
或dispatch({ type: 'module名/action方法名'[, 参数的项: 值] })
)触发。可以进行异步操作。
可以返回任何内容,包括Promise。
state
、getters
、commit
、dispatch
(,模块模式还有:rootState
、rootGetters
)。mapActions
非模块模式使用vuex
```javascript import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) // 注入 export default new Vuex.Store({ // 导出Vuex实例 state: { // 暴露的state数据 state1: 0, state2: 0 }, getters: { // 暴露的state计算数据 getters1: (state, getters) => { // 第一个参数是state对象,第二个参数是getters对象(只读) return state.state1 }, getters2: (state, getters) => { return state.state2 + getters['getters1'] }, getters3: (state, getters) => { // 成员可以返回方法,若返回方法则不缓存,每次都需要调用执行 return () => state.state1 * 2 } }, mutations: { // 暴露的改变state的方法 mutate1 (state, 参数) { // 第一个参数是state对象,第二个参数是commit调用方法的第二个参数 // 仅同步操作 state.state1++ } }, actions: { // 暴露的触发mutations的方法(可异步操作,也可返回Promise实例,也可使用async-await,还可触发其他actions) act1 (context, 参数) { // 第一个参数是类似Vuex.Store实例对象(只读),第二个参数是dispatch调用方法的第二个参数 return new Promise((resolve, reject) => { setTimeout(() => { context.commit('mutate1', 参数) resolve() }, 0) }) }, async act2 (context) { await context.dispatch('act1', 123) // 等待 act1 完成 console.log('act1完成后才往下继续执行') } } }) // new Vue({ store: 上面导出的内容 })的实例中通过this.$store访问Vuex实例 this.$store.state.state1 // 输出state this.$store.getters['getters1'] // 输出getters this.$store.getters['getters3']() // 输出getters方法的值 this.$store.commit('mutate1', 参数) // 触发mutations this.$store.dispatch('act1', 参数) // 触发actions ```
模块方式
模块内部
模块vuex对根vuex均是只读。
rootState
是根state、第四个参数rootGetters
是根getters。context
的:rootState
属性是根state、rootGetters
属性是根getters。使用其他模块的信息:
rootState.模块名.state属性
、rootGetters.模块名.getters属性
。
state.模块路径.state名
获取。命名空间namespaced
(主要针对getters、mutations、actions)
不使用
mapState
的直接使用states时,无论namespaced
是false/true
,都是相同的引用方式:vm.$store.state.module名.state名
。
false
(默认)
(组件使用) getters、mutations、actions是注册在全局命名空间,与非模块方式调用相同;无法针对某个模块而使用mapState/mapGetters/mapMutations/mapActions
的第一个参数或createNamespacedHelpers
。
此时若要用
mapState
,则只能:...mapState({ 名字: state => state.module名.state名 })
true
(组件使用) getters、mutations、actions调用要在名称中增加路径:模块路径/名称
;可以针对某个模块而使用mapState/mapGetters/mapMutations/mapActions
的第一个参数或createNamespacedHelpers
。
actions的定义内部
commit
和dispatch
只对本模块;添加{ root: true }
至第三参数,表示针对根vuex:
commit('mutation方法名 或 模块名/mutation方法名', 参数, { root: true })
dispatch('action方法名 或 模块名/action方法名', 参数, { root: true })
动态注册registerModule
、动态卸载unregisterModule
new
Vue实例时,把(已经Vue.use(Vuex)
的)Vuex.Store实例通过store
属性注入,则子组件内就能通过vm.$store
访问此Vuex实例。快速构建Vue应用的脚手架,可以使用Vue官方或第三方模板来进行Vue应用的配置,主要包括webpack等工具的配置。
任何放置在public文件夹的静态资源都会被简单的复制,而不经过webpack。需要通过绝对路径来引用。
- 绝对路径:
/images/foo.png
、http://xxx
- 相对路径:以
.
、~
、@
开头。作为一个模块请求被webpack解析。
构建时:根据命令额外加载环境变量文件
构建时,Node.js环境中的
process.env
都会加入(但没有BASE_URL
)。
.env
vue-cli-service serve
加载 .env.development
vue-cli-service build
加载 .env.production
客户端页面:自动添加环境变量文件中以VUE_APP_
开头的环境变量
除了
VUE_APP_
开头的环境变量之外,还有NODE_ENV
和BASE_URL
会自动添加到客户端页面。
process.env.VUE_APP_名字/NODE_ENV/BASE_URL
<%= VUE_APP_名字/NODE_ENV/BASE_URL %>
基于Vue的通用应用框架(SPA或SSR),把webpack、babel、vue-server-renderer、vue-router、vuex、vue-meta等工具整合在一起,并通过自带的nuxt.config.js
统一配置,不需要对每个工具进行单独配置。
框架内的Vue组件都是以Vue单文件组件的形式,每一个
pages
目录下的组件都是一个页面路由。
目录结构
大部分新增的方法都可以用
async-await
。
pages
:页面目录
Vue单文件组件。目录中的.vue
文件自动生成对应的路由配置和页面。
pages
目录下组件新增几个属性(其他目录下无效)assets
:待编译资源目录
默认使用webpack的vue-loader、file-loader、url-loader加载器进行编译的资源,如:脚本(js、jsx、tsx、coffee)
、样式(css、scss、sass、less)
、模版(html、tpl)
、JSON
、图片
、字体
文件。
~/assets/
对于不需要编译的静态资源可以放在
static
目录。
static
:静态资源目录
不需要webpack编译的静态资源。该目录下的文件会映射至项目根路径。
/
生产环境不允许随便用第三方CDN,可以把不打包的资源放到
static
目录从而放到自己服务端里去引用。
components
:组件目录
Vue单文件组件。提供给项目中所有组件使用。
~/components/
plugins
:Vue插件目录
JS文件(可注入Vue
,拥有上下文)。在Vue根应用的实例化前需要运行的Vue插件(Vue添加全局功能,仅执行一次。切换路由之后不再执行)。
除了Vue原本就有的Vue.use(插件)
,还可用nuxt特有的export default 方法
(操作context、inject)
e.g.
```javascript // plugins/stat-plugin.js export default (context, inject) => { // 在Vue实例、组件、`pages`组件新增属性(`asyncData`、`fetch`、`layout`)的上下文.app、store的actions/mutations,创建`$stat`方法 inject('stat', () => {}) // 直接在context上新建属性(不推荐),之后所有用到context的地方都能使用这个属性 context.mydata = '值' } ```
引用方式:nuxt.config.js
中加入plugins
属性
nuxt.config.js
文件引用plugins
目录下插件的方式store
:状态目录
若
store
目录存在,则:引入vuex
-> 增加vuex
至vendor -> 设置Vue根实例的store
属性。
创建
普通方式:
store/index.js
导出Vuex.Store实例对象,包含state
、getters
、mutations
、actions
属性。
模块方式:
store
目录下index.js
为根vuex对象,其他每个.js
文件被转换为以文件名命名的状态模块(开启namespaced
)。都导出state
、getters
、mutations
、actions
变量。
使用
store
属性后,即可在组件的实例内使用vm.$store
。引入所有store
文件夹内的.js
(index.js
是非模块方式引入,其他文件均为模块方式引入)。layouts
:布局目录
Vue单文件组件。扩展默认布局(default.vue
、error.vue
)或新增自定义布局,在布局文件中添加<nuxt/>
指定页面主体内容。
可添加供所有页面使用的通用组件(另外:静态的自定义通用内容添加在根目录的
app.html
)。所有路径最后都会嵌套在
default.vue
内,设置了layout
的也会再次嵌套,error.vue
也会再次嵌套。layouts/error.vue被当做pages来使用(增加了/error的路由):在layouts中创建了
error.vue
后,就不要在pages中创建。error.vue
引用方式:pages
组件中加入layout
属性
pages
目录下组件引用layouts
目录下布局的方式middleware
:中间件目录
JS文件(拥有上下文)。路由跳转之后,在页面渲染之前运行自定义函数(在加载plugins之前引入,但仅在首次路由加载和每次路由切换时执行)。执行顺序:nuxt.config.js
-> layouts
-> pages
。
可以做权限、UA等判断后执行跳转、修改store或其他行为。
引用方式:nuxt.config.js
文件或layouts
组件或pages
组件中加入middleware
属性
nuxt.config.js
文件或layouts
或pages
目录下组件引用middleware
目录下中间件的方式api
存放接口请求、utils
存放通用方法).nuxt
:nuxt构建过程资源目录(不要修改、加入.gitignore)
nuxt.config.js
的buildDir
可修改。
别名
1. `srcDir`:`~`或`@` 2. `rootDir`:`~~`或`@@` - 默认的`srcDir`等于`rootDir`
nuxt.config.js
:配置文件内置组件
<nuxt/>
仅在layouts
组件中标识引入的pages
组件。
<nuxt-child/>
仅在嵌套路由的pages
组件中引入pages
组件。
<nuxt-link/>
与<router-link/>
类似。
区别
<nuxt-link/>
会预加载<nuxt-link/>
指向的所有pages脚本(如:pages_路由1.js
),这些脚本执行也会加载相应的样式内容(如:<style>
或link href
)
<router-link/>
不会预加载<router-link/>
指向的pages脚本。
<no-ssr/>
设置组件内部内容不在服务器渲染中呈现。
流程
middleware
,最后初始化plugins
至Vue实例;随后在首次路由加载和每次路由切换时进行middleware
导出方法的运行;最后进行页面的asyncData
,然后进行组件的vue原本钩子和页面的fetch
:beforeCreate
-> props -> data -> computed -> watch
-> created
-> beforeMount
(SSR没有) -> fetch
-> mounted
(SSR没有)。created
(包括)。beforeCreate
、created
在客户端和服务端均被调用(服务端渲染),其他钩子仅在客户端被调用。路由
依据pages
目录结构和文件自动生成vue-router
模块的路由配置;在nuxt.config.js
的generate
和router
属性中修改默认路由配置。
动态路由:.vue
文件或文件夹增加_
前缀
pages/
--| users/
-----| _id.vue # <- /users/id动态值
-----| index.vue # <- /users/
# 或
--| _num/
-----| comments.vue # <- /num动态值/comments
-----| index.vue # <- /num动态值/
针对动态路由进行静态化(
1. nginx: ```text location / { try_files $uri $uri/ /index.html; } ```nuxt generate
),需要在服务端设置内部重定向
嵌套路由:新增与.vue
文件(父级)同名的文件夹存放子级,在父级内添加<nuxt-child/>
展示子级
总是显示父级内容,其中
<nuxt-child/>
根据路由选择子级组件。
pages/
--| users/
-----| _id.vue # 若路由为/users/id动态值,则父级内容的<nuxt-child/>指向此组件
-----| index.vue # 若路由为/users/,则父级内容的<nuxt-child/>指向此组件
--| users.vue # 父级内容
父级、子级组件,根据vm.$route.name
来判断在哪一个嵌套路由
/mine
或/mine/
(没有 /mine/index
mine
/mine/post
或/mine/post/
:路由名字mine-post
动态嵌套路由:动态的父级嵌套动态的子级
在组件中使用<nuxt-link/>
进行路由跳转(与vue-router的<router-link/>
一致)
自定义路由配置:
nuxt.config.js
的router.extendRoutes
配置视图
layouts
组件(可添加供所有页面使用的通用组件)nuxt.config.js
的head
属性定制HTML模板
在根目录添加app.html
,可添加供所有页面使用的静态的自定义通用内容。
app.html
样式
<style src="@/assets/文件名.css"/>
多处引入会导致重复引入。
Vue在多个组件多次引入同一个CSS文件(允许同时使用JS和CSS两种方式混合引入),最终只会引入同一个CSS文件一次。
样式加载规则:
Vue提前统一加载所有全局样式,而不是路由抵达时才加载:在任意地方引入样式,导致所有页面都使用样式。
策略:
scoped
的<style>
之外)在单独使用样式的pages中,仅用<script>import '@/assets/文件名.css'</script>
,不用 <style src="@/assets/文件名.css"/>
nuxt.config.js
的css
使用预处理器
(nuxt内置依赖了vue-loader,因此若安装了对应的依赖包后,则能够)在组件中的<template>
、<script>
、<style>
通过lang
属性使用各种预处理器。
错误页面
在layouts中创建error.vue
可作为:页面报错后自动跳转、或调用上下文对象的error
跳转、或路由/error
的页面。
在layouts中创建了
error.vue
后,就不要在pages中创建。error.vue
nuxt
CI
nuxt
:以开发模式启动一个基于vue-server-renderer的服务端
- 添加
--spa
:以SPA的方式打开服务,asyncData/fetch
在客户端执行(因此所有请求都会在客户端发起),否则asyncData/fetch
会在服务端执行。- 添加
--port 端口号
:指定端口号。
nuxt build
:利用webpack编译应用,压缩JS、CSS(用于发布SSR或SPA)
--spa
:创建所有路由的.html
文件,且这些.html
都完全一致,加载时根据vue-router进行路由计算(可以刷新的SPA)。
vue相关资源不会自动tree shaking,但是会去除注释掉的内容(开发模式不会去除);额外引用的文件会tree shaking+去除注释。
nuxt start
:以生产模式启动一个基于vue-server-renderer的服务端(依赖nuxt build
生成的资源)nuxt generate
:预渲染生成静态化文件(用于发布预渲染的静态页面)
创建所有路由的
.html
文件(去除动态路由),每个文件都是预渲染完成的不同页面。
会跑一遍nuxt的流程(Vue、vue router、vuex的初始化 -> middleware、plugins的初始化 -> middleware -> asyncData -> beforeCreate -> props->data->computed->watch
-> created -> fetch),生成静态化文件。
增加
--help
参数查看nuxt命令可带参数。
部署发布
SSR
nuxt build
-> nuxt start
预渲染
nuxt generate
- 针对无动态数据的静态页面(没有服务端返回的动态数据)。
- 忽略动态路由。
- 构建时跑完一遍nuxt流程,所以客户端不会再进行
asyncData、fetch的执行。
SPA
nuxt build --spa
输出至生产环境的方案
静态化页面
动态路由
要设置服务端重定向至index.html
,用history
路由模式。针对无法修改服务端设置或本地文件打开形式的访问(file://
),用hash
路由模式。
服务端是否设置重定向都可行。
做的事情
jQuery
(旧时代到现在)相对于原生JS,更好的API,兼容性极好的DOM、AJAX操作。面向网页元素编程。
Vue.js
实现MVVM的vm和view的「双向绑定」(单向数据流),实现自己的组件系统。面向数据编程。
优劣势对比
jQuery
Vue.js