mixin
全局混入
会影响每一个之后创建的 Vue 实例
Vue.mixin({
created: function () {
// }
})
局部混入
mixins: [myMixin]
缺点:数据来源不明确
const mixin = {
data () {
return {
b: 2
}
}
}
export default {
name: 'App',
components: {
// HelloWorld
},
mixins: [mixin],
data () {
a: 1
}
}
当 mixins 与 当前元素下的数据有冲突时,会进行递归合并;如果 key 有冲突,则会以组件的为主,mixins 的值会被覆盖
const mixin = {
data () {
return {
b: 2,
info: {
name: 'mixin'
}
}
}
}
export default {
name: 'App',
components: {
// HelloWorld
},
mixins: [mixin],
data () {
return {
a: 1,
info: {
name: 'appComponent',
age: 12
}
}
}
}
Mixins 数据合并:
-
数据类型的合并,如 data props computed 遵循 webpack merge 递归的合并,只在key 冲突的情况下替换
-
钩子函数中的合并,如生命周期钩子,相当于将他们放在一个数组中 -> [() => {}, ()=> {}] 但是mixins会定义的先执行
const mixin = {
created () {
console.log('mixin created!')
}
}
export default {
name: 'App',
created () {
console.log('app created!')
}
}
- 选项类型的合并,如methods,组件可以直接调用,并以组件的数据为主
const mixin = {
methods: {
test () {
console.log('mixin test')
}
}
}
export default {
name: 'App',
created () {
this.test ();
console.log('app created!')
},
methods: {
test () {
console.log('app test')
}
}
}
elementUi 中的mixin应用
场景:input 给 form 传递数据
一般思路为父子组件间传递,但是也许中间也会隔一些组件,因此抽离一个mixin
- 定义 mixin github.com/ElemeFE/ele…
// 利用递归一层层往上找,找到 组件 name
broadcast(componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
- 组件引入 mixin github.com/ElemeFE/ele…
export default {
name: 'ElDialog',
mixins: [Popup, emitter, Migrating],
props: {
title: {
type: String,
default: ''
},
...
- 组件使用 mixin github.com/ElemeFE/ele…
updatePopper() {
this.broadcast('ElSelectDropdown', 'updatePopper');
this.broadcast('ElDropdownMenu', 'updatePopper');
},
插槽 - react 中是this.props.children
- 默认插槽:
子组件中定义插槽
<template>
<div class="">
<slot></slot>
</div>
</template>
父组件定义具体内容
<template>
<div id="app">
<el-dialog>
<span>这是一段标签</span>
</el-dialog>
</div>
</template>
- 具名插槽:
子组件定义名称
<template>
<div>
<slot name="footer"></slot>
</div>
</template>
父组件定义,需要使用 v-slot绑定名字
<template>
<div id="app">
<el-dialog>
<span>这是一段标签</span>
<template v-slot:footer>
这是尾部标签
</template>
</el-dialog>
</div>
</template>
- 插槽作用域
普通插槽:数据可以是当前组件中的data
<el-dialog>
<span>这是一段标签 {{ a }}</span>
</el-dialog>
data () {
return {
a: 1
}
}
作用域插槽:组件把数据传递给插槽
3.1 具名插槽作用域
// el-dialog
data () {
return {
userInfo: {
name: '',
age: ''
}
}
},
mounted () {
setTimeout(() => {
this.userInfo = {
name: '张三',
age: 12
}
}, 2000);
}
当前组件中传递数据给插槽: v-bind
// el-dialog
<slot name="footer" :userInfo="userInfo"></slot>
父组件如何拿到数据:v-slot: footer="slotProps" " "内可以随便命名
<template v-slot:footer="slotProps">
这是尾部标签 {{ slotProps }}
</template>
3.2 默认插槽作用域
传入数据时,父级中要在<template>标签中设置v-slot:default="slotDefaultProps"
<template v-slot:default="slotDefaultProps">
<span>这是一段标签 {{ slotDefaultProps.spanInfo }}</span>
</template>
- 插槽的编译
插槽的编译都是在各自的模版中完成的,父级模版的内容都是在父级模版编译的,子级模版的内容都是在子级模版编译的;template -> render() 渲染:render() -> vnode
普通插槽就在当前作用域中渲染,作用域插槽首先变成function(proprs) {},将其插入到子集组件内部渲染
插件
用插件为 vue 添加全局功能,如vuex vue-router
- 注册插件
Vue.use(Vuex);
Vue.use(VueRouter);
- 编写插件 2.1 暴露install 方法 2.2 Vue.use(MyPlugin, options)会执行插件的install方法,并传入Vue和options
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) {
// ... }
}
- 实现elementUI中的message 插件
3.1 简单的 Message 组件
// Message
<template>
<div>
<div v-for="m in messages" :key="m.id"></div>
</div>
</template>
<script>
export default {
name: 'ElMessage',
data () {
return {
messages: [] // { id, message, duration }
}
}
}
</script>
3.2 调用方式 3.2.1 挂在 Vue.prototype 上,添加全局方法$message,实例上可以使用 Message 方法
暴露install 方法,使其成为插件
// message.js
export default {
install(Vue, options) {
console.log(options)
Vue.prototype.$message = {
info: Message.info,
}
}
}
主文件中注册 插件
// main.js
import Message from './components/Plugin/message'
Vue.use(Message, {
size: 'small'
})
3.2.2 单独引用
注册
// App.vue
import { Message } from './components/Plugins/message.js';
// 使用
// Message.info({ message: '消息', duration: 2000})
写插件
// message.js
export const Message = {
info(options) {
console.log(options)
}
}
全局点击调用
//App.vue
<button @click="showMessage">全局调用message</button>
showMessage() {
Message.info({ message: `消息提示: ${++id}`, duration: 2000})
}
拿到options 数据之后 通过MessageComponent.add(options) 将数据放入Message 组件中
mounted() {
this.id = 0
},
methods: {
add(options) {
const layer = {
...options,
id: this.id++
}
this.messages.push(layer); // 点击加入数据
// 一段时间后自动消失
setTimeout(() => {
this.remove(layer);
}, layer.duration);
},
remove(layer) {
return this.messages = this.messages.filter(m => m.id !== layer.id)
}
}
message.js 中拿到MessageComponent 组件并调用add方法
// message.js
import Vue from 'vue'
import MessageComponent from './Message.vue'
// 获取实例
// 单例模式,避免生成多个div
let vm = null
const getInstance = () => {
const getInstance = function() {
if (!vm) {
vm = new Vue({
render: h => h(MessageComponent)
}).$mount() // 转换成真实DOM
// 将模版显示到页面上
document.body.appendChild(vm.$el)
}
return vm.$children[0] // MessageComponent 组件
}
export const Message = {
info(options) {
console.log(options)
getInstance().add(options)
}
}
过滤器
- 定义:将原数据进行格式化显示,而不改变原数据
- 应用:货币符号、时间格式化(一般用在与业务关联不大的情况下,否则用computed)
- 全局过滤器 例子:时间
// main
Vue.filter('timeFormat', function(val, function(val, formatter = 'YYYY:MM:DD')) {
return moment(val).format(formatter)
})
- 局部过滤器
// App.vue
name: 'App',
components: {
// HelloWorld
// ElDialog,
// BlogPost
},
filters: {
timeFormat(val, formatter) {
return moment(val).format(formatter)
}
},
mixins: [mixin],
- 无法访问 this
Vue 响应式原理
- 核心 API Object.defineProperty
<div>
<div>{{ a }}</div>
<div>{{ info.name }}</div>
</div>
data () {
return {
a: 1, // get set dep
info: { // get set dep
name: 'zhangsan', // get set dep
age: 12
}
}
},
watch: {
a() {
}
}
Object.defineProperty() 会递归的遍历这些数据,给数据添加getter 和 setter get 收集依赖,set 通知依赖
const dep1 = new Dep()
Object.defineProperty(this.$data, 'a', {
get() {
dep1.depend() // 收集依赖
return value
},
set(newValue) {
if (newValue === value) return;
value = newValue
dep1.notify() // 通知依赖
}
})
const dep2 = new Dep()
Object.defineProperty(this.$data, 'info', {
...
})
const dep3 = new Dep()
Object.defineProperty(this.$data.info, 'name', {
...
})
- 每个组件实例对应一个渲染watcher,对touch 的数据进行求值 -> 触发每个值对应的get 什么是touch -- 模版里面用到了
- a -> get, info -> get, name -> get ,调用dep.depend()收集依赖,渲染watcher
- a 的 dep 渲染watcher,watch里面的watcher
- 当 给a更改值,this.a = 2 -> 触发a - set, 通过dep.notify 通知依赖
- info.age 没有用的话,改变this.info.age 不会触发
收集依赖、触发更新
- 收集依赖
- 每个组件实例对应一个watcher实例
- 在组件渲染过程中,把“touch”过的数据记录为依赖(触发getter -> 将当前watcher实例收集到属性对应的dep中)
- 触发更新
- 数据更新后 -> 会触发属性对应的setter -> 通过dep去通知watcher -> 关联的组件重新渲染
注意事项:
- 对象
- vue无法监测对象的添加
- 解决方案:this.$set(this.someObject, 'b' ,2)
- 注意:Vue 不允许动态添加根级别的响应式 property
- 数组
- Object.defineProperty无法监听数组索引值的变化,比如 this.a[0] = 44
- 解决方案:
- this.$set(this.a, 0, 44)
- this.a.splice(0, 1, 44)
- 数组长度的变化也无法监听
- 解决方案:this.a.splice(newLength) // 省略第二个参数,表明删除从newLength及后面的数据
- 重写了数组的方法(push() pop() shift() unshift() splice() sort()reverse())
- this.a[1].name = 'lisi' // 这是更改对象的属性值
- 其他
- 递归的循环data中的属性(可能会导致性能问题)
- 对于一些数据获取后不更改,仅仅用来展示的数据(比如说省、市下拉框,获取一次之后,下拉框的 options 数据就不变了(城市不变),改变的是value (选的city 值))可以使用Object.freeze来优化性能
this.city = Object.freeze(data.city)不用再去添加setter getter
动画
- 单元素/组件动画(transition 组件)
<template>
<div>
<h2>单元素 css 动画</h2>
<button @click="toggleVisible">切换</button>
<transition
name="fade"
>
<div class="box" v-show="visible"></div>
</transition>
<transition
enter-active-class="animate__animated animate__bounce"
leave-active-class="animate__animated animate__tada
"
>
<div class="box" v-show="visible"></div>
</transition>
</div>
</template>
import 'animate.css'
export default {
data() {
return {
visible: true,
}
},
methods: {
toggleVisible() {
this.visible = !this.visible
}
}
}
.box {
margin-top: 30px;
width: 100px;
height: 100px;
background: red;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
.fade-enter-active, .fade-leave-active {
transition: opacity 1s ease-out;;
}
1.1 用途:单元素/组件动画(比如:进入/离开) 1.2 css3 transition
-
v-enter
- 用来定义动画的初始状态
- 元素插入之前被添加,元素插入后被移除
-
v-enter-active
- 定义动画语句
- 元素插入之前被添加,动画结束后被移除
-
v-enter-to
- 用来定义动画的结束状态(一般不设置,因为元素有默认的显示状态,比如opacity默认为1)
- 元素插入后被添加,动画结束后被移除
-
v-leave
-
v-leave-active
-
v-leave-to
-
多元素动画 区别在于会真实生成一个 dom 元素
-
动画库
animate.css
- css动画库
velocity
- Js动画库(类似JQuery的$.animate)处理一些CSS属性,比如opacity, position等
- 注意 npm install velocity-animate
gasp
- Js动画库,除了CSS属性外,可提供状态过渡