[vue] vue 、vue-loader
生命周期
beforecreated:el 和 data 并未初始化
created:完成了 data 数据的初始化,el没有
beforeMount:完成了 el 和 data 初始化(模板插入)
mounted :完成挂载(渲染出真实dom)
data,props,computed,methods是在created之前beforecreate之后创建的
父组件与子组件的生命周期是同步的:
父组件created -> 子组件created -> 父组件mounted -> 子组件mounted
过渡
在进入/离开的过渡中,会有 6 个 class 切换。
-
v-enter
:定义进入过渡的开始状态。在元素被插入时生效,在下一个帧移除。 -
v-enter-active
:定义过渡的状态。在元素整个过渡过程中作用,在元素被插入时生效,在 transition/animation 完成之后移除。这个类可以被用来定义过渡的过程时间,延迟和曲线函数。 -
v-enter-to
: 定义进入过渡的结束状态。在元素被插入一帧后生效 (于此同时 v-enter 被删除),在 transition/animation 完成之后移除。 -
v-leave
: 定义离开过渡的开始状态。在离开过渡被触发时生效,在下一个帧移除。 -
v-leave-active
:定义过渡的状态。在元素整个过渡过程中作用,在离开过渡被触发后立即生效,在 transition/animation 完成之后移除。这个类可以被用来定义过渡的过程时间,延迟和曲线函数。 -
v-leave-to
: 定义离开过渡的结束状态。在离开过渡被触发一帧后生效 (于此同时 v-leave 被删除),在 transition/animation 完成之后移除。
createElement
// @returns {VNode}
createElement(
// {String | Object | Function}
// 一个 HTML 标签字符串,组件选项对象,或者一个返回值类型为 String/Object 的函数,必要参数
'div',
// {Object}
// 一个包含模板相关属性的数据对象
// 这样,您可以在 template 中使用这些属性。可选参数。
{
// 和`v-bind:class`一样的 API
'class': {
foo: true,
bar: false
},
// 和`v-bind:style`一样的 API
style: {
color: 'red',
fontSize: '14px'
},
// 正常的 HTML 特性
attrs: {
id: 'foo'
},
// 组件 props
props: {
myProp: 'bar'
},
// DOM 属性
domProps: {
innerHTML: 'baz'
},
// 事件监听器基于 `on`
// 所以不再支持如 `v-on:keyup.enter` 修饰器
// 需要手动匹配 keyCode。
on: {
click: this.clickHandler
},
// 仅对于组件,用于监听原生事件,而不是组件内部使用 `vm.$emit` 触发的事件。
nativeOn: {
click: this.nativeClickHandler
},
// 自定义指令。注意事项:不能对绑定的旧值设值
// Vue 会为您持续追踪
directives: [
{
name: 'my-custom-directive',
value: '2',
expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
// Scoped slots in the form of
// { name: props => VNode | Array<VNode> }
scopedSlots: {
default: props => createElement('span', props.text)
},
// 如果组件是其他组件的子组件,需为插槽指定名称
slot: 'name-of-slot',
// 其他特殊顶层属性
key: 'myKey',
ref: 'myRef'
},
// {String | Array}
// 子节点 (VNodes),由 `createElement()` 构建而成,
// 或使用字符串来生成“文本节点”。可选参数。
[
'先写一些文字',
createElement('h1', '一则头条'),
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
scoped
如果希望 scoped 样式中的一个选择器能够作用得'更深',例如影响子组件,可以使用 >>>
操作符:
<style scoped>
.a >>> .b { /* ... */ }
</style>
有些像 SASS 之类的预处理器无法正确解析 >>>
。这种情况下你可以使用 /deep/
操作符取而代之——这是一个 >>>
的别名,同样可以正常工作。
通过 v-html 创建的 DOM 内容不受作用域内的样式影响,但是仍然可以通过深度作用选择器来为他们设置样式。
scoped是以该级向下的,会污染下级,并不是每个级单独独立的.
data()
data 可以理解为react的state
数组
Vue 不能检测以下变动的数组:
当利用索引直接设置一个项时,例如:
vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如:
vm.items.length = newLength
为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue
相同的效果,同时也将触发状态更新:
// Vue.set
Vue.set(example1.items, indexOfItem, newValue)
// Array.prototype.splice
example1.items.splice(indexOfItem, 1, newValue)
为了解决第二类问题,可以使用 splice:
example1.items.splice(newLength)
对象
Vue.set(object, key, value)
//实例
vm.$set(object, key, value)
有时想向已有对象上添加一些属性,例如使用 Object.assign()方法来添加属性。但是,添加到对象上的新属性不会触发更新。
在这种情况下可以创建一个新的对象,让它包含原对象的属性和新的属性:
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
其他
data()时将props的值赋给state,但是并不会跟随props变化而变化,可以用watch或者computed.
data(){}里以 _
或 $
开头的属性 不会 被 Vue 实例代理,
因为它们可能和 Vue 内置的属性、API 方法冲突。
比较复杂的数据,比如说[{},{}]这种结构,
比较粗暴可以试试将key改为动态的,不过并不是推荐这种方式.
推荐开启watch的deep:true,并且
this.stateData.splice(this.propsData.length);
this.propsData.forEach((v, k) => {
let obj = Object.assign({}, v);
this.$set(this.stateData, k, obj);
});
keep-live
Props:
- include - 字符串或正则表达式。只有匹配的组件会被缓存。
- exclude - 字符串或正则表达式。任何匹配的组件都不会被缓存。
<keep-alive>
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 <transition>
相似,<keep-alive>
是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。
当组件在 <keep-alive>
内被切换,它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行。
activated 和 deactivated 将会在 <keep-alive>
树内的所有嵌套组件中触发。
主要用于保留组件状态或避免重新渲染。
匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。
匿名组件不能被匹配。
<!-- 逗号分隔字符串 -->
<keep-alive include="a,b">
<component :is="view"></component>
</keep-alive>
<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>
<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
<component :is="view"></component>
</keep-alive>
v-model 与 .sync
v-model
<input v-model="something">
//相当于
<input
v-bind:value="something"
v-on:input="something = $event.target.value">
默认的props是value,默认的event是input,
可以通过model自定义
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean,
// 这样就允许拿 `value` 这个 prop 做其它事了
value: String
},
如果要说v-model和.sync有什么区别的话,
那就是v-model默认绑的事件是input,
.sync默认绑的update,而且一般是update:xx,
而v-model还可以自定义event.
实现上来说感觉没什么区别
.sync
相当于省去了父组件传给子组件的改值回调方法
//父
<comp :foo.sync="bar"></comp>
//相当于
<comp :foo="bar" @update:foo="val => bar = val"></comp>
//子 需要手动执行
this.$emit('update:foo', newValue)
可以同时给多个值添加.sync
<comp v-bind.sync="{ foo: 1, bar: 2 }"></comp>
父组件传递对象/数组到子组件,子组件使用相应的浅复制,
为了方便更改两者建立了.sync,但是当两者成功建立时,如果建立的是子组件的浅复制,
那么父组件相应的数据也是对该浅复制的引用,那么就容易出现问题需要注意.
computed , watch
- 如果一个数据依赖于其他数据,那么把这个数据设计为computed的
- 如果需要在某个数据变化时做一些事情,使用watch来观察这个数据变化
一般用watch监测props的变化,同步state.
执行顺序为:
默认加载的时候先computed再watch,不执行methods;等触发某一事件后,则是:先methods再watch。
nextTick
可以在数据变化之后立即使用
Vue.nextTick(callback)
vm.$nextTick()
nextTick([callback,context])在下次 DOM 更新循环结束之后执行延迟回调。
可在修改数据之后立即使用这个方法,获取更新后的 DOM。
但在动画里似乎不那么好用.
props
props: {
// 基础类型检测 (`null` 指允许任何类型)
propA: Number,
// 可能是多种类型
propB: [String, Number],
// 必传且是字符串
propC: {
type: String,
required: true
},
// 数值且有默认值
propD: {
type: Number,
default: 100
},
// 数组/对象的默认值应当由一个工厂函数返回
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
return value > 10
}
}
}
type 可以是下面原生构造器:
String
Number
Boolean
Function
Object
Array
Symbol
type 也可以是一个自定义构造器函数,使用 instanceof 检测。
当props为boolean时,不传值只写属性传入也是true.
vue里不能直接通过props改变state,只可以初始化时赋值一次,与react不一样
v-if v-show v-for
v-for 优先级比 v-if 高。
v-if可用于控制组件销毁
v-show则是控制display:block/none
is
is
可以将某些dom绑定为需要的组件,主要用于遍历时动态变换
//template
<div is="IButton" plain>plain</div>
<div :is="xx" plain>plain</div>
<component :is="xx" plain>plain</component>
<component is="IButton" plain>plain</component>
//js
import IButton from '../components/Button.vue';
export default {
data() {
return {
xx: IButton
}
},
components: {
IButton
},
}
错误处理
全局的错误收集,可以在这里进行发送错误日志
Vue.config.errorHandler = function (err, vm, info) {
// handle error
// `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
// 只在 2.2.0+ 可用
}
组件的catch错误生命周期
errorCaptured(err: Error, vm: Component, info: string) => ?boolean
组件通信
父组件使用子组件方法,$ref,$child.
子组件使用父组件方法,$parent,props回调,$emit,$listeners.
兄弟组件通信,$emit发射,$on接收.
vue2.0开始去除了$dispatch()和$broadcast(),只能通过上面几种方式多绕几层,
也可以用一种bus的方式替代$dispatch()和$broadcast().
建议使用vuex.
provide / inject,支持父级往下传,但似乎不能正确获取到this,所以似乎传递函数不好用,传递数据因为只能被inject读取,也不是响应式的,所以价值也不大
vue-loader
Src 导入
如果希望分隔 .vue 文件到多个文件中,可以通过 src 属性导入外部文件:
<template src="./template.html"></template>
<style src="./style.css"></style>
<script src="./script.js"></script>
需要注意的是 src 导入遵循和 require() 一样的规则,这意味着你相对路径需要以 ./ 开始,还可以从 NPM 包中直接导入资源,例如:
<!-- import a file from the installed "todomvc-app-css" npm package -->
<style src="todomvc-app-css/index.css">
在自定义块上同样支持 src 导入,例如:
<unit-test src="./unit-test.js">
</unit-test>
有作用域的 CSS
子组件的根元素
使用 scoped 后,父组件的样式将不会渗透到子组件中。不过一个子组件的根节点会同时受其父组件有作用域的 CSS 和子组件有作用域的 CSS 的影响。
也就是会出现同名css,父级污染子级的情况。
深度作用选择器
如果希望 scoped 样式中的一个选择器能够作用得“更深”,
例如影响子组件,可以使用 >>> 操作符:
<style scoped>
.a >>> .b { /* ... */ }
</style>
上述代码将会编译成:
.a[data-v-f3f3eg9] .b { /* ... */ }
有些像 SASS 之类的预处理器无法正确解析 >>>。
这种情况下可以使用 /deep/
操作符取而代之——这是一个 >>> 的别名,同样可以正常工作。
动态生成的内容
通过 v-html 创建的 DOM 内容不受作用域内的样式影响,但是仍然可以通过深度作用选择器来为他们设置样式。
CSS 作用域不能代替 class。
考虑到浏览器渲染各种 CSS 选择器的方式,当 p { color: red }
设置了作用域时 (即与特性选择器组合使用时) 会慢很多倍。如果你使用 class 或者 id 取而代之,比如 .example { color: red }
,性能影响就会消除。
在递归组件中小心使用后代选择器
对选择器 .a .b
中的 CSS 规则来说,如果匹配 .a
的元素包含一个递归子组件,则所有的子组件中的 .b
都将被这个规则匹配。
资源路径处理
vue-loader可是识别webpack的alias,特别是@?
url-loader 允许有条件将文件转换为内联的 base-64 URL (当文件小于给定的阈值),这会减少小文件的 HTTP 请求。如果文件大于该阈值,会自动的交给 file-loader 处理。
提取 CSS 到单个文件
可以通过extractCSS设置extract-text-webpack-plugin提取vue里的css。
options: {
extractCSS: true
}
单元测试
import Vue from 'vue' // 导入Vue用于生成Vue实例
import Hello from '@/components/Hello' // 导入组件
// 测试脚本里面应该包括一个或多个describe块,称为测试套件(test suite)
describe('Hello.vue', () => {
// 每个describe块应该包括一个或多个it块,称为测试用例(test case)
it('should render correct contents', () => {
const Constructor = Vue.extend(Hello) // 获得Hello组件实例
const vm = new Constructor().$mount() // 将组件挂在到DOM上 //断言:DOM中class为hello的元素中的h1元素的文本内容为Welcome to Your Vue.js App
expect(vm.$el.querySelector('.hello h1').textContent)
.to.equal('Welcome to Your Vue.js App')
})
})
杂
当把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。
Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因。
computed,watch里用箭头函数会出问题。
$emit和$listeners不存在父子间穿透的问题.
extend的component即使一直执行document.body.appendChild,也只会append一次,暂不知机制是什么...
项目内组件不能和已注册过的组件同名,也就是要注意export default { name: 'X' }
导出的name,甚至可以不设置这个name,在dev-tools里会与文件名保持同名.
vue里的父子组件里同时用
vue里的stylus引入stylus文件,如果引入了其他css文件会报错.但是如果在js里引是可以的.
vue里的stylus使用url()会报错,但css可以,可能是因为stylus的url会走publicPath或者static的目录.
模板字符串的使用
vue :custom="`hello${var}`"
非受控组件容易出问题,
父子组件利用v-model或者.sync,父组件传递的数据是对象/数组的某键值,$emit的改变很可能会出问题,
因为Vue不能检测对象/数组键值的添加或删除.
用concat会丢失双向绑定
.self 等价 if (event.target !== event.currentTarget) return
.native
主要是给自定义的组件添加原生事件,不加.native
可能@click
会被组件识别为某种$listener
,html则没有这个问题.
vue包含css打包 比 vue与css拆分打包 更大