这几个小技巧,让你书写不一样的Vue!
前言
最近一直在阅读Vue
的源码,发现了几个实战中用得上的小技巧,下面跟大家分享一下。
同时也可以阅读我之前写的Vue
文章
隐藏在源码中的技巧
在实例化Vue
时,首先调用的是Vue.prototype._init
方法,而在此方法中mergeOptions()
方法返回的options
将运用在所有的初始化函数中。也就是如下代码:
vm.$options = mergeOptions( resolveConstructorOptions(vm.coustructor), options || {}, vm)
其中mergeOptions
方法在core/util/options.js
文件中,最后这个方法返回的是一个options
对象。
return options
这就说明了mergeOptions
方法最终将合并处理后的选项返回,并以该返回值作为vm.$options
的值。
自定义选项的合并方法
如果我们在options
中添加自定义的属性会怎么样?来自官方的例子:
new Vue({
customOption: 'foo',
created: function () {
console.log(this.$options.customOption) // => 'foo'
}
})
在创建Vue
实例的时候传递了一个自定义选项:customOptions
,在created
中可以通过this.$options.customOption
进行访问。原理其实就是使用mergeOptions
函数对自定义选项进行合并处理。对于自定义选项的合并,采用的是默认的合并策略:
// 当一个选项没有对应的策略函数时,使用默认策略
const strat = strats[key] || defaultStrat
defaultStrat
函数就定义在options.js
文件内,源码如下
/** * Default strategy. */
const defaultStrat = function (parentVal: any, childVal: any): any { return childVal === undefined ? parentVal : childVal}
defaultStart
作用如其名,它是一个默认的合并策略:只要子选项不是undefined
则使用子选项,否则使用父选项。最终的效果就是你初始化了什么,得到的就是什么。
而且Vue
也提供了一个全局配置叫Vue.config.optionMergeStrategies
,这个对象就是选项合并中的策略对象,所以可以通过它指定某一个选项的合并策略,常用于指定自定义选项的合并策略,比如可以给customOption
选项指定一个合并策略,只需要在config.optionMergeStrategies
上添加与选项同名的策略函数即可:
Vue.config.optionMergeStrategies.customOption = function (parentVal, childVal) {
return parentVal ? (parentVal + childVal) : childVal
}
自定义customOption
的合并策略是:如果没有parentVal
则直接返回childVal
,否则返回两者的和。
比如下面例子:
// 创建子类
const Sub = Vue.extend({ customOption: 1})
// 以子类创建实例
const vm = new Sub({
customOption: 2,
created () {
console.log(this.$options.customOption) // 3
}
})
结果是在created
方法中打印数字3。自定义合并选项的策略函数,这非常实用。
vm.$createElement
在initRender
中,定义了vm.$createElement()
方法,initRender
在core/instance/render.js
文件。在该函数中有以下一段代码:
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
这段代码在Vue
实例对象上添加了两个方法:
vm._c
vm.$createElement
这两个方法实际上是对内函数createElement
的包装。
render: function (createElement) { return createElement('h2', 'title')}
总所周知,渲染函数的第一个参数就是createElement
函数,该函数用来创建虚拟节点,通过上面的了解,你也可以这样写
render: function () { return this.$createElement('h2', 'title')}
上面这两段代码是完全等价的。
provide & inject
最后来瞧瞧provide和inject的原理。
如果一个组件使用了provide
选项,那么该选项指定的数据将会被注入到该组件的所有子组件中,在子组件中可以使用inject
选项选择性注入,这样子组件就拿到了父组件提供的数据。
provide
和inject
都是在Vue.prototype._init
方法中完成初始化工作的:
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm)
initState(vm)
initProvide(vm)
callHook(vm, 'created')
可以发现initInjections
函数在initProvide
函数之前被调用,这说明对于任何一个组件来说,总是要优先初始化inject
选项,再初始化provide
选项的。
先来看看initInjections
函数,它定义在src/core/instance/inject.js
文件中
export function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm)
if (result) {
// 省略...
}
}
该函数接收组件实例对象作为参数,在函数内部首先定义了result
常量,接下来要判断result
为真才执行if
语句内的代码。
result
常量的值是resolveInject
函数的返回值。
export function resolveInject (inject: any, vm: Component): ?Object { if (inject) { // 省略... return result }}
可以看到,resolveInject
函数接收两个参数,分别是inject
选项以及组件实例对象。如果inject
为真,那么将执行if
语句块内的代码,最后返回result
,否则返回undefined
。
if
语句块内的代码:
const result = Object.create(null)
const keys = hasSymbol ? Reflect.ownKeys(inject).filter(key => { /* istanbul ignore next */ return Object.getOwnPropertyDescriptor(inject, key).enumerable }) : Object.keys(inject)
首先通过Object.create(null)
创建一个空对象并赋值给result
。接着定义keys
常量,保存inject
选项对象的每一个键名。
首先对hasSymbol
进行判断,如果为真,则使用Reflect.ownKeys
获取inject
对象中所有可枚举的键名,否则使用Object.keys
。
使用Reflect.ownKeys
的好处是可以支持Symbol
类型作为键名。
接下来使用for
循环遍历刚刚获取的keys
数组
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const provideKey = inject[key].from
let source = vm
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
}
provideKey
常量保存的是每一个inject
选项所定义的注入对象的from
属性的值。
「题外话:」
在规范化阶段中,inject
选项会被规范为一个对象,并且对象必然含有from
属性。
inject: ['data1', 'data2']
那么被规范化后vm.$options.inject
选项将变为:
{
'data1': { from: 'data1' },
'data2': { from: 'data2' }
}
接着while
循环,内部有一个if
条件判断,检测source._provided
属性是否存在,并且source._provided
对象自身是否拥有provideKey
键,如果有,则说明找到了注入的数据:source._provided[provideKey]
,并将其赋值给result
对象的同名属性。
如果if
为假,则执行source = source.$parent
; 重新赋值source
变量,使其引用父组件,以及类推就完成了向父代组件查找数据的需求,直到找到数据为止。
如果找不到数据:
if (!source) {
if ('default' in inject[key]) {
const provideDefault = inject[key].default
result[key] = typeof provideDefault === 'function' ? provideDefault.call(vm) : provideDefault
} else if (process.env.NODE_ENV !== 'production') {
warn(`Injection "${key}" not found`, vm)
}
}
查看inject[key]
对象中是否定义了default
选项,如果定义了default
选项则使用default
选项提供的数据作为注入数据,否则在非生产环境下会提示开发者。
最后如果查找到数据,则将result
返回。但是result
常量的值可能是不存在的,所以需要进行一个result
的判断。为真时,说明成功取得注入的数据,此时执行if
语句块的内容。
toggleObserving(false)
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(`Avoid mutating an injected value directly since the changes will be ` + `overwritten whenever the provided component re-renders. ` + `injection being mutated: "${key}"`, vm )
})
}else {
defineReactive(vm, key, result[key])
}
})toggleObserving(true)
通过遍历result
常量并调用defineReactive
函数在当前组件实例对象vm
上定义与注入名称相同的变量,并赋予取得的值。
在使用defineReactive
之前,调用了toggleObsesrving(false)
函数关闭响应式定义的开关,之后又将开关开启,这会导致使用defineReactive
定义属性时不会将该属性的值转换为响应式。
provide
做的事很简单,先看看initProvide
的内容:
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function' ? provide.call(vm) : provide
}
}
同样是接收组件实例对象作为参数。定义provide
,是vm.$options.provide
选项的引用,接着是if
判断语句,只有在provide
选项存在时才执行。
procide
可以是对象,也可以是函数,所以使用typeof
检测类型,如果是函数则执行该函数获取数据,否则直接将provide
本身作为数据。最后将数据赋值给组件实例对象的vm._provided
属性。也就是前面说的inject
内,注入的数据就是从父组件实例的vm._provided
属性中获取的。
「最后提示:」provide
和inject
主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。
作者: zhangwinwin
链接:这几个小技巧,让你书写不一样的Vue!
来源:github
这几个小技巧,让你书写不一样的Vue!的更多相关文章
- How Javascript works (Javascript工作原理) (二) 引擎,运行时,如何在 V8 引擎中书写最优代码的 5 条小技巧
个人总结: 一个Javascript引擎由一个标准解释程序,或者即时编译器来实现. 解释器(Interpreter): 解释一行,执行一行. 编译器(Compiler): 全部编译成机器码,统一执行. ...
- 11个不常被提及的JavaScript小技巧
这次我们主要来分享11个在日常教程中不常被提及的JavaScript小技巧,他们往往在我们的日常工作中经常出现,但是我们又很容易忽略. 1.过滤唯一值 Set类型是在 ES6中新增的,它类似于数组,但 ...
- [转]11个教程中不常被提及的JavaScript小技巧
原文地址: https://www.cnblogs.com/ld1024/p/10723827.html 这次我们主要来分享11个在日常教程中不常被提及的JavaScript小技巧,他们往往在我们的日 ...
- 11个教程中不常被提及的JavaScript小技巧
这次我们主要来分享11个在日常教程中不常被提及的JavaScript小技巧,他们往往在我们的日常工作中经常出现,但是我们又很容易忽略. 1.过滤唯一值 Set类型是在ES6中新增的,它类似于数组,但是 ...
- 【js】中的小技巧
本文主要介绍一些JS中用到的小技巧 1. 类型强制转换 1.1 string强制转换为数字 可以用*1来转化为数字(实际上是调用.valueOf方法) 然后使用Number.isNaN来判断是否为 ...
- [原创][FPGA]Quartus实用小技巧(长期更新)
0. 简介 在使用Quartus软件时,经常会时不时的发现一些小技巧,本文的目的是总结所查阅或者发现到的小技巧,本文长期更新. 1. Quartus中的模板功能 最近在Quartus II的菜单里找到 ...
- C#中??和?分别是什么意思? 在ASP.NET开发中一些单词的标准缩写 C#SESSION丢失问题的解决办法 在C#中INTERFACE与ABSTRACT CLASS的区别 SQL命令语句小技巧 JQUERY判断CHECKBOX是否选中三种方法 JS中!=、==、!==、===的用法和区别 在对象比较中,对象相等和对象一致分别指的是什么?
C#中??和?分别是什么意思? 在C#中??和?分别是什么意思? 1. 可空类型修饰符(?):引用类型可以使用空引用表示一个不存在的值,而值类型通常不能表示为空.例如:string str=null; ...
- 可以提升幸福感的js小技巧(下)
4.数字 4.1 不同进制表示法 ES6中新增了不同进制的书写格式,在后台传参的时候要注意这一点. 29 // 10进制 035 // 8进制29 原来的方式 0o35 // 8进制29 ES6的方式 ...
- 【每日一个小技巧】Python | input的提示信息换行输出,提示信息用变量表示
[每日一个小技巧]Python | input的提示信息换行输出,提示信息用变量表示 在书写代码的途中,经常会实现这样功能: 请输入下列选项前的序号: 1.选择1 2.选择2 3.选择3 在pytho ...
随机推荐
- 想成为Git大神?从学会reset开始吧
大家好,今天我们来着重介绍一个非常关键的功能就是reset.在上一篇文章介绍修改历史记录的时候曾经提到过,当我们需要拆分一个历史提交记录的时候需要使用reset.估计很多小伙伴不明白,reset究竟做 ...
- session在什么时候创建,以及session一致性问题
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/wowwilliam0/article/d ...
- ip,子网掩码,网关以及dns简述
ip 描述 ip地址用于标识不同的计算机身份,ip地址=网络地址+主机地址 例子 192.168.1.168(ip地址)=192.168.1.0(网络地址)+0.0.0.168(主机地址) 寻址过程 ...
- 深入理解CSS盒模型【转载】
下面本文章将会从以下几个方面谈谈盒模型. 基本概念:标准模型 和IE模型 CSS如何设置这两种模型 JS如何设置获取盒模型对应的宽和高 实例题(根据盒模型解释边距重叠) BFC(边距重叠解决方案) 基 ...
- 企业集群架构-03-NFS
NFS 目录 NFS NFS基本概述 NFS应用场景 NFS实现原理 NFS总结 NFS服务端安装 环境准备 服务端安装NFS 服务端NFS配置 服务端开机自启 服务端验证配置 NFS客户端挂载卸载 ...
- 第三章节 BJROBOT 角速度校正 【ROS全开源阿克曼转向智能网联无人驾驶车】
1.把小车平放在地板上,用资料里的虚拟机,打开一个终端 ssh 过去主控端启动roslaunch znjrobot bringup.launch . 2.再打开一个终端 ssh 过去主控端,启动校 ...
- 上传功能-弹窗实现-vue
-引入弹窗页面 import fileUpload from 'src/page/cuApplyManage/fileUpload.vue'; -页面布局 <div> <fileUp ...
- Class 类文件结构
本文部分摘自<深入理解 Java 虚拟机第三版> 概述 我们知道,Java 具有跨平台性,其实现基础就是虚拟机和字节码存储格式.Java 虚拟机不与 Java 语言绑定,只与 Class ...
- Hive数据导入Hbase
方案一:Hive关联HBase表方式 适用场景:数据量不大4T以下(走hbase的api导入数据) 一.hbase表不存在的情况 创建hive表hive_hbase_table映射hbase表hbas ...
- [从源码学设计]蚂蚁金服SOFARegistry 之 服务注册和操作日志
[从源码学设计]蚂蚁金服SOFARegistry之服务注册和操作日志 目录 [从源码学设计]蚂蚁金服SOFARegistry之服务注册和操作日志 0x00 摘要 0x01 整体业务流程 1.1 服务注 ...