Vue Cli
npm install -g @vue/cli
vue create my-projects
命名规则
? 建议在模板中都使用烤串方式,包括属性等,因为在html中是大小写不敏感的,组件的名称使用AaaBbb,函数及变量使用驼峰
<video-item :big-prop="1"></video-item>
// 虽然声明props时采用驼峰,但是vue中会做一个值的映射,会把驼峰转成烤串方式
使用JSX
// 可以直接这样
data() { ... },
render() {
return <div></div>
}
Prop属性校验
props: {
parentName: {
type: String, // [String, Array]
required: true,
default: ‘1‘,
validator(value) { return true }
}
}
操作数据的局限性
- 不能监测到对象属性的添加或删除,给Data新增属性也不行
- 不能监测到数组长度的变化(通过改变length而增加的长度不能监测到)
- 不是因为defineProperty的局限性,而是出于性能考虑,不会对数组的每个元素都监听(因为数组有可能是海量的数据,前期进行依赖收集会很耗费性能)
Vue.set(this.classmates, "2", { id: 1, name: ‘wang‘})
this.$set(this.classmates, "2", { id: 1, name: ‘wang‘})
Vue.delete(this.classmates, "2")
this.$set(this.classmates, "2")
? set其实就是又做了一层definePropert
生命周期
? 首先是beforeCreate,实例初始化后被调用,在 beforeCreate
钩子函数调用的时候,是获取不到 props
或者 data
中的数据的。然后会执行 created
钩子函数,在这一步的时候已经可以访问到之前不能访问到的数据,但是这时候组件还没被挂载,所以是看不到的。
? 接下来会先执行 beforeMount
(模板编译后,渲染之前) 钩子函数,开始创建 VDOM,最后执行 mounted
钩子,并将 VDOM 渲染为真实 DOM 并且渲染数据。组件中如果有子组件的话,会递归挂载子组件,只有当所有子组件全部挂载完毕,才会执行根组件的挂载钩子。
? 接下来是数据更新时会调用的钩子函数 beforeUpdate
(数据改变后,模板改变前)和 updated
,这两个钩子函数没什么好说的,就是分别在组件更新前和更新后会调用。
? 另外还有 keep-alive
独有的生命周期,分别为 activated
和 deactivated
。用 keep-alive
包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated
钩子函数,命中缓存渲染后会执行 actived
钩子函数。
? 最后就是销毁组件的钩子函数 beforeDestroy
和 destroyed
。前者适合移除事件、定时器等等,否则可能会引起内存泄露的问题。然后进行一系列的销毁操作,如果有子组件的话,也会递归销毁子组件,所有子组件都销毁完毕后才会执行根组件的 destroyed
钩子函数。(解绑自定义事件event.$off,解绑自定义的dom事件如window.scroll)
组件通信
简单通信
? 父传子简单,即通过props,子传父就通过$emit事件的方式
// 父组件中监听add
<Cmp @add="myadd($event)"/>
// 子组件中触发add
this.$emit(‘add‘, good)
? 如果是兄弟组件,可以用它们共同的父组件或者根组件
this.$parent.$on(‘foo‘, msg => { console.log(msg) })
this.$parent.$emit(‘foo‘, ‘some message‘)
? 祖先和后代之间就是provide和inject(依赖注入的方式),其实主要用于UI库,但是它不是响应式的,也就是说父组件中修改他,子组件是不会更新的
provide() {
return {
foo: ‘foo‘
}
}
inject:[‘foo‘]
任意组件
? 使用eventBus或者vuex
// 事件总线方法
// event.js
import Vue from ‘vue‘
export default new Vue()
// component
import event from ‘./event.js‘
event.$on(‘add‘, this.getName)
event.$emit(‘add‘, this.name)
beforeDestroy() {
event.$off(‘add‘, this.getName)
}
? 此外还有一些非常规的通信方法,主要就是获取Vue实例,并访问它的一些属性和方法
$root
$parent
$children
$refs
this.$children[0].eat(‘some message‘)
? 注意点是$children
是不保证子组件顺序的,比如某个子组件是异步组件
事件总线的原理
class Bus {
constructor() {
this.callbacks = {}
}
$on(name, fn) {
this.callbacks[name] = this.callbacks[name] || []
this.callbacks[name].push(fn)
}
$emit(name, args) {
if(this.callbacks[name]) {
this.callbacks[name].forEach(cb => cb(args))
}
}
}
$attrs & $listeners
? $attrs
是指如果父组件传给子组件了一些属性,如果子组件没有用props接收,则会存在$attrs
里面,当然是除了class和style。$listeners与$attrs是类似的,比如在父组件中定义一个事件,可以在子组件中用$listeners展开监听,在做组件的时候会很有应用价值
// 父组件中声明点击事件
<cq-el-input @click="handleClick"/>
// $listeners里面就是父组件传过来的这些事件
// 假设我们要二次封装饿了么的ui库, 我们肯定要把事件都写到外面传入到elementUI中,就可以这么接收
<template>
<el-input v-on="$listeners"></el-input>
...
</template>
事件
<div @click="fn"></div> // 使用fn的时候默认会传event参数
<div @click="fn(‘1‘)"></div> // 使用fn时event会被放到第二个参数位置
<div @click="fn(‘1‘, $event)"></div> // $event显示的传入事件
事件修饰符
- .stop:阻止冒泡
- .prevent:阻止默认行为
- .capture:使用捕获
- .self:只有本身触发才执行
- .once:只绑一次
- .passive:该修饰符表示就是设置{passive:true},表示处理事件函数中不会调用preventDefault函数,就会减少了额外的监听,从而提高了性能;所以不能和.prevent修饰符一同使用,否则浏览器会报错
- .keyCode
- .enter
- .down
- .exact
- .native:指定监听原生事件,而不是组件自定义事件
@click.stop
@keyup.13 // 13某个keyCode,当按下键是13的时候
常用指令
v-pre
? 忽略这个组件以及它的子元素的编译,比如你就想输出{{}}这个东西
v-for
? v-for也可以遍历object,v-for优先级高于v-if,先循环,然后判断是否要渲染,就会做三次重复的判断,是不好的
v-text
? {{ }}
这个的问题,有时候内容比较多的话会先显示{{ title }}这种样子,就可以用v-text解决
v-cloak
? 也可以解决上面的问题,不过需要配合css属性选择器
[v-cloak]: {
display: none;
}
// 当渲染完后会自动将选择器里面的值设置为 display: block
v-once
? 只渲染一次,后期的更新不再渲染
指令修饰符
.lazy // 懒
.trim // 去除首尾空格
.number // 转数字
v-model.lazy="val"
自定义指令
? 两种形式,全局和局部的
全局指令
Vue.directive(‘test‘, { ... })
局部指令
? 只在当前组件中可以使用
new Vue({
directive: {
name: { ... }
}
})
? v-name即可使用
自定义指令的生命周期
- bind:第一次绑定到元素的时候调用,只调用一次,可以做一些初始化,但是这时候页面可能还没有插入这个元素
- inserted:被绑定元素被插入父节点时使用
- update:所在组件更新时调用
- componentUpdated:所在组件更新完成时调用
- unbind:解绑时调用,只调用一次
// 会传入el和binding
el就是所在的元素
binding是指令配置细节,如name,value,oldValue,expression等
// 此外还有vnode和oldVnode
实现获取焦点指令
Vue.directive(‘focus‘, {
inserted(el, binding) {
binding.isFocus && el.focus()
}
});
<input v-focus="isFocus" />
watch
? 监听引用类型必须要深度监听,引用类型拿不到oldValue,因为是引用类型,指向同一个地址,值被修改了变成一样了
// 正常的watch,当name变化后会触发
watch: {
name(newValue, oldValue) {
this.a = newValue // 刚开始不会执行,以后变了才会执行
}
}
// 带选项的watch
watch: {
a: {
handler: function() {}, // 拿不到oldValue
deep: true, // 深度监听这么写
immediate: true // 表示一开始就监听,否则在第一次渲染时不会触发handler方法
}
}
此外需要注意watch和计算属性的区别
- 前者是一个值变化了影响多个值;后者多个值影响一个值
- 计算属性有缓存性
- 计算属性不接受异步
? 在数据变化后执行异步操作或者开销比较大的操作建议使用watch
$nextTick有什么用
- Vue是异步渲染
- data改变之后DOM不会立即渲染,就跟state一样做一个整合,多次修改只会渲染一次
- $nextTick会在DOM渲染完后被触发,以获取最新的DOM节点
this.list.push(666)
this.$nextTick(() => {
console.log(domList.length)
})
// list变化dom也变化,拿到最新的dom列表项的长度
动态组件
? 有的时候组件类型不确定
<component :is="componentName"></component>
? 这样就会根据data里的componentName动态的渲染组件,其中is必须是动态的
异步组件
? 体积比较大的组件很影响体验,如果全部同步打包进来体积会非常大而且加载会非常慢,严重影响性能。所以如果首页不需要加载我们就先不加载它
<template>
<Demo v-if="showDemo"/>
</template>
<script>
import Cmp from ‘./xxx‘ // 同步加载, 打包的时候也是打一个包
export default {
compoennts: {
Demo: () => import(‘./xxx‘) // 异步加载
}
}
</script>
? 你发现当showDemo为true时,开发工具的NetWork里才会出现一个文件,打开以后发现就是这个Demo的打包文件,刚开始的时候并没有加载这个Demo
keep-allive
? 其实就是缓存组件,一般就是频繁切换的时候用
<keep-alive>
<TabA />
<TabB />
<TabC />
</keep-alive>
渲染函数
render: function(createElement) {
return createElement(tag, data, children)
}
函数式组件
? 如果组件没有管理任何状态,也没有监听任何传递给它的状态(watch),也没有生命周期,可以标记为函数式组件,意味着它没有状态(没有响应式),也没有实例,意味着更加轻量,性能更好
Vue.component(‘xxx‘, {
functional: true
})
vue自定义v-model
// 1. parent.vue
<MyInput v-model="name"/>
// 2. MyInput.vue
<template>
<input
type="text"
:value="name"
@input="$emit(‘change‘, $event.target.value)">
</template>
<script>
export default {
props: [‘name‘]
model: {
prop: ‘name‘, // 从父到子,对应props里的value
event: change // 从子到父
}
}
</script>
? 总结,1里的name变化,则2里的model里的prop对应的name也会变化,而触发change,则1里面的name也会变化
.sync
? 它的意思就是说将来会把它展开成一个事件
<text-document v-bind:title.sync="doc.title"></text-document>
// 触发此事件时必须用 this.$emit(‘update:title‘, newTitle)
? 相当于
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
??
-
v-for比v-if优先级高,每次循环都要if判断影响效率,建议用计算属性代替
-
根组件不能挂到html或者body上,因为他会替换
-
attribute是html标签上的属性,property是dom(对象)的属性