高级语言是不是一场灾难?
汇编已经能做的事,非用一堆新技术来做,程序员要多花时间成本学,学之后,必须要高工资,否则收不回成本。企业必须多付工资。10年内90%的网络公司会倒闭。C、C++等语言让倒闭几率从90%,提高到95%
讯享网
我是临时翻了下源码,如有错误请斧正。
Vue源码对data的处理在这个位置
export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { 讯享网<span class="nx">initData</span><span class="p">(</span><span class="nx">vm</span><span class="p">)</span> <span class="c1">// 初始化data,对data进行依赖收集
} else { <span class="nx">observe</span><span class="p">(</span><span class="nx">vm</span><span class="p">.</span><span class="nx">_data</span> <span class="o">=</span> <span class="p">{},</span> <span class="kc">true</span> <span class="cm">/* asRootData */</span><span class="p">)</span>
} if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { 讯享网<span class="nx">initWatch</span><span class="p">(</span><span class="nx">vm</span><span class="p">,</span> <span class="nx">opts</span><span class="p">.</span><span class="nx">watch</span><span class="p">)</span>
} }
讯享网
我们再看下initData方法
function initData (vm: Component) { // 这里就是对data选项进行处理 let data = vm.$options.data data = vm._data = typeof data === ‘function’ 讯享网<span class="o">?</span> <span class="nx">getData</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="nx">vm</span><span class="p">)</span> <span class="o">:</span> <span class="nx">data</span> <span class="o">||</span> <span class="p">{}</span>
if (!isPlainObject(data)) { <span class="nx">data</span> <span class="o">=</span> <span class="p">{}</span>
<span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NODE_ENV</span> <span class="o">!==</span> <span class="s1">'production'</span> <span class="o">&&</span> <span class="nx">warn</span><span class="p">(</span>
<span class="s1">'data functions should return an object:\n'</span> <span class="o">+</span>
<span class="s1">'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function'</span><span class="p">,</span>
<span class="nx">vm</span>
<span class="p">)</span>
} // proxy data on instance const keys = Object.keys(data) const props = vm.\(options</span><span class="p">.</span><span class="nx">props</span> <span class="kr">const</span> <span class="nx">methods</span> <span class="o">=</span> <span class="nx">vm</span><span class="p">.</span><span class="nx">\)options.methods let i = keys.length while (i–) { 讯享网<span class="kr">const</span> <span class="nx">key</span> <span class="o">=</span> <span class="nx">keys</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="k">if</span> <span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NODE_ENV</span> <span class="o">!==</span> <span class="s1">'production'</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">methods</span> <span class="o">&&</span> <span class="nx">hasOwn</span><span class="p">(</span><span class="nx">methods</span><span class="p">,</span> <span class="nx">key</span><span class="p">))</span> <span class="p">{</span> <span class="nx">warn</span><span class="p">(</span> <span class="sb">`Method "</span><span class="si">${</span><span class="nx">key</span><span class="si">}</span><span class="sb">" has already been defined as a data property.`</span><span class="p">,</span> <span class="nx">vm</span> <span class="p">)</span> <span class="p">}</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="nx">props</span> <span class="o">&&</span> <span class="nx">hasOwn</span><span class="p">(</span><span class="nx">props</span><span class="p">,</span> <span class="nx">key</span><span class="p">))</span> <span class="p">{</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NODE_ENV</span> <span class="o">!==</span> <span class="s1">'production'</span> <span class="o">&&</span> <span class="nx">warn</span><span class="p">(</span> <span class="sb">`The data property "</span><span class="si">${</span><span class="nx">key</span><span class="si">}</span><span class="sb">" is already declared as a prop. `</span> <span class="o">+</span> <span class="sb">`Use prop default value instead.`</span><span class="p">,</span> <span class="nx">vm</span> <span class="p">)</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isReserved</span><span class="p">(</span><span class="nx">key</span><span class="p">))</span> <span class="p">{</span> <span class="nx">proxy</span><span class="p">(</span><span class="nx">vm</span><span class="p">,</span> <span class="sb">`_data`</span><span class="p">,</span> <span class="nx">key</span><span class="p">)</span> <span class="p">}</span>
} // observe data 依赖收集。 observe(data, true /* asRootData */) }
再节奏看看上面的getData方法做了什么
export function getData (data: Function, vm: Component): any { // #7573 disable dep collection when invoking data getters pushTarget() try { 讯享网<span class="k">return</span> <span class="nx">data</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="nx">vm</span><span class="p">,</span> <span class="nx">vm</span><span class="p">)</span>
} catch (e) { <span class="nx">handleError</span><span class="p">(</span><span class="nx">e</span><span class="p">,</span> <span class="nx">vm</span><span class="p">,</span> <span class="sb">`data()`</span><span class="p">)</span> <span class="k">return</span> <span class="p">{}</span>
} finally { 讯享网<span class="nx">popTarget</span><span class="p">()</span>
} }
data.call(vm, vm) 因此我觉得data必须是函数是由于要解决绑定this的引用问题。
比如下面这个demo场景, data是一个普通对象。
data是一个函数
官方文档很详细了,而且学起来效率会更高,不推荐跟视频学习。如果实在想看视频学习,推荐先过一遍文档,再跟着实战教程学习吧。B站Vue教程很多,看最多收藏or点击吧,祝顺利。
Vue.js公司的大佬所有的api请求都要求用vuex
开始前先提一下吧,题主这句话很让人迷惑,我觉得你应该是把数据和请求给搞混了,vuex是应用状态管理,这和api请求有什么关系呢?
如果真有关系,我猜想你想说的应该是在vuex的Action这一步去做ajax请求,然后把获取到的远端数据进行commit通过Mutations保存到Stat中。
那么我们什么时候应该使用vuex呢?
很明显,首先你这个数据应该是在多个组件都需要被使用到的,如果你只是在一个组件中使用,那你直接用data属性就不就完了。
但是,多个组件中数据的共享有很多方法,如果只是一些简单的数据传递,那么我们无需去引用一个独立于组件之外的vuex转态管理,杀鸡用牛刀,太笨重了吧。
除了显得笨重,安全方面也应该考虑在内。你vuex是独立在组件之外的,所有组件都可以直接访问store来对这些数据进行操作。所以一些能通过传递props就能解决的组件间通信,就别用vuex了吧。
组件之间的一些简单的父子组件之间的数据传递,我们可以考虑用下以这些组件本身就支持的方式:
- 父传子的props,provide/inject,之类
- 子传父的event/emit, v-model,作用域插槽之流
- 其他?
如果是要考虑到兄弟和祖孙之间的数据传递,那么我们可以考虑:
- 还有全局事件总线eventBus
- 第三方消息订阅发布pubsub
如果我们是跨路由之间传递数据呢?我此时一般会考虑:
- 如果是简单的传个参数,我一般会考虑直接通过url的querystring来直接传递,比如http://localhost:8081/todo?id=xxxx
- 如果是一些需要持久化的,会考虑LocalStorage,当然session内的就用SessionStorage。但是用xxxStorage比较麻烦点的是经常要JSON.parse/JSON.stringify的。
- 如果是要统一访问接口,且数据比较复杂的,那么这个时候我就会考虑用vuex了,一般我会在Actions中做统一的ajax请求,然后顺着vuex的规范进行mutation操作等。这样的好处是我一般在主页面做一次数据请求,然后其他所有不同路由页面和组件都能访问到我要的数据。
关于vuex的使用,我觉得可以打一个这样的比方:我们整个app就像一栋楼,各个组件就好比各个房间,而整栋楼共享同一个杂物房,就是我们vuex中的store。
那么各个房间之间的通信方法就有很多种,比如两家私交比较好的,可以直接串门或者通过电话等(类似我们上面说的props/事件总线这些方法)完成通信和数据交换,通信内容也只有他们双方知道。
同时,大家也可以往杂物房里面存放一些东西,只是这里面的东西所有人都可以看到,甚至,有人可能会不小心将里面的物品损坏或者丢弃等,其他人再想用的话可能就再也找不着了。
我是@天地会珠海分舵,觉得我说的还有那么点道理的不妨点个赞关注下!
比较简单,刚好之前学过这部分的内容。
使用 npm 安装 animate 库:
npm install animate.css
安装好之后,在 Vue 脚手架创建的项目入口文件 main.js 中引入这个库:
讯享网import “animate.css”;
引入之后,在 vue 文件中有两种用法:
- 帧动画:在 v-enter-active 和 v-leave-active 中配置 animation 名称、时长和动画模式
- 直接使用库定义好的类名
下面只说第二种用法:
通过这种方式使用 animate.css 库,我们不需要给 template 模板的 transition 组件添加 name 属性。
去到 animate.css 动画效果展示页面,点击动画名称右侧的「复制」按钮,可以复制动画对应的 class 类名。
将 class 分别粘贴到 transition 组件的 enter-active-class 和 leave-active-class,就可以应用动画了。
不过每一个动画的前面都要加多一个公有的类名 animateanimated,才能真正让动画生效。
之所以每个类名前面要加上 animateanimated,是因为在 animate.css 库中,这个类名定义了动画的持续时间,有了时间,动画才能真正生效。
以上,希望有帮助。
看很多网上的人的封装 Axios 教程,但或多或少都有不太合适的点,这里为大家推荐我的**实践。
网上的文章都让你用 拦截器 直接返回数据,这种作法其实是非常不妥的,这样会让你后续的功能很难进行拓展。
不推荐的做法
import Axios from axios const client = Axios.create({ // 你的配置 }) client.interceptors.response.use(response => { // 网上的做法都是让你直接返回数据 // 这导致后续的一些功能难以支持 return response.data }) export default client 复制代码
推荐的做法
推荐使用函数代替拦截器
讯享网import Axios, { AxiosRequestConfig } from axios const client = Axios.create({ // 你的配置 }) export async function request(url: string, config?: AxiosRequestConfig) { const response = await client.request({ url, …config }) const result = response.data // 你的业务判断逻辑 return result } export default client 复制代码
到这里可能有人会说太麻烦,请稍等,继续往下看。
很多时候,我们的开发流程是这样的:
发送请求 => 拿到数据 => 渲染内容 复制代码
但可惜的是,这只是理想情况,在某些特殊情况下,你还是需要处理异常或额外的支持,如:
- 当请求失败,希望能够自动重试3次以上再失败
- 分页数据中,当新的请求发出,自动中断上一次的请求
- 第三方提供 jsonp 接口,而你又只能使用静态页时 (ps: Axios 不支持 jsonp)
- 更多
当发送以上场景时,你只能默默的写代码支持,但如果你不拦截 Axios 的响应,那就可以使用开源社区提供的方案。
安装 axios-retry[1],可以让你的 Axios 支持自动重试的功能
讯享网import Axios, { AxiosRequestConfig } from axios import axiosRetry from axios-retry const client = Axios.create({ // 你的配置 }) // 安装 retry 插件 // 当请求失败后,自动重新请求,只有3次失败后才真正失败 axiosRetry(client, { retries: 3 }) export async function request(url: string, config?: AxiosRequestConfig) { const response = await client.request({ url, …config }) const result = response.data // 你的业务判断逻辑 return result } // 只有3次失败后才真正失败 const data = request( http://example.com/test ) 复制代码
PS: axios-retry 插件支持配置单个请求
安装 axios-jsonp[2],可以让你的 Axios 支持 jsonp 的功能。
import Axios, { AxiosRequestConfig } from axios import jsonpAdapter from axios-jsonp const client = Axios.create({ // 你的配置 }) export async function request(url: string, config?: AxiosRequestConfig) { const response = await client.request({ url, …config }) const result = response.data // 你的业务判断逻辑 return result } export function jsonp(url: string, config?: AxiosRequestConfig) { return request(url, { …config, adapter: jsonpAdapter }) } // 你现在可以发送 jsonp 的请求了 const data = jsonp( http://example.com/test-jsonp ) 复制代码
有开发 Web API 经验的人都会遇到一个问题,如果 API 出现重大变更的时候,如何保证旧版可用的情况下,发布新的 API?
这种情况在服务端开发场景中,其实并不罕见,尤其是对外公开的 API,如: 豆瓣 API (已失效)。
当前主流的支持 3 种类型的版本控制:
URI 版本控制 是指在请求的 URI 中传递的版本,例如 https://example.com/v1/route 和 https://example.com/v2/route。
讯享网import Axios, { AxiosRequestConfig } from axios const client = Axios.create({ // 你的配置 }) client.interceptors.request.use(config => { // 最简单的方案 config.url = config.url.replace( {version} , v1 ) return config }) // GET /api/v1/users request( /api/{version}/users ) 复制代码
Header 和 Media Type 模式,可以参考如下文章来实现
- 实现 Web API Versioning 功能[3]
- nest versioning[4]
在一个支持翻页的后台表格页,一个用户击翻页按钮,请求发出等待响应,但用户这时点击搜索,需要重新获取数据,支持你的情况可能是:
翻页请求先回,搜索数据再回,数据显示正常
搜索数据先回,翻页数据再回,数据显示异常(通常在负载均衡的场景中出现)
为此,你希望能够自动取消上一次请求,于是你看了 Axios 的取消请求,但好多地方都需要用到,于是可以将这个功能封装成独立的函数。
import Axios from axios const CancelToken = Axios.CancelToken export function withCancelToken(fetcher) { let abort function send(data, config) { 讯享网cancel() // 主动取消 const cancelToken = new CancelToken(cancel => (abort = cancel)) return fetcher(data, { ...config, cancelToken })
} function cancel(message = abort ) { if (abort) { abort(message) abort = null }
} return [send, cancel] } 复制代码
使用
讯享网function getUser(id: string, config?: AxiosRequestConfig) { return request(api/user/${id}, config) } // 包装请求函数 const [fetchUser, abortRequest] = withCancelToken(getUser) // 发送请求 // 如果上一次请求还没回来,会被自动取消 fetchUser( 1000 ) // 通常不需要主动调用 // 但可以在组件销毁的生命周期中调用 abortRequest() 复制代码
这样不需要自动取消的时候,直接使用原函数即可,也不会影响原函数的使用
Axios 封装其实还有很多事情可以做,比如 全局错误处理 (一样不能影响正常请求) 等,封装也不应该只是利用拦截器直接返回数据。
另外请求模块应该保持独立,推荐拓展 AxiosRequestConfig 或做成一个个独立的函数,提供给外部调用,方便做成一个独立的 HTTP 模块。
最后分享一套前端视频,让初学者一步步的掌握前端开发的各项相关技能,最终达到企业对初级前端开发工程师,中级前端开发工程师等职位的要求!
零基础前端入门到精通觉得有用的话,就关注+点赞+收藏吧!
小慕来分享一篇来自慕课网手记的文章,帮助大家了解如何在Vue3中自定义指令~
TienChin 项目前端是 Vue3,前端有这样的一个需求:有一些前端页面上的按钮要根据用户的权限来决定是否展示出来,如果用户具备相应的权限,那么就展示对应的按钮;如果用户不具备对应的权限,那么按钮就隐藏起来。大致上就这样一个需求。
看到这个需求,可能有小伙伴首先想到用 v-if 指令,这个指令确实也能做,但是,由于用户具备的权限一般来说可能是多个,甚至可能还有通配符,所以这个比对并不是一个容易的事情,肯定得写方法。。。所以,如果能用一个指令来实现这个功能,那么就会显得专业很多了。
说干就干,我们来看看 Vue3 中如何自定义指令。
我们先来看看实现自定义指令最终的使用方式:
<button @click=“btnClick” v-hasPermission=“[‘user:delete’]”>删除用户</button>
小伙伴们看到,这个 v-hasPermission 就是我们的自定义指令,如果当前用户具备 user:delete 权限,这个按钮就会展示出来,如果当前用户不具备这个权限,这个按钮就不会展示出来。
先要和小伙伴们说一下,Vue2 和 Vue3 在自定义指令上有一些差异,并不完全一致,下面的介绍主要是针对 Vue3 的介绍。
我先来和小伙伴们分享一下我们具体是怎么做的,然后在讲解代码的时候再来和大家说说各个参数的含义。
自定义指令可以定义全局的,也可以定义局部的。
在正式开搞之前,小伙伴们需要先明白,自定义指令有两种作用域,一种是局部的自定义指令,还有一种是全局的自定义指令。局部的自定义指令就只能在当前 .vue 文件中使用,全局的则可以在所有的 .vue 文件中使用。
2.1.1 局部指令
直接在当前 .vue 文件中定义即可,如下:
讯享网directives: { focus: { // 指令的定义 mounted(el) { el.focus() } } }
不过,在 Vue3 中,也可以这样写:
<template> <div> <button v-onceClick=“10000” @click=“btnClick”>ClickMe</button> </div> </template> <script> import {ref} from ‘vue’; export default { name: “MyVue01”, setup() { const a = ref(1); const btnClick = () => { a.value++; } return {a, btnClick} }, directives: { onceClick: { mounted(el, binding, vnode) { el.addEventListener(‘click’, () => { if (!el.disabled) { el.disabled = true; setTimeout(() => { el.disabled = false; }, binding.value || 1000); } }); } } } } </script>
这里我自定义了一个名叫 onceClick 的指令,给一个 button 按钮加上这个指令之后,可以设置这个 button 按钮在点击多久之后,处于禁用状态,防止用户重复点击。
小伙伴们看,这个指令的执行逻辑其实很简单,el 相当于添加了这个指令的元素,监听该元素的点击事件,如果点击该元素时,该元素不是处于禁用状态,那么就设置该元素为禁用,给一个定时任务,到期后使该元素变为可用。这里边具体的参数,松哥下面会跟大家详细介绍。
不过这只是一个局部指令,只能在当前 .vue 文件中使用,我们也可以定义全局指令,这样就可以在所有的 .vue 文件中使用了。
全局指令我们一般写在 main.js 中,或者写一个单独的 js 文件然后在 main.js 中引入,下面的例子是直接写在 main.js 中:
讯享网const app = createApp(App); app.directive(‘onceClick’,{ mounted(el, binding, vnode) { el.addEventListener(‘click’, () => { if (!el.disabled) { el.disabled = true; setTimeout(() => { el.disabled = false; }, binding.value || 1000); } }); } })
这样,我们就可以随时随地去使用 v-onceClick 这个指令了。
可能小伙伴感觉比较疑惑,自定义指令时候的 mounted 以及这里的参数都是咋回事,那么接下来松哥就来和大家详细介绍一下这些方法和参数。
在 Vue3 中,自定义指令的钩子函数主要有如下七种(这块跟 Vue2 差异较大):
- created:在绑定元素的 attribute 或事件监听器被应用之前调用。在指令需要附加在普通的 v-on 事件监听器调用前的事件监听器中时,这很有用。
- beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用。
- mounted:在绑定元素的父组件被挂载后调用,大部分自定义指令都写在这里。
- beforeUpdate:在更新包含组件的 VNode 之前调用。
- updated:在包含组件的 VNode 及其子组件的 VNode 更新后调用。
- beforeUnmount:在卸载绑定元素的父组件之前调用
- unmounted:当指令与元素解除绑定且父组件已卸载时,只调用一次。
虽然钩子函数比较多,看着有点唬人,不过我们日常开发中用的最多的其实是 mounted 函数。
这里七个钩子函数,钩子函数中有回调参数,回调参数有四个,含义基本上和 Vue2 一致:
- el:指令所绑定的元素,可以用来直接操作 DOM,我们松哥说想实现一个可以自动判断组件显示还是隐藏的指令,那么就可以通过 el 对象来操作 DOM 节点,进而实现组件的隐藏。
- binding:我们通过自定义指令传递的各种参数,主要存在于这个对象中,该对象属性较多,如下属性是我们日常开发使用较多的几个:
- name:指令名,不包括 v- 前缀。
- value:指令的绑定值,例如:
v-hasPermission=“[‘user:delete’]”中,绑定值为‘user:delete’,不过需要小伙伴们注意的是,这个绑定值可以是数组也可以是普通对象,关键是看你具体绑定的是什么,在 2.1 小节的案例中,我们的 value 就是一个数字。 - expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。
- arg:传给指令的参数,可选。例如
v-hasPermission:[name]=“‘zhangsan’”中,参数为 “name”。 - vnode:Vue 编译生成的虚拟节点。
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
有一种动态参数,这里也和小伙伴们分享下。正常情况下,我们自定义指令时传递的参数都是通过 binding.value 来获取到的,不过在这之外还有一种方式就是通过 binding.arg 获取参数。
我举一个简单例子,假设我们上面这个 onceClick 指令,默认的时间单位时毫秒,假设现在想给时间设置单位,那么我们就可以这样写:
const app = createApp(App); app.directive(‘onceClick’,{ mounted(el, binding, vnode) { el.addEventListener(‘click’, () => { if (!el.disabled) { el.disabled = true; let time = binding.value; if (binding.arg == “s”) { time = time * 1000; } setTimeout(() => { el.disabled = false; }, time); } }); } })
在自定义指令的时候,获取到 binding.arg 的值,这样就可以知道时间单位了,在使用该指令的时候,方式如下:
讯享网<button v-onceClick:[timeUnit]=“10” @click=“btnClick”>ClickMe</button> <script> import {ref} from ‘vue’; export default { name: “MyVue01”, setup() { const timeUnit = ref(‘s’); return {timeUnit} } } </script>
timeUnit 是一个提前定义好的变量。
好啦,有了上面的基础知识,接下来就来看我们本文的主题,自定义权限指令,我写一个简单的例子大家来看下:
const usersPermissions = [‘user’]; app.directive(‘hasPermission’, { mounted(el, binding, vnode) { const {value} = binding; let f = usersPermissions.some(p => { return p.indexOf(value) !== -1; }); if (!f) { el.parentNode && el.parentNode.removeChild(el); } } })
usersPermissions 表示当前用户所具备的权限,正常该数据应该是从服务端加载而来,但是我这里简单起见,就直接定义好了。
具体的逻辑很简单,先从 binding 中提取出 value 的值,这就是当前控件所需要的权限,然后遍历 usersPermissions 用一个 some 函数,去查看 usersPermissions 中是否有满足条件的值,如果没有,说明当前用户不具备展示该组件所需要的权限,那么就要隐藏这个组件,隐藏的方式就是获取到当前组件的父组件,然后从父组件中移除当前组件即可。
这是一个全局的指令,定义好之后,我们就可以在组件中直接使用了:
讯享网<button @click=“btnClick” v-hasPermission=“[‘user:delete’]”>删除用户</button>
好啦,Vue3 自定义组件学会了没?松哥在最近的 TienChin 项目视频中也会和大家分享这块的内容,敬请期待。
推荐阅读:
慕课网:前端Web开发入门级书籍推荐
程序员面试怎么准备,面试技巧有哪些?
慕课网:一个 Java 猿眼中 Vue3 和 Vue2 的差异
慕课网:尤雨溪又杠上了React,引发推特热议!
作者:江南一点雨
链接:https://www.imooc.com/article/326975
来源:慕课网
本文原创发布于慕课网 ,转载请注明出处,谢谢合作
如果data不是函数返回object,而是直接暴露一个object
那么多个实例不是公用了一个object作为data来处理。
自己写过类似实例的应该知道。
根实例 是因为 唯一性吧。
vuex的仓库有5个模块,分别是 state,mutations, actions, getters, modules
我们将组件的公共状态定义在 vuex仓库的state中,state是只读的,无法直接修改,必须调动仓库中的某个mutation才能修改状态,getters可以理解为vuex中的计算属性,当我们在某个组件中使用vuex中的某个state时,不是直接使用原值,而是需要派生出一个新的值,就可以定义getters,可以在组件中获取。当依赖的state发生改变,此时getters会重新计算得到新值,同时 action中可以发送异步请求,得到数据后,commit mutation来给state赋值
具体代码如下:
仓库代码
const store = new Vuex.Store({ 讯享网<span class="nx">state</span><span class="o">:</span> <span class="p">{</span> <span class="nx">items</span><span class="o">:</span> <span class="p">[]</span> <span class="c1">// 定义一个公共的购物车数据
}, <span class="nx">getters</span><span class="o">:</span> <span class="p">{</span> <span class="c1">// 可以基于已有的state 派生新的状态
selectedItems (state) { 讯享网 <span class="c1">// 过滤购物车中未选中的商品
return state.items.filter(item => item.selected) <span class="p">}</span> <span class="p">},</span> <span class="nx">mutations</span><span class="o">:</span> <span class="p">{</span> <span class="c1">// 定义mutation来修改state
INIT_ITEMS(state, items){ 讯享网 <span class="nx">state</span><span class="p">.</span><span class="nx">items</span> <span class="o">=</span> <span class="nx">items</span> <span class="p">}</span> <span class="p">},</span> <span class="nx">actions</span><span class="o">:</span> <span class="p">{</span> <span class="c1">// action可以发送异步请求,得到数据后commit mutation将请求结果传入
FETCH_ITEMS({commit}, params = {}){ <span class="c1">// 调用封装好的 接口函数
fetchItem(params).then(res => { 讯享网 <span class="k">if</span><span class="p">(</span><span class="nx">res</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">code</span> <span class="o">===</span> <span class="mi">200</span><span class="p">)</span> <span class="p">{</span> <span class="nx">commit</span><span class="p">(</span><span class="s1">'INIT_ITEMS'</span><span class="p">,</span> <span class="nx">res</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">data</span><span class="p">)</span> <span class="p">}</span> <span class="p">})</span> <span class="p">}</span> <span class="p">}</span>
})
组件中使用 使用vuex
// 获取state this.$store.state.items // 直接获取 { 讯享网<span class="n">computed</span><span class="p">:</span> <span class="p">{</span> <span class="p">...</span><span class="n">mapState</span><span class="p">([</span><span class="err">'</span><span class="n">items</span><span class="err">'</span><span class="p">])</span> <span class="c1">// 助手函数获取
} } // 获取getters this.$store.getters.selectedItems // 直接获取 { <span class="n">computed</span><span class="p">:</span> <span class="p">{</span> <span class="p">...</span><span class="n">mapGetters</span><span class="p">([</span><span class="err">'</span><span class="n">selectedItems</span><span class="err">'</span><span class="p">])</span> <span class="c1">// 助手函数获取
} } // 组件中提交action this.$store.dispatch(‘FETCH_ITEMS’, {token: ‘xxx’}) { 讯享网<span class="n">methods</span><span class="p">:</span> <span class="p">{</span> <span class="p">...</span><span class="n">mapActions</span><span class="p">([</span><span class="err">'</span><span class="n">FETCH_ITEMS</span><span class="err">'</span><span class="p">])</span> <span class="c1">// 助手函数 直接调用this.FETCH_ITEMS(params)触发
} } // 组件中也可以直接commit mutation this.$store.commit(‘INIT_ITEMS’[,参数]) { <span class="n">methods</span><span class="p">:{</span> <span class="p">...</span><span class="n">mapMutations</span><span class="p">([</span><span class="err">'</span><span class="n">INIT_ITEMS</span><span class="err">'</span><span class="p">])</span> <span class="c1">// 助手函数 直接调用this.INIT_ITEMS(参数)
} }
希望以上的回答能帮到大家。
在 vue 中我们可以使用 Vue.directive()方法注册全局指令。也可以只用 directives 选项注册局部指令。
- 输入框防抖指令 v-debounce
讯享网const debounce = { inserted: function (el, binding) { <span class="kd">let</span> <span class="nx">timer</span><span class="p">;</span> <span class="nx">el</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"keyup"</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">timer</span><span class="p">)</span> <span class="p">{</span> <span class="nx">clearTimeout</span><span class="p">(</span><span class="nx">timer</span><span class="p">);</span> <span class="p">}</span> <span class="nx">timer</span> <span class="o">=</span> <span class="nx">setTimeout</span><span class="p">(()</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">binding</span><span class="p">.</span><span class="nx">value</span><span class="p">();</span> <span class="p">},</span> <span class="mi">1000</span><span class="p">);</span> <span class="p">});</span>
}, }; export default debounce;
- 复制粘贴指令 v-copy
讯享网const copy = { bind(el, { value }) { <span class="nx">el</span><span class="p">.</span><span class="nx">$value</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span> <span class="nx">el</span><span class="p">.</span><span class="nx">handler</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">el</span><span class="p">.</span><span class="nx">$value</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 值为空的时候,给出提示。可根据项目UI仔细设计
console.log(“无复制内容”); 讯享网 <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// 动态创建 textarea 标签
const textarea = document.createElement(“textarea”); <span class="c1">// 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
textarea.readOnly = “readonly”; 讯享网 <span class="nx">textarea</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">position</span> <span class="o">=</span> <span class="s2">"absolute"</span><span class="p">;</span> <span class="nx">textarea</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">left</span> <span class="o">=</span> <span class="s2">"-9999px"</span><span class="p">;</span> <span class="c1">// 将要 copy 的值赋给 textarea 标签的 value 属性
textarea.value = el.$value; <span class="c1">// 将 textarea 插入到 body 中
document.body.appendChild(textarea); 讯享网 <span class="c1">// 选中值并复制
textarea.select(); <span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">execCommand</span><span class="p">(</span><span class="s2">"Copy"</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="nx">result</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"复制成功"</span><span class="p">);</span> <span class="c1">// 可根据项目UI仔细设计
} 讯享网 <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">removeChild</span><span class="p">(</span><span class="nx">textarea</span><span class="p">);</span> <span class="p">};</span> <span class="c1">// 绑定点击事件,就是所谓的一键 copy 啦
el.addEventListener(“click”, el.handler); }, // 当传进来的值更新的时候触发 componentUpdated(el, { value }) { <span class="nx">el</span><span class="p">.</span><span class="nx">$value</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
}, // 指令与元素解绑的时候,移除事件绑定 unbind(el) { 讯享网<span class="nx">el</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s2">"click"</span><span class="p">,</span> <span class="nx">el</span><span class="p">.</span><span class="nx">handler</span><span class="p">);</span>
}, }; export default copy;
- 长按指令 v-longpress
const longpress = { bind: function (el, binding, vNode) { 讯享网<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">binding</span><span class="p">.</span><span class="nx">value</span> <span class="o">!==</span> <span class="s2">"function"</span><span class="p">)</span> <span class="p">{</span> <span class="k">throw</span> <span class="s2">"callback must be a function"</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// 定义变量
let pressTimer = null; <span class="c1">// 创建计时器( 2秒后执行函数 )
let start = (e) => { 讯享网 <span class="k">if</span> <span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="kr">type</span> <span class="o">===</span> <span class="s2">"click"</span> <span class="o">&&</span> <span class="nx">e</span><span class="p">.</span><span class="nx">button</span> <span class="o">!==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="nx">pressTimer</span> <span class="o">===</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span> <span class="nx">pressTimer</span> <span class="o">=</span> <span class="nx">setTimeout</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span> <span class="nx">handler</span><span class="p">();</span> <span class="p">},</span> <span class="mi">2000</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> <span class="c1">// 取消计时器
let cancel = (e) => { <span class="k">if</span> <span class="p">(</span><span class="nx">pressTimer</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span> <span class="nx">clearTimeout</span><span class="p">(</span><span class="nx">pressTimer</span><span class="p">);</span> <span class="nx">pressTimer</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> <span class="p">}</span> <span class="p">};</span> <span class="c1">// 运行函数
const handler = (e) => { 讯享网 <span class="nx">binding</span><span class="p">.</span><span class="nx">value</span><span class="p">(</span><span class="nx">e</span><span class="p">);</span> <span class="p">};</span> <span class="c1">// 添加事件监听器
el.addEventListener(“mousedown”, start); <span class="nx">el</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"touchstart"</span><span class="p">,</span> <span class="nx">start</span><span class="p">);</span> <span class="c1">// 取消计时器
el.addEventListener(“click”, cancel); 讯享网<span class="nx">el</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"mouseout"</span><span class="p">,</span> <span class="nx">cancel</span><span class="p">);</span> <span class="nx">el</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"touchend"</span><span class="p">,</span> <span class="nx">cancel</span><span class="p">);</span> <span class="nx">el</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"touchcancel"</span><span class="p">,</span> <span class="nx">cancel</span><span class="p">);</span>
}, // 当传进来的值更新的时候触发 componentUpdated(el, { value }) { <span class="nx">el</span><span class="p">.</span><span class="nx">$value</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
}, // 指令与元素解绑的时候,移除事件绑定 unbind(el) { 讯享网<span class="nx">el</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s2">"click"</span><span class="p">,</span> <span class="nx">el</span><span class="p">.</span><span class="nx">handler</span><span class="p">);</span>
}, }; export default longpress;
- 禁止表情及特殊字符 v-emoji
let findEle = (parent, type) => { return parent.tagName.toLowerCase() === type 讯享网<span class="o">?</span> <span class="nx">parent</span> <span class="o">:</span> <span class="nx">parent</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="nx">type</span><span class="p">);</span>
}; const trigger = (el, type) => { const e = document.createEvent(“HTMLEvents”); e.initEvent(type, true, true); el.dispatchEvent(e); }; const emoji = { bind: function (el, binding, vnode) { <span class="c1">// 正则规则可根据需求自定义
var regRule = /[^u4E00-u9FA5|d|a-zA-Z|rns,.?!,。?!…—&$=()-+/*{}[]]|s/g; 讯享网<span class="kd">let</span> <span class="nx">$inp</span> <span class="o">=</span> <span class="nx">findEle</span><span class="p">(</span><span class="nx">el</span><span class="p">,</span> <span class="s2">"input"</span><span class="p">);</span> <span class="nx">el</span><span class="p">.</span><span class="nx">$inp</span> <span class="o">=</span> <span class="nx">$inp</span><span class="p">;</span> <span class="nx">$inp</span><span class="p">.</span><span class="nx">handle</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">val</span> <span class="o">=</span> <span class="nx">$inp</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span> <span class="nx">$inp</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">val</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="nx">regRule</span><span class="p">,</span> <span class="s2">""</span><span class="p">);</span> <span class="nx">trigger</span><span class="p">(</span><span class="nx">$inp</span><span class="p">,</span> <span class="s2">"input"</span><span class="p">);</span> <span class="p">};</span> <span class="nx">$inp</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"keyup"</span><span class="p">,</span> <span class="nx">$inp</span><span class="p">.</span><span class="nx">handle</span><span class="p">);</span>
}, unbind: function (el) { <span class="nx">el</span><span class="p">.</span><span class="nx">$inp</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s2">"keyup"</span><span class="p">,</span> <span class="nx">el</span><span class="p">.</span><span class="nx">$inp</span><span class="p">.</span><span class="nx">handle</span><span class="p">);</span>
}, }; export default emoji;
- 图片懒加载 v-LazyLoad
讯享网const LazyLoad = { // install方法 install(Vue, options) { <span class="kr">const</span> <span class="nx">defaultSrc</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="k">default</span><span class="p">;</span> <span class="nx">Vue</span><span class="p">.</span><span class="nx">directive</span><span class="p">(</span><span class="s2">"lazy"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">bind</span><span class="p">(</span><span class="nx">el</span><span class="p">,</span> <span class="nx">binding</span><span class="p">)</span> <span class="p">{</span> <span class="nx">LazyLoad</span><span class="p">.</span><span class="nx">init</span><span class="p">(</span><span class="nx">el</span><span class="p">,</span> <span class="nx">binding</span><span class="p">.</span><span class="nx">value</span><span class="p">,</span> <span class="nx">defaultSrc</span><span class="p">);</span> <span class="p">},</span> <span class="nx">inserted</span><span class="p">(</span><span class="nx">el</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">IntersectionObserver</span><span class="p">)</span> <span class="p">{</span> <span class="nx">LazyLoad</span><span class="p">.</span><span class="nx">observe</span><span class="p">(</span><span class="nx">el</span><span class="p">);</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nx">LazyLoad</span><span class="p">.</span><span class="nx">listenerScroll</span><span class="p">(</span><span class="nx">el</span><span class="p">);</span> <span class="p">}</span> <span class="p">},</span> <span class="p">});</span>
}, // 初始化 init(el, val, def) { 讯享网<span class="nx">el</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="s2">"data-src"</span><span class="p">,</span> <span class="nx">val</span><span class="p">);</span> <span class="nx">el</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="s2">"src"</span><span class="p">,</span> <span class="nx">def</span><span class="p">);</span>
}, // 利用IntersectionObserver监听el observe(el) { <span class="kd">var</span> <span class="nx">io</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">IntersectionObserver</span><span class="p">((</span><span class="nx">entries</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span> <span class="kr">const</span> <span class="nx">realSrc</span> <span class="o">=</span> <span class="nx">el</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">src</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="nx">entries</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">isIntersecting</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">realSrc</span><span class="p">)</span> <span class="p">{</span> <span class="nx">el</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="nx">realSrc</span><span class="p">;</span> <span class="nx">el</span><span class="p">.</span><span class="nx">removeAttribute</span><span class="p">(</span><span class="s2">"data-src"</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="p">});</span> <span class="nx">io</span><span class="p">.</span><span class="nx">observe</span><span class="p">(</span><span class="nx">el</span><span class="p">);</span>
}, // 监听scroll事件 listenerScroll(el) { 讯享网<span class="kr">const</span> <span class="nx">handler</span> <span class="o">=</span> <span class="nx">LazyLoad</span><span class="p">.</span><span class="nx">throttle</span><span class="p">(</span><span class="nx">LazyLoad</span><span class="p">.</span><span class="nx">load</span><span class="p">,</span> <span class="mi">300</span><span class="p">);</span> <span class="nx">LazyLoad</span><span class="p">.</span><span class="nx">load</span><span class="p">(</span><span class="nx">el</span><span class="p">);</span> <span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"scroll"</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">handler</span><span class="p">(</span><span class="nx">el</span><span class="p">);</span> <span class="p">});</span>
}, // 加载真实图片 load(el) { <span class="kr">const</span> <span class="nx">windowHeight</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">clientHeight</span><span class="p">;</span> <span class="kr">const</span> <span class="nx">elTop</span> <span class="o">=</span> <span class="nx">el</span><span class="p">.</span><span class="nx">getBoundingClientRect</span><span class="p">().</span><span class="nx">top</span><span class="p">;</span> <span class="kr">const</span> <span class="nx">elBtm</span> <span class="o">=</span> <span class="nx">el</span><span class="p">.</span><span class="nx">getBoundingClientRect</span><span class="p">().</span><span class="nx">bottom</span><span class="p">;</span> <span class="kr">const</span> <span class="nx">realSrc</span> <span class="o">=</span> <span class="nx">el</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">src</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="nx">elTop</span> <span class="o">-</span> <span class="nx">windowHeight</span> <span class="o"><</span> <span class="mi">0</span> <span class="o">&&</span> <span class="nx">elBtm</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">realSrc</span><span class="p">)</span> <span class="p">{</span> <span class="nx">el</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="nx">realSrc</span><span class="p">;</span> <span class="nx">el</span><span class="p">.</span><span class="nx">removeAttribute</span><span class="p">(</span><span class="s2">"data-src"</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span>
}, // 节流 throttle(fn, delay) { 讯享网<span class="kd">let</span> <span class="nx">timer</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">prevTime</span><span class="p">;</span> <span class="k">return</span> <span class="kd">function</span> <span class="p">(...</span><span class="nx">args</span><span class="p">)</span> <span class="p">{</span> <span class="kr">const</span> <span class="nx">currTime</span> <span class="o">=</span> <span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">();</span> <span class="kr">const</span> <span class="nx">context</span> <span class="o">=</span> <span class="k">this</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">prevTime</span><span class="p">)</span> <span class="nx">prevTime</span> <span class="o">=</span> <span class="nx">currTime</span><span class="p">;</span> <span class="nx">clearTimeout</span><span class="p">(</span><span class="nx">timer</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="nx">currTime</span> <span class="o">-</span> <span class="nx">prevTime</span> <span class="o">></span> <span class="nx">delay</span><span class="p">)</span> <span class="p">{</span> <span class="nx">prevTime</span> <span class="o">=</span> <span class="nx">currTime</span><span class="p">;</span> <span class="nx">fn</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="nx">context</span><span class="p">,</span> <span class="nx">args</span><span class="p">);</span> <span class="nx">clearTimeout</span><span class="p">(</span><span class="nx">timer</span><span class="p">);</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="nx">timer</span> <span class="o">=</span> <span class="nx">setTimeout</span><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span> <span class="nx">prevTime</span> <span class="o">=</span> <span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">();</span> <span class="nx">timer</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> <span class="nx">fn</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="nx">context</span><span class="p">,</span> <span class="nx">args</span><span class="p">);</span> <span class="p">},</span> <span class="nx">delay</span><span class="p">);</span> <span class="p">};</span>
}, }; export default LazyLoad;
- 权限校验指令 v-premission
function checkArray(key) { let arr = [“1”, “2”, “3”, “4”]; let index = arr.indexOf(key); if (index > -1) { 讯享网<span class="k">return</span> <span class="kc">true</span><span class="p">;</span> <span class="c1">// 有权限
} else { <span class="k">return</span> <span class="kc">false</span><span class="p">;</span> <span class="c1">// 无权限
} } const permission = { inserted: function (el, binding) { 讯享网<span class="kd">let</span> <span class="nx">permission</span> <span class="o">=</span> <span class="nx">binding</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span> <span class="c1">// 获取到 v-permission的值
if (permission) { <span class="kd">let</span> <span class="nx">hasPermission</span> <span class="o">=</span> <span class="nx">checkArray</span><span class="p">(</span><span class="nx">permission</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">hasPermission</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 没有权限 移除Dom元素
el.parentNode && el.parentNode.removeChild(el); 讯享网 <span class="p">}</span> <span class="p">}</span>
}, }; export default permission;
- 实现页面水印 v-waterMarker
function addWaterMarker(str, parentNode, font, textColor) { // 水印文字,父元素,字体,文字颜色 var can = document.createElement(“canvas”); parentNode.appendChild(can); can.width = 200; can.height = 150; can.style.display = “none”; var cans = can.getContext(“2d”); cans.rotate((-20 * Math.PI) / 180); cans.font = font || “16px Microsoft JhengHei”; cans.fillStyle = textColor || “rgba(180, 180, 180, 0.3)”; cans.textAlign = “left”; cans.textBaseline = “Middle”; cans.fillText(str, can.width / 10, can.height / 2); parentNode.style.backgroundImage = “url(” + can.toDataURL(“image/png”) + “)”; } const waterMarker = { bind: function (el, binding) { 讯享网<span class="nx">addWaterMarker</span><span class="p">(</span> <span class="nx">binding</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">text</span><span class="p">,</span> <span class="nx">el</span><span class="p">,</span> <span class="nx">binding</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">font</span><span class="p">,</span> <span class="nx">binding</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">textColor</span> <span class="p">);</span>
}, }; export default waterMarker;
- 拖拽指令 v-draggable
const draggable = { inserted: function (el) { 讯享网<span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">cursor</span> <span class="o">=</span> <span class="s2">"move"</span><span class="p">;</span> <span class="nx">el</span><span class="p">.</span><span class="nx">onmousedown</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">disx</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">pageX</span> <span class="o">-</span> <span class="nx">el</span><span class="p">.</span><span class="nx">offsetLeft</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">disy</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">pageY</span> <span class="o">-</span> <span class="nx">el</span><span class="p">.</span><span class="nx">offsetTop</span><span class="p">;</span> <span class="nb">document</span><span class="p">.</span><span class="nx">onmousemove</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">x</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">pageX</span> <span class="o">-</span> <span class="nx">disx</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">y</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">pageY</span> <span class="o">-</span> <span class="nx">disy</span><span class="p">;</span> <span class="kd">let</span> <span class="nx">maxX</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">clientWidth</span> <span class="o">-</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">getComputedStyle</span><span class="p">(</span><span class="nx">el</span><span class="p">).</span><span class="nx">width</span><span class="p">);</span> <span class="kd">let</span> <span class="nx">maxY</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">clientHeight</span> <span class="o">-</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">getComputedStyle</span><span class="p">(</span><span class="nx">el</span><span class="p">).</span><span class="nx">height</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="nx">x</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">x</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">x</span> <span class="o">></span> <span class="nx">maxX</span><span class="p">)</span> <span class="p">{</span> <span class="nx">x</span> <span class="o">=</span> <span class="nx">maxX</span><span class="p">;</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="nx">y</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">y</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">y</span> <span class="o">></span> <span class="nx">maxY</span><span class="p">)</span> <span class="p">{</span> <span class="nx">y</span> <span class="o">=</span> <span class="nx">maxY</span><span class="p">;</span> <span class="p">}</span> <span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">left</span> <span class="o">=</span> <span class="nx">x</span> <span class="o">+</span> <span class="s2">"px"</span><span class="p">;</span> <span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">top</span> <span class="o">=</span> <span class="nx">y</span> <span class="o">+</span> <span class="s2">"px"</span><span class="p">;</span> <span class="p">};</span> <span class="nb">document</span><span class="p">.</span><span class="nx">onmouseup</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span> <span class="nb">document</span><span class="p">.</span><span class="nx">onmousemove</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">onmouseup</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> <span class="p">};</span> <span class="p">};</span>
}, }; export default draggable;
本文就分享到这里了,希望能帮到大家。
在 Vue.js 2.x 版本中,一个 Vue 实例只能有一个根元素,这是因为 Vue.js 的模板编译器在编译模板时需要将模板编译为一个渲染函数,并将这个渲染函数挂载到根元素上。如果一个 Vue 实例有多个根元素,那么模板编译器就无法将模板编译为一个渲染函数。
然而,在 Vue.js 3.x 版本中,这个限制已经被移除了。Vue.js 3.x 版本中可以在一个 Vue 实例中包含多个根元素,可以通过在根元素上使用 v-for、v-if 等指令来实现。在 Vue.js 3.x 版本中,一个 Vue 实例不再需要一个单一的根元素,而是将多个根元素封装在一个特殊的组件中,这个组件被称为 Fragment。使用 Fragment 可以在一个组件中包含多个根元素,而不需要额外添加不必要的包裹元素。
对前端应届生来说,什么才叫项目? - 李李的回答 - 知乎 对前端应届生来说,什么才叫项目?
我知道的两种方式:
- 第一种:挂载到Vue的prototype上。把全局方法写到一个文件里面,然后for循环挂载到Vue的prototype上,缺点是调用这个方法的时候没有提示
Object.keys(tools).forEach(key => { 讯享网 Vue.prototype[key] = tools[key]
})
第二种:利用全局混入mixin,因为mixin里面的methods会和创建的每个单文件组件合并。这样做的优点是调用这个方法的时候有提示
Vue.mixin(mixin) new Vue({ 讯享网store, router, render: h => h(App),
}).$mount(‘#app’)
import tools from “https://www.zhihu.com/topic//tools" import filters from ”https://www.zhihu.com/topic//filters" import Config from ‘https://www.zhihu.com/topic/config' import CONSTANT from ’https://www.zhihu.com/topic//const_var' export default { 讯享网data() { return { CONFIG: Config, CONSTANT: CONSTANT } }, methods: { // //将tools里面的方法挂载到vue上,以方便调用,直接this.$xxx方法名就可以了 // Object.keys(tools).forEach(key => { // Vue.prototype[key] = tools[key] // }) //将tools里面的方法用对象展开符混入到mixin上,以方便调用,直接this.$xxx方法名就可以了 ...tools }, filters: { // //将filter里面的方法添加了vue的筛选器上 // Object.keys(filters).forEach(key => { // Vue.filter(key, filters[key]) // }) ...filters }
}
1、通过prototype,这个非常方便。Vue.prototype[method]=method;
2、通过插件Vue.use(plugin);
3、通过mixin,Vue.mixin(mixins);
前言
很多人提起 Vue 中的 computed,第一反应就是计算属性会缓存,那么它到底是怎么缓存的呢?缓存的到底是什么,什么时候缓存会失效,相信还是有很多人对此很模糊。
本文以 Vue 2.6.11 版本为基础,就深入原理,带你来看看所谓的缓存到底是什么样的。
注意
本文假定你对 Vue 响应式原理已经有了基础的了解,如果对于 Watcher、Dep和什么是 渲染watcher 等概念还不是很熟悉的话,可以先找一些基础的响应式原理的文章或者教程看一下。视频教程的话推荐黄轶老师的,如果想要看简化实现,也可以先看我写的文章:
手把手带你实现一个最精简的响应式系统来学习Vue的data、computed、watch源码
注意,这篇文章里我也写了 computed 的原理,但是这篇文章里的 computed 是基于 Vue 2.5 版本的,和当前 2.6 版本的变化还是非常大的,可以仅做参考。
按照我的文章惯例,还是以一个最简的示例来演示。
<div id=“app”> <span @click=“change”>{{sum}}</span> </div> <script src=“https://www.zhihu.com/topic//vue2.6.js"></script> <script> new Vue({ 讯享网<span class="nx">el</span><span class="o">:</span> <span class="s2">"#app"</span><span class="p">,</span> <span class="nx">data</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">{</span> <span class="nx">count</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span> <span class="p">}</span> <span class="p">},</span> <span class="nx">methods</span><span class="o">:</span> <span class="p">{</span> <span class="nx">change</span><span class="p">()</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">count</span> <span class="o">=</span> <span class="mi">2</span> <span class="p">},</span> <span class="p">},</span> <span class="nx">computed</span><span class="o">:</span> <span class="p">{</span> <span class="nx">sum</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span> <span class="p">},</span> <span class="p">},</span>
}) </script>
这个例子很简单,刚开始页面上显示数字2,点击数字后变成3。
进入正题,Vue 初次运行时会对 computed 属性做一些初始化处理,首先我们回顾一下 watcher 的概念,它的核心概念是get求值,和update更新。
- 在求值的时候,它会先把自身也就是 watcher 本身赋值给
Dep.target这个全局变量。
- 然后求值的过程中,会读取到响应式属性,那么响应式属性的 dep 就会收集到这个 watcher 作为依赖。
- 下次响应式属性更新了,就会从 dep 中找出它收集到的 watcher,触发
watcher.update()去更新。
所以最关键的就在于,这个 get 到底用来做什么,这个 update 会触发什么样的更新。
在基本的响应式更新视图的流程中,以上概念的 get 求值就是指 Vue 的组件重新渲染的函数,而 update 的时候,其实就是重新调用组件的渲染函数去更新视图。
而 Vue 中很巧妙的一点,就是这套流程也同样适用于 computed 的更新。
初始化 computed
这里先提前剧透一下,Vue 会对 options 中的每个 computed 属性也用 watcher 去包装起来,它的get函数显然就是要执行用户定义的求值函数,而update则是一个比较复杂的流程,接下来我会详细讲解。
首先在组件初始化的时候,会进入到初始化 computed 的函数
if (opts.computed) { initComputed(vm, opts.computed); }
进入initComputed看看
讯享网var watchers = vm._computedWatchers = Object.create(null); // 依次为每个 computed 属性定义 for (const key in computed) { const userDef = computed[key] watchers[key] = new Watcher( <span class="nx">vm</span><span class="p">,</span> <span class="c1">// 实例
getter, // 用户传入的求值函数 sum noop, // 回调函数 可以先忽视 { lazy: true } // 声明 lazy 属性 标记 computed watcher ) // 用户在调用 this.sum 的时候,会发生的事情 defineComputed(vm, key, userDef) }
首先定义了一个空的对象,用来存放所有计算属性相关的 watcher,后文我们会把它叫做 计算watcher。
然后循环为每个 computed 属性生成了一个 计算watcher。
它的形态保留关键属性简化后是这样的:
讯享网{ <span class="nx">deps</span><span class="o">:</span> <span class="p">[],</span> <span class="nx">dirty</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">getter</span><span class="o">:</span> <span class="nx">ƒ</span> <span class="nx">sum</span><span class="p">(),</span> <span class="nx">lazy</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">value</span><span class="o">:</span> <span class="kc">undefined</span>
}
可以看到它的 value 刚开始是 undefined,lazy 是 true,说明它的值是惰性计算的,只有到真正在模板里去读取它的值后才会计算。
这个 dirty 属性其实是缓存的关键,先记住它。
接下来看看比较关键的 defineComputed,它决定了用户在读取 this.sum 这个计算属性的值后会发生什么,继续简化,排除掉一些不影响流程的逻辑。
讯享网Object.defineProperty(vm, ‘sum’, { <span class="nx">get</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// 从刚刚说过的组件实例上拿到 computed watcher
const watcher = this._computedWatchers && this._computedWatchers[key] 讯享网 <span class="k">if</span> <span class="p">(</span><span class="nx">watcher</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// ✨ 注意!这里只有dirty了才会重新求值
if (watcher.dirty) { <span class="c1">// 这里会求值 调用 get
watcher.evaluate() 讯享网 <span class="p">}</span> <span class="c1">// ✨ 这里也是个关键 等会细讲
if (Dep.target) { <span class="nx">watcher</span><span class="p">.</span><span class="nx">depend</span><span class="p">()</span> <span class="p">}</span> <span class="c1">// 最后返回计算出来的值
return watcher.value 讯享网 <span class="p">}</span> <span class="p">}</span>
})
这个函数需要仔细看看,它做了好几件事,我们以初始化的流程来讲解它:
首先 dirty 这个概念代表脏数据,说明这个数据需要重新调用用户传入的 sum 函数来求值了。我们暂且不管更新时候的逻辑,第一次在模板中读取到 {{sum}} 的时候它一定是 true,所以初始化就会经历一次求值。
evaluate () { // 调用 get 函数求值 this.value = this.get() // 把 dirty 标记为 false this.dirty = false }
这个函数其实很清晰,它先求值,然后把 dirty 置为 false。
再回头看看我们刚刚那段 Object.defineProperty 的逻辑,
下次没有特殊情况再读取到 sum 的时候,发现 dirty是false了,是不是直接就返回 watcher.value 这个值就可以了,这其实就是计算属性缓存的概念。
更新
初始化的流程讲完了,相信大家也对 dirty 和 缓存 有了个大概的概念(如果没有,再仔细回头看一看)。
接下来就讲更新的流程,细化到本文的例子中,也就是 count 的更新到底是怎么触发 sum 在页面上的变更。
首先回到刚刚提到的 evalute 函数里,也就是读取 sum 时发现是脏数据的时候做的求值操作。
讯享网evaluate () { // 调用 get 函数求值 this.value = this.get() // 把 dirty 标记为 false this.dirty = false }
Dep.target 变更为 渲染watcher
这里进入 this.get(),首先要明确一点,在模板中读取 {{ sum }} 变量的时候,全局的 Dep.target 应该是 渲染watcher,这里不理解的话可以到我最开始提到的文章里去理解下。
全局的 Dep.target 状态是用一个栈 targetStack 来保存,便于前进和回退 Dep.target,至于什么时候会回退,接下来的函数里就可以看到。
此时的 Dep.target 是 渲染watcher,targetStack 是 [ 渲染watcher ] 。
get () { pushTarget(this) let value const vm = this.vm try { 讯享网<span class="nx">value</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">getter</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="nx">vm</span><span class="p">,</span> <span class="nx">vm</span><span class="p">)</span>
} finally { <span class="nx">popTarget</span><span class="p">()</span>
} return value }
首先刚进去就 pushTarget,也就是把 计算watcher 自身置为 Dep.target,等待收集依赖。
执行完 pushTarget(this) 后,
Dep.target 变更为 计算watcher
此时的 Dep.target 是 计算watcher,targetStack 是 [ 渲染watcher,计算watcher ] 。
然后会执行到 value = this.getter.call(vm, vm),
其实 getter 函数,上一章的 watcher 形态里已经说明了,就是用户传入的 sum 函数。
讯享网sum() { <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span>
}
这里在执行的时候,读取到了 this.count,注意它是一个响应式的属性,所以冥冥之中它们开始建立了千丝万缕的联系……
这里会触发 count 的 get 劫持,简化一下
讯享网// 在闭包中,会保留对于 count 这个 key 所定义的 dep const dep = new Dep() // 闭包中也会保留上一次 set 函数所设置的 val let val Object.defineProperty(vm, ‘count’, { get: function reactiveGetter () { <span class="kr">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">val</span> <span class="c1">// Dep.target 此时就是计算watcher
if (Dep.target) { 讯享网 <span class="c1">// 收集依赖
dep.depend() <span class="p">}</span> <span class="k">return</span> <span class="nx">value</span>
}, })
那么可以看出,count会收集计算watcher作为依赖,具体怎么收集呢
讯享网// dep.depend() depend () { if (Dep.target) { <span class="nx">Dep</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">addDep</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>
} }
其实这里是调用Dep.target.addDep(this)去收集,又绕回到计算watcher的addDep函数上去了,这其实主要是 Vue 内部做了一些去重的优化。
讯享网// watcher 的 addDep函数 addDep (dep: Dep) { // 这里做了一系列的去重操作 简化掉
// 这里会把 count 的 dep 也存在自身的 deps 上 this.deps.push(dep) // 又带着 watcher 自身作为参数 // 回到 dep 的 addSub 函数了 dep.addSub(this) }
又回到dep上去了。
class Dep { subs = [] addSub (sub: Watcher) { 讯享网<span class="k">this</span><span class="p">.</span><span class="nx">subs</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">sub</span><span class="p">)</span>
} }
这样就保存了 计算watcher 作为 count 的 dep 里的依赖了。
经历了这样的一个收集的流程后,此时的一些状态:
sum 的计算watcher:
{ 讯享网<span class="nx">deps</span><span class="o">:</span> <span class="p">[</span> <span class="nx">count的dep</span> <span class="p">],</span> <span class="nx">dirty</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="c1">// 求值完了 所以是false
value: 2, // 1 + 1 = 2 getter: ƒ sum(), <span class="nx">lazy</span><span class="o">:</span> <span class="kc">true</span>
}
count的dep:
讯享网{ <span class="nx">subs</span><span class="o">:</span> <span class="p">[</span> <span class="nx">sum的计算watcher</span> <span class="p">]</span>
}
可以看出,计算属性的 watcher 和它所依赖的响应式值的 dep,它们之间互相保留了彼此,相依为命。
此时求值结束,回到 计算watcher 的 getter 函数:
讯享网get () { pushTarget(this) let value const vm = this.vm try { <span class="nx">value</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">getter</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="nx">vm</span><span class="p">,</span> <span class="nx">vm</span><span class="p">)</span>
} finally {
讯享网<span class="c1">// 此时执行到这里了
popTarget() } return value }
执行到了 popTarget,计算watcher 出栈。
Dep.target 变更为 渲染watcher
此时的 Dep.target 是 渲染watcher,targetStack 是 [ 渲染watcher ] 。
然后函数执行完毕,返回了 2 这个 value,此时对于 sum 属性的 get 访问还没结束。
Object.defineProperty(vm, ‘sum’, { 讯享网<span class="nx">get</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// 此时函数执行到了这里
if (Dep.target) { <span class="nx">watcher</span><span class="p">.</span><span class="nx">depend</span><span class="p">()</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">watcher</span><span class="p">.</span><span class="nx">value</span> <span class="p">}</span> <span class="p">}</span>
})
此时的Dep.target当然是有值的,就是渲染watcher,所以进入了watcher.depend()的逻辑,这一步相当关键。
讯享网// watcher.depend depend () { let i = this.deps.length while (i–) { <span class="k">this</span><span class="p">.</span><span class="nx">deps</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">depend</span><span class="p">()</span>
} }
还记得刚刚的 计算watcher 的形态吗?它的 deps 里保存了 count 的 dep。
也就是说,又会调用 count 上的 dep.depend()
讯享网class Dep { subs = [] depend () { <span class="k">if</span> <span class="p">(</span><span class="nx">Dep</span><span class="p">.</span><span class="nx">target</span><span class="p">)</span> <span class="p">{</span> <span class="nx">Dep</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">addDep</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="p">}</span>
} }
这次的Dep.target已经是渲染watcher了,所以这个count的 dep 又会把渲染watcher存放进自身的subs中。
讯享网{ <span class="nx">subs</span><span class="o">:</span> <span class="p">[</span> <span class="nx">sum的计算watcher</span><span class="err">,</span><span class="nx">渲染watcher</span> <span class="p">]</span>
}
那么来到了此题的重点,这时候 count 更新了,是如何去触发视图更新的呢?
再回到 count 的响应式劫持逻辑里去:
讯享网// 在闭包中,会保留对于 count 这个 key 所定义的 dep const dep = new Dep() // 闭包中也会保留上一次 set 函数所设置的 val let val Object.defineProperty(vm, ‘count’, { set: function reactiveSetter (newVal) { <span class="nx">val</span> <span class="o">=</span> <span class="nx">newVal</span> <span class="c1">// 触发 count 的 dep 的 notify
dep.notify() 讯享网<span class="p">}</span>
}) })
好,这里触发了我们刚刚精心准备的count的 dep 的notify函数,感觉离成功越来越近了。
class Dep { subs = [] notify () { 讯享网<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">l</span> <span class="o">=</span> <span class="nx">subs</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">l</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="nx">subs</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">update</span><span class="p">()</span> <span class="p">}</span>
} }
这里的逻辑就很简单了,把 subs 里保存的 watcher 依次去调用它们的 update 方法,也就是
- 调用
计算watcher的 update - 调用
渲染watcher的 update
拆解来看。
update () { if (this.lazy) { 讯享网<span class="k">this</span><span class="p">.</span><span class="nx">dirty</span> <span class="o">=</span> <span class="kc">true</span>
} }
wtf,就这么一句话…… 没错,就仅仅是把 计算watcher 的 dirty 属性置为 true,静静的等待下次读取即可。
渲染watcher 的 update
这里其实就是调用 vm._update(vm._render()) 这个函数,重新根据 render 函数生成的 vnode 去渲染视图了。
而在 render 的过程中,一定会访问到 sum 这个值,那么又回回到 sum 定义的 get 上:
Object.defineProperty(vm, ‘sum’, { 讯享网<span class="nx">get</span><span class="p">()</span> <span class="p">{</span> <span class="kr">const</span> <span class="nx">watcher</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">_computedWatchers</span> <span class="o">&&</span> <span class="k">this</span><span class="p">.</span><span class="nx">_computedWatchers</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> <span class="k">if</span> <span class="p">(</span><span class="nx">watcher</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// ✨上一步中 dirty 已经置为 true, 所以会重新求值
if (watcher.dirty) { <span class="nx">watcher</span><span class="p">.</span><span class="nx">evaluate</span><span class="p">()</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="nx">Dep</span><span class="p">.</span><span class="nx">target</span><span class="p">)</span> <span class="p">{</span> <span class="nx">watcher</span><span class="p">.</span><span class="nx">depend</span><span class="p">()</span> <span class="p">}</span> <span class="c1">// 最后返回计算出来的值
return watcher.value 讯享网 <span class="p">}</span> <span class="p">}</span>
})
由于上一步中的响应式属性更新,触发了 计算 watcher 的 dirty 更新为 true。 所以又会重新调用用户传入的 sum 函数计算出最新的值,页面上自然也就显示出了最新的值。
至此为止,整个计算属性更新的流程就结束了。
缓存生效的情况
根据上面的总结,只有计算属性依赖的响应式值发生更新的时候,才会把 dirty 重置为 true,这样下次读取的时候才会发生真正的计算。
这样的话,假设 sum 函数是一个用户定义的一个比较耗费时间的操作,优化就比较明显了。
<div id=”app“> <span @click=”change“>{{sum}}</span> <span @click=”changeOther“>{{other}}</span> </div> <script src=”https://www.zhihu.com/topic//vue2.6.js"></script> <script> new Vue({ 讯享网<span class="nx">el</span><span class="o">:</span> <span class="s2">"#app"</span><span class="p">,</span> <span class="nx">data</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">{</span> <span class="nx">count</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">other</span><span class="o">:</span> <span class="s1">'Hello'</span> <span class="p">}</span> <span class="p">},</span> <span class="nx">methods</span><span class="o">:</span> <span class="p">{</span> <span class="nx">change</span><span class="p">()</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">count</span> <span class="o">=</span> <span class="mi">2</span> <span class="p">},</span> <span class="nx">changeOther</span><span class="p">()</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">other</span> <span class="o">=</span> <span class="s1">'ssh'</span> <span class="p">}</span> <span class="p">},</span> <span class="nx">computed</span><span class="o">:</span> <span class="p">{</span> <span class="c1">// 非常耗时的计算属性
sum() { <span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">99999</span> <span class="k">while</span><span class="p">(</span><span class="nx">i</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">i</span><span class="o">--</span> <span class="p">}</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span> <span class="p">},</span> <span class="p">},</span>
}) </script>
在这个例子中,other 的值和计算属性没有任何关系,如果 other 的值触发更新的话,就会重新渲染视图,那么会读取到 sum,如果计算属性不做缓存的话,每次都要发生一次很耗费性能的没有必要的计算。
所以,只有在 count 发生变化的时候,sum 才会重新计算,这是一个很巧妙的优化。
总结
2.6 版本计算属性更新的路径是这样的:
- 响应式的值
count更新 - 同时通知
computed watcher和渲染 watcher更新 computed watcher把 dirty 设置为 true- 视图渲染读取到 computed 的值,由于 dirty 所以
computed watcher重新求值。
通过本篇文章,相信你可以完全理解计算属性的缓存到底是什么概念,在什么样的情况下才会生效了吧?
对于缓存和不缓存的情况,分别是这样的流程:
不缓存:
count改变,先通知到计算watcher更新,设置dirty = true- 再通知到
渲染watcher更新,视图重新渲染的时候去计算watcher中读取值,发现dirty是 true,重新执行用户传入的函数求值。
other改变,直接通知渲染watcher更新。- 视图重新渲染的时候去
计算watcher中读取值,发现dirty为 false,直接用缓存值watcher.value,不执行用户传入的函数求值。
展望
事实上这种通过 dirty 标志位来实现计算属性缓存的方式,和 Vue3 中的实现原理是一致的。这可能也说明在各种需求和社区反馈的千锤百炼下,尤大目前认为这种方式是实现 computed 缓存的相对最优解了。
如果对 Vue3 的 computed 实现感兴趣的同学,还可以看我的这篇文章,原理大同小异。只是收集的方式稍有变化。
深度解析:Vue3如何巧妙的实现强大的computed
作者:晨曦时梦见兮
链接:https://juejin.im/post/5e8fd7a3f265da47c35d7d29
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Vue 3还没有正式发布,但是维护者已经发布了beta版本,以供我们的用户尝试并提供反馈
如果您想知道Vue 3的主要特性和主要变化,我将在本文中通过使用Vue 3 beta 9创建一个简单的应用程序来强调它们
我将介绍尽可能多的新内容,包括片段、传送、复合API和其他一些模糊的更改。我也会尽我所能来解释这个特性或变更的基本原理
我们将构建一个带有模态窗口功能的简单应用程序。我选择这个是因为它方便地允许我展示一些Vue 3的更改。
下面是这款应用在打开和关闭状态下的样子,这样你就可以在脑海中想象出我们正在做的事情:
与其直接安装Vue 3,不如克隆Vue -next- Webpack -preview项目,它将为我们提供包括Vue 3在内的最小的Webpack设置。
讯享网\( git clone https://github.com/vuejs/vue-next-webpack-preview.git vue3-experiment \) cd vue3-experiment \( npm i</code></pre></div><p data-pid="DC8VVM_v">一旦克隆并安装了NPM模块,我们所需要做的就是删除样板文件并创建一个新的main.js文件,这样我们就可以从头创建Vue 3应用程序了。</p><div class="highlight"><pre><code class="language-ps1con">\) rm -rf src/* \( touch src/main.js</code></pre></div><p data-pid="chbgEw4D">现在我们将运行开发服务器:</p><p data-pid="GrQt-bVp">马上,我们启动一个新的Vue应用程序的方式改变了。我们现在需要导入新的createApp方法,而不是使用新的Vue()</p><p data-pid="DEmrxsAk">然后我们调用这个方法,传递我们的Vue实例定义对象,并将返回对象分配给一个变量app</p><p data-pid="2L1E_NYM">接下来,我们将在app上调用mount方法,并传递一个CSS选择器来指示我们的mount元素,就像我们在Vue 2中使用\)mount实例方法一样
import { createApp } from “vue”; const app = createApp({ // root instance definition }); app.mount(“#app”);
在旧的API中,我们添加的任何全局配置(插件、混合、原型属性等)都会永久地改变全局状态。例如:
讯享网// Affects both instances Vue.mixin({ … }) const app1 = new Vue({ el: ‘#app-1’ }) const app2 = new Vue({ el: ‘#app-2’ })
这在单元测试中确实是一个问题,因为它使确保每个测试与上一个测试隔离变得很棘手。在新的API下,调用createApp将返回一个新的app实例,该实例不会被应用于其他实例的任何全局配置所污染。Learn more:Global API change RFC.我们的模式窗口可以处于两种状态之一——打开或关闭。让我们用一个布尔状态属性modalOpen来管理它,我们会给它一个初始值false
const app = createApp({ data: { 讯享网<span class="nx">modalOpen</span><span class="o">:</span> <span class="kc">false</span>
} });
这是不允许的。相反,必须为数据分配一个返回状态对象的工厂函数。这是您必须为Vue组件做的事情,但是现在它也对Vue应用程序实例强制执行
const app = createApp({ data: () => ({ 讯享网<span class="nx">modalOpen</span><span class="o">:</span> <span class="kc">false</span>
}) });
使用对象而不是工厂函数的优点是,首先,它在语法上更简单,其次,你可以在多个根实例之间共享顶层状态,例如:
const state = { sharedVal: 0 }; const app1 = new Vue({ state }); const app2 = new Vue({ state }); // Affects both instances app1._data.sharedVal = 1;
这种用例很少,可以使用。因为有两种类型的声明是不适合初学者的,所以决定删除这个特性。Learn more:Data object declaration removed RFC在继续之前,让我们添加一个方法来切换modalOpen值。这与Vue 2没有什么不同。
讯享网const app = createApp({ data: () => ({ <span class="nx">modalOpen</span><span class="o">:</span> <span class="kc">true</span>
}), methods: { 讯享网<span class="nx">toggleModalState</span><span class="p">()</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">modalOpen</span> <span class="o">=</span> <span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">modalOpen</span><span class="p">;</span> <span class="p">}</span>
} });
如果您现在转到浏览器并检查控制台,您将看到警告“组件缺少呈现函数”,因为我们还没有为根实例定义模板。Vue 2的**实践是为根实例创建一个最小的模板,并创建一个应用程序组件,其中将声明主应用程序标记。我们在这里也做一下。
touch src/App.vue
现在我们可以获得根实例来呈现该组件。不同之处在于,在Vue 2中,我们通常会使用渲染函数来完成以下操作:
讯享网import App from “https://www.zhihu.com/topic//App.vue"; const app = createApp({ … render: h => h(App) }); app.mount(”#app“);
我们仍然可以这样做,但是Vue 3有一个更简单的方法——让应用程序成为一个根组件。为此,我们可以删除根实例定义并传递App组件。
import App from ”https://www.zhihu.com/topic//App.vue"; const app = createApp(App); app.mount(“#app”);
这意味着App组件不仅由根实例呈现,而且是根实例。在此过程中,让我们通过删除app变量来简化一下语法:
讯享网createApp(App).mount(“#app”);
现在移动到根组件,让我们重新添加状态和方法到这个组件:
<script> export default { data: () => ({ 讯享网<span class="nx">modalOpen</span><span class="o">:</span> <span class="kc">true</span>
}), methods: { <span class="nx">toggleModalState</span><span class="p">()</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">modalOpen</span> <span class="o">=</span> <span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">modalOpen</span><span class="p">;</span> <span class="p">}</span>
} }; </script>
让我们也为模态特性创建一个新组件:
讯享网touch src/Modal.vue
现在,我们将提供一个包含内容插槽的最小模板。这确保了我们的模式是可重用的。稍后我们将向该组件添加更多内容。
<template> <div class=“modal”> 讯享网<span class="o"><</span><span class="nx">slot</span><span class="o">><</span><span class="err">/slot></span>
</div> </template>
现在让我们为根组件创建模板。我们将创建一个按钮来打开模态,它将触发toggleModalState方法我们还将使用刚刚创建的模态组件,它将根据modalState的值呈现。我们还可以在内容槽中插入一段文本。
<template> <button @click=“toggleModalState”>Open modal</button> <modal v-if=“modalOpen”> 讯享网<span class="o"><</span><span class="nx">p</span><span class="o">></span><span class="nx">Hello</span><span class="p">,</span> <span class="nx">I</span><span class="err">'</span><span class="nx">m</span> <span class="nx">a</span> <span class="nx">modal</span> <span class="nb">window</span><span class="p">.</span><span class="o"><</span><span class="err">/p></span>
</modal> </template> <script> import Modal from “https://www.zhihu.com/topic//Modal.vue"; export default { components: { <span class="nx">Modal</span>
}, … } </script>
注意到这个模板有什么奇怪的地方吗?看一遍。我将等待。没错,有两个根元素。在Vue 3中,由于一个称为fragment的特性,它不再强制拥有单个根元素!Vue 3的旗舰特性是复合API。这个新的API允许您使用setup函数定义组件功能,而不是使用添加到组件定义对象的属性。现在,让我们重构应用程序组件,以使用复合API。在我解释代码之前,要清楚我们所做的一切都是重构——组件的功能是相同的。还要注意,模板没有改变,因为复合API只影响我们定义组件功能的方式,而不是我们呈现它的方式。
讯享网<template> <button @click=”toggleModalState“>Open modal</button> <modal v-if=”modalOpen“> <span class="o"><</span><span class="nx">p</span><span class="o">></span><span class="nx">Hello</span><span class="p">,</span> <span class="nx">I</span><span class="err">'</span><span class="nx">m</span> <span class="nx">a</span> <span class="nx">modal</span> <span class="nb">window</span><span class="p">.</span><span class="o"><</span><span class="err">/p></span>
</modal> </template> <script> import Modal from ”https://www.zhihu.com/topic//Modal.vue"; import { ref } from “vue”; export default { setup () { 讯享网<span class="kr">const</span> <span class="nx">modalState</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span> <span class="kr">const</span> <span class="nx">toggleModalState</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">modalState</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="o">!</span><span class="nx">modalState</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span> <span class="p">};</span> <span class="k">return</span> <span class="p">{</span> <span class="nx">modalState</span><span class="p">,</span> <span class="nx">toggleModalState</span> <span class="p">}</span>
} }; </script>
首先,请注意我们导入了ref函数,该函数允许我们定义一个反应变量modalState。这个变量等价于This . modalstate。toggleModalState方法只是一个普通的JavaScript函数。但是,请注意,要更改方法体中的modalState的值,我们需要更改它的子属性值。这是因为使用ref创建的反应变量被包装在一个对象中。这对于保持它们在传递过程中的活性是必要的。如果您想详细了解refs的工作方式,最好查阅Vue Composition API文档。最后,我们从setup方法返回modalState和toggleModalState,因为它们是在模板呈现时传递给模板的值。请记住,组合API不是一个更改,因为它完全是可选的。主要动机是考虑更好的代码组织和组件之间的代码重用(因为mixin本质上是一种反模式)如果您认为在本例中重构应用程序组件以使用复合API是不必要的,那么您是正确的。但是,如果这是一个更大的组件,或者我们需要与其他组件共享它的特性,那么您就会看到它的有用性。子由风:vue3.0 Composition API 翻译版本(周末研究版)提供更深入的示例超出了本文的范围,所以如果您有兴趣了解更多关于新API的使用,请参阅我的另一篇文章,了解何时使用新Vue复合API(以及何时不使用)。When To Use The New Vue Composition API (And When Not To)如果您以前创建过模态特性,您就会知道它通常被放置在关闭的标记之前。
<body> <div> 讯享网<span class="c"><!--</span><span class="nx">main</span> <span class="nx">page</span> <span class="nx">content</span> <span class="nx">here</span><span class="o">--></span>
</div> <!–modal here–> </body>
这样做是因为情态动词通常有一个页面覆盖的背景(如果你不明白我的意思,请参阅开头的图片)。要使用CSS实现这一点,您不需要处理父元素定位和z-index叠加上下文,因此最简单的解决方案是将模态放在DOM的最底部。这就与Vue产生了问题。不过,它假设UI将被构建为一个组件树。为了允许树的片段移动到DOM中的其他位置,Vue 3中添加了一个新的传送组件要使用传送,让我们首先向页面添加一个元素,我们希望将模态内容移动到该页面。我们将转到index.html,并在Vue的挂载元素旁边放置一个带ID modal-wrapper的div。
<body> … <div id=“app”></div><!–Vue mounting element–> <div id=“modal-wrapper”> 讯享网<span class="c"><!--</span><span class="nx">modal</span> <span class="nx">should</span> <span class="nx">get</span> <span class="nx">moved</span> <span class="nx">here</span><span class="o">--></span>
</div> </body>
现在,回到App.vue,我们将把模态内容包装在传送组件中。我们还需要指定一个to属性,它将被分配一个用于标识目标元素的查询选择器,在本例中是#modal-wrapper。
<template> <button @click=“toggleModalState”>Open modal</button> <teleport to=“#modal-wrapper”> 讯享网<span class="o"><</span><span class="nx">modal</span> <span class="nx">v</span><span class="o">-</span><span class="k">if</span><span class="o">=</span><span class="s2">"modalOpen"</span><span class="o">></span> <span class="o"><</span><span class="nx">p</span><span class="o">></span><span class="nx">Hello</span><span class="p">,</span> <span class="nx">I</span><span class="err">'</span><span class="nx">m</span> <span class="nx">a</span> <span class="nx">modal</span> <span class="nb">window</span><span class="p">.</span><span class="o"><</span><span class="err">/p></span> <span class="o"><</span><span class="err">/modal></span>
</teleport> </template>
就是这样。传送中的任何内容都将在目标元素中呈现。然而,它仍然会像它在层级中的最初位置一样工作(关于道具,事件等)。因此,在您保存代码之后,重新加载页面,在开发工具中检查DOM,您会感到惊讶!Learn more:Teleport RFC现在让我们在模态中添加一个按钮来关闭它。为此,我们将向modal tempate添加一个按钮元素,并使用一个发出事件close的click处理程序。
<template> <div class=“modal”> 讯享网<span class="o"><</span><span class="nx">slot</span><span class="o">><</span><span class="err">/slot></span> <span class="o"><</span><span class="nx">button</span> <span class="err">@</span><span class="nx">click</span><span class="o">=</span><span class="s2">"$emit('close')"</span><span class="o">></span><span class="nx">Dismiss</span><span class="o"><</span><span class="err">/button></span>
</div> </template>
然后父组件将捕捉此事件,并切换modalState的值,使其在逻辑上为假,并导致窗口关闭。
<template> … 讯享网<span class="o"><</span><span class="nx">modal</span> <span class="nx">v</span><span class="o">-</span><span class="k">if</span><span class="o">=</span><span class="s2">"modalOpen"</span> <span class="err">@</span><span class="nx">click</span><span class="o">=</span><span class="s2">"toggleModalState"</span> <span class="o">></span> <span class="o"><</span><span class="nx">p</span><span class="o">></span><span class="nx">Hello</span><span class="p">,</span> <span class="nx">I</span><span class="err">'</span><span class="nx">m</span> <span class="nx">a</span> <span class="nx">modal</span> <span class="nb">window</span><span class="p">.</span><span class="o"><</span><span class="err">/p></span> <span class="o"><</span><span class="err">/modal></span>
</teleport> </template>
到目前为止,这个特性与Vue 2中的特性完全相同。但是,在Vue 3中,现在建议您使用新的component选项显式地声明组件的事件。就像使用道具一样,您可以简单地创建一个字符串数组来命名组件将发出的每个事件
<template>…</template> <script> export default { emits: [ “close” ] } </script>
想象一下,打开别人编写的组件文件,并查看显式声明的组件的道具和事件。马上,您就会理解这个组件的接口,即它要发送和接收什么。除了提供自我记录的代码之外,您还可以使用事件声明来验证事件负载,尽管在本例中我找不到这样做的理由。Learn more:Emits Option RFC为了使我们的模式可重用,我们为内容提供了一个插槽。让我们通过向组件添加样式标签来开始对该内容进行样式化。在我们的组件中使用限定范围的CSS是一个很好的实践,以确保我们提供的规则不会对页面中的其他内容产生意外的影响让我们把任何段落文本放到槽里都改成斜体。为此,我们将使用p选择器创建一个新的CSS规则。
讯享网<template>…</template> <script>…</script> <style scoped> p { <span class="nx">font</span><span class="o">-</span><span class="nx">style</span><span class="o">:</span> <span class="nx">italic</span><span class="p">;</span>
} </style>
如果你试一下,你会发现它不起作用。问题是,当槽内容仍然属于父内容时,在编译时确定了作用域样式。Vue 3提供的解决方案是提供一个伪选择器::v- sloated(),允许您使用提供插槽的组件中的作用域规则来锁定插槽内容。Here‘s how we use it:
讯享网<style scoped> ::v-slotted(p) { <span class="nx">font</span><span class="o">-</span><span class="nx">style</span><span class="o">:</span> <span class="nx">italic</span><span class="p">;</span>
} </style>
Vue 3 also includes some other new scoped styling selectors::v-deepand::v-globalwhich you can learn more about here:Scoped Styles RFC好了,这就是我可以在一个简单的例子中介绍的所有新特性。我得到了大部分主要的,但这里有一些我认为重要到足以在结束文章之前提到,你可以自己研究:Added:Global API treeshakingRemoved:
- Filters
- Inline templates
- Event interface for components (no more event bus!)
Changed:
- Async component API
- Custom directive API
- Render function syntax
There are also various changes regarding Vue Router but I’ll be dedicating a separate article to those! 当用户在当前商品中停留2秒中,就将该商品添加到我的足迹中1.用定时器,在进入该商品页面的时候,在mounted中调用定时器2.当没有停留够2秒退出页面,在vue生命周期destroyed中销毁定时器
讯享网 mounted() { <span class="k">this</span><span class="p">.</span><span class="nx">postFoot</span><span class="p">()</span><span class="c1">//开启定时器
}, // 关闭定时器 destroyed() { 讯享网<span class="nx">clearInterval</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">time</span><span class="p">)</span>
}, methods: { <span class="c1">//间隔2秒的时候进行调用
postFoot() { 讯享网 <span class="k">this</span><span class="p">.</span><span class="nx">time</span> <span class="o">=</span> <span class="nx">setTimeout</span><span class="p">(()</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">collect</span><span class="p">({</span> <span class="nx">category</span><span class="o">:</span> <span class="s1">'foot'</span><span class="p">,</span> <span class="nx">id</span><span class="o">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">$route</span><span class="p">.</span><span class="nx">query</span><span class="p">.</span><span class="nx">id</span> <span class="p">}).</span><span class="nx">then</span><span class="p">(</span><span class="nx">res</span> <span class="p">=></span> <span class="p">{</span> <span class="p">})</span> <span class="p">},</span> <span class="mi">2000</span><span class="p">)</span> <span class="p">},</span>
在网上看到了另外一种实现方法 利用$once监听
mounted() { 讯享网<span class="k">this</span><span class="p">.</span><span class="nx">postFoot</span><span class="p">()</span> <span class="c1">//开启定时器
}, methods: { <span class="c1">//间隔2秒的时候进行调用
postFoot() { 讯享网 <span class="kr">const</span> <span class="nx">timer</span> <span class="o">=</span> <span class="nx">setTimeout</span><span class="p">(()</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">collect</span><span class="p">({</span> <span class="nx">category</span><span class="o">:</span> <span class="s1">'foot'</span><span class="p">,</span> <span class="nx">id</span><span class="o">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">$route</span><span class="p">.</span><span class="nx">query</span><span class="p">.</span><span class="nx">id</span> <span class="p">}).</span><span class="nx">then</span><span class="p">(</span><span class="nx">res</span> <span class="p">=></span> <span class="p">{})</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'成功'</span><span class="p">);</span> <span class="p">},</span> <span class="mi">2000</span><span class="p">)</span> <span class="c1">// 通过$once来监听定时器,在beforeDestroy钩子可以被清除。
this.$once(‘hook:beforeDestroy’, () => { <span class="nx">clearTimeout</span><span class="p">(</span><span class="nx">timer</span><span class="p">)</span> <span class="p">})</span>
这里用的时候看到网上用的是setInterval ,是会不断调用的,这里只需要调用一次,用setTimeout()更合适
\(once 是一个函数,可以为 vue 实例绑定一个自定义事件,但是这个事件只会被触发一次,触发之后就会被移除。而 \)on.就是一直监听
详细可以看看上面这位前辈的讲述1.借用前辈在结尾写的一段话,没有无用的api,方法,只是暂时还没有遇到合适需求需要用到,遇到问题的时候就可以联想到自己平时所学的知识点,是自己不知道的就去不断查找,多看官网,查漏补缺,学会学习
在vue官方文档中是这样描述的,自定义指令主要是为了重用涉及普通元素的底层DOM访问的逻辑。
其实了解到自定义指令的作用在于,
在某些场景下需要对普通DOM元素进行操作就行。
Vue 2.X 与 Vue 3.X 相比,钩子函数是存在变化的Vue 2.X钩子函数:
- bind:自定义指令绑定到DOM后调用。只调用一次,指令第一次绑定到元素的调用。在这里可以进行一次性的初始化设置。注意:只是加入进了DOM,但是渲染没有完成。
- inserted:自定义指令所在DOM,插入到父DOM后调用,渲染已经完成(父节点存在即可调用,不必存在于document中)。
- update:元素更新,但子元素尚未更新,将调用此钩子(自定义指令所在组件更新时执行,但是不保证更新完成),和自定义指令所在组件有关。
- componentUpdated:组件和子级更新后执行(自定义指令所在组件更新完成,且子组件也完成更新)
- unbind:解绑(销毁)(自定义指令所在DOM销毁时执行),只调用一次。
Vue 3.X钩子函数:
- created:自定义指令所在组件,创建后调用
- beforeMount:相当于Vue 2.X中的bind,当元素**入到DOM前调用
- mounted:相当于Vue 2.X中的inserted,当绑定元素的父组件被挂载后调用
- beforeUpdate:绑定元素的父组件更新前调用
- updated:相当于Vue 2.X中的componentUpdated,在绑定元素的父组件及他自己的所有子节点都更新后调用
- beforeUnmount:绑定元素的父组件卸载前调用
- unmounted:绑定元素的父组件卸载后调用
指令的钩子会传递以下几种参数:
el:指令绑定到的元素。这可以直接操作DOM。binding:一个对象,包含以下属性。value:传递给指令的值。例如在v-my-directive=“1+1”中,值是2.oldValue:之前的值,仅在beforeUpdate和updated中可用。无论值是否更改,它都可用.arg:传递给指令的参数(如果有的话)。例如在v-my-directive:foo中,参数是“foo”.modifiers:一个包含修饰符的对象(如果有的话)。例如在v-my-directive:foo:bar中,修饰符对象是{ foo: true, bar: true }.instance:使用该指令的组件实例.dir:指令的定义对象.vnode:代表绑定元素的底层VNode。虚拟DOM节点,一个真实DOM元素的蓝图,对应el.prevNode:上一个虚拟节点。之前的渲染中代表指令所绑定元素的VNode。仅在beforUpdate和updated钩子中使用.
自定义指令要分全局自定义指令和局部指令全局指令:通过应用实例身上的directive()注册一个全局自定义指令Vue.directive(指令名, { 自定义指令生命周期 })
讯享网// Vue 2.X import Vue from ‘vue’ Vue.directive(‘focus’, { <span class="nx">inserted</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">el</span><span class="p">)</span> <span class="p">{</span> <span class="nx">el</span><span class="p">.</span><span class="nx">focus</span><span class="p">()</span> <span class="p">}</span>
}) // Vue 3.X const app = createApp({}) app.directive(‘focus’, { 讯享网<span class="nx">mounted</span><span class="p">(</span><span class="nx">el</span><span class="p">)</span> <span class="p">{</span> <span class="nx">el</span><span class="p">.</span><span class="nx">focus</span><span class="p">()</span> <span class="p">}</span>
})
这是一个简单的小案例,通过注册一个
v-focus指令,实现一个在页面加载完成后自动让输入框获取焦点的小功能。
局部指令:可在组件中配置directives选项来注册局部指令directives(指令名, { 自定义指令生命周期 })
// Vue 2.X directives: { 讯享网<span class="nx">focus</span><span class="o">:</span> <span class="p">{</span> <span class="nx">inserted</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">el</span><span class="p">)</span> <span class="p">{</span> <span class="nx">el</span><span class="p">.</span><span class="nx">focus</span><span class="p">()</span> <span class="p">}</span> <span class="p">}</span>
} // Vue 3.X directives: { <span class="nx">focus</span><span class="o">:</span> <span class="p">{</span> <span class="nx">mounted</span><span class="p">(</span><span class="nx">el</span><span class="p">)</span> <span class="p">{</span> <span class="nx">el</span><span class="p">.</span><span class="nx">focus</span><span class="p">()</span> <span class="p">}</span> <span class="p">}</span>
}
批量注册是为了方便注册更多的自定义指令
- 创建专门放指令的文件夹directives,在文件夹中创建index.js文件。
- 在index.js文件中将所有指令引入后,写到一个对象中,并导出。
讯享网import copy from ‘https://www.zhihu.com/topic//copy' import longpress from ’https://www.zhihu.com/topic//longpress' const directives = { copy, <span class="nx">longpress</span>
} export default { 讯享网 <span class="nx">install</span><span class="p">(</span><span class="nx">Vue</span><span class="p">)</span> <span class="p">{</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">directives</span><span class="p">).</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">key</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">Vue</span><span class="p">.</span><span class="nx">directive</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">directives</span><span class="p">[</span><span class="nx">key</span><span class="p">])</span> <span class="p">})</span> <span class="p">}</span>
}
3. 在main.js文件中引入
// Vue 2.X import Vue from ‘vue’ import Directive from ‘https://www.zhihu.com/topic//directives' Vue.use(Directive)
讯享网// Vue 3.X import { createApp } from ’vue‘ import App from ’https://www.zhihu.com/topic//App.vue' import Directive from ‘https://www.zhihu.com/topic//directives' const app = createApp(App) app.use(Directive) app.mount(’#app‘)
实现:一键复制文本内容,用于粘贴思路:
- 动态创建
textarea标签,并设置reaOnly属性及移除可视区域 - 将要复制的值赋给
textarea标签的value属性,并插入到body - 选中值
textarea并复制 - 将
body中插入的textarea移除 - 在第一次调用时绑定事件,在解绑时移除事件
const copy = { 讯享网<span class="nx">bind</span><span class="p">(</span><span class="nx">el</span><span class="p">,</span> <span class="p">{</span> <span class="nx">value</span> <span class="p">})</span> <span class="p">{</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_value</span> <span class="o">=</span> <span class="nx">value</span> <span class="nx">el</span><span class="p">.</span><span class="nx">handler</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">el</span><span class="p">.</span><span class="nx">_value</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 复制的值为空时,给出的提示操作。
console.log(’无可复制的内容‘) <span class="k">return</span> <span class="p">}</span> <span class="c1">// 创建 textarea 标签
const textarea = document.createElement(’textarea‘) 讯享网 <span class="c1">// 设置readOnly(规定字段为只读)属性,防止 iOS下自动唤起键盘,并移除可视区域
textarea.readOnly = ’readOnly‘ <span class="nx">textarea</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">position</span> <span class="o">=</span> <span class="s1">'absolute'</span> <span class="nx">textarea</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">left</span> <span class="o">=</span> <span class="s1">'-9999px'</span> <span class="c1">// 将需要复制的值,赋给 textarea 标签的 value 值
textarea.value = el._value 讯享网 <span class="c1">// 将 textarea 插入到 body 中
document.body.appendChild(textarea) <span class="c1">// 选中值并复制
textarea.select() 讯享网 <span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">execCommand</span><span class="p">(</span><span class="s1">'Copy'</span><span class="p">)</span> <span class="k">if</span> <span class="p">(</span><span class="nx">result</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'复制成功'</span><span class="p">)</span> <span class="p">}</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">removeChild</span><span class="p">(</span><span class="nx">textarea</span><span class="p">)</span> <span class="p">}</span> <span class="c1">// 绑定点击事件
el.addEventListener(’click‘, el.handler) <span class="p">},</span> <span class="c1">// 当传进来的值更新的时候触发
componentUpdated(el, { value }) { 讯享网 <span class="nx">el</span><span class="p">.</span><span class="nx">_value</span> <span class="o">=</span> <span class="nx">value</span> <span class="p">},</span> <span class="c1">// 指令与元素解绑的时候触发,移除事件绑定
unbind(el) { <span class="nx">el</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s1">'click'</span><span class="p">,</span> <span class="nx">el</span><span class="p">.</span><span class="nx">handler</span><span class="p">)</span> <span class="p">}</span>
} export default copy
使用:给DOM节点加上v-copy及复制文本即可
讯享网<template> <span class="p"><</span><span class="nt">button</span> <span class="na">v-copy</span><span class="o">=</span><span class="s">"text"</span><span class="p">></span>复制<span class="p"></</span><span class="nt">button</span><span class="p">></span>
</template> <script> 讯享网<span class="kr">export</span> <span class="k">default</span> <span class="p">{</span> <span class="nx">data</span> <span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">{</span> <span class="nx">text</span><span class="o">:</span> <span class="s1">'这是一条复制文本'</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span>
</script>
实现:长按超过两秒,执行回调函数思路:
- 创建一个计时器,两秒后执行函数
- 当用户按下按钮时触发
mousedown事件(移动端touchstart事件),启动计时器; - 用户松开按钮时调用
mouseout事件(移动端touchend事件) - 如果
mouseup事件在两秒内触发,此事件当作普通点击事件 - 如果计时器没有在两秒内清除,定为长按事件,触发相关回调函数
const longpress = { 讯享网<span class="nx">bind</span><span class="o">:</span> <span class="p">(</span><span class="nx">el</span><span class="p">,</span> <span class="nx">binding</span><span class="p">,</span> <span class="nx">vNode</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 没有绑定函数抛出错误
if (typeof binding.value !== ’function‘) { <span class="k">throw</span> <span class="s1">'longpress callback not a function'</span> <span class="p">}</span> <span class="c1">// 计时器变量
el._timer = null 讯享网 <span class="c1">// 运行函数
el._handler = e => { <span class="nx">binding</span><span class="p">.</span><span class="nx">value</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">}</span> <span class="c1">// 创建计时器(2秒后执行函数)
el._start = e => { 讯享网 <span class="c1">// 0为鼠标左键
if (e.type === ’click‘ && e.button !== 0) return <span class="k">if</span> <span class="p">(</span><span class="nx">el</span><span class="p">.</span><span class="nx">_timer</span> <span class="o">===</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_timer</span> <span class="o">=</span> <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">_</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_handler</span><span class="p">()</span> <span class="p">},</span> <span class="mi">2000</span><span class="p">)</span> <span class="c1">// 取消浏览器默认事件
el.addEventListener(’contextmenu‘, e => { 讯享网 <span class="nx">e</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">()</span> <span class="p">})</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">// 两秒内松手,取消计时器
el._cancel = e => { <span class="k">if</span> <span class="p">(</span><span class="nx">el</span><span class="p">.</span><span class="nx">_timer</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span> <span class="nx">clearTimeout</span><span class="p">(</span><span class="nx">el</span><span class="p">.</span><span class="nx">_timer</span><span class="p">)</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_timer</span> <span class="o">=</span> <span class="kc">null</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">// 添加计时监听
el.addEventListener(’mousedown‘, el._start) 讯享网 <span class="nx">el</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">'touchstart'</span><span class="p">,</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_start</span><span class="p">)</span> <span class="c1">// 添加取消监听
el.addEventListener(’click‘, el._cancel) <span class="nx">el</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">'mouseout'</span><span class="p">,</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_cancel</span><span class="p">)</span> <span class="nx">el</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">'touchend'</span><span class="p">,</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_cancel</span><span class="p">)</span> <span class="nx">el</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">'touchcancel'</span><span class="p">,</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_cancel</span><span class="p">)</span> <span class="p">},</span> <span class="nx">unbind</span><span class="p">(</span><span class="nx">el</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 移除监听
el.removeEventListener(’mousedown‘, el._start) 讯享网 <span class="nx">el</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s1">'touchstart'</span><span class="p">,</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_start</span><span class="p">)</span> <span class="nx">el</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s1">'click'</span><span class="p">,</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_cancel</span><span class="p">)</span> <span class="nx">el</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s1">'mouseout'</span><span class="p">,</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_cancel</span><span class="p">)</span> <span class="nx">el</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s1">'touchend'</span><span class="p">,</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_cancel</span><span class="p">)</span> <span class="nx">el</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s1">'touchcancel'</span><span class="p">,</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_cancel</span><span class="p">)</span> <span class="p">}</span>
} export default longpress
使用:给DOM节点加上v-longpress及相应回调函数
<template> 讯享网<span class="p"><</span><span class="nt">button</span> <span class="na">v-longpress</span><span class="o">=</span><span class="s">"handleLongpress"</span><span class="p">></span>复制<span class="p"></</span><span class="nt">button</span><span class="p">></span>
</template> <script> <span class="kr">export</span> <span class="k">default</span> <span class="p">{</span> <span class="nx">methods</span><span class="o">:</span> <span class="p">{</span> <span class="nx">handleLongpress</span><span class="p">()</span> <span class="p">{</span> <span class="nx">alert</span><span class="p">(</span><span class="s1">'这是长按指令'</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span>
</script>
场景:项目开发中,经常会遇到按钮多次点击后,重复请求接口,造成数据混乱,例如表单提交。实现:使用防抖,防止按钮在短时间内多次点击,设置按钮在1秒内只能点击一次。思路:
- 定义延时方法,在1秒内再调用该方法,则重新计算执行时间
- 将事件绑定在点击方法上
讯享网const debounce = { <span class="nx">inserted</span><span class="o">:</span> <span class="p">(</span><span class="nx">el</span><span class="p">,</span> <span class="nx">binding</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span> <span class="c1">// 没有绑定函数抛出错误
if (typeof binding.value !== ’function‘) { 讯享网 <span class="k">throw</span> <span class="s1">'debounce callback not a function'</span> <span class="p">}</span> <span class="kd">let</span> <span class="nx">timer</span> <span class="nx">el</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">'click'</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">timer</span><span class="p">)</span> <span class="nx">clearTimeout</span><span class="p">(</span><span class="nx">timer</span><span class="p">)</span> <span class="nx">timer</span> <span class="o">=</span> <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">_</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">binding</span><span class="p">.</span><span class="nx">value</span><span class="p">()</span> <span class="p">},</span> <span class="mi">1000</span><span class="p">)</span> <span class="p">})</span> <span class="p">}</span>
} export default debounce
使用:给DOM节点加上v-debounce及回调函数
<template> 讯享网<span class="p"><</span><span class="nt">button</span> <span class="na">v-debounce</span><span class="o">=</span><span class="s">"handleDebounce"</span><span class="p">></span>防抖<span class="p"></</span><span class="nt">button</span><span class="p">></span>
</template> <script> <span class="kr">export</span> <span class="k">default</span> <span class="p">{</span> <span class="nx">methods</span><span class="o">:</span> <span class="p">{</span> <span class="nx">handleDebounce</span><span class="p">()</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'防抖,触发一次'</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span>
</script>
场景:同防抖指令场景实现:使用节流方法,限制1秒内按钮只能点击一次思路:
- 定义一个开关,默认时打开状态
- 在点击按钮后关闭开关,在1秒内再点击按钮,不执行回调函数
- 1秒过后,开关打开
讯享网const throttle = { <span class="nx">bind</span><span class="o">:</span> <span class="p">(</span><span class="nx">el</span><span class="p">,</span> <span class="nx">binding</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span> <span class="c1">// 没有绑定函数抛出错误
if (typeof binding.value !== ’function‘) { 讯享网 <span class="k">throw</span> <span class="s1">'throttle callback not a function'</span> <span class="p">}</span> <span class="c1">// 开关
el._flag = true <span class="nx">el</span><span class="p">.</span><span class="nx">_timer</span> <span class="o">=</span> <span class="kc">null</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_handler</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">el</span><span class="p">.</span><span class="nx">_flag</span><span class="p">)</span> <span class="k">return</span> <span class="c1">// 函数执行后关闭开关
el._flag && binding.value() 讯享网 <span class="nx">el</span><span class="p">.</span><span class="nx">_flag</span> <span class="o">=</span> <span class="kc">false</span> <span class="k">if</span> <span class="p">(</span><span class="nx">el</span><span class="p">.</span><span class="nx">_timer</span><span class="p">)</span> <span class="nx">clearTimeout</span><span class="p">(</span><span class="nx">el</span><span class="p">.</span><span class="nx">_timer</span><span class="p">)</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_timer</span> <span class="o">=</span> <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">_</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_flag</span> <span class="o">=</span> <span class="kc">true</span> <span class="p">},</span> <span class="mi">1000</span><span class="p">)</span> <span class="p">}</span> <span class="nx">el</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">'click'</span><span class="p">,</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_handler</span><span class="p">)</span> <span class="p">},</span> <span class="nx">unbind</span><span class="o">:</span> <span class="p">(</span><span class="nx">el</span><span class="p">,</span> <span class="nx">binding</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">el</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s1">'click'</span><span class="p">,</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_handler</span><span class="p">)</span> <span class="p">}</span>
} export default throttle
使用:给DOM节点加上v-throttle及回调函数
<template> 讯享网<span class="p"><</span><span class="nt">button</span> <span class="na">v-throttle</span><span class="o">=</span><span class="s">"handleThrottle"</span><span class="p">></span>节流<span class="p"></</span><span class="nt">button</span><span class="p">></span>
</template> <script> <span class="kr">export</span> <span class="k">default</span> <span class="p">{</span> <span class="nx">methods</span><span class="o">:</span> <span class="p">{</span> <span class="nx">handleThrottle</span><span class="p">()</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'节流,触发一次'</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span>
</script>
场景:弹窗,点击弹窗外部(即蒙版),关闭弹窗实现:点击指定区域外部,执行相应回调函数思路:
- 判断点击的元素是否指定元素
- 是则不执行,否则执行相应回调函数
讯享网const clickOut = { <span class="nx">bind</span><span class="o">:</span> <span class="p">(</span><span class="nx">el</span><span class="p">,</span> <span class="nx">binding</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_handler</span> <span class="o">=</span> <span class="nx">e</span> <span class="p">=></span> <span class="p">{</span> <span class="c1">// 判断点击的元素是否本身,是则返回
if (el.contains(e.target)) return 讯享网 <span class="c1">// 如果绑定了函数,则调用函数
if (typeof binding.value === ’function‘) { <span class="nx">binding</span><span class="p">.</span><span class="nx">value</span><span class="p">()</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">// 添加事件监听
setTimeout(_ => { 讯享网 <span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">'click'</span><span class="p">,</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_handler</span><span class="p">)</span> <span class="p">},</span> <span class="mi">0</span><span class="p">)</span> <span class="p">},</span> <span class="nx">unbind</span><span class="p">(</span><span class="nx">el</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 解除事件监听
document.removeEventListener(’click‘, el._handler) <span class="p">}</span>
} export default clickOut
使用:给指定DOM节点加v-clickOut及回调函数
讯享网<template> <span class="c"><!-- v-clickOut、v-click-out都可以使用 --></span> <span class="p"><</span><span class="nt">div</span> <span class="na">style</span><span class="o">=</span><span class="s">"width:100px;height:100px;background-color:red;"</span> <span class="na">v-click-out</span><span class="o">=</span><span class="s">"handleClickOut"</span><span class="p">></span>target DOM<span class="p"></</span><span class="nt">div</span><span class="p">></span>
</template> <script> 讯享网<span class="kr">export</span> <span class="k">default</span> <span class="p">{</span> <span class="nx">methods</span><span class="o">:</span> <span class="p">{</span> <span class="nx">handleClickOut</span><span class="p">()</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'外部元素触发'</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span>
</script>
实现:指定元素在可视区域内任意拖拽思路:
- 设置指定元素相对定位,父元素绝对定位
- 鼠标按下时(触发
onmousedown事件)时记录指定元素当前的left、top值 - 鼠标移动时(触发
onmousemove事件)时计算每次移动的横向距离、纵向距离,并改变left、top值 - 鼠标松开时(触发
onmouseup事件)时完成一次拖拽
const draggable = { 讯享网<span class="nx">inserted</span><span class="o">:</span> <span class="p">(</span><span class="nx">el</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">cursor</span> <span class="o">=</span> <span class="s1">'move'</span> <span class="nx">el</span><span class="p">.</span><span class="nx">_onmousedown</span> <span class="o">=</span> <span class="nx">e</span> <span class="p">=></span> <span class="p">{</span> <span class="c1">// 记录当前的left、top值
let disX = e.pageX - el.offsetLeft <span class="kd">let</span> <span class="nx">disY</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">pageY</span> <span class="o">-</span> <span class="nx">el</span><span class="p">.</span><span class="nx">offsetTop</span> <span class="c1">// 鼠标移动
document.onmousemove = e => { 讯享网 <span class="kd">let</span> <span class="nx">x</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">pageX</span> <span class="o">-</span> <span class="nx">disX</span> <span class="kd">let</span> <span class="nx">y</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">pageY</span> <span class="o">-</span> <span class="nx">disY</span> <span class="c1">// 临界值处理
let maxX = document.body.clientWidth - parseInt(window.getComputedStyle(el).width) <span class="kd">let</span> <span class="nx">maxY</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">clientHeight</span> <span class="o">-</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">getComputedStyle</span><span class="p">(</span><span class="nx">el</span><span class="p">).</span><span class="nx">height</span><span class="p">)</span> <span class="k">if</span> <span class="p">(</span><span class="nx">x</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">x</span> <span class="o">=</span> <span class="mi">0</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">x</span> <span class="o">></span> <span class="nx">maxX</span><span class="p">)</span> <span class="p">{</span> <span class="nx">x</span> <span class="o">=</span> <span class="nx">maxX</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="nx">y</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">y</span> <span class="o">=</span> <span class="mi">0</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">y</span> <span class="o">></span> <span class="nx">maxY</span><span class="p">)</span> <span class="p">{</span> <span class="nx">y</span> <span class="o">=</span> <span class="nx">maxY</span> <span class="p">}</span> <span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">left</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">x</span><span class="si">}</span><span class="sb">px`</span> <span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">top</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">y</span><span class="si">}</span><span class="sb">px`</span> <span class="p">}</span> <span class="nb">document</span><span class="p">.</span><span class="nx">onmouseup</span> <span class="o">=</span> <span class="nx">_</span> <span class="p">=></span> <span class="p">{</span> <span class="nb">document</span><span class="p">.</span><span class="nx">onmousemove</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">onmouseup</span> <span class="o">=</span> <span class="kc">null</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span>
} export default draggable
使用:给指定DOM节点加v-draggable即可
讯享网<template> <span class="c"><!-- 需要加上position:absolute --></span> <span class="p"><</span><span class="nt">div</span> <span class="na">style</span><span class="o">=</span><span class="s">"width:40px;height:40px;background-color:green;position:absolute;"</span> <span class="na">v-draggable</span><span class="p">></</span><span class="nt">div</span><span class="p">></span>
</template>
实现:根据正则表达式,设计自定义处理表单输入规则的指令思路:
- 定位
input输入框(el-input输入框外会包裹一层div) - 监听输入事件,对只保留整数或保留小数分别处理
- 通过正则表达式对保留小数做处理
- 将匹配后的值赋值给输入框
讯享网const inputNumber = { <span class="nx">bind</span><span class="o">:</span> <span class="p">(</span><span class="nx">el</span><span class="p">,</span> <span class="nx">binding</span><span class="p">,</span> <span class="nx">vnode</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span> <span class="c1">// 定位输入框
let input = el.tagName === ’INPUT‘ ? el : vnode.elm.children[0] 讯享网 <span class="c1">// compositionstart -> 开始新的输入合成时会触发
input.addEventListener(’compositionstart‘, _ => { <span class="nx">vnode</span><span class="p">.</span><span class="nx">inputLocaking</span> <span class="o">=</span> <span class="kc">true</span> <span class="p">})</span> <span class="c1">// compostitonend -> 合成完成或取消时触发
input.addEventListener(’compostitonend‘, _ => { 讯享网 <span class="nx">vnode</span><span class="p">.</span><span class="nx">inputLocaking</span> <span class="o">=</span> <span class="kc">false</span> <span class="nx">input</span><span class="p">.</span><span class="nx">dispatchEvent</span><span class="p">(</span><span class="k">new</span> <span class="nx">Event</span><span class="p">(</span><span class="s1">'input'</span><span class="p">))</span> <span class="p">})</span> <span class="nx">input</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">'input'</span><span class="p">,</span> <span class="nx">_</span> <span class="p">=></span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">vnode</span><span class="p">.</span><span class="nx">inputLocaking</span><span class="p">)</span> <span class="k">return</span> <span class="kd">let</span> <span class="nx">oldValue</span> <span class="o">=</span> <span class="nx">input</span><span class="p">.</span><span class="nx">value</span> <span class="kd">let</span> <span class="nx">newValue</span> <span class="o">=</span> <span class="nx">input</span><span class="p">.</span><span class="nx">value</span> <span class="k">if</span> <span class="p">(</span><span class="nx">binding</span><span class="p">.</span><span class="nx">modifiers</span><span class="p">.</span><span class="kr">float</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 清除数字和‘.’以外的字符
newValue = newValue.replace(/[^\dhttps://www.zhihu.com/topic//g, ’‘) <span class="c1">// 只保留第一个‘.’,清除多余的
newValue = newValue.replace(/.{2,}/g, ’.‘) 讯享网 <span class="c1">// 第一个字符如果是‘.’,补充前缀0
newValue = newValue.replace(/^\https://www.zhihu.com/topic//g, ’0.‘) <span class="c1">// 0开头的只有保留第一个0,清除多余的
newValue = newValue.replace(/^0{2,}/g, ’0‘) 讯享网 <span class="c1">// 两位数以上不能0开头
if (/^0\d+/.test(newValue)) { <span class="nx">newValue</span> <span class="o">=</span> <span class="nx">newValue</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">}</span> <span class="c1">// 保证‘.’只出现一次,而不能出现两次以上
newValue = newValue.replace(’.‘, ’\(#\)‘).replace(/\https://www.zhihu.com/topic//g, ’‘).replace(’\(#\)‘, ’.‘) 讯享网 <span class="c1">// 保留几位小数
if (typeof binding.value !== ’undefined‘) { <span class="c1">// 期望保留的最大小数位数
let poinKeep = 0 讯享网 <span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">binding</span><span class="p">.</span><span class="nx">value</span> <span class="o">===</span> <span class="s1">'string'</span> <span class="o">||</span> <span class="k">typeof</span> <span class="nx">binding</span><span class="p">.</span><span class="nx">value</span> <span class="o">===</span> <span class="s1">'number'</span><span class="p">)</span> <span class="p">{</span> <span class="nx">pointKeep</span> <span class="o">=</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">binding</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">pointKeep</span><span class="p">))</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">Number</span><span class="p">.</span><span class="nx">isInteger</span><span class="p">(</span><span class="nx">pointKeep</span><span class="p">)</span> <span class="o">||</span> <span class="nx">pointKeep</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">pointKeep</span> <span class="o">=</span> <span class="mi">0</span> <span class="p">}</span> <span class="kr">const</span> <span class="nx">str</span> <span class="o">=</span> <span class="s1">'^(\\d+)\\.(\\d{'</span><span class="o">+</span><span class="nx">pointKeep</span><span class="o">+</span><span class="s1">'}).*$'</span> <span class="kr">const</span> <span class="nx">reg</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">RegExp</span><span class="p">(</span><span class="nx">str</span><span class="p">)</span> <span class="k">if</span> <span class="p">(</span><span class="nx">pointKeep</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 不需要小数点
newValue = newValue.replace(reg, ’$1‘) <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="c1">// 通过正则表达式保留小数点后指定的位数
newValue = newValue.replace(reg, ’\(1.\)2‘) 讯享网 <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="c1">// 只保留整数
newValue = newValue.replace(/[^\d]/g, ’‘) <span class="nx">newValue</span> <span class="o">=</span> <span class="nx">newValue</span> <span class="o">?</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">newValue</span><span class="p">)</span> <span class="o">:</span> <span class="s1">''</span> <span class="p">}</span> <span class="c1">// 判断是否需要更新,避免进入死循环
if (+newValue !== +oldValue) { 讯享网 <span class="nx">input</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">newValue</span> <span class="nx">input</span><span class="p">.</span><span class="nx">dispatchEvent</span><span class="p">(</span><span class="k">new</span> <span class="nx">Event</span><span class="p">(</span><span class="s1">'input'</span><span class="p">))</span> <span class="p">}</span> <span class="p">})</span> <span class="p">}</span>
} export default inputNumber
使用:整数直接给输入框加v-inputNumber,带小数的加上参数v-inputNumber.float=“2”
<template> 讯享网<span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"input-wrapper"</span><span class="p">></span> <span class="p"><</span><span class="nt">el-input</span> <span class="na">v-model</span><span class="o">=</span><span class="s">"value1"</span> <span class="na">placeholder</span><span class="o">=</span><span class="s">"整数"</span> <span class="na">v-inputNumber</span><span class="p">></</span><span class="nt">el-input</span><span class="p">></span> <span class="p"><</span><span class="nt">el-input</span> <span class="na">v-model</span><span class="o">=</span><span class="s">"value2"</span> <span class="na">placeholder</span><span class="o">=</span><span class="s">"保留两位小数"</span> <span class="na">v-inputNumber</span><span class="err">.</span><span class="na">float</span><span class="o">=</span><span class="s">"2"</span><span class="p">></</span><span class="nt">el-input</span><span class="p">></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span>
</template> <script> <span class="kr">export</span> <span class="k">default</span> <span class="p">{</span> <span class="nx">data</span> <span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">{</span> <span class="nx">value1</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span> <span class="nx">value2</span><span class="o">:</span> <span class="s1">''</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span>
</script>
实现:给指定页面添加背景水印思路:
- 使用
canvas特性生成base64格式的图片文件,设置其字体大小,颜色等。 - 将其设置为背景图,实现水印效果
讯享网function addWaterMarker (str, parentNode, font, textColor) { <span class="c1">// 水印文字,父元素,字体,文字颜色
var can = document.createElement(’canvas‘) 讯享网<span class="nx">parentNode</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">can</span><span class="p">)</span> <span class="nx">can</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="mi">200</span> <span class="nx">can</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="mi">150</span> <span class="nx">can</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="s1">'none'</span> <span class="kd">var</span> <span class="nx">cans</span> <span class="o">=</span> <span class="nx">can</span><span class="p">.</span><span class="nx">getContext</span><span class="p">(</span><span class="s1">'2d'</span><span class="p">)</span> <span class="nx">cans</span><span class="p">.</span><span class="nx">rotate</span><span class="p">((</span><span class="o">-</span><span class="mi">20</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">PI</span><span class="p">)</span> <span class="o">/</span> <span class="mi">180</span><span class="p">)</span> <span class="nx">cans</span><span class="p">.</span><span class="nx">font</span> <span class="o">=</span> <span class="nx">font</span> <span class="o">||</span> <span class="s1">'16px Microsoft JhengHei'</span> <span class="nx">cans</span><span class="p">.</span><span class="nx">fillStyle</span> <span class="o">=</span> <span class="nx">textColor</span> <span class="o">||</span> <span class="s1">'rgba(180, 180, 180, 0.3)'</span> <span class="nx">cans</span><span class="p">.</span><span class="nx">textAlign</span> <span class="o">=</span> <span class="s1">'left'</span> <span class="nx">cans</span><span class="p">.</span><span class="nx">textBaseline</span> <span class="o">=</span> <span class="s1">'Middle'</span> <span class="nx">cans</span><span class="p">.</span><span class="nx">fillText</span><span class="p">(</span><span class="nx">str</span><span class="p">,</span> <span class="nx">can</span><span class="p">.</span><span class="nx">width</span> <span class="o">/</span> <span class="mi">10</span><span class="p">,</span> <span class="nx">can</span><span class="p">.</span><span class="nx">height</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span> <span class="nx">parentNode</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">backgroundImage</span> <span class="o">=</span> <span class="s1">'url('</span> <span class="o">+</span> <span class="nx">can</span><span class="p">.</span><span class="nx">toDataURL</span><span class="p">(</span><span class="s1">'image/png'</span><span class="p">)</span> <span class="o">+</span> <span class="s1">')'</span>
} const waterMarker = { <span class="nx">bind</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">el</span><span class="p">,</span> <span class="nx">binding</span><span class="p">)</span> <span class="p">{</span> <span class="nx">addWaterMarker</span><span class="p">(</span><span class="nx">binding</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">text</span><span class="p">,</span> <span class="nx">el</span><span class="p">,</span> <span class="nx">binding</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">font</span><span class="p">,</span> <span class="nx">binding</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">textColor</span><span class="p">)</span> <span class="p">}</span>
} export default waterMarker
使用:给DOM节点加上v-waterMarker,并设置水印文案,字体大小及颜色等
讯享网<template> <span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"water-marker"</span> <span class="na">v-waterMarker</span><span class="o">=</span><span class="s">"waterMarker"</span><span class="p">></</span><span class="nt">div</span><span class="p">></span>
</template> <script> 讯享网<span class="kr">export</span> <span class="k">default</span> <span class="p">{</span> <span class="nx">waterMarker</span><span class="o">:</span> <span class="p">{</span> <span class="nx">text</span><span class="o">:</span> <span class="s1">'Fuleny版权所有'</span><span class="p">,</span> <span class="nx">font</span><span class="o">:</span> <span class="s1">'14px Microsoft JhengHei'</span><span class="p">,</span> <span class="nx">textColor</span><span class="o">:</span> <span class="s1">'rgba(180, 180, 180, 0.4)'</span> <span class="p">}</span> <span class="p">}</span>
</script> <style lang=“scss” scoped> .water-marker { width: 400px; height: 400px; border: 1px solid #eee; } </style>

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/150151.html