前言
这里开始介绍一些vue技巧,用起来提升效率也比较方便!
vue指令
指令在vue代码中很常见,形如v-model,v-bind,v-on等,官方还提供了简写方式。
目标在与弄清v-xx的含义,以及如何封装自定义指令
1: 为何在代码中可以使用v-focus的写法,是由于全局注册了如下指令
// 指令使用<div v-focus="xxx">baby</div>// 注册一个全局自定义指令 `v-focus`Vue.directive('focus', { // 当被绑定的元素插入到 DOM 中时…… inserted: function (el) { // 聚焦元素 el.focus() }})// 也可以局部注册,在组件中// 局部注册时由于是可复用逻辑,一般放在mixin中directives: { focus: { // 指令的定义 inserted: function (el) { el.focus() } }}
2: 看到以上的写法自然需要关注所提供的钩子函数和可选参数
钩子函数,有点类似于组件的生命周期
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
- inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
- update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
- componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
- unbind:只调用一次,指令与元素解绑时调用。
可选参数
el:指令所绑定的元素,可以用来直接操作 DOM 。
- 虽然vue希望数据驱动,在一些场景下还是需要操作dom,但是需要保证dom的渲染更新,需要配合nextTick
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 }。
- vnode:Vue 编译生成的虚拟节点,也就是vue组件的产物,又叫做virtual dom
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。一般只在patch的过程中涉及二者的对比
3: 可以看到指令中主要还是涉及到dom元素的获取以及属性的赋值,不要滥用自定义指令!
4: 一个移动端无限加载指令的例子
- 下拉一定距离实现加载数据
// 使用<div v-load-more="func"></div> const loadMore = { directives: { 'load-more': { bind: (el, binding) => { let windowHeight = window.screen.height; // 屏幕高度 let height; let setTop; let paddingBottom; let marginBottom; let requestFram; let oldScrollTop; let scrollEl; let heightEl; // 获取元素属性 let scrollType = el.attributes.type && el.attributes.type.value; let scrollReduce = 2; // 这个是一个缓冲值,处理手指滑动与实际移动距离 // 根据type确定scroll对象 if (scrollType == 2) { scrollEl = el; heightEl = el.children[0]; } else { scrollEl = document.body; heightEl = el; } // 移动端事件处理三段式 el.addEventListener('touchstart', () => { height = heightEl.clientHeight; if (scrollType == 2) { height = height } // 返回当前元素相对于其 offsetParent 元素的顶部的距离 setTop = el.offsetTop; paddingBottom = getStyle(el, 'paddingBottom'); marginBottom = getStyle(el, 'marginBottom'); }, false) el.addEventListener('touchmove', () => { loadMore(); }, false) el.addEventListener('touchend', () => { oldScrollTop = scrollEl.scrollTop; moveEnd(); }, false) const moveEnd = () => { requestFram = requestAnimationFrame(() => { if (scrollEl.scrollTop != oldScrollTop) { oldScrollTop = scrollEl.scrollTop; moveEnd() } else { cancelAnimationFrame(requestFram); height = heightEl.clientHeight; loadMore(); } }) } const loadMore = () => { // 滚动值逻辑触发loadmore if (scrollEl.scrollTop + windowHeight >= height + setTop + paddingBottom + marginBottom - scrollReduce) { // 实际绑定在指令的函数调用 binding.value(); } } } } }};
5: 比较好奇的是v-model背后做了什么?
基于以上的理解我们可以来自己一步步实现v-model
- v-model做了什么,在<input v-model = "text">这种写法下
在代码中修改text值,输入框显示改变 model->view,直接在输入框中修改显示的值,监听onchange事件打印text值修改 model->view。从数据流的角度考虑vue是单向流动,v-model的实现只是一种语法糖。我们实际还是遵守的model->view。
//data到模版的部分vue已经做了directives: { 'model': { bind: (el, binding) => { // 监听onchange即可 el.addEventListener('onChange', () => { this.text = binding.value // text的修改触发视图变化,实际还是数据驱动 }, false) } }}
插件
插件plugin的使用比指令的范围更广,而且插件的目的是全局使用,它的install方法决定在vue实例上挂载相应属性
插件通常会为 Vue 添加全局功能。插件的范围没有限制——一般有下面几种:
- 1.添加全局方法或者属性,如: vue-custom-element
- 2.添加全局资源:指令/过滤器/过渡等,如 vue-touch
- 3.通过全局 mixin 方法添加一些组件选项,如: vue-router
- 4.添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
- 5.一个库,提供自己的 API,同时提供上面提到的一个或多个功能,如 vue-router
我们知道在注册插件的时候一般是 vue.use(xxxplugin)
- 安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。
- install其实就是手动提供vue实例全局挂载的方式,可以是指令,全局方法,属性等,这个需要看提供的xxxplugin是哪一种形式,别问,问就全局生效!
export function initUse (Vue: GlobalAPI) {// use的参数是函数或者对象 Vue.use = function (plugin: Function | Object) { // 检查是否已经注册过 const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) if (installedPlugins.indexOf(plugin) > -1) { return this } // additional parameters // 提供的参数除了插件以外的数组 const args = toArray(arguments, 1) // 头部是vue实例 args.unshift(this) if (typeof plugin.install === 'function') { // 执行obj本身的install plugin.install.apply(plugin, args) } else if (typeof plugin === 'function') { // 没有就直接把plugin当Install执行 plugin.apply(null, args) } installedPlugins.push(plugin) return this }}
参考阅读
https://segmentfault.com/a/1190000016256277(use源码)
https://segmentfault.com/a/1190000012827871(vue指令介绍)
https://segmentfault.com/a/1190000012823043(filter参考)