关于vue.js:Yank-Note-系列-04-编辑和预览同步滚动方案

Yank Note 是我编写的一款面向程序员的笔记利用。这里我将会写下一些对于 Yank Note 的文章Yank Note 实现了从源码到预览的单向同步滚动。钻研了一些同步滚动计划,大体分为上面三种。 行号匹配Yank Note 最后就是采纳的这种计划,简略间接,前提是渲染出的 Dom 节点须要蕴含行号信息。 监听编辑器可视区域变动/滚动事件,获取顶部的行号拿着行号找到对应行号的 Dom 元素调用 Dom 元素的 scrollIntoView 办法这个计划长处是比拟精准,毛病是行号匹配不到时候滚动不太好,也不够平滑。 等比例滚动这个计划就是不论行号匹配,间接依照编辑器滚动高度和可视区域滚动高度来等比例设定滚动条地位。 这个计划长处是不必计算行号,适宜 textarea 做编辑器的场景,滚动也比拟平滑。毛病则是滚动不太精准,特地是有图片的时候。 综合计划依据题目做等比例滚动掘金的 Markdown 编辑器是应用的字节的 Bytemd 编辑器。 每次编辑器滚动,题目到顶时候,始终保障题目元素是平齐的。也就是两个题目之间的滚动做等比例滚动。 长处是滚动十分平滑,也兼顾了一部分滚动精确性,只能准确到题目。 行号匹配和等比例联合Markdown-it Demo 是用的这种算法。 在行号匹配的根底上: 如果能找到对应行号的 Dom 元素,间接将对应 Dom 元素滚动到可视区域顶部。如果不能找到对应行号的 Dom 元素,那就获取该行号的上一个 Dom 元素 和下一个 Dom 元素。间接依据行号偏移做等比例滚动。这个计划对很多行源码生成一个比拟高的元素,如围栏和代码块等,成果较好。对单行元素生成较高的元素如图片则成果还是差一点(比间接行号匹配强)。 因为 Yank Note 里这种场景比拟多(Mermaid 图形、脑图、HTML 小工具等),滚动平滑度我集体感觉不是那么重要,所以最终采纳了这个计划。 进一步在应用 Sublime Merge 的过程中,我发现它的解决抵触界面,同步滚动做得比拟合乎我的预期。这种交互我感觉能够再钻研一下,看能不能作为 Markdown 编辑和预览同步滚动的另一种办法。 参考markdown编辑与预览窗口同步算法 - 少年小白 - 博客园markdown-it.github.io/index.js at masterbytemd/editor.svelte at main如果你对 Yank Note 感兴趣,想应用或者参加奉献,能够到 Github 理解更多。 ...

March 21, 2022 · 1 min · jiezi

关于vue.js:Yank-Note-系列-03-同内存泄露的艰难战斗

Yank Note 是我编写的一款面向程序员的笔记利用。这里我将会写下一些对于 Yank Note 的文章前言在前一篇文章里,我讲述了给 Yank Note 做性能优化的一些事件。然而影响一个利用体验的,也不仅仅是性能,还包含性能是否满足用户需要、界面是否好看、交互操作是否正当晦涩、运行是否稳固。 要保障利用稳固运行,除了 Bug 要少,内存占用也要尽量少和稳固。Yank Note 是 Web 技术开发的,通常的 Web 利用无需太关怀内存占用,因为用户用完即走,切实卡顿了还能刷新页面解决。而 Yank Note 不一样,用户通常会始终关上利用,而当初的计算机通常也会很久都不关机,所以管制内存占用,缩小内存泄露就成为 Yank Note 稳固运行必须要做的工作。 在 v3.14.2 版本,Yank Note 针对内存方面做了不少优化,本文将形容在解决内存泄露过程中所做的事件。 试验形式和排查工具试验场景Yank Note 在应用过程中,用户会进行较多的文字输出,也会进行文档切换。两个场景是潜在内存泄露的重点关注中央。 得益于 Yank Note 凋谢的 Api,我只须要写几句脚本,即可在实在利用中模仿下面两个操作。 渲染脚本:模仿文本输出后的渲染 for (let i = 0; i < 100; i ++) { ctx.view.render(); await ctx.utils.sleep(50);}文档切换脚本:模仿文档切换 for (let i = 0; i < 30; i ++) { await ctx.doc.showHelp('FEATURES.md'); await ctx.utils.sleep(1000); await ctx.doc.showHelp('README.md'); await ctx.utils.sleep(2000);}工具应用 Chrome 隐衷标签,排除插件烦扰。用到了 Chrome 的性能监视器面板,内存面板,性能面板。 ...

March 21, 2022 · 2 min · jiezi

关于vue.js:Yank-Note-系列-02-Markdown-渲染性能优化之路

Yank Note 是我编写的一款面向程序员的笔记利用。这里我将会写下一些对于 Yank Note 的文章前言在 Yank Note 开发初期,根本没思考过性能这方面的问题。前面因为一些性能问题影响到了交互体验,开始着手优化性能。 这里我将形容为性能优化所做的工作: 引入虚构 DOM提早简单内容渲染微调用户体验引入虚构 DOMYank Note 采纳 Markdown-it 做解析器。Markdown-it 默认应用 HTML 输入。因而在 Yank Note 3.0 以前,都是采纳给渲染容器 innerHTML 间接赋值的形式来做渲染。 从上图能够看到,HTML 直出毛病很显著,每一次编辑文本,都要走一遍渲染流程,从新构建的 HTML。在每一次打字编辑的时候,都会造成页面从新布局,一些嵌入的性能如脑图、小工具也会重头渲染,导致页面闪动,用户体验差。 所以这里须要想方法做增量渲染,即在编辑时候,只渲染变动局部。 考查了一些增量渲染计划,如 markdown-it-incremental-dom。因为自身这个我的项目应用 Vue,用 Vue 也能更好和 Vue 组件配合工作,所以前面决定应用 Vue 虚构 DOM 的形式来做增量渲染。 收益从交互角度,分为两个局部来探讨: 首次渲染: 关上一个新的文件,HTML 直出性能和 虚构 DOM 相差不大,或者比虚构 DOM 还好一些,毕竟省略了虚构节点结构的流程。编辑渲染: 编辑文件,生成的 Vue VNode 虚构 DOM 来,后续的渲染均由 Vue 优化接管,做增量渲染,这一点就比 HTML 性能高了太多了。增量渲染的益处不仅仅是在编辑时候有更高的渲染性能,也能更好的反对 Vue 组件,优化如脑图、HTML 小工具等的交互体验。 具体实现1. Token 流转换 在这个文件里,我将 Markdown-it 默认的 HTML 渲染器,替换为了本人的渲染器。 ...

March 21, 2022 · 2 min · jiezi

关于vue.js:Yank-Note-系列-01-为什么要自己写笔记软件

Yank Note 是我编写的一款面向程序员的笔记利用。这里我将会写下一些对于 Yank Note 的文章tl;dr 我想做一款我本人(用户)能够齐全掌控的笔记软件 缘起从大学开始,我就始终在尝试应用各种笔记软件,从 Sublime Text 间接编辑文件,到应用印象笔记、OneNote等各种云笔记,最初我抉择了为知笔记。 然而起初,为知笔记开始免费,越来越臃肿,反馈慢,Bug 也多。最终,我放弃了它。备份笔记花了一些功夫,我开始不再置信云产品,本人电脑上的才是真正属于本人的。 于是我开始寻找一款本地笔记软件,断断续续是用了不少产品,这些当中只有 Joplin 让我应用工夫最长,但也不是齐全称心。前面,我还是回退到了应用 Sublime Text 写 Markdown 的计划。 最终在三年前的某一天,我决定本人写一个供本人应用的笔记工具。花了一个下午工夫,我拼凑出了第一个能够应用的版本。应用 Node.js 做服务端,Vue 做前端。性能简陋到什么水平呢?只有读取和写入文件的性能;没有按钮,只有快捷键;界面随便搭在一起。完完全全就是一个性能简陋且俊俏的记事本。 我过后也不晓得本人须要什么,要做出什么。起初我才缓缓找到这样的指标 界面字大不费眼 :)Markdown 撰写图片文件保留在本地,导出 Markdown 文件可简略解决离线工作反对一些流程图表绘制 plantuml mermaid反对加密解密,用来保留账号等隐衷文件,文件可独自设置明码不须要更多花哨性能,简略够用就行尽量少依赖三方库,也不花心思保护,杂凑在一起的性能,恰好工作即可 ^_^在文档中运行 PHP Python Node.js 代码块而这时候,界面长这样 总的来说,这时候我还是都是依照本人想法来做这个利用,齐全不思考是否好看,是否不便,只有性能满足本人需要即可。 起初,我的一位共事感觉这个利用很实用,开始应用它。我在掘金上发了一篇文章 Markdown 编辑器 (自家用),掘友 @KDA 评论道:“求更新欠缺,很好用”。于是我开始想,这个货色除了我本人,是不是能够再欠缺欠缺,给其他人应用。 然而这时,才发现我还没给这个编辑器取一个名字。GitHub 仓库名是随便起的“yn”,所以顺着这个,正式起名 “Yank Note” 为了不便其他人应用,也决定采纳 Electron 开发,不便启动运行。 进化这里再思考一下,我到底须要一款怎么的笔记利用,最初我定了上面几点准则,依照重要性排序 回绝云服务: 数据要保留在本地,利用也要在本机运行,备份和同步的需要能够走 Git 或者云盘,私人笔记,无需合作性能。甚至于图床之类,也不要应用,就存本地。回绝专有格局: 应用 Markdown 文件保留所有信息,不要给我什么 json 格局或者其余格局的,万一我哪一天弃用这个软件,要能很不便迁徙。开源: 一款软件,我要短暂应用,那么不能是关闭的。如果哪一天利用作者不更新了,我要能本人 fork 一份来本人维持根本的运行。可拓展: 这款利用要具备足够的拓展性,我遇到一些本人特定的需要,或者应用软件有不棘手的中央,我要能本人写插件或者拓展来批改和实现。克服: 利用须要放弃简略,不要太臃肿,不要太花哨,不要有过多动画烦扰我的操作。比方 Notion 的菜单动画,第一工夫感觉很炫酷,然而应用一下就会感觉很拖沓。而它的一些工具提醒又展现太快,变成操作的烦扰项。这里我十分认同 Sublime Merge 的理念。Sublime Merge 是我应用过的体验最好的 Git 客户端,应用它是一种享受。跨平台: 我同时应用 Windows,macOS,Android,iOS。我须要让这款利用是跨平台的,至多在桌面端是要跨平台的。不过我的大部分笔记和文章都是在电脑上实现,手机上只须要记录一些灵感即可,这种需要很多软件都能实现得很好,比方 iOS 自带的备忘录。另外既然文件格式也不是专属,也不绑定云服务,那么很多挪动端的 Markdown 编辑器也能很好的工作,在挪动端跨平台的需要就没那么强烈。下面的准则都是为了让我能够短暂应用一款软件。如果你认同下面的观点,那么 Yank Note 或者是你的菜。 ...

March 21, 2022 · 1 min · jiezi

关于vue.js:ModStartCMS模块化建站系统-v350-多图字段支持系统优化升级

ModStartCMS是基于Laravel的全栈极速开发CMS框架,反对动静模型配置和多模板反对,轻松搭建CMS内容管理系统。 ModStartCMS公布v3.5.0版本,新性能和Bug修复累计11项,多图字段反对,系统优化降级。 2022年03月21日ModStartCMS公布v3.5.0版本,减少了以下11个个性: ·[新性能] 后盾利用详情显示零碎名称 ·[新性能] 内容列表文件、日期字段显示异样问题 ·[新性能] 用户VIP界面优化,减少主题目和副标题 ·[新性能] 用户VIP页面不登录拜访不受限 ·[新性能] 后盾链接减少抉择找回明码 ·[新性能] 模块配置 suggest 配置,倡议装置模块 ·[系统优化] 标签款式显示方式 ·[系统优化] 表格刷新主动跳转到顶部优化 ·[系统优化] 表格无数据时内容优化(减少图标) ·[Bug修复] 后盾链接抉择同类别不能主动合并问题 ·[Bug修复] 后盾浮动确定区域左侧宽度问题调整 ModStartCMS,基于 Laravel 模块化极速开发框架,基于 Apache 2.0 开源协定,收费且不限度商业应用。。 运行环境: Laravel 5.1 版本 PHP 5.6 PHP 7.0MySQL >=5.0PHP Extension:FileinfoApache/NginxLaravel 9.0 版本 PHP 8.0 PHP 8.1MySQL >=5.0PHP Extension:FileinfoApache/Nginx咱们的测试基于 PHP 的 5.6 / 7.0 / 8.0 / 8.1 版本,零碎稳定性最好零碎演示:https://cms.demo.tecmz.com/ 下载试用:https://modstart.com/download

March 21, 2022 · 1 min · jiezi

关于vue.js:金三银四的-Vue-面试准备

前言为了金三银四的跳槽季做筹备,并且我是 vue 技术栈的,所以整顿了若干个 vue 的面试题。 每次看他人的博客,都会不自主的去看答案,为了不便测验本人的把握水平,我特意将答案折叠起来,大家能够先看题目,在脑海中设想一下如果你被问到会怎么答复,而后再开展答案看看和本人的答案有什么不同。 答案非官方,仁者见仁智者见智,仅供参考。 根底应用MVVM、MVC有什么区别MVC 通过拆散 Model、View 和 Controller 的形式来组织代码构造。 其中 View 负责页面的显示逻辑,Model 负责存储页面的业务数据,以及对相应数据的操作。Controller 层是 View 层和 Model 层的纽带,它次要负责用户与利用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来实现对 Model 的批改,而后 Model 层再去告诉 View 层更新。MVVM 分为 Model、View、ViewModel。 Model 代表数据模型,数据和业务逻辑都在 Model 层中定义;View 代表 UI 视图,负责数据的展现;ViewMode 负责监听 Model 中数据的扭转并且管制视图的更新,解决用户交互操作;Model 和 View 并无间接关联,而是通过 ViewModel 来进行分割的,Model 和 ViewModel 之间有着双向数据绑定的分割。因而当 Model 中的数据扭转时会触发 View 层的刷新,View 中因为用户交互操作而扭转的数据也会在 Model 中同步。 这种模式实现了 Model 和 View 的数据主动同步,因而开发者只须要专一于数据的保护操作即可,而不须要本人操作 DOM。 Vue 并没有齐全遵循 MVVM 思维呢? 严格的 MVVM 要求 View 不能和 Model 间接通信,而 Vue 提供了 $refs 这个属性,让 Model 能够间接操作 View,违反了这一规定,所以说 Vue 没有齐全遵循 MVVM。Vue的长处渐进式框架:能够在任何我的项目中轻易的引入; ...

March 21, 2022 · 11 min · jiezi

关于vue.js:手写-Vue2-系列-之-computed

当学习成为了习惯,常识也就变成了常识。 感激各位的 关注、点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 前言上一篇文章 手写 Vue2 系列 之 patch —— diff 实现了 DOM diff 过程,实现页面响应式数据的更新。 指标本篇的指标是实现 computed 计算属性,实现模版中计算属性的展现。波及的知识点: 计算属性的实质计算属性的缓存原理实现接下来就开始实现 computed 计算属性,。 _init/src/index.js/** * 初始化配置对象 * @param {*} options */Vue.prototype._init = function (options) { // ... // 初始化 options.data // 代理 data 对象上的各个属性到 Vue 实例 // 给 data 对象上的各个属性设置响应式能力 initData(this) // 初始化 computed 选项,并将计算属性代理到 Vue 实例上 // 联合 watcher 实现缓存 initComputed(this) // 装置运行时的渲染工具函数 renderHelper(this) // ...}initComputed/src/initComputed.js/** * 初始化 computed 配置项 * 为每一项实例化一个 Watcher,并将其 computed 属性代理到 Vue 实例上 * 联合 watcher.dirty 和 watcher.evalute 实现 computed 缓存 * @param {*} vm Vue 实例 */export default function initComputed(vm) { // 获取 computed 配置项 const computed = vm.$options.computed // 记录 watcher const watcher = vm._watcher = Object.create(null) // 遍历 computed 对象 for (let key in computed) { // 实例化 Watcher,回调函数默认懒执行 watcher[key] = new Watcher(computed[key], { lazy: true }, vm) // 将 computed 的属性 key 代理到 Vue 实例上 defineComputed(vm, key) }}defineComputed/src/initComputed.js/** * 将计算属性代理到 Vue 实例上 * @param {*} vm Vue 实例 * @param {*} key computed 的计算属性 */function defineComputed(vm, key) { // 属性描述符 const descriptor = { get: function () { const watcher = vm._watcher[key] if (watcher.dirty) { // 阐明以后 computed 回调函数在本次渲染周期内没有被执行过 // 执行 evalute,告诉 watcher 执行 computed 回调函数,失去回调函数返回值 watcher.evalute() } return watcher.value }, set: function () { console.log('no setter') } } // 将计算属性代理到 Vue 实例上 Object.defineProperty(vm, key, descriptor)}Watcher/src/watcher.js/** * @param {*} cb 回调函数,负责更新 DOM 的回调函数 * @param {*} options watcher 的配置项 */export default function Watcher(cb, options = {}, vm = null) { // 备份 cb 函数 this._cb = cb // 回调函数执行后的值 this.value = null // computed 计算属性实现缓存的原理,标记以后回调函数在本次渲染周期内是否曾经被执行过 this.dirty = !!options.lazy // Vue 实例 this.vm = vm // 非懒执行时,间接执行 cb 函数,cb 函数中会产生 vm.xx 的属性读取,从而进行依赖收集 !options.lazy && this.get()}watcher.get/src/watcher.js/** * 负责执行 Watcher 的 cb 函数 * 执行时进行依赖收集 */Watcher.prototype.get = function () { pushTarget(this) this.value = this._cb.apply(this.vm) popTarget()}watcher.update/src/watcher.js/** * 响应式数据更新时,dep 告诉 watcher 执行 update 办法, * 让 update 办法执行 this._cb 函数更新 DOM */Watcher.prototype.update = function () { // 通过 Promise,将 this._cb 的执行放到 this.dirty = true 的前面 // 否则,在点击按钮时,computed 属性的第一次计算会无奈执行, // 因为 this._cb 执行的时候,会更新组件,获取计算属性的值的时候 this.dirty 仍然是 // 上一次的 false,导致无奈失去最新的的计算属性的值 // 不过这个在有了异步更新队列之后就不须要了,当然,毕竟异步更新对象的实质也是 Promise Promise.resolve().then(() => { this._cb() }) // 执行完 _cb 函数,DOM 更新结束,进入下一个渲染周期,所以将 dirty 置为 false // 当再次获取 计算属性 时就能够从新执行 evalute 办法获取最新的值了 this.dirty = true}watcher.evalute/src/watcher.jsWatcher.prototype.evalute = function () { // 执行 get,触发计算函数 (cb) 的执行 this.get() // 将 dirty 置为 false,实现一次刷新周期内 computed 实现缓存 this.dirty = false}pushTarget/src/dep.js// 存储所有的 Dep.target// 为什么会有多个 Dep.target?// 组件会产生一个渲染 Watcher,在渲染的过程中如果解决到用户 Watcher,// 比方 computed 计算属性,这时候会执行 evalute -> get// 如果间接赋值 Dep.target,那 Dep.target 的上一个值 —— 渲染 Watcher 就会失落// 造成在 computed 计算属性之后渲染的响应式数据无奈实现依赖收集const targetStack = []/** * 备份本次传递进来的 Watcher,并将其赋值给 Dep.target * @param {*} target Watcher 实例 */export function pushTarget(target) { // 备份传递进来的 Watcher targetStack.push(target) Dep.target = target}popTarget/src/dep.js/** * 将 Dep.target 重置为上一个 Watcher 或者 null */export function popTarget() { targetStack.pop() Dep.target = targetStack[targetStack.length - 1]}后果好了,到这里,Vue computed 属性实现就实现了,如果你能看到如下效果图,则阐明一切正常。 ...

March 21, 2022 · 3 min · jiezi

关于vue.js:最好的-6-款-React-后台管理系统模板和框架

本文首发:《最好的 6 款 React 后盾管理系统模板和框架》 React admin框架繁多,在本文里咱们介绍 React 下最好的 6 款后盾零碎,每款均严格测试后,整顿它们的优缺点不便你来筛选。同时咱们给出一些实用倡议,帮你防止选型时不留神可能导致的埋坑。 为什么后盾零碎极其重要一个成熟好用的后盾管理系统对于公司经营效率有极大晋升,而对程序员来说,很多时候写后盾零碎其实都是在“帮个忙”,起因是这些后盾零碎少数状况下和公司的主营业务并没有太大关系。 比如说,经营会请你写个零碎来帮忙治理商品,人事会请你写个零碎来治理公司人员等等。因而,找一个称手的模板和框架能够省下大量的工夫。如果须要马上搭建马上能上线的后盾零碎,能够间接思考卡拉云(见后文),比模板和二次开发更快更不便,同时功能丰富得多。 如何筛选 React admin后盾有的时候抉择一个后盾零碎框架时,一个开始没留神到的小问题可能导致之后零碎齐全无奈应用。举个例子,如果你的整套零碎用了 SCSS 来写界面,而这个后盾框架并不反对 SCSS,那么你还须要额定的精力把 SCSS 集成进去。因而,在下文评测中咱们尽量把这些细节展现进去。 咱们用以下一些规范来评判 React 下的后盾零碎模板。 开发者体验首先后盾零碎要能用得起来,开发者(也就是你)得尽量少花功夫在这个框架上,也就是说这个框架自身的 API、组件或者接口须要设计得足够好,足够能用。否则,如果须要大量蛮力适配(brute-force)能力满足需要的话,还不如不必框架本人写好了。 权限治理一个后盾零碎是不是具备齐备的权限治理,对于绝大多数公司来说都极为重要。比方,很难设想让一个公司请的外包经营成员来间接操作公司的用户数据库。而正确的做法是给他们调配适合的权限,让一部分人员只能操作限定的动作。 权限治理说起来简略,然而绝大多数前端模板中是无奈实现的,必须配合后端。而是不是有好的接口设计就决定了与后端的权限治理对接时是否足够容易。 组件丰盛水平有一些组件对于一些后盾来说极为重要,比如说如果你的后盾是一个员工报销零碎,那么工夫选择器可能就十分重要,因为可能须要注明发票报销的工夫。如果你的后盾须要展现服务器运行工夫、用户数,那么报表、图表组件就很重要。 对绝大多数后盾来说,表格组件也十分重要。然而不论是表格、图表还是工夫选择器,都是实现比拟艰难的前端组件。所以要找到一个框架要刚好把这三个组件都做得好,是不太容易的事件。 因而组件的丰盛水平和欠缺度,也是筛选后盾零碎的重要规范。 React 后盾零碎的类别React 后盾零碎能够粗略地分为以下几种类别 仅前端模板前后端集成系统,须要数据迁徙前后端集成且有不便的数据接口对于 1,适宜后盾曾经成型,所有后盾 API、数据库连贯均曾经包好的状况下。 第一种是指只有前端代码的 React 后盾零碎。当然,后盾零碎显然是用来操作后端数据的,所以不接“后端”的后盾零碎是不存在的,选用这种后盾零碎的话,你须要本人提供后端接口和数据库接口等。这个类型的代表有 antd 和各种纯 UI 的 react 框架,比方 Material UI, Mi-UI 等。 第二种是指不仅有前端代码,同时也蕴含了后端服务。如果须要一些新的后端接口,则须要调整和开发后端接口。通常来说,这类后盾零碎都是打包销售,比方 SAP,SalesForce 发售的一些 SaaS 后盾零碎,本文里就不展开讨论了。 第三种是指,前端后端集成,但不须要数据迁徙。框架提供了数据接口,你只须要把你的数据接入即可应用。这个类型的代表有卡拉云。 那么什么状况下抉择哪种后盾零碎呢? 状况一 如果你曾经有一个成型的后盾零碎,当初只须要把后盾界面做得更不便、丑陋,或者减少一些组件的状况下:,你能够应该抉择计划 1 或者计划 3。 起因是你曾经十分明确你须要哪些组件,工作流的流程怎么,甚至曾经有后端接口能够间接接入,那么二次开发一下前端框架可能就能够满足你的需要了。或者用计划 3,能够比拟轻松地把数据接口接入,而不必对后盾接口做太多调整。 状况二 还没有后端,只有数据库。 这个状况应该是绝大多数守业公司在寻找第一个后盾零碎时遇到的状况,工程师资源全副集中在公司业务上,没有人有精力和工夫来开发后盾零碎。然而后盾零碎又极为重要,所以开始急冲冲地找一个计划。 在这种状况下,咱们举荐应用计划 3,起因是后端接口还不成型,工作流不清晰可能还须要大量迭代。这种状况下间接开始固化后端是不明智的。 状况三 没有前端,只有后端接口。这样的状况在守业公司,甚至不少大厂中也占有肯定比例。没有前端的状况下,有时候开发者甚至会间接采纳间接查问和批改数据库的形式来应用“后盾零碎”。 在这种状态下,也举荐应用计划 3,间接将数据库接入,同时用拖拽、配置的形式生成前端。 react-adminReact-admin 是一款提供前端组件,同时还提供后端数据接口的框架。目前它在 github 上有 19.1K 的星。留神,通常来说咱们讲的“react后盾零碎”个别英文翻译为react admin,而这款框架自身比拟讨巧,名字自身就叫react-admin,请留神不要混同了概念。 ...

March 21, 2022 · 1 min · jiezi

关于vue.js:vue2版本中slot的基本使用详解

前言在vue的开发过程中,咱们会常常应用到vue的slot插槽组件,vue官网文档的形容: Vue 实现了一套内容散发的 API,这套 API 的设计灵感源自 Web Components 标准草案,将 <slot> 元素作为承载散发内容的进口slot大略分为以下几种: 根底slot组件(匿名插槽)匿名插槽次要应用场景并不波及特地简单的业务,更像是纯展现组件内容 <!--子组件--><template> <span> 我是根底slot子组件, 父组件传过来的值: <span style="color: red"><slot></slot></span> </span></template><!--父组件--><li> 根底slot组件(匿名插槽):<Base>这是一段父组件传过来的文字</Base></li>import Base from "./Base.vue";具名插槽具名插槽,须要在父组件和子组件约定插槽名称 <!--子组件--><template> <span> <span style="color: red"> <slot name="name1"></slot> <slot name="name2"></slot> </span> </span></template><!--父组件--><li> <p>具名插槽:</p> <Specific> <template v-slot:name1> <p>name1传过来的内容</p> </template> <template v-slot:name2> <p>name2传过来的内容</p> </template> </Specific></li>import Specific from "./Specific.vue";作用域插槽作用域插槽,子组件提供数据,父组件接管子组件的值并展现和解决逻辑 <!--子组件--><template> <span> <span> <slot name="scopeName" v-bind:scopeData="age"></slot> </span> </span></template><script lang="ts">import { Component, Vue, Prop } from "vue-property-decorator";@Componentexport default class Scope extends Vue { private age: Number = 23;}</script><!--父组件--><li> <p>作用域插槽</p> <Scope> <template v-slot:scopeName="childData"> 作用域子组件slot返回的数据: <span style="color: red"> {{ childData.scopeData }} </span> </template> </Scope></li>import Specific from "./Specific.vue";解构插槽解构插槽,相似在js书写对象过程中的对象解构 ...

March 21, 2022 · 1 min · jiezi

关于vue.js:手把手教你用-Vue-搭建带预览的上传图片管理后台

本文首发:《Vue 搭建带预览的「上传图片」治理后盾》 手把手教你开发带预览的 Vue 图片上传组件,即图片上传治理后盾。只有你跟本教程一步一步走,最终能很好的了解整个前后端传图的工程逻辑。前端咱们应用 Vue + Axios + Multipart 来搭建前端上传图片利用,后端咱们应用 Node.js + Express + Multer 来搭建后端上传图片的后端解决。 本教程还会教给大家如何写一个可限度上传图片大小,有进度条,可报错,可显示图片列表,可下载已上传图片的图片治理后盾。 最初实现的上传图片工具后盾如下图,追随本教学习,你也能够搭进去。 如果你对前端不是很相熟,不想写前端,举荐应用卡拉云搭建后盾管理系统,卡拉云是新一代低代码开发工具,不必懂任何前端技术,仅靠鼠标拖拽,即可疾速搭建包含「图片上传」在内的任何后盾管理工具。立刻试用卡拉云 1 分钟搭建「图片上传」工具。详情见本文文末。 Vue + Node.js「图片上传」前后端我的项目构造 Vue 前端局部 UploadFilesService.js:这个脚本调用通过 Axios 保留文件和获取文件的办法UploadFiles.vue:这个组件蕴含所有图片上传相干的信息和操作App.vue:把咱们的组件导入到 Vue 起始页index.html:用于导入 Bootstraphttp-common.js:配置并初始化 Axiosvue.config.js:配置 APP 端口Node.js 后端局部 resources/static/assets/uploads:用于存储上传的文件middleware/upload.js:初始化 Multer 引擎并定义中间件file.controller.js:配置 Rest APIroutes/index.js:路由,定义前端申请后端如何执行server.js:运行Node.js Express 利用✦ 前端局部 - Vue + Vue 图片上传组件 + Axios + Multipart配置 Vue 环境应用 npm 装置 Vue 脚手架 vue-cli npm install -g @vue/cli而后咱们创立一个 Vue 我的项目 kalacloud-vue-upload-image vue create kalacloud-vue-upload-image装置实现后,cd 进入 kalacloud-vue-upload-image 目录,接下来所有操作都在这个目录之下。 ...

March 21, 2022 · 6 min · jiezi

关于vue.js:打包发布你的第一个vue组件到npm

如果在你的工作中用到了vue,那么你可能会发现同一个组件常常在你的利用中会被反复的应用。兴许你写了一个十分有价值的组件,想要和他人分享,那么把它打包公布到npm是一个不错的抉择。本文将以一个音乐播放器为例,一步步介绍如何分装组件、打包、公布。那么让咱们开始吧!装置vue-cli应用上面命令装置vue-cli,如果你已装置能够跳过这一步 npm install -g @vue/cli# ORyarn global add @vue/cli如果你不确定是否装置过,能够应用上面的命令检查一下: vue --version@vue/cli 4.5.15呈现版本号,则表明你曾经胜利装置。 创立一个我的项目运行以下命令来创立一个新我的项目: vue create leo-player执行命令后会有一些选项,能够参考 这里 抉择,直到我的项目目录生成。 在你的IDE中关上我的项目,是上面的样子,除去红框里的两个文件。红框的两个文件是公布npm package须要用到的。 当然你能够把public和src下的assets、components 目录和 main.js 都删除,因为公布组件不须要它们。不过临时留着能够不便你测试本人组件是不是你预期的样子。 先看下LeoPlayer.vue <template> <div class="leo_drawer"> ... </div></template><script>...</script><style scoped>...</style>就是规范的vue模板文件,细节省略不再赘述。 配置vue插件的入口文件首先咱们要给咱们的插件指定入口,不便他人在他们的我的项目中装置应用。 VueJS 插件的装置个别是用 Vue.use() 办法,所以咱们指定的入口文件须要蕴含一个 install 办法,如下: // install.jsimport LeoPlayer from "./LeoPlayer";export default { install: (Vue) => { Vue.component("leo-player", LeoPlayer) }}配置 Package.json咱们须要在 Package.json 文件中做一系列配置,来反对后续的打包公布,按上面的步骤进行: scriptsvue-cli-service build --target lib --name myLib [entry] 在 package.json 的 scripts 中增加 build-library,对照下面的命令myLib 是你公布的package的名称。 "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "build-library" : "vue-cli-service build --target lib --name leo-player ./src/install.js", "lint": "vue-cli-service lint"},在你的终端执行,npm run build-library,失去上面编译后的目录文件: ...

March 19, 2022 · 2 min · jiezi

关于vue.js:Vue3Nuxt3打造SSR网站应用0到1实现服务端渲染

download:Vue3+Nuxt3打造SSR网站利用,0到1实现服务端渲染一、网络层面 DNS预解析概念DNS-prefetch 是一种 DNS 预解析技术。它会在请求跨域资源之前,事后解析并进行DNS缓存,以缩小真正请求时DNS解析导致的请求提早。对于打开蕴含有许多第三方连接的网站,成果显著。实操增加ref属性为“dns-prefetch”的link标签。一般放在在html的head中。复制代码href的值就是要预解析的域名,对应前面要加载的资源或用户有可能打开链接的域名。备注同理,也有“ TCP/IP预连接”,叫preconnect。参考资料中有完整的描述。利用阅读器缓存概念阅读器缓存是阅读器存放在本地磁盘或者内存中的请求后果的备份。当有雷同请求进来时,间接响应本地备份,而无需每次都从原始服务器获取。这样不只晋升了客户端的响应效率,同时还能缓解服务器的拜访压力。其间,约定何时、如何使用缓存的规定,被称为缓存策略。分为强缓存和协商缓存。整个缓存执行的过程大抵如下:①. 请求发动,阅读器判断本地缓存,如果有且未到期,则命中强缓存。阅读器响应本地备份,状态码为200。控制台Network中size那一项浮现disk cache;②. 如果没有缓存或者缓存已过期,则请求原始服务器询问文件是否有变动。服务器根据请求头中的相干字段,判断目标文件新鲜度;③. 如果目标文件没变更,则命中协商缓存,服务器设置新的过期工夫,阅读器响应本地备份,状态码为304;④. 如果目标文件有变动,则服务器响应新文件,状态码为200。阅读器更新本地备份。上述过程有几个关键点如何判断缓存是否过期?阅读器读取缓存的请求后果中响应头的Expires 和Cache-Control,与以后工夫进行比较。其中,Expires是HTTP 1.0的字段,值是一个是相对工夫。Expires: Tue, 18 Jan 2022 09:53:23 GMT复制代码比较相对工夫,有一个弊病,它依赖计算机时钟被正确设置。为理解决这个问题,HTTP1.1 新增了Cache-Control字段,它的值是一个是绝对工夫。Cache-Control: max-age=60 //单位是秒复制代码 如何判断文件是否变动?首先可能通过比较 最初修改工夫。// 缓存后果的 响应头Last-Modified: Mon, 10 Jan 2022 09:06:14 GMT// 新请求的 请求头If-Modified-Since: Mon, 10 Jan 2022 09:06:14 GMT复制代码阅读器取出缓存后果中Last-Modified的值,通过If-Modified-Since上送到服务端。与服务器中目标文件的最初修改工夫做比较。再者可能通过比较 Etag。 Etag实体标签是附加到文档上的任意标签(引用字符串)。它们可能蕴含了文档的序列号或版本名,或者是文档内容的校验和及其他指纹信息。当发布者对文档进行修改时,会修改文档的实体标签来说明这是个新的版本。 从响应头的ETag取值,通过请求头的If-None-Match上送,与服务器目标文件的Etag标签比对。// 缓存的 响应头ETag: "61dbf706-142"// 上送的 请求头If-None-Match: "61dbf706-142"复制代码和下面一样,新增的字段也是为理解决前一种打算的某些缺点: 有些文档可能会被周期性地重写(比如,从一个后盾过程中写入),但实际蕴含的数据经常是一样的。尽管内容没有变动,但修改日期会发生变动。有些文档可能被修改了,但所做修改并不重要,不需要让世界范畴内的缓存都重装数据(比如对拼写或正文的修改)。有些服务器无奈准确地判定其页面的最初修改日期。有些服务器提供的文档会在亚秒间隙发生变动(比如,实时监视器),对这些服务器来说,以一秒为粒度的修改日期可能就不够用了。 如果两个版本的字段同时存在,怎么办?出于阅读器兼容方面的考虑 ,一般两组字段会被同时使用。他们没有优先级一说,取并集。同时出现时,只有当两个条件都满足,才会命中相应缓存。 实操缓存是web服务器和阅读器的核心能力,支流的web服务框架 nginx、koa-static等都内置有上述缓存策略的实现。开箱即用,无需额定编程或配置。以Nginx举例。强缓存的配置字段是expires,它接受一个数字,单位是秒。server {listen 8080;location / {root /Users/zhp/demo/cache-koa/static;index index.html; # 注意try_files会导致缓存配置不失效try_files $uri $uri/ /index.html;expires 60;}}复制代码实际工作中确实配置一下就好了,但这体现不出什么学识点。为了加深印象,我这用koa简陋的模拟了一下,算是对下面那些学识点的考据。上面是一个极简的动态资源服务,不带缓存的。app.use(async (ctx) => { // 1.根据拜访路径读取指定文件 const content = fs.readFileSync(./static${ctx.path}, "utf-8"); // 2.设置响应ctx.body = content;});复制代码这种情况,无论拜访几次都是不进缓存的。现在,在响应头加上强缓存所需的Exprise和Cache-Control字段app.use(async (ctx) => { // 1.根据拜访路径读取指定文件 const content = fs.readFileSync(./static${ctx.path}, "utf-8"); // 2.设置缓存 ctx.response.set("Cache-Control", "max-age=60"); ctx.response.set('Exprise', new Date(new Date().getTime()+60*1000));// 3.设置响应ctx.body = content;});复制代码查看Network,响应头会多出上面两个字段,且间隔60秒内的请求会走缓存,符合预期。Expires: Tue, 18 Jan 2022 10:05:09 GMTCache-Control: max-age=60复制代码备注抱着引用一手权威资料的想法,扒了《HTTP权威指南》,但读感着实差强人意。老手倡导《图解HTTP》起手,要敌对很多。参考资料 ...

March 18, 2022 · 2 min · jiezi

关于vue.js:elementUI编辑时清除表单检验

clearvali() { this.$refs.form.clearValidate() }, handleEdit(row) { this.open = true this.title = '批改工作类型' console.log('row', row) this.getDetail(row.id) if (this.$refs['form'] !== undefined) { this.clearvali() } },

March 18, 2022 · 1 min · jiezi

关于vue.js:手写-Vue2-系列-之-patch-diff

当学习成为了习惯,常识也就变成了常识。 感激各位的 关注、点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 前言上一篇文章 手写 Vue2 系列 之 初始渲染 中实现了原始标签、自定义组件、插槽的的初始渲染,当然其中也波及到 v-bind、v-model、v-on 指令的原理。实现首次渲染之后,接下来就该进行后续的更新了: 响应式数据产生更新 -> setter 拦挡到更新操作 -> dep 告诉 watcher 执行 update 办法 -> 进而执行 updateComponent 办法更新组件 -> 执行 render 生成新的 vnode -> 将 vnode 传递给 vm._update 办法 -> 调用 patch 办法 -> 执行 patchVnode 进行 DOM diff 操作 -> 实现更新 指标所以,本篇的指标就是实现 DOM diff,实现后续更新。波及知识点只有一个:DOM diff。 实现接下来就开始实现 DOM diff,实现响应式数据的后续更新。 patch/src/compiler/patch.js/** * 负责组件的首次渲染和后续更新 * @param {VNode} oldVnode 老的 VNode * @param {VNode} vnode 新的 VNode */export default function patch(oldVnode, vnode) { if (oldVnode && !vnode) { // 老节点存在,新节点不存在,则销毁组件 return } if (!oldVnode) { // oldVnode 不存在,阐明是子组件首次渲染 } else { if (oldVnode.nodeType) { // 实在节点,则示意首次渲染根组件 } else { // 后续的更新 patchVnode(oldVnode, vnode) } }}patchVnode/src/compiler/patch.js/** * 比照新老节点,找出其中的不同,而后更新老节点 * @param {*} oldVnode 老节点的 vnode * @param {*} vnode 新节点的 vnode */function patchVnode(oldVnode, vnode) { // 如果新老节点雷同,则间接完结 if (oldVnode === vnode) return // 将老 vnode 上的实在节点同步到新的 vnode 上,否则,后续更新的时候会呈现 vnode.elm 为空的景象 vnode.elm = oldVnode.elm // 走到这里阐明新老节点不一样,则获取它们的孩子节点,比拟孩子节点 const ch = vnode.children const oldCh = oldVnode.children if (!vnode.text) { // 新节点不存在文本节点 if (ch && oldCh) { // 阐明新老节点都有孩子 // diff updateChildren(ch, oldCh) } else if (ch) { // 老节点没孩子,新节点有孩子 // 减少孩子节点 } else { // 新节点没孩子,老节点有孩子 // 删除这些孩子节点 } } else { // 新节点存在文本节点 if (vnode.text.expression) { // 阐明存在表达式 // 获取表达式的新值 const value = JSON.stringify(vnode.context[vnode.text.expression]) // 旧值 try { const oldValue = oldVnode.elm.textContent if (value !== oldValue) { // 新老值不一样,则更新 oldVnode.elm.textContent = value } } catch { // 避免更新时遇到插槽,导致报错 // 目前不解决插槽数据的响应式更新 } } }}updateChildren/src/compiler/patch.js/** * diff,比对孩子节点,找出不同点,而后将不同点更新到老节点上 * @param {*} ch 新 vnode 的所有孩子节点 * @param {*} oldCh 老 vnode 的所有孩子节点 */function updateChildren(ch, oldCh) { // 四个游标 // 新孩子节点的开始索引,叫 新开始 let newStartIdx = 0 // 新完结 let newEndIdx = ch.length - 1 // 老开始 let oldStartIdx = 0 // 老完结 let oldEndIdx = oldCh.length - 1 // 循环遍历新老节点,找出节点中不一样的中央,而后更新 while (newStartIdx <= newEndIdx && oldStartIdx <= oldEndIdx) { // 根为 web 中的 DOM 操作特点,做了四种假如,升高工夫复杂度 // 新开始节点 const newStartNode = ch[newStartIdx] // 新完结节点 const newEndNode = ch[newEndIdx] // 老开始节点 const oldStartNode = oldCh[oldStartIdx] // 老完结节点 const oldEndNode = oldCh[oldEndIdx] if (sameVNode(newStartNode, oldStartNode)) { // 假如新开始和老开始是同一个节点 // 比照这两个节点,找出不同而后更新 patchVnode(oldStartNode, newStartNode) // 挪动游标 oldStartIdx++ newStartIdx++ } else if (sameVNode(newStartNode, oldEndNode)) { // 假如新开始和老完结是同一个节点 patchVnode(oldEndNode, newStartNode) // 将老完结挪动到新开始的地位 oldEndNode.elm.parentNode.insertBefore(oldEndNode.elm, oldCh[newStartIdx].elm) // 挪动游标 newStartIdx++ oldEndIdx-- } else if (sameVNode(newEndNode, oldStartNode)) { // 假如新完结和老开始是同一个节点 patchVnode(oldStartNode, newEndNode) // 将老开始挪动到新完结的地位 oldStartNode.elm.parentNode.insertBefore(oldStartNode.elm, oldCh[newEndIdx].elm.nextSibling) // 挪动游标 newEndIdx-- oldStartIdx++ } else if (sameVNode(newEndNode, oldEndNode)) { // 假如新完结和老完结是同一个节点 patchVnode(oldEndNode, newEndNode) // 挪动游标 newEndIdx-- oldEndIdx-- } else { // 下面几种假如都没命中,则老诚实的遍历,找到那个雷同元素 } } // 跳出循环,阐明有一个节点首先遍历完结了 if (newStartIdx < newEndIdx) { // 阐明老节点先遍历完结,则将残余的新节点增加到 DOM 中 } if (oldStartIdx < oldEndIdx) { // 阐明新节点先遍历完结,则将残余的这些老节点从 DOM 中删掉 }}sameVNode/src/compiler/patch.js/** * 判断两个节点是否雷同 * 这里的判读比较简单,只做了 key 和 标签的比拟 */function sameVNode(n1, n2) { return n1.key == n2.key && n1.tag === n2.tag}后果好了,到这里,虚构 DOM 的 diff 过程就实现了,如果你能看到如下效果图,则阐明一切正常。 ...

March 18, 2022 · 3 min · jiezi

关于vue.js:用明道云直击商业培训痛点实现高效管理

文/明道云施行参谋 刘明菲编辑/蒋礼轩 一、行业背景商业培训机构次要为企业提供实战治理教育征询。他们的客户群体次要是企业的创始人或其它重要骨干。其旨在为企业创始人提供实战、实效、实用的治理培训,帮忙企业疾速成长。 国内的商业培训是随着改革开放成长进去的企业一起而生的。尽管只有短短二十几年的历史,然而商业培训机构曾经逐步在经济倒退的大潮中处于“浪尖”的引领地位。特地是在现如今初创公司如同雨后春笋个别涌现的期间,商业培训机构在其中的作用不容忽视。但因为近两年疫情的影响,这些机构也面临着许多治理、保护等的问题。 二、次要痛点大多数学员信息没有失去及时的治理。信息录入不及时不利于与学员沟通,最终导致复购率升高,学员散失。课程材料无奈对立记录、保留。课程反馈没有留存记录,业务员无奈收集相干数据(邀约成功率、到场率等)用以统计。工作协同效率较低,进度反馈不及时。依据业务痛点梳理出整体需要后,能够发现:商业培训治理次要分为学员和课程两大主体,再下分出各业务场景。联合明道云aPaaS的灵便个性,咱们就能够搭建出商业培训行业的个性化业务场景利用。三、场景展现1.数据概览及快捷入口 使用明道云自定义页面性能,配置个性化数据看板。培训机构可将较为关注的统计数据以图表或报表模式展现在首页,例如:学员新增状况、回访数据、邀约转化漏斗以及课程相干统计等。以上所提到的数据信息都能够实时同步在手机端展现,不便决策者随时随地进行经营剖析。 自定义页面也可嵌入快捷操作按钮。通过嵌入操作,业务人员能够疾速实现学员信息录入、发送课程邀约、填写回访记录等操作。以此能够升高页面切换频率,进步操作效率。 2.学员治理 1)学员根本信息保护 依据参加课程状况,业务员能够将学员分为不同等级。业务员还可通过地区、行业、企业阶段、企业规模等多维度对学员进行分类管理。通过上述步骤中的分级分类,学员画像会变得更加丰盛平面,以便日后在课程或流动邀约时定向推送给学员。 关联显示学员历史回访记录以及加入过的课程信息,实现学员残缺生命周期治理,防止因为业务人员流动导致的学员信息失落。业务人员到职时,管理员可一键在零碎中批量批改学员信息归属人,业务交接不再是难题。另外,特色的画廊视图可用于学员风采的展现。 2)重要事项揭示 通过设置工作流,业务员可在学员生日当天主动发送邮件或短信祝愿;业务员在编制回访记录时可增加重要事项揭示,零碎可在对应工夫给业务人员发送回访告诉。这样能促成业务员与学员之间保持良好的沟通,进步学员活跃度,缩小学员散失,也可减少转介绍几率。 3)员工内勤访问 业务人员外出访问客户时,可在手机端现场签到,主动获取以后地位,并反对现场拍照。PC端反对日历视图查看业务人员外出访问记录。 3.课程管理 1)课程档案管理 业务员能够记录课程根本信息、邀约报名和收款状况;还可上传课程相干材料文件,造成企业常识积淀。 2)邀约与公开报名 课程凋谢报名后,业务员可在零碎中对批量邀约学员。邀约信息将以邮件或短信的模式发送给学员,且在零碎中生成邀约记录。 学员可间接点击链接在线报名。报名信息会间接反馈到零碎中,更新邀约状态。业务人员可进行后续跟进,录入收款信息。 业务员除了被动邀约之外,还可分享公开报名链接。报名人提交胜利后,可主动同步创立学员信息,无需再由业务人员手动录入。 3)课前筹备与现场执行 课程整体流程及工夫安顿可在零碎中以日历视图清晰展现,不便业务员实时地查看和调整。在课前筹备时,明道云能够实现课程工作派发、课程看板展现、推送工作告诉给相干负责人等场景;老师也能够实时更新工作状态,上传相干文件,晋升合作效率。 在现场执行阶段,通过零碎能够实现:学员扫码签到,零碎同步创立签到记录,主动关联到对应课程——从而不便统计到场状况,辞别纸质签到,节俭人力老本。 4)课程反馈 传统的课程评估多通过纸质问卷或第三方问卷网站进行收集,效率较低且难以关联到对应课程。明道云能够通过链接或二维码将公开表单分享给学员,学员间接扫码填写课程评估,并且间接关联至对应课程。这样能够不便员工及时依据反馈调整课程内容及安顿,助力课程研发。 四、结语通过明道云APaaS搭建管理系统,商业培训机构不仅解决本身的治理痛点,标准治理学员信息和课程档案,还帮忙业务人员与学员客户建设良好的互动关系,从而逐步提高转化率;工作协同更能让企业外部沟通合作前所未有的高效有序。随着工夫的推移,零碎中的业务数据将一直积攒;商业培训机构通过对历史数据进行效率和效益剖析,为企业战略决策提供要害的数据撑持。

March 18, 2022 · 1 min · jiezi

关于vue.js:记一次播放器页面白屏时间优化方案

优化对象此次优化是针对,Web 端视频播放页面。白屏工夫依照播放视频状态有两种状况,别离是: 内部进入,包含筛选页进入、分享链接进入、用户详情页进入和页面强制刷新,此时白屏工夫能够了解为浏览器输出 url 到播放器播放的工夫,次要流程分为四局部: 齐全白屏,FP(First Paint) 之前流程初始渲染视图和 loading 呈现,sessions 接口申请极其依赖逻辑执行sessions 之后的流程loading 隐没,开始播放外部切换,通过手动点击【用户会话】列表项切换视频,此时白屏工夫能够了解为 loading 呈现到隐没的阶段,有三局部: loading 呈现sessions 之后的流程loading 隐没,开始播放整顿流程图示如下: 预期后果失常状况下,新版本视频指标达到 秒开,老版本视频因为须要手动解析事件,可能会稍慢一些。 已做工作事件流分页(加载第一页数据后即开始播放)提前渲染,不期待数据筹备结束,第一工夫渲染播放器、会话列表和事件流框架,播放前展现 loading ,给用户视觉上更好的体验,「感觉快」引入 Web Worker 新版本视频因为返回数据为字符串,须要手动解析为 JSON,因而下载和解析逻辑移入 Web Worker,充分利用 CPU 资源,防止影响主线程逻辑和渲染,播放前次要是不影响数据流,事件流的解析和渲染。老版本视频下载和解析逻辑移入 Web Worker。全页面去掉自定义滚动条开发者工具 source 面板懒加载整体剖析借助 Chrome Performance 录制白屏至视频开播之间的片段,进行剖析: 因为 FP 用于记录页面第一次绘制像素的工夫,之后即会进入动静代码阶段,所以上面以 FP 为界线,别离剖析前和后两局部。 FP 之前剖析上面是 FP 之前的详情截图 从图中能够看出耗时的工作次要为两局部: my-details.js 文件下载(目前 gzip 压缩之后,体积为737.25 KB)一个大 Task问题定位文件下载阶段,打包文件过大,通过 webpack 的打包插件 webpack-bundle-analyzer 剖析,如图所示: 通过剖析,三个最大的文件是 ts.worker.js(gzip 1010.22 KB) 、my-details.js(gzip 737.25 KiB) 和 chunk-vendor.js(gzip 442.56 KiB),三个大文件中的较大模块如下: ...

March 17, 2022 · 2 min · jiezi

关于vue.js:webpack-性能调优报告

调优成绩 优化前优化后优化比例打包体积32.01 MB16.56 MB48.27%Gzip 体积3.37 MB1.82 MB45.99%文件数量821976.83%资源压缩服务器 br 压缩客户端 br 压缩1压缩级别611(最高)1构建速度60s +41.1s +31.67%播放页 FP 耗时显著感知无感知1(以上数据起源为本地测试,仅供参考) 优化指标升高播放页 FP 耗时缩小动态资源申请数量晋升开发体验晋升构建产物品质(文章基于 webpack@4.46.0) 问题剖析先看我的项目以后的打包剖析后果 以上是用 vue-cli 生成的构建统计报告,它会帮忙剖析包中蕴含的模块们的大小,简略列一下报告信息如下: 整体打包体积 32.01 MBts.worker.js 是 monaco 编辑器的语言反对文件,次要提供 typescript 语法反对,体积 10.92 MBmy-details.js 是播放页文件,网站外围资源文件,体积 6.81 MBchunk-vendors.js 第三方模块捆绑包,体积 4.14 MBhtml.workar.js 是 monaco 编辑器的 html 语法反对文件css.workar.js 是 monaco 编辑器的 css 语法反对文件json.worker.js 是 monaco 编辑器的 json 语法反对文件app.js 我的项目入口文件,体积 1.02 MB小文件文件数量太多,加起来靠近百个,导致 http 申请过多通过剖析总结,定位问题如下: monaco-editor 是最大的问题,体积占据半壁江山,重大影响加载速度,须要优化chunk-vendors.js 作为公共模块,形成我的项目必不可少的一些根底类库,降级频率都不高,但每个页面都须要它们,当初它体积过大,应该在正当范畴内拆分成更小一些的 js,以利用浏览器的并发申请,优化首页加载体验。其中蕴含了三个大家伙:elementUI、moment 和 lodash,更是须要独自做优化my-details.js 和 app.js 作为我的项目次要的资源文件,体积过大,须要优化小文件数量太多,须要合并解决思路查看下有没有哪些产出是不必要的,在无限的工夫空间和算力下,去除低效的反复(提出公共大模块),进行正当的冗余(小文件容许反复),达到工夫和空间综合考量上的最优。 上面分步骤实现每一个优化项。 ...

March 17, 2022 · 2 min · jiezi

关于vue.js:fastposter-v261-发布-程序员专属海报生成器

fastposter v2.6.1 公布 程序员专属海报生成器fastposter电商级海报生成器,程序员专属海报生成器,一分钟实现海报开发,轻松在线作图。反对Java、Python、PHP、 Go、JavaScript等多种语言。v2.6.1 公布 程序员专属海报生成器 解决Windows下读取yml文件编码问题欠缺更新文档(社区版、专业版)减少专业版演示地址下载依赖ssl异样文档补充专业版-v2.0.4 减少阿里云OSS存储反对fixbug: 设置配置文件编码 UTF-8,避免Windows下文件编码导致启动异样sql脚本,去除fastposter前缀获取用户信息后,从新加载字体减少监控查看接口,不便容器环境下监控: actuator/health文本减少对齐形式文本减少主动换行删除组件从新计算图层加载海报从新计算图层减少快捷键操作 方向:(上、下、左、右 )图层:上移、下移删除组件保留海报仓库地址开发文档:https://poster.prodapi.cn/docs/在线体验:https://poster.prodapi.cn/专业版在线体验:https://poster.prodapi.cn/pro/代码仓库-github代码仓库-gitee只需三步,即可实现海报开发 启动服务 > 编辑海报 > 生成代码一、启动服务docker run --name fast-poster -p 5000:5000 tangweixin/fast-poster二、编辑海报 三、生成代码 实用场景:在线作图Java生成二维码分享海报图片Java Graphics2D绘制海报图片微信小程序生成海报分享朋友圈PHP生成二维码海报图片自定义商业海报图片H5生成海报图片二维码分享海报图片canvas生成海报图片通过JSON生成海报图片捐献如果你感觉 fastposter 对你有帮忙,或者想对咱们渺小的工作一点反对,欢送给咱们捐献 社区进群加作者微信,请备注来自fastposter。

March 17, 2022 · 1 min · jiezi

关于vue.js:elementUI多个表单同时提交校验问题

我的项目中遇到新增页面时,因为页面内容简单,不只是表单内容,还有表格等,这时把form表单分成多个,然而在提交时会进行校验,再次记录下。如下所示: 根本信息表单名称是:basicForm其余信息表单名称是:otherForm表格中的所有数据都是可编辑的,用一个大的表单包起来,名称是:tableForm当初提交时,要对页面中的三个form进行校验,没有问题之后能力调用接口。 其实中心思想用的是promise.all,对所有的表单进行校验没问题,进入到.then()中解决提交接口逻辑。

March 17, 2022 · 1 min · jiezi

关于vue.js:手写-Vue2-系列-之-初始渲染

当学习成为了习惯,常识也就变成了常识。 感激各位的 关注、点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 前言上一篇文章 手写 Vue2 系列 之 编译器 中实现了从模版字符串到 render 函数的工作。当咱们失去 render 函数之后,接下来就该进入到真正的挂载阶段了: 挂载 -> 实例化渲染 Watcher -> 执行 updateComponent 办法 -> 执行 render 函数生成 VNode -> 执行 patch 进行首次渲染 -> 递归遍历 VNode 创立各个节点并解决节点上的一般属性和指令 -> 如果节点是自定义组件则创立组件实例 -> 进行组件的初始化、挂载 -> 最终所有 VNode 变成实在的 DOM 节点并替换掉页面上的模版内容 -> 实现初始渲染 指标所以,本篇文章指标就是实现下面形容的整个过成,实现初始渲染。整个过程中波及如下知识点: render helperVNodepatch 初始渲染指令(v-model、v-bind、v-on)的解决实例化子组件插槽的解决实现接下来就正式进入代码实现过程,一步步实现上述所有内容,实现页面的初始渲染。 mount/src/compiler/index.js/** * 编译器 */export default function mount(vm) { if (!vm.$options.render) { // 没有提供 render 选项,则编译生成 render 函数 // ... } mountComponent(vm)}mountComponent/src/compiler/mountComponent.js/** * @param {*} vm Vue 实例 */export default function mountComponent(vm) { // 更新组件的的函数 const updateComponent = () => { vm._update(vm._render()) } // 实例化一个渲染 Watcher,当响应式数据更新时,这个更新函数会被执行 new Watcher(updateComponent)}vm._render/src/compiler/mountComponent.js/** * 负责执行 vm.$options.render 函数 */Vue.prototype._render = function () { // 给 render 函数绑定 this 上下文为 Vue 实例 return this.$options.render.apply(this)}render helper/src/compiler/renderHelper.js/** * 在 Vue 实例上装置运行时的渲染帮忙函数,比方 _c、_v,这些函数会生成 Vnode * @param {VueContructor} target Vue 实例 */export default function renderHelper(target) { target._c = createElement target._v = createTextNode}createElement/src/compiler/renderHelper.js/** * 依据标签信息创立 Vnode * @param {string} tag 标签名 * @param {Map} attr 标签的属性 Map 对象 * @param {Array<Render>} children 所有的子节点的渲染函数 */function createElement(tag, attr, children) { return VNode(tag, attr, children, this)}createTextNode/src/compiler/renderHelper.js/** * 生成文本节点的 VNode * @param {*} textAst 文本节点的 AST 对象 */function createTextNode(textAst) { return VNode(null, null, null, this, textAst)}VNode/src/compiler/vnode.js/** * VNode * @param {*} tag 标签名 * @param {*} attr 属性 Map 对象 * @param {*} children 子节点组成的 VNode * @param {*} text 文本节点的 ast 对象 * @param {*} context Vue 实例 * @returns VNode */export default function VNode(tag, attr, children, context, text = null) { return { // 标签 tag, // 属性 Map 对象 attr, // 父节点 parent: null, // 子节点组成的 Vnode 数组 children, // 文本节点的 Ast 对象 text, // Vnode 的实在节点 elm: null, // Vue 实例 context }}vm._update/src/compiler/mountComponent.jsVue.prototype._update = function (vnode) { // 老的 VNode const prevVNode = this._vnode // 新的 VNode this._vnode = vnode if (!prevVNode) { // 老的 VNode 不存在,则阐明时首次渲染根组件 this.$el = this.__patch__(this.$el, vnode) } else { // 后续更新组件或者首次渲染子组件,都会走这里 this.$el = this.__patch__(prevVNode, vnode) }}装置 \_\_patch\_\_、render helper/src/index.js/** * 初始化配置对象 * @param {*} options */Vue.prototype._init = function (options) { // ... initData(this) // 装置运行时的渲染工具函数 renderHelper(this) // 在实例上装置 patch 函数 this.__patch__ = patch // 如果存在 el 配置项,则调用 $mount 办法编译模版 if (this.$options.el) { this.$mount() }}patch/src/compiler/patch.js/** * 初始渲染和后续更新的入口 * @param {VNode} oldVnode 老的 VNode * @param {VNode} vnode 新的 VNode * @returns VNode 的实在 DOM 节点 */export default function patch(oldVnode, vnode) { if (oldVnode && !vnode) { // 老节点存在,新节点不存在,则销毁组件 return } if (!oldVnode) { // oldVnode 不存在,阐明是子组件首次渲染 createElm(vnode) } else { if (oldVnode.nodeType) { // 实在节点,则示意首次渲染根组件 // 父节点,即 body const parent = oldVnode.parentNode // 参考节点,即老的 vnode 的下一个节点 —— script,新节点要插在 script 的后面 const referNode = oldVnode.nextSibling // 创立元素 createElm(vnode, parent, referNode) // 移除老的 vnode parent.removeChild(oldVnode) } else { console.log('update') } } return vnode.elm}createElm/src/compiler/patch.js/** * 创立元素 * @param {*} vnode VNode * @param {*} parent VNode 的父节点,实在节点 * @returns */function createElm(vnode, parent, referNode) { // 记录节点的父节点 vnode.parent = parent // 创立自定义组件,如果是非组件,则会持续前面的流程 if (createComponent(vnode)) return const { attr, children, text } = vnode if (text) { // 文本节点 // 创立文本节点,并插入到父节点内 vnode.elm = createTextNode(vnode) } else { // 元素节点 // 创立元素,在 vnode 上记录对应的 dom 节点 vnode.elm = document.createElement(vnode.tag) // 给元素设置属性 setAttribute(attr, vnode) // 递归创立子节点 for (let i = 0, len = children.length; i < len; i++) { createElm(children[i], vnode.elm) } } // 如果存在 parent,则将创立的节点插入到父节点内 if (parent) { const elm = vnode.elm if (referNode) { parent.insertBefore(elm, referNode) } else { parent.appendChild(elm) } }}createTextNode/src/compiler/patch.js/** * 创立文本节点 * @param {*} textVNode 文本节点的 VNode */function createTextNode(textVNode) { let { text } = textVNode, textNode = null if (text.expression) { // 存在表达式,这个表达式的值是一个响应式数据 const value = textVNode.context[text.expression] textNode = document.createTextNode(typeof value === 'object' ? JSON.stringify(value) : String(value)) } else { // 纯文本 textNode = document.createTextNode(text.text) } return textNode}setAttribute/src/compiler/patch.js/** * 给节点设置属性 * @param {*} attr 属性 Map 对象 * @param {*} vnode */function setAttribute(attr, vnode) { // 遍历属性,如果是一般属性,间接设置,如果是指令,则非凡解决 for (let name in attr) { if (name === 'vModel') { // v-model 指令 const { tag, value } = attr.vModel setVModel(tag, value, vnode) } else if (name === 'vBind') { // v-bind 指令 setVBind(vnode) } else if (name === 'vOn') { // v-on 指令 setVOn(vnode) } else { // 一般属性 vnode.elm.setAttribute(name, attr[name]) } }}setVModel/src/compiler/patch.js/** * v-model 的原理 * @param {*} tag 节点的标签名 * @param {*} value 属性值 * @param {*} node 节点 */function setVModel(tag, value, vnode) { const { context: vm, elm } = vnode if (tag === 'select') { // 下拉框,<select></select> Promise.resolve().then(() => { // 利用 promise 提早设置,间接设置不行, // 因为这会儿 option 元素还没创立 elm.value = vm[value] }) elm.addEventListener('change', function () { vm[value] = elm.value }) } else if (tag === 'input' && vnode.elm.type === 'text') { // 文本框,<input type="text" /> elm.value = vm[value] elm.addEventListener('input', function () { vm[value] = elm.value }) } else if (tag === 'input' && vnode.elm.type === 'checkbox') { // 抉择框,<input type="checkbox" /> elm.checked = vm[value] elm.addEventListener('change', function () { vm[value] = elm.checked }) }}setVBind/src/compiler/patch.js/** * v-bind 原理 * @param {*} vnode */function setVBind(vnode) { const { attr: { vBind }, elm, context: vm } = vnode for (let attrName in vBind) { elm.setAttribute(attrName, vm[vBind[attrName]]) elm.removeAttribute(`v-bind:${attrName}`) }}setVOn/src/compiler/patch.js/** * v-on 原理 * @param {*} vnode */function setVOn(vnode) { const { attr: { vOn }, elm, context: vm } = vnode for (let eventName in vOn) { elm.addEventListener(eventName, function (...args) { vm.$options.methods[vOn[eventName]].apply(vm, args) }) }}createComponent/src/compiler/patch.js/** * 创立自定义组件 * @param {*} vnode */function createComponent(vnode) { if (vnode.tag && !isReserveTag(vnode.tag)) { // 非保留节点,则阐明是组件 // 获取组件配置信息 const { tag, context: { $options: { components } } } = vnode const compOptions = components[tag] const compIns = new Vue(compOptions) // 将父组件的 VNode 放到子组件的实例上 compIns._parentVnode = vnode // 挂载子组件 compIns.$mount() // 记录子组件 vnode 的父节点信息 compIns._vnode.parent = vnode.parent // 将子组件增加到父节点内 vnode.parent.appendChild(compIns._vnode.elm) return true }}isReserveTag/src/utils.js/** * 是否为平台保留节点 */export function isReserveTag(tagName) { const reserveTag = ['div', 'h3', 'span', 'input', 'select', 'option', 'p', 'button', 'template'] return reserveTag.includes(tagName)}插槽原理以下示例是插槽的罕用形式。插槽的原理其实很简略,只是实现起来略微有些麻烦罢了。 ...

March 17, 2022 · 7 min · jiezi

关于vue.js:在页面中直接嵌入vuesfc的方法

咱们晓得,Vue举荐应用单文件组件(Single File Component,简称SFC),能够说SFC是Vue框架的特色。 然而,咱们在学习和练习的时候,如果想要用非常简单的形式在一个惯例的HTML文件,或者简略的Playground(比方JSBin或者CodePen)外面应用Vue的SFC形式,是不太容易的。 因而Vue官网提供了专门的SFC Playground来不便大家学习Vue。 不过,有没有方法不必SFC Playground,在本地单个HTML文件或者CodePen和JSBin这样的平台应用Vue-SFC呢? 方法是有的,我先放一个例子: 这是一个在CodePen中写的Vue组件 这是怎么做到的呢? 其实要分成三个步骤。 第一步 嵌入SFC内容首先是要在一般的HTML文件中内联嵌入Vue-SFC组件。这里的麻烦之处在于,SFC中蕴含有HTML标签,而且还有<script>标签,因而,将它放在页面中内联,浏览器就会解析这些标签。只管咱们能够通过给<script>设置type来防止浏览器执行脚本,然而仍然不能阻止浏览器解析这些标签自身。 那有同学就动脑筋想了,咱们是否能够把SFC的内容放到一个不解析HTML内容的元素中,比方<textarea>标签。这样当然是能够的,然而也有些许麻烦,就是咱们要给这个textarea元素设置款式将它暗藏起来。 实际上,有个不容易想到的,更简略的标签,那就是<noscript>。失常状况下,浏览器不会解析<noscript>标签中的元素,而且又能够和其余标签一样,通过textConent获取其中的内容,所以咱们用<noscript>标签来搁置SFC的内容是再适合不过的了。 第二步 编译SFC组件接着,咱们要编译SFC组件。这个能够通过官网提供的 vue/compile-sfc 模块来实现。 compile-sfc 如何应用,官网文档里写得非常简单,但这不障碍咱们通过钻研@vitejs/plugin-vue和webpack插件vue-loader来找到它的用法。用法其实也不简单,外围就是先parse源代码,成为descriptor对象,而后再一一编译script、template和styles。最终,再把这些模块拼起来,拼接的时候,如果不思考兼容性,最简略的形式是间接应用ES-Module来拼接。 以下是编译SFC的外围代码。 import * as compiler from '@vue/compiler-sfc';function generateID() { return Math.random().toString(36).slice(2, 12);}function transformVueSFC(source, filename) { const {descriptor, errors} = compiler.parse(source, {filename}); if(errors.length) throw new Error(errors.toString()); const id = generateID(); const hasScoped = descriptor.styles.some(e => e.scoped); const scopeId = hasScoped ? `data-v-${id}` : undefined; const templateOptions = { id, source: descriptor.template.content, filename: descriptor.filename, scoped: hasScoped, slotted: descriptor.slotted, compilerOptions: { scopeId: hasScoped ? scopeId : undefined, mode: 'module', }, }; const script = compiler.compileScript(descriptor, {id, templateOptions, sourceMap:true}); if(script.map) { script.content = `${script.content}\n//# sourceMappingURL=data:application/json;base64,${btoa(JSON.stringify(script.map))}`; } const template = compiler.compileTemplate({...templateOptions, sourceMap: true}); if(template.map) { template.map.sources[0] = `${template.map.sources[0]}?template`; template.code = `${template.code}\n//# sourceMappingURL=data:application/json;base64,${btoa(JSON.stringify(template.map))}`; } let cssInJS = ''; if(descriptor.styles) { const styled = descriptor.styles.map((style) => { return compiler.compileStyle({ id, source: style.content, scoped: style.scoped, preprocessLang: style.lang, }); }); if(styled.length) { const cssCode = styled.map(s => s.code).join('\n'); cssInJS = `(function(){const el = document.createElement('style');el.innerHTML = \`${cssCode}\`;document.body.appendChild(el);}());`; } } const moduleCode = ` import script from '${getBlobURL(script.content)}'; import {render} from '${getBlobURL(template.code)}'; script.render = render; ${filename ? `script.__file = '${filename}'` : ''}; ${scopeId ? `script.__scopeId = '${scopeId}'` : ''}; ${cssInJS} export default script; `; return moduleCode;}复制代码那么最终,代码就编译进去了。 ...

March 17, 2022 · 2 min · jiezi

关于vue.js:7种最棒的Vue-Loading加载动画组件测评与推荐-穷尽市面上所有加载动画效果Vue-loader类型-卡拉云

扩大浏览:《7 种最棒的 Vue Loading 加载动画组件测评与举荐 - 穷尽市面上所有加载动画成果(Vue loader)类型 - 卡拉云》 Vue Loading 加载动画组件 (Vue-loader) 看起来很简略不重要,实际上它是保障用户留存的要害一环。抉择好 Loading 加载动画,用户留存率翻倍。 本文介绍 7 种不同的加载动画 UI 成果(Vue loader),每一种都有其对应的应用场景。举例,旋转加载动画适宜短时间加载,旋转加载还能更细分,比方在按钮上的旋转加载,适宜提交数据的极短时间,旋转动画在全局的适宜多表格数据加载,旋转动画图片可自定义的适宜高度定制化的 APP / 网站等。再举例,进度条类的加载动画适宜长时间加载,进度条类也能够更细分,比方有蒙层的进度条,有加载进度条带勾销按钮,有加载进度条放在网页顶部,显得更轻捷快捷。 本文不仅是「Vue loader 动画加载」组件测评,更是从产品层面介绍目前支流的 Vue Loader 加载动画 UI 对应的利用场景,帮忙大家抉择到最适宜你的加载动画组件。 另外,这个世界曾经悄悄发生变化,当初基本无需写任何前端代码,间接应用卡拉云 —— 新一代低代码开发工具帮你搭建后盾工具,卡拉云可一键接入常见数据库及 API ,无需懂前端,内置欠缺的各类前端组件,无需调试,拖拽即用。原来三天的工作量,当初 1 小时搞定,谁用谁晓得,用上早上班,详见本文文末。 7 种不同类型的 Vue Loading 加载动画组件Vue Simple Spinner - Loading 加载动画根底款,简略可配置代码优良Vue Radial Progress - Loading 加载进度条根底款,依据步长显示进度,可自定义多种变量nprogress - 网页顶部加载进度条,全新 UI 视觉效果愉悦TB Skeleton - APP / 网页构造加载动画,全局加载显示王者Vue Loading Overlay - 加载进度条,内置工作勾销按钮,触发事件勾销用户执行工作Vue Progress Path - Google Material 设计格调,可替换你本人设计的 loading 图,高度可定制化Vue Loading Button - 轻捷的按钮 Loading 加载动画成果组件1. Vue Simple Spinner - Loading 加载动画根底款,简略可配置代码优良 ...

March 16, 2022 · 2 min · jiezi

关于vue.js:vuecliservice-build-不同环境配置

背景在我的项目部署时,咱们须要在测试环境和生产环境应用不同的变量。vue-cli提供了vue-cli-service build打包命令,然而vue-cli-service build默认的环境变量值则为production。那咱们通过npm run build打包构建,想要实现不同环境应用不同变量,临时不能实现。 介绍vue-cli生成我的项目时,在package.json中会设置: "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build"}vue-cli-service serve 命令会启动一个开发服务器,默认指定的环境模式为development。vue-cli-service build 会在 dist/ 目录产生一个可用于生产环境的包,带有 JS/CSS/HTML 的压缩,和为更好的缓存而做的主动的vendor chunk splitting。 环境变量和模式在我的项目的根目录下咱们能够创立不同模式的文件: .env # 在所有的环境中被载入.env.local # 在所有的环境中被载入,但会被 git 疏忽.env.[mode] # 只在指定的模式中被载入.env.[mode].local # 只在指定的模式中被载入,但会被 git 疏忽一般来说,咱们会存在本地环境、测试环境、线上环境,那咱们就须要创立三个模式文件。 .env.development开发环境模式// 环境变量NODE_ENV=development// 以 VUE_APP_ 结尾的变量会被 webpack.DefinePlugin 动态嵌入到客户端侧的包中VUE_APP_ENV = 'development'.env.test测试环境模式// 环境变量(这里的环境变量是跟打包无关的,production则会进行压缩代码等,真正跟每个环境无关的变量是上面以VUE_APP结尾的变量)NODE_ENV=production// 以 VUE_APP_ 结尾的变量会被 webpack.DefinePlugin 动态嵌入到客户端侧的包中VUE_APP_ENV = 'test'.env.production线上环境模式// 环境变量NODE_ENV=production// 以 VUE_APP_ 结尾的变量会被 webpack.DefinePlugin 动态嵌入到客户端侧的包中VUE_APP_ENV = 'production'配置不同模式部署时,构建打包执行npm run build,则会执行vue-cli-service build,默认模式为production,对应.env.production文件,取此文件中的环境变量。 想要配置测试环境,须要在scripts下减少脚本: "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "build-test": "vue-cli-service build --mode test"}测试环境打包构建时,执行npm run build-test即可。 ...

March 16, 2022 · 1 min · jiezi

关于vue.js:vueclipluginwebpackbundleanalyzer-关闭自动打开分析结果页面

优化实现后,不想每次启动看到弹出的剖析页面。 增加vue.config.js中的 pluginOptions配置: module.exports = { devServer: xxx, configureWebpack : xxx, ..., pluginOptions: { webpackBundleAnalyzer: { analyzerMode: 'disabled' } } }

March 16, 2022 · 1 min · jiezi

关于vue.js:手写-Vue2-系列-之-编译器

当学习成为了习惯,常识也就变成了常识。 感激各位的 关注、点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 前言接下来就要正式进入手写 Vue2 系列了。这里不会从零开始,会基于 lyn-vue 间接进行降级,所以如果你没有浏览过 手写 Vue 系列 之 Vue1.x,请先从这篇文章开始,依照程序进行学习。 都晓得,Vue1 存在的问题就是在大型利用中 Watcher 太多,如果不分明其原理请查看 手写 Vue 系列 之 Vue1.x。 所以在 Vue2 中通过引入了 VNode 和 diff 算法来解决该问题。通过升高 Watcher 的粒度,一个组件对应一个 Watcher(渲染 Watcher),这样就不会呈现大型页面 Watcher 太多导致性能降落的问题。 在 Vue1 中,Watcher 和 页面中的响应式数据一一对应,当响应式数据产生扭转,Dep 告诉 Watcher 实现对应的 DOM 更新。然而在 Vue2 中一个组件对应一个 Watcher,当响应式数据产生扭转时,Watcher 并不知道这个响应式数据在组件中的什么地位,那又该如何实现更新呢? 浏览过之前的 源码系列,大家必定都晓得,Vue2 引入了 VNode 和 diff 算法,将组件 编译 成 VNode,每次响应式数据发生变化时,会生成新的 VNode,通过 diff 算法比照新旧 VNode,找出其中产生扭转的中央,而后执行对应的 DOM 操作实现更新。 ...

March 16, 2022 · 6 min · jiezi

关于vue.js:Es6根据某个属性将数组分组

Es6依据某个属性将数组分组:比方如下数组,将依据sex字段来分组,sex为男的放到一起,为女的放到一起:次要代码如下: const array = [{ id: 1, name: '小明', sex: '男'},{ id: 3, name: '小红', sex: '女'},{ id: 2, name: '小刚', sex: '男'},{ id: 4, name: '小花', sex: '女'},{ id: 5, name: '小甜甜', sex: '女'},]; function getList(list) { const map = new Map() list.forEach((item, index, arr) => { if (!map.has(item.sex)) { map.set( item.sex, arr.filter(a => a.sex == item.sex) )}}) return Array.from(map).map(item => [...item[1]])}const list = getList(array)console.log(array, '分组前');console.log(list, '分组后');

March 16, 2022 · 1 min · jiezi

关于vue.js:petitevue源码剖析双向绑定vmodel的工作原理

前言双向绑定v-model不仅仅是对可编辑HTML元素(select, input, textarea和附带[contenteditable=true])同时附加v-bind和v-on,而且还能利用通过petite-vue附加给元素的_value、_trueValue和_falseValue属性提供存储非字符串值的能力。 深刻v-model工作原理 export const model: Directive< HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement> = ({ el, exp, get, effect, modifers }) => { const type = el.type // 通过`with`对作用域的变量/属性赋值 const assign = get(`val => { ${exp} = val }`) // 若type为number则默认将值转换为数字 const { trim, number = type ==== 'number'} = modifiers || {} if (el.tagName === 'select') { const sel = el as HTMLSelectElement // 监听控件值变动,更新状态值 listen(el, 'change', () => { const selectedVal = Array.prototype.filter .call(sel.options, (o: HTMLOptionElement) => o.selected) .map((o: HTMLOptionElement) => number ? toNumber(getValue(o)) : getValue(o)) assign(sel.multiple ? selectedVal : selectedVal[0]) }) // 监听状态值变动,更新控件值 effect(() => { value = get() const isMultiple = sel.muliple for (let i = 0, l = sel.options.length; i < i; i++) { const option = sel.options[i] const optionValue = getValue(option) if (isMulitple) { // 当为多选下拉框时,入参要么是数组,要么是Map if (isArray(value)) { option.selected = looseIndexOf(value, optionValue) > -1 } else { option.selected = value.has(optionValue) } } else { if (looseEqual(optionValue, value)) { if (sel.selectedIndex !== i) sel.selectedIndex = i return } } } }) } else if (type === 'checkbox') { // 监听控件值变动,更新状态值 listen(el, 'change', () => { const modelValue = get() const checked = (el as HTMLInputElement).checked if (isArray(modelValue)) { const elementValue = getValue(el) const index = looseIndexOf(modelValue, elementValue) const found = index !== -1 if (checked && !found) { // 勾选且之前没有被勾选过的则退出到数组中 assign(modelValue.concat(elementValue)) } else if (!checked && found) { // 没有勾选且之前已勾选的排除后在从新赋值给数组 const filered = [...modelValue] filteed.splice(index, 1) assign(filtered) } // 其它状况就啥都不干咯 } else { assign(getCheckboxValue(el as HTMLInputElement, checked)) } }) // 监听状态值变动,更新控件值 let oldValue: any effect(() => { const value = get() if (isArray(value)) { ;(el as HTMLInputElement).checked = looseIndexOf(value, getValue(el)) > -1 } else if (value !== oldValue) { ;(el as HTMLInputElement).checked = looseEqual( value, getCheckboxValue(el as HTMLInputElement, true) ) } oldValue = value }) } else if (type === 'radio') { // 监听控件值变动,更新状态值 listen(el, 'change', () => { assign(getValue(el)) }) // 监听状态值变动,更新控件值 let oldValue: any effect(() => { const value = get() if (value !== oldValue) { ;(el as HTMLInputElement).checked = looseEqual(value, getValue(el)) } }) } else { // input[type=text], textarea, div[contenteditable=true] const resolveValue = (value: string) => { if (trim) return val.trim() if (number) return toNumber(val) return val } // 监听是否在输入法编辑器(input method editor)输出内容 listen(el, 'compositionstart', onCompositionStart) listen(el, 'compositionend', onCompositionEnd) // change事件是元素失焦后前后值不同时触发,而input事件是输出过程中每次批改值都会触发 listen(el, modifiers?.lazy ? 'change' : 'input', () => { // 元素的composing属性用于标记是否处于输入法编辑器输出内容的状态,如果是则不执行change或input事件的逻辑 if ((el as any).composing) return assign(resolveValue(el.value)) }) if (trim) { // 若modifiers.trim,那么当元素失焦时马上移除值前后的空格字符 listen(el, 'change', () => { el.value = el.value.trim() }) } effect(() => { if ((el as any).composing) { return } const curVal = el.value const newVal = get() // 若以后元素处于活动状态(即失去焦点),并且元素以后值进行类型转换后值与新值雷同,则不必赋值; // 否则只有元素以后值和新值类型或值不雷同,都会从新赋值。那么若新值为数组[1,2,3],赋值后元素的值将变成[object Array] if (document.activeElement === el && resolveValue(curVal) === newVal) { return } if (curVal !== newVal) { el.value = newVal } }) }}// v-bind中应用_value属性保留任意类型的值,在v-modal中读取const getValue = (el: any) => ('_value' in el ? el._value : el.value)const getCheckboxValue = ( el: HTMLInputElement & {_trueValue?: any, _falseValue?: any}, // 通过v-bind定义的任意类型值 checked: boolean // checkbox的默认值是true和false) => { const key = checked ? '_trueValue' : '_falseValue' return key in el ? el[key] : checked}const onCompositionStart = (e: Event) => { // 通过自定义元素的composing元素,用于标记是否在输入法编辑器中输出内容 ;(e.target as any).composing = true} const onCompositionEnd = (e: Event) => { const target = e.target as any if (target.composing) { // 手动触发input事件 target.composing = false trigger(target, 'input') }}const trigger = (el: HTMLElement, type: string) => { const e = document.createEvent('HTMLEvents') e.initEvent(type, true, true) el.dispatchEvent(e)}复制代码compositionstart和compositionend是什么?compositionstart是开始在输入法编辑器上输出字符触发,而compositionend则是在输入法编辑器上输出字符完结时触发,另外还有一个compositionupdate是在输入法编辑器上输出字符过程中触发。 ...

March 15, 2022 · 5 min · jiezi

关于vue.js:基于-d2admin-搭建项目

1.VueRouter 配置创立 router 的 js 文件const frameIn = [ { path: '/head1', redirect: { name: 'head1-1' }, component: layoutHeaderAside, children: [ // 首页 { path: 'page1-1', name: 'head1-1', meta: { auth: true, title: '第一页' }, component: _import('demo/page1') } ] },// 从新组织后导出export default [...frameIn, ...frameOut, ...errorPage]实例化 VueRouter 对象import Vue from 'vue'import VueRouter from 'vue-router'// 路由数据import routes from './routes'// 导出路由 在 main.js 里应用const router = new VueRouter({ routes})在 main.js 中注册 routerimport router from './router'new Vue({ router, store, i18n, render: (h) => h(App),2.侧边栏配置menu.js 中配置菜单的 json 数据export const menuAside = supplementPath([ { path: '/index', title: '首页', icon: 'home' }, { title: '/head1', icon: 'folder-o', children: [ { path: '/page1', title: '页面 1-1' }, { path: '/page2', title: '页面 1-2' }, { path: '/page3', title: '页面 1-3' } ] },监听路由变动动静生成侧边栏watch: { '$route.matched': { handler(val) { const menuAsideTem = menuAside.find((item) => { return item.title === val[0].path }) let aide = [] if (menuAsideTem && menuAsideTem.children) { aide = menuAsideTem.children } this.$store.commit('d2admin/menu/asideSet', aide) } }}成果如下菜单顶栏也是雷同的原理 ...

March 15, 2022 · 2 min · jiezi

关于vue.js:手写-Vue-系列-之-从-Vue1-升级到-Vue2

当学习成为了习惯,常识也就变成了常识。 感激各位的 关注、点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 前言上一篇文章 手写 Vue 系列 之 Vue1.x 带大家从零开始实现了 Vue1 的外围原理,包含如下性能: 数据响应式拦挡 一般对象数组数据响应式更新 依赖收集 DepWatcher编译器 文本节点v-on:clickv-bindv-model在最初也具体解说了 Vue1 的诞生以及存在的问题:Vue1.x 在中小型零碎中性能会很好,定向更新 DOM 节点,然而大型零碎因为 Watcher 太多,导致资源占用过多,性能降落。于是 Vue2 中通过引入 VNode 和 Diff 的来解决这个问题, 所以接下来的系列内容就是降级上一篇文章编写的 lyn-vue 框架,将它从 Vue1 降级到 Vue2。所以倡议整个系列大家按程序去浏览学习,如若强行浏览,可能会产生云里雾里的感觉,事倍功半。 另外欢送 关注 以防迷路,同时系列文章都会收录到 精通 Vue 技术栈的源码原理 专栏,也欢送关注该专栏。 指标降级后的框架须要将如下示例代码跑起来 示例<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Lyn Vue2.0</title></head><body> <div id="app"> <h3>数据响应式更新 原理</h3> <div>{{ t }}</div> <div>{{ t1 }}</div> <div>{{ arr }}</div> <h3>methods + computed + 异步更新队列 原理</h3> <div> <p>{{ counter }}</p> <div>{{ doubleCounter }}</div> <div>{{ doubleCounter }}</div> <div>{{ doubleCounter }}</div> <button v-on:click="handleAdd"> Add </button> <button v-on:click="handleMinus"> Minus </button> </div> <h3>v-bind</h3> <span v-bind:title="title">右键审查元素查看我的 title 属性</span> <h3>v-model 原理</h3> <div> <input type="text" v-model="inputVal" /> <div>{{ inputVal }}</div> </div> <div> <input type="checkbox" v-model="isChecked" /> <div>{{ isChecked }}</div> </div> <div> <select v-model="selectValue"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <div>{{ selectValue }}</div> </div> <h3>组件 原理</h3> <comp></comp> <h3>插槽 原理</h3> <scope-slot></scope-slot> <scope-slot> <template v-slot:default="scopeSlot"> <div>{{ scopeSlot }}</div> </template> </scope-slot> </div> <script type="module"> import Vue from './src/index.js' const ins = new Vue({ el: '#app', data() { return { // 原始值和对象的响应式原理 t: 't value', t1: { tt1: 'tt1 value' }, // 数组的响应式原理 arr: [1, 2, 3], // 响应式更新 counter: 0, // v-bind title: "I am title", // v-model inputVal: 'test', isChecked: true, selectValue: 2, } }, // methods + 事件 + 数据响应式更新 原理 methods: { handleAdd() { this.counter++ }, handleMinus() { this.counter-- } }, // computed + 异步更新队列 的原理 computed: { doubleCounter() { console.log('evalute doubleCounter') return this.counter * 2 } }, // 组件 components: { // 子组件 'comp': { template: ` <div> <p>{{ compCounter }}</p> <p>{{ doubleCompCounter }}</p> <p>{{ doubleCompCounter }}</p> <p>{{ doubleCompCounter }}</p> <button v-on:click="handleCompAdd"> comp add </button> <button v-on:click="handleCompMinus"> comp minus </button> </div>`, data() { return { compCounter: 0 } }, methods: { handleCompAdd() { this.compCounter++ }, handleCompMinus() { this.compCounter-- } }, computed: { doubleCompCounter() { console.log('evalute doubleCompCounter') return this.compCounter * 2 } } }, // 插槽 'scope-slot': { template: ` <div> <slot name="default" v-bind:slotKey="slotKey">{{ slotKey }}</slot> </div> `, data() { return { slotKey: 'scope slot content' } } } } }) // 数据响应式拦挡 setTimeout(() => { console.log('********** 属性值为原始值时的 getter、setter ************') console.log(ins.t) ins.t = 'change t value' console.log(ins.t) }, 1000) setTimeout(() => { console.log('********** 属性的新值为对象的状况 ************') ins.t = { tt: 'tt value' } console.log(ins.t.tt) }, 2000) setTimeout(() => { console.log('********** 验证对深层属性的 getter、setter 拦挡 ************') ins.t1.tt1 = 'change tt1 value' console.log(ins.t1.tt1) }, 3000) setTimeout(() => { console.log('********** 将值为对象的属性更新为原始值 ************') console.log(ins.t1) ins.t1 = 't1 value' console.log(ins.t1) }, 4000) setTimeout(() => { console.log('********** 数组操作方法的拦挡 ************') console.log(ins.arr) ins.arr.push(4) console.log(ins.arr) }, 5000) </script></body></html>知识点示例代码波及的知识点包含: ...

March 15, 2022 · 3 min · jiezi

关于vue.js:vue-watch监听触发防抖

<template> <div> <el-input v-model="search" /> </div></template><script>export default { name: 'HelloWorld', data () { return { search: '', timer:null } }, watch: { search: { handler (newVal, oldVal) { if (this.timer) { clearTimeout(this.timer) } this.timer = setTimeout(() => { this.getGoods(); }, 1000) }, deep: true } }, methods: { getGoods(){ console.log('申请一次') } }}</script> 裁减知识点什么是防抖防抖,即短时间内大量触发同一事件,只会执行一次函数,实现原理为设置一个定时器,约定在xx毫秒后再触发事件处理,每次触发事件都会从新设置计时器,直到xx毫秒内无第二次操作,防抖罕用于搜寻框/滚动条的监听事件处理,如果不做防抖,每输出一个字/滚动屏幕,都会触发事件处理,造成性能节约。 什么是截流防抖是提早执行,而节流是距离执行,函数节流即每隔一段时间就执行一次,实现原理为设置一个定时器,约定xx毫秒后执行事件,如果工夫到了,那么执行函数并重置定时器,和防抖的区别在于,防抖每次触发事件都重置定时器,而节流在定时器到工夫后再清空定时器

March 15, 2022 · 1 min · jiezi

关于vue.js:petitevue源码剖析双向绑定vmodel的工作原理

前言双向绑定v-model不仅仅是对可编辑HTML元素(select, input, textarea和附带[contenteditable=true])同时附加v-bind和v-on,而且还能利用通过petite-vue附加给元素的_value、_trueValue和_falseValue属性提供存储非字符串值的能力。 深刻v-model工作原理export const model: Directive< HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement> = ({ el, exp, get, effect, modifers }) => { const type = el.type // 通过`with`对作用域的变量/属性赋值 const assign = get(`val => { ${exp} = val }`) // 若type为number则默认将值转换为数字 const { trim, number = type ==== 'number'} = modifiers || {} if (el.tagName === 'select') { const sel = el as HTMLSelectElement // 监听控件值变动,更新状态值 listen(el, 'change', () => { const selectedVal = Array.prototype.filter .call(sel.options, (o: HTMLOptionElement) => o.selected) .map((o: HTMLOptionElement) => number ? toNumber(getValue(o)) : getValue(o)) assign(sel.multiple ? selectedVal : selectedVal[0]) }) // 监听状态值变动,更新控件值 effect(() => { value = get() const isMultiple = sel.muliple for (let i = 0, l = sel.options.length; i < i; i++) { const option = sel.options[i] const optionValue = getValue(option) if (isMulitple) { // 当为多选下拉框时,入参要么是数组,要么是Map if (isArray(value)) { option.selected = looseIndexOf(value, optionValue) > -1 } else { option.selected = value.has(optionValue) } } else { if (looseEqual(optionValue, value)) { if (sel.selectedIndex !== i) sel.selectedIndex = i return } } } }) } else if (type === 'checkbox') { // 监听控件值变动,更新状态值 listen(el, 'change', () => { const modelValue = get() const checked = (el as HTMLInputElement).checked if (isArray(modelValue)) { const elementValue = getValue(el) const index = looseIndexOf(modelValue, elementValue) const found = index !== -1 if (checked && !found) { // 勾选且之前没有被勾选过的则退出到数组中 assign(modelValue.concat(elementValue)) } else if (!checked && found) { // 没有勾选且之前已勾选的排除后在从新赋值给数组 const filered = [...modelValue] filteed.splice(index, 1) assign(filtered) } // 其它状况就啥都不干咯 } else { assign(getCheckboxValue(el as HTMLInputElement, checked)) } }) // 监听状态值变动,更新控件值 let oldValue: any effect(() => { const value = get() if (isArray(value)) { ;(el as HTMLInputElement).checked = looseIndexOf(value, getValue(el)) > -1 } else if (value !== oldValue) { ;(el as HTMLInputElement).checked = looseEqual( value, getCheckboxValue(el as HTMLInputElement, true) ) } oldValue = value }) } else if (type === 'radio') { // 监听控件值变动,更新状态值 listen(el, 'change', () => { assign(getValue(el)) }) // 监听状态值变动,更新控件值 let oldValue: any effect(() => { const value = get() if (value !== oldValue) { ;(el as HTMLInputElement).checked = looseEqual(value, getValue(el)) } }) } else { // input[type=text], textarea, div[contenteditable=true] const resolveValue = (value: string) => { if (trim) return val.trim() if (number) return toNumber(val) return val } // 监听是否在输入法编辑器(input method editor)输出内容 listen(el, 'compositionstart', onCompositionStart) listen(el, 'compositionend', onCompositionEnd) // change事件是元素失焦后前后值不同时触发,而input事件是输出过程中每次批改值都会触发 listen(el, modifiers?.lazy ? 'change' : 'input', () => { // 元素的composing属性用于标记是否处于输入法编辑器输出内容的状态,如果是则不执行change或input事件的逻辑 if ((el as any).composing) return assign(resolveValue(el.value)) }) if (trim) { // 若modifiers.trim,那么当元素失焦时马上移除值前后的空格字符 listen(el, 'change', () => { el.value = el.value.trim() }) } effect(() => { if ((el as any).composing) { return } const curVal = el.value const newVal = get() // 若以后元素处于活动状态(即失去焦点),并且元素以后值进行类型转换后值与新值雷同,则不必赋值; // 否则只有元素以后值和新值类型或值不雷同,都会从新赋值。那么若新值为数组[1,2,3],赋值后元素的值将变成[object Array] if (document.activeElement === el && resolveValue(curVal) === newVal) { return } if (curVal !== newVal) { el.value = newVal } }) }}// v-bind中应用_value属性保留任意类型的值,在v-modal中读取const getValue = (el: any) => ('_value' in el ? el._value : el.value)const getCheckboxValue = ( el: HTMLInputElement & {_trueValue?: any, _falseValue?: any}, // 通过v-bind定义的任意类型值 checked: boolean // checkbox的默认值是true和false) => { const key = checked ? '_trueValue' : '_falseValue' return key in el ? el[key] : checked}const onCompositionStart = (e: Event) => { // 通过自定义元素的composing元素,用于标记是否在输入法编辑器中输出内容 ;(e.target as any).composing = true} const onCompositionEnd = (e: Event) => { const target = e.target as any if (target.composing) { // 手动触发input事件 target.composing = false trigger(target, 'input') }}const trigger = (el: HTMLElement, type: string) => { const e = document.createEvent('HTMLEvents') e.initEvent(type, true, true) el.dispatchEvent(e)}compositionstart和compositionend是什么?compositionstart是开始在输入法编辑器上输出字符触发,而compositionend则是在输入法编辑器上输出字符完结时触发,另外还有一个compositionupdate是在输入法编辑器上输出字符过程中触发。 ...

March 14, 2022 · 5 min · jiezi

关于vue.js:浅谈VueUse设计与实现

前言大家好,我是webfansplz.首先跟大家分享一个好消息,我退出VueUse团队啦,感激@antfu的邀请,很开心成为团队的一员.明天跟大家聊聊VueUse的设计与实现. 介绍大家都晓得Vue3引入了组合式API,大大晋升了逻辑复用能力.VueUse基于组合式API实现了很多易用、实用且乏味的性能.比方: useMagicKeysuseMagicKeys 监听按键状态,并提供了组合热键的性能,十分的神奇和乏味.应用它,咱们能够很容易的监听咱们应用CV大法的次数 :) useScrolluseScroll 提供了一些响应式的状态和值,比方滚动状态、到达状态、滚动方向以及以后滚动地位. useElementByPointuseElementByPoint 用于实时获取以后坐标地位最顶层的元素,配合 useMouse,咱们能够做一些乏味的交互和成果. 用户体验使用者体验VueUse无论是面向使用者还是开发者都做到了很棒的用户体验.咱们先来看看使用者体验: 强类型反对VueUse采纳了TypeScript进行编写并且带有残缺的TS文档,有良好的TS反对能力. SSR反对咱们对SSR进行了敌对的反对,它能够在服务端渲染场景工作的很好. 易用性一些反对传入配置选项的函数咱们会为使用者提供一套罕用的默认选项,这样能够保障用户在大多数利用 场景下并不需要过多的关注你的性能实现和细节.以 useScroll 为例: <script setup lang="ts">import { useScroll } from '@vueuse/core'const el = ref<HTMLElement | null>()// 只需传入滚动元素就能够工作const { x, y } = useScroll(el)// 节流反对选项const { x, y } = useScroll(el, { throttle: 200 })</script>useScroll 对一些有性能要求的开发者提供了节流选项.然而咱们心愿的是用户有需要的时候才关注到有这个配置,因为当配置参数一多的时候,了解参数含意和配置其实是一种心智累赘.另外,通用默认配置其实也是开箱能力的一种体现 ! 应用文档应用文档咱们提供了可交互的Demo和精简的Usage,用户能够通过把玩Demo进一步理解性能,也能够通过CV大法复制Usage很容易的就用上性能.真香 ! 兼容性后面咱们提到了Vue3引入了组合式API的概念,然而得益于composition-api插件的实现,咱们也能在Vue2我的项目应用组合式API.为了让更多的用户可能应用VueUse,Anthony Fu 实现了vue-demi ,它通过判断用户装置环境 (Vue2我的项目 援用composition-api插件,Vue3我的项目援用官网包),这样Vue2用户也能用上VueUse啦,奈斯 ! 开发者体验目录构造 在基于Monorepo的根底上,我的项目采纳了扁平化目录构造,便于开发者查找相应的函数. 咱们为每个函数的实现创立了一个独立的文件夹,这样开发者在修复Bug和新增性能的时候,只须要关注该文件夹下具体函数的实现,并不需要关注我的项目自身实现的细节,大大降低了上手的老本.Demo和文档的编写也在该文件夹下实现,防止了高低重复横跳寻找目录构造文件的蹩脚研发体验. 奉献指南咱们提供了十分具体的奉献指南帮忙想要奉献的开发者疾速开始并且编写了一些自动化脚本帮忙开发者防止一些手动的工作. 原子化CSS我的项目应用原子化CSS作为CSS的编写计划,我集体感觉原子化CSS能够帮忙咱们疾速的编写出演示Demo,并且每个函数的Demo独立不耦合,不会产生形象复用的心智累赘. 设计思维可组合的函数可组合的函数简略来说就是函数间能够建设组合关系,举个例子: useScroll 的实现组合了三个函数,将一个个繁多职责的函数组合造成另一个函数,达到逻辑复用的能力,我感觉这也便是组合式函数的魅力所在吧.当然,每个函数也都能够进行独立应用,用户能够依据本人的须要进行抉择. 开发者在解决性能函数的时候能够做到更好的关注点拆散,比方解决 useScroll 时咱们只须要关注滚动性能的实现,并不需要关注防抖节流及事件绑定外部的逻辑与实现. ...

March 14, 2022 · 2 min · jiezi

关于vue.js:手写-Vue-系列-之-Vue1x

当学习成为了习惯,常识也就变成了常识。 感激各位的 关注、点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 前言后面咱们用 12 篇文章具体解说了 Vue2 的框架源码。接下来咱们就开始手写 Vue 系列,写一个本人的 Vue 框架,用最简略的代码实现 Vue 的外围性能,进一步了解 Vue 外围原理。 为什么要手写框架有人会有疑难:我曾经具体浏览过框架源码了,甚至不止两三遍,这难道还不够吗?我自认为对框架的源码曾经很相熟了,我感觉没必要再手写。 有没有必要手写框架 这个事件,和 有没有必要浏览框架源码 的答案一样。看你的出发点是什么。 读源码如果你是抱以学习的态度,那不用说,浏览框架源码必定是有必要的。 大家都明确,平时的业务开发中,你身边人的程度可能都跟你差不多,所以你在业务中根本是看不到太多的优良编码和思维。 而一个框架所蕴含的优良设计和最佳实际就很多了,在浏览的时候有太多让你豁然开朗和惊艳的中央。即便你感觉本人当初段位不够,可能看不到那么多,然而源码对你的影响是耳濡目染的。看多了优良的代码,在你本人平时的编码中会不盲目的利用你学到的这些优良编码方式。更何况 Vue 的大部分代码都是尤大本人写的,代码品质那是毋庸置疑的。 手写框架至于 手写框架是否有必要 ?只有你读了框架源码,就必须本人手写一个。理由很简略,你浏览框架源码的目标是学习,你说你对源码曾经十分熟了,你说你都学会了,那怎么测验?测验的形式也很简略,把你学到的东西向外输入,分三个阶段: 写技术博客、画思维导图(把握 30%)给别人分享,比方组内分享、录视频都行(把握 60%)手写框架,造轮子是测验你学习成绩最好的形式(把握 90%)有没有发现前两阶段都是在讲别人的货色,你说你学到了,的确,你能向外输入,学你必定是学到了,然而学到了多少呢?我感觉差不多是 60%,举个例子: 他人问你 Vue 的响应式原理是什么?通过前两个阶段的输入,你可能说的有条有理,比方 Object.defineProperty、getter、setter、依赖收集、依赖告诉 watcher 更新等等。然而这整个过程你是否写进去呢?如果你第一次写,大概率是不行的,实现的时候会发现,基本不像你说的那么简略,要思考货色远不止你说的那些。如果不信大家能够试试,测验一下。 要晓得,造轮子的过程其实就是你利用的过程,只有你真的写进去了,你才算是真的学到了。如果只看不写,基本上能够算是进阶版的 只看不练。 所以,测验你是否真的学会并深刻了解某个框架的实现原理,模拟 造轮子 是最好的测验形式。 手写 Vue1.x在开始之前,咱们先做好筹备工作,在本人的工作目录下,新建咱们的源码目录,比方: mkdir lyn-vue && cd lyn-vue这里我不想额定装置和配置打包工具,太麻烦了,采纳古代浏览器原生反对的 ESM 的形式,所以大家须要在本地装一个 serve,起一个服务器。vite 就是这个原理,只不过它的服务端是本人实现的,因为它须要针对 import 的不同资源做相应的解决,比方解析 import 申请的是 node_modules 还是 用户本人的模块,亦或者是 TS 模块的转译等等。 ...

March 14, 2022 · 10 min · jiezi

关于vue.js:浅谈云时代如何解决身份管理

01用户明码疲劳只管 SaaS 给最后应用用户更容易拜访他们的应用程序,但复杂性会随着应用程序的数量迅速减少。每个应用程序都有本人的身份存储,具备本人的登录 URL 和明码要求。因为用户花工夫尝试在所有应用程序中重置、记住和治理这些一直变动的明码和 URL,面对登录凭据的激增会导致用户生产力降落和用户挫败感减少。 用户对这种“明码疲劳”的反馈是应用显著的、不平安的明码或在多个零碎中重复使用雷同的明码,从而导致平安危险。甚至,这些明码通常写在便当贴上或保留在笔记本电脑上的不平安文本文档中。 基于云的 OneAuth 服务能够通过在所有这些应用程序中提供单点登录 (SSO) 来缓解这些问题,为用户提供一个应用单个用户名和明码拜访其所有资源的核心地位。SSO 解决方案能够很好地连贯到云应用程序和本地应用程序,解决了很多公司对这两种连贯形式的需要。 大多数企业应用 Microsoft Active Directory (AD) 作为权威用户目录,用于治理对根本 IT 服务的拜访,例如电子邮件和文件共享。AD 通常还用于管制对更宽泛的业务应用程序和 IT 零碎的拜访。 正确的按需 IAM 解决方案应该利用 Active Directory,并容许用户持续应用他们的 AD 凭证来拜访 SaaS 应用程序——这减少了用户找到他们公司提供的最新和最好的 SaaS 应用程序的可能性。 02容易出错的手动权限赋予和撤销的过程当新员工入职时,IT部门通常会为员工提供公司网络、文件服务器、电子邮件帐户和打印机的拜访权限。因为许多 SaaS 应用程序是在部门级别治理的,因而对这些应用程序的拜访通常由特定应用程序的管理员独自授予,而不是由 IT 部门对立授予。 鉴于其按需架构,SaaS 应用程序应该易于集中配置。古代 IAM 解决方案应该可能主动配置新的 SaaS 应用程序,作为现有入职流程的天然扩大。将用户增加到外围目录服务(例如 Active Directory)时,他们在特定平安组中的成员身份应确保主动为他们提供适当的应用程序并为其角色授予拜访权限。 员工到职是一个更大的问题。IT 能够集中撤销对电子邮件和公司网络的拜访权限,但他们必须依附内部应用程序管理员来撤销终止员工对每个 SaaS 应用程序的拜访权限。权限撤销的提早使公司容易受到攻打——要害的业务应用程序和数据把握在可能心怀不满的前员工手中。 弱小的 IAM 解决方案不仅应使 IT 可能主动增加新应用程序,而且 还应提供: 跨所有应用程序主动勾销用户配置与所有用户存储深度集成,包含 Active Directory 和 LDAPIAM 服务应该让公司更加安心,即便员工到职,公司的相干登录权限也得以及时发出。 03合规性可见性:谁能够拜访什么?理解谁有权拜访应用程序和数据、他们在哪里拜访以及应用哪些利用很重要的。 您的管理员要理解哪些员工能够拜访您的应用程序和数据,您须要对所有零碎进行集中可见性和管制。您的 IAM 服务应该使您可能设置跨服务的拜访权限,并提供跨拜访权限、供给和勾销供给以及用户和管理员流动的集中合规性报告。 04每个应用程序的孤立用户目录大多数企业都在应用企业目录(如 Microsoft Active Directory),以治理对本地网络资源的拜访。 ...

March 11, 2022 · 1 min · jiezi

关于vue.js:Vue30新版API之compositionapi入坑指南

对于VUE3.0因为vue3.0语法跟vue2.x的语法简直是齐全兼容的,本文次要介绍了如何应用composition-api,次要分以下几个方面来讲 应用vite体验vue3.0composition-api解决了什么问题语法糖介绍 vite的装置应用vite仓库地址 https://github.com/vuejs/vite 下面有具体的装置应用教程,依照步骤装置即可。 composition-api解决了什么问题应用传统的option配置办法写组件的时候问题,随着业务复杂度越来越高,代码量会一直的加大;因为相干业务的代码须要遵循option的配置写到特定的区域,导致后续保护十分的简单,同时代码可复用性不高,而composition-api就是为了解决这个问题而生的 语法糖介绍compositon-api提供了一下几个函数 reactivewatchEffectcomputedreftoRefs生命周期的hooks reactive import { reactive, computed } from 'vue' export default { setup() { const state = reactive({ count: 0 }) function increment() { state.count++ } return { state, increment } } }Reactive 相当于以后的 Vue.observable () API,通过reactive解决后的函数能变成响应式的数据,相似于option api外面的data属性的值 watchEffectimport { reactive, computed, watchEffect } from 'vue'export default { setup() { const state = reactive({ count: 0 }) const double = computed(() => state.count * 2) function increment() { state.count++ } watchEffect(() => { console.log(double.value) }) return { state, increment } }}Vue 中检测状态变动的办法,咱们能够在渲染期间应用它。 因为依赖关系跟踪,当反馈状态发生变化时,视图会自动更新。 在 DOM 中出现某些内容被认为是一种“副作用” : 咱们的程序在程序自身(DOM)之外批改状态。 要利用并主动从新利用基于反馈状态的副作用,咱们能够应用 watchEffect API ...

March 9, 2022 · 2 min · jiezi

关于vue.js:Vue自定义表格组件测试版

Vue自定义表格组件思考到最近在应用element框架开发治理后盾,用到表格组件的次数比拟多,简略做了一个数据展现型的表格组件,至于交互按钮下面还没有通过验证。 <br/> 表格列提供4种数据处理展现,文本(默认)、图片、匹配文本、匹配文本按钮,做一下解释。字段类型必填备注propString是后盾返回的列字段(所有数据处理必填)showImageBoolean否style字段自定义款式optionsObject[Object]否匹配文本,格局如{1:{title:'文案1',type:'primary'}}onActionFunction否用于匹配按钮事件传参permissionBoolean否用于提供匹配按钮权限1.<Table>组件代码 <template> <div class="w-match m-t-1"> <!-- 表格 --> <el-table :data="dataList" border fit highlight-current-row> <el-table-column label="序号" width="70" align="center"> <template slot-scope="scope"> {{ (page - 1) * limit + scope.$index + 1 }} </template> </el-table-column> <el-table-column v-for="item in columns" :prop="item.prop" :label="item.label" :key="item.prop"> <template slot-scope="scope"> <span v-if="!item.permission && item.options"> {{ item.options[scope.row[item.prop]]['title'] || scope.row[item.prop] }} </span> <img v-else-if="item.showImage" :src="scope.row[item.prop]" :style="item.style" class="w-match" alt=""> <el-button :type="item.options[scope.row[item.prop]].type" size="mini" icon="el-icon-edit" v-else-if="item.permission && item.onAction" @click="item.onAction(scope.row)"> {{ item.options[scope.row[item.prop]].title }} </el-button> <span v-else>{{ scope.row[item.prop] || '' }}</span> </template> </el-table-column> <el-table-column label="操作" align="center" width="200" v-if="actions && actions.length>0"> <template slot-scope="scope"> <div class="" v-for="item in actions" :key="item.name"> <el-button :type="item.type" :size="item.size||'mini'" @click="item.onAction(scope.row)" v-if="item.permission != undefined ? item.permission : true">{{ item.title }} </el-button> </div> <!-- <el-button type="danger" size="mini" @click="removeDataById(scope.row.id)"--> <!-- v-if="hasPerm('activity.delete')">删除--> <!-- </el-button>--> </template> </el-table-column> </el-table> <!-- 分页 --> <el-pagination :current-page="page" :page-size="limit" :total="total" style="padding: 30px 0; text-align: center;" layout="total, sizes, prev, pager, next, jumper" @current-change="getList" :page-sizes="[10, 20, 30, 40, 50, 100]" @size-change="handleSizeChange" /> </div></template><script>export default { name: "TableComponent", props: { dataList: { //数据源 type: Array, default: [], required: true }, total: { //页大小 type: Number, default: 0 }, columns: { //表格列 type: Array, default: [], required: true }, actions: { //表格按钮 type: Array, default: [], required: true }, onPageChange: { type: Function, default: null } }, data() { return { page: 1, limit: 10, } }, created() { this.getList() }, methods: { getList(page = 1) { this.page = page this.$emit("onPageChange", this.page, this.limit);//通知父组件页码发生变化 }, handleSizeChange(number) { this.limit = number this.$emit("onPageChange", this.page, this.limit);//通知父组件页大小发生变化 }, }}</script><style scoped></style><br/> ...

March 9, 2022 · 2 min · jiezi

关于vue.js:Vue-源码解读12-patch

当学习成为了习惯,常识也就变成了常识。 感激各位的 关注、点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 前言后面咱们说到,当组件更新时,实例化渲染 watcher 时传递的 updateComponent 办法会被执行: const updateComponent = () => { // 执行 vm._render() 函数,失去 虚构 VNode,并将 VNode 传递给 vm._update 办法,接下来就该到 patch 阶段了 vm._update(vm._render(), hydrating)}首先会先执行 vm._render() 函数,失去组件的 VNode,并将 VNode 传递给 vm._update 办法,接下来就该进入到 patch 阶段了。明天咱们就来深刻了解组件更新时 patch 的执行过程。 历史1.x 版本的 Vue 没有 VNode 和 diff 算法,那个版本的 Vue 的外围只有响应式原理:Object.defineProperty、Dep、Watcher。 Object.defineProperty: 负责数据的拦挡。getter 时进行依赖收集,setter 时让 dep 告诉 watcher 去更新Dep:Vue data 选项返回的对象,对象的 key 和 dep 一一对应Watcher:key 和 watcher 时一对多的关系,组件模版中每应用一次 key 就会生成一个 watcher<template> <div class="wrapper"> <!-- 模版中每援用一次响应式数据,就会生成一个 watcher --> <!-- watcher 1 --> <div class="msg1">{{ msg }}</div> <!-- watcher 2 --> <div class="msg2">{{ msg }}</div> </div></template><script>export default { data() { return { // 和 dep 一一对应,和 watcher 一 对 多 msg: 'Hello Vue 1.0' } }}</script>当数据更新时,dep 告诉 watcher 去间接更新 DOM,因为这个版本的 watcher 和 DOM 时一一对应关系,watcher 能够十分明确的晓得这个 key 在组件模版中的地位,因而能够做到定向更新,所以它的更新效率是十分高的。 ...

March 9, 2022 · 14 min · jiezi

关于vue.js:浅谈决策引擎在身份管理的应用

在咱们生存处处可见引擎的踪影,对于游戏来说引擎是游戏的要害外围,对于汽车来说 引擎是外围的发动机是提供能源的源泉,对于杀毒引擎来说, 引擎是其外围病毒库和甄别组件形成。那么什么是身份引擎? 01什么是决策引擎?决策引擎是指企业针对其客户提供个性化服务的决策平台,这些个性化服务决策通常包含:危险决策、准确营销决策等,然而利用在身份畛域,OneAuth是这一场景的开创者。 决策引擎是事实世界决策的高纬度形象, 蕴含了三个对象,即:输出、决策引擎、输入。不同场景下对应不同的输出,同时利用的模型不同,产生决策后果也不同。其中输出和输入很容易了解,那么具象化的引擎是什么? 决策引擎能够了解为是规定、模型运行的容器,可依据随时依据场景的须要,来扭转要求的输出和扭转规定和模型,从而无需而外开发来适配新的场景。 02什么是身份治理?让咱们先从IAM来说起 IAM是 Identity and Access Management 的缩写,即身份与拜访治理,或称为身份治理与访问控制。 IAM次要为了达到一个目标:让失当的人或物,有失当的权限,拜访失当的资源。其中“人或物”称为主体(Subject),“资源”称为客体(Object)。 传统的IAM个别蕴含如下几局部,常被称为“4A”或“5A”,然而往往因为事实世界的账号、权限、认证形式和拜访的客体是动静的,传统的IAM曾经不再适应多元化,多场景的需要。  IAM尽管能实现身份治理的最根本治理性能,随着近年基于云利用,云原生的企业越来越多,SaaS行业倒退逐步凋敝,时代的倒退,企业解决业务习惯也逐渐相云迁徙,或基于云。云原生利用 SaaS等泛滥产品 ,给人们带来极大的便当同时也面临着身份治理的问题。企业业务散布在不同的云利用上,然而对于员工身份信息管理,企业组织架构,权限调配,却是割裂且独立的。 03什么是身份“引擎”面向未来的身份引擎OS -OneAuth™ Cloud Engine引擎与OS的定位是要为使用者/开发者赋予更多的抉择、模块化的自由组合。 OS 的形象 - 身份治理去耦合 为了实用于每种APP的拜访体验,OneAuth将身份形象为四个外围的构建,每个构建援用一组或者多组策略引擎——将用户拜访过程的合成为身份、受权、注销和注册、签发四个外围块,以底层的策略引擎提供决策反对。 客户能够创立动静的、基于上下文的用户旅程,以起码的自定义代码解决有限数量的身份用例的能力。应用无关用户、设施、应用程序、网络和用户行为的上下文来告知任何用户的身份旅程,从而相应地调整拜访体验。OneAuth 身份引擎由一系列独自的步骤组成,能够解决从注册到身份验证到受权的整个用户流程。  应用模块化组件定义任意流程外围OS模块化构建能够使您可能评估策略、触发 Hook、公布事件、提醒用户采取行动或间接拜访内部服务。定制能够依据用例和利用的上下文而有所不同。能够通过配置跳过OneAuth引擎中的默认的步骤。而且,您能够为任何应用程序或体验中的任何工夫点、抉择不同的步骤来运行和或让用户来抉择是否跳过,从而创立各种身份的拜访流程。 应用策略引擎定义平安的设置OneAuth的身份引擎是决策引擎在身份场景下的一个实际。 OneAuth身份引擎依据平安或者业务的须要,能够自定义限度拜访的条件,依据用户、组成员身份、设施、地位或工夫作为访问控制的条件。 比方,依据不同利用敏感水平,设计不同的身份验证步骤,对于敏感的利用、或者可疑的身份,须要输出OTP或者SMS 一次性明码进行MFA的身份验证能力拜访利用;比方,须要引入更多的因子,或者账户复原的过程中,须要更多的验证。 在OneAuth中反对以下策略类型:OneAuth登录策略:用于管制谁能够登录,在何种环境下容许用户登录 OneAuth,是否须要MFA 的验证,以及登录后容许的放弃登录状态的工夫。利用登录策略:在拜访应用程序之前,判断是否须要执行的额定身份验证。MFA策略:管制用户能够应用哪些 MFA 的办法,以及设置用户在什么工夫去注册是哪种因素。明码策略:依据不同的用户,给予不同的明码长度和复杂度,明码的有效期。以及账户锁定和解锁的条件,包含用户在何种环境下容许自助进行账户复原的操作。IDP路由策略:存在多个IDP服务,用户尝试登录时,路由策略依据物理地位、终端、利用、用户等条件,将用户路由设定的IDP。OAuth 受权策略:依据特定客户端、用户、申请的受权范畴的组合条件,给予规定定义的特定令牌,包含令牌生命周期的设定。应用Hook进行额定的扩大通过Inline Hook 和Event Hook的能力使您可能反对有限的用例。Hooks钩子为 OneAuth身份引擎增加了额定的可扩展性,容许您增加自定义代码来批改运行过程并告诉内部服务。   Hooks 有两种类型: Inline Hook - 容许您向组件增加自定义逻辑Event Hook- 容许您依据 OneAuth 系统日志中公布的事件启动上游集成可自定义的用户流程依据利用的自定义,OneAuth能够在每个外围组件中依据决策采取下一步的口头,以推动用户实现他们所拜访的流程。 身份引擎充当了对外部API和开发都须要调用的驱动引擎当向开发者API/组件服务不满足这一驱动引擎的标准时,内部开发人员会发现错误。这要求开发者在平安设计方面更加的标准的思考和设计,否则不能与身份引擎进行对接,从底层推动了安全性和品质。 另外,基于此设计,也让业务在本身平安思考方面不须要思考的太多,而让开发者更加专一本身的业务,让业务变得更加纯正,减少其将来的扩展性。这将帮忙开发者整体晋升本人的工程实际。 须要的不是一个IAM软件 而是一个规范OneAuth™ Cloud Engine 它不是一个产品,更是一个基于引擎OS(一套标准化的身份实际),缩小保护老本,晋升企业身份平安,进步生产力,欠缺身份建设。用身份引擎来驱动更多的企业发明更大的愿景。 从新定义身份,任何科技,任何形式,任何地位,任何事件与人的连贯 ...

March 9, 2022 · 1 min · jiezi

关于vue.js:vue-elementui使用

1.第一个问题,dialogue组件会展现上一次关上的数据 解决:dialog组件增加v-if <el-dialog :title="dialogTitle" :visible.sync="popVisible" v-if="popVisible" width="50%" > <el-form :ref="pageName" :rules="rules" :model="notice" label-width="140px" > <el-form-item label="ID" prop="id"> <el-input v-model="notice.id" placeholder="请输出ID(步长10)" ></el-input> </el-form-item> <el-form-item label="标签" prop="key"> <el-input v-model="notice.key" placeholder="请输出标签"></el-input> </el-form-item> <el-form-item label="文案" prop="message"> <el-input v-model="notice.message" type="textarea" placeholder="请输出文案" ></el-input> </el-form-item> <el-form-item label="模板" prop="tpl"> <el-select v-model="notice.tpl" width="100"> <el-option value="common" label="专用">专用</el-option> <el-option v-for="(item, key) in options.webConfigKV['tpl']" :key="key" :value="key" :label="item" >{{ item }}</el-option ></el-select > </el-form-item> <el-form-item label="备注" prop="memo"> <el-input v-model="notice.memo" placeholder="请输出备注"></el-input> </el-form-item> </el-form> <span slot="footer" class="dialog-footer"> <el-button @click="popVisible = false">取 消</el-button> <el-button type="primary" @click="save" :disabled="btnDisabled" >确 定</el-button > </span> </el-dialog>

March 9, 2022 · 1 min · jiezi

关于vue.js:懒加载预加载

WEB性能优化--图片媒体篇目标为了晋升客户端成果体验。原生js实现(懒加载)原理:图片的getBoundingClientRect().top高度 是否小于以后可视视图高度,小于则把data-src的url替换给src1、第一种办法:获取对应元素举例顶部的间隔来判断。 // onload是等所有的资源文件加载结束当前再绑定事件window.onload = function(){ // 获取图片列表,即img标签列表 var imgs = document.querySelectorAll('img'); // 获取到浏览器顶部的间隔 function getTop(e){ return e.getBoundingClientRect().top; } // 懒加载实现 function lazyload(imgs){ // 可视区域高度 var h = window.innerHeight; //滚动区域高度 for(var i=0;i<imgs.length;i++){ //图片间隔顶部的间隔大于可视区域和滚动区域之和时懒加载 if (h>getTop(imgs[i])) { // 真实情况是页面开始有2秒空白,所以应用setTimeout定时2s (function(i){ setTimeout(function(){ // 不加立刻执行函数i会等于9 // 隐形加载图片或其余资源, //创立一个长期图片,这个图片在内存中不会到页面下来。实现隐形加载 var temp = new Image(); temp.src = imgs[i].getAttribute('data-src');//只会申请一次 // onload判断图片加载结束,真是图片加载结束,再赋值给dom节点 temp.onload = function(){ // 获取自定义属性data-src,用真图片替换假图片 imgs[i].src = imgs[i].getAttribute('data-src') } },2000) })(i) } } } lazyload(imgs); // 滚屏函数 window.onscroll =function(){ lazyload(imgs); }}2、第二种办法:利用intersectionObserver-api监听元素是否进入到可视区域(较第一种办法更简略) ...

March 9, 2022 · 2 min · jiezi

关于vue.js:vue3tsvite-问题汇总环境变量三

环境变量配置第一步:装置cross-envyarn add cross-env -D 第二步:在根目录新建 .env 、 .env.test 和 .env.prod,并写入对应代码# 我的项目本地运行端口号VITE_PORT = 8848# 开发环境读取配置文件门路VITE_PUBLIC_PATH = /# 开发环境代理VITE_PROXY_DOMAIN = /api# 开发环境路由历史模式(Hash 模式:`hash`、HTML5 模式:`h5`、Hash 模式带base参数:"hash,base参数"、HTML5 模式带base参数:"h5,base参数")VITE_ROUTER_HISTORY = "hash"# 开发环境后端地址VITE_PROXY_DOMAIN_REAL = "http://127.0.0.1:3000"第三步:配置 package.json 的运行脚本//第一种在这里写环境 "scripts": { + "dev": "cross-env --max_old_space_size=4096 vite --mode dev", + "build": "cross-env vue-tsc --noEmit && vite build --mode dev", .... }//执行命令npm run dev第二种:也能够间接在命令行批改npm run dev --mode production 第四步:组件中应用import.meta.VITE_PORT 第五步:vite.config.ts中应用import { defineConfig, loadEnv, UserConfig } from 'vite';...export default defineConfig(({ mode }: UserConfig): UserConfig => { const env = loadEnv(mode, __dirname); return { server: { host: '127.0.0.1', //解决"vite use `--host` to expose" port: Number(env.VITE_PORT), ...

March 8, 2022 · 1 min · jiezi

关于vue.js:elementtable-无数据的时候把暂无数据-改成其他文字或图片

在el-table 外面插入 <template v-slot:empty> <span style="color: #969799;">No more data</span></template><el-table :data="tableData" style="width: 100%" :header-cell-style="{background:'#F0F2F5',color:'#585858',textAlign: 'center'}" > <template v-slot:"empty"> <span style="color: #969799;">No more data</span> </template> <el-table-column prop="content" label="OrderId" min-width="100" show-overflow-tooltip> </el-table-column> <el-table-column prop="amount" label="amount" min-width="80" show-overflow-tooltip> </el-table-column></el-table>

March 8, 2022 · 1 min · jiezi

关于vue.js:Vue-源码解读11-render-helper

前言上一篇文章 Vue 源码解读(10)—— 编译器 之 生成渲染函数 最初讲到组件更新时,须要先执行编译器生成的渲染函数失去组件的 vnode。 渲染函数之所以能生成 vnode 是通过其中的 _c、_l、、_v、_s 等办法实现的。比方: 一般的节点被编译成了可执行 _c 函数v-for 节点被编译成了可执行的 _l 函数...然而到目前为止咱们都不分明这些办法的原理,它们是如何生成 vnode 的?只晓得它们是 Vue 实例办法,明天咱们就从源码中找答案。 指标在 Vue 编译器的根底上,进一步深刻了解一个组件是如何通过这些运行时的工具办法(render helper)生成 VNode 的 源码解读入口咱们晓得这些办法是 Vue 实例办法,依照之前对源码的理解,实例办法个别都放在 /src/core/instance 目录下。其实之前在 Vue 源码解读(6)—— 实例办法 浏览中见到过 render helper,在文章的最初。 /src/core/instance/render.jsexport function renderMixin (Vue: Class<Component>) { // install runtime convenience helpers // 在组件实例上挂载一些运行时须要用到的工具办法 installRenderHelpers(Vue.prototype) // ...}installRenderHelpers/src/core/instance/render-helpers/index.js/** * 在实例上挂载简写的渲染工具函数,这些都是运行时代码 * 这些工具函数在编译器生成的渲染函数中被应用到了 * @param {*} target Vue 实例 */export function installRenderHelpers(target: any) { /** * v-once 指令的运行时帮忙程序,为 VNode 加上打上动态标记 * 有点多余,因为含有 v-once 指令的节点都被当作动态节点解决了,所以也不会走这儿 */ target._o = markOnce // 将值转换为数字 target._n = toNumber /** * 将值转换为字符串模式,一般值 => String(val),对象 => JSON.stringify(val) */ target._s = toString /** * 运行时渲染 v-for 列表的帮忙函数,循环遍历 val 值,顺次为每一项执行 render 办法生成 VNode,最终返回一个 VNode 数组 */ target._l = renderList target._t = renderSlot /** * 判断两个值是否相等 */ target._q = looseEqual /** * 相当于 indexOf 办法 */ target._i = looseIndexOf /** * 运行时负责生成动态树的 VNode 的帮忙程序,实现了以下两件事 * 1、执行 staticRenderFns 数组中指定下标的渲染函数,生成动态树的 VNode 并缓存,下次在渲染时从缓存中间接读取(isInFor 必须为 true) * 2、为动态树的 VNode 打动态标记 */ target._m = renderStatic target._f = resolveFilter target._k = checkKeyCodes target._b = bindObjectProps /** * 为文本节点创立 VNode */ target._v = createTextVNode /** * 为空节点创立 VNode */ target._e = createEmptyVNode}_o = markOnce/src/core/instance/render-helpers/render-static.js/** * Runtime helper for v-once. * Effectively it means marking the node as static with a unique key. * v-once 指令的运行时帮忙程序,为 VNode 加上打上动态标记 * 有点多余,因为含有 v-once 指令的节点都被当作动态节点解决了,所以也不会走这儿 */export function markOnce ( tree: VNode | Array<VNode>, index: number, key: string) { markStatic(tree, `__once__${index}${key ? `_${key}` : ``}`, true) return tree}markStatic/src/core/instance/render-helpers/render-static.js/** * 为 VNode 打动态标记,在 VNode 上增加三个属性: * { isStatick: true, key: xx, isOnce: true or false } */function markStatic ( tree: VNode | Array<VNode>, key: string, isOnce: boolean) { if (Array.isArray(tree)) { // tree 为 VNode 数组,循环遍历其中的每个 VNode,为每个 VNode 做动态标记 for (let i = 0; i < tree.length; i++) { if (tree[i] && typeof tree[i] !== 'string') { markStaticNode(tree[i], `${key}_${i}`, isOnce) } } } else { markStaticNode(tree, key, isOnce) }}markStaticNode/src/core/instance/render-helpers/render-static.js/** * 标记动态 VNode */function markStaticNode (node, key, isOnce) { node.isStatic = true node.key = key node.isOnce = isOnce}_l = renderList/src/core/instance/render-helpers/render-list.js/** * Runtime helper for rendering v-for lists. * 运行时渲染 v-for 列表的帮忙函数,循环遍历 val 值,顺次为每一项执行 render 办法生成 VNode,最终返回一个 VNode 数组 */export function renderList ( val: any, render: ( val: any, keyOrIndex: string | number, index?: number ) => VNode): ?Array<VNode> { let ret: ?Array<VNode>, i, l, keys, key if (Array.isArray(val) || typeof val === 'string') { // val 为数组或者字符串 ret = new Array(val.length) for (i = 0, l = val.length; i < l; i++) { ret[i] = render(val[i], i) } } else if (typeof val === 'number') { // val 为一个数值,则遍历 0 - val 的所有数字 ret = new Array(val) for (i = 0; i < val; i++) { ret[i] = render(i + 1, i) } } else if (isObject(val)) { // val 为一个对象,遍历对象 if (hasSymbol && val[Symbol.iterator]) { // val 为一个可迭代对象 ret = [] const iterator: Iterator<any> = val[Symbol.iterator]() let result = iterator.next() while (!result.done) { ret.push(render(result.value, ret.length)) result = iterator.next() } } else { // val 为一个一般对象 keys = Object.keys(val) ret = new Array(keys.length) for (i = 0, l = keys.length; i < l; i++) { key = keys[i] ret[i] = render(val[key], key, i) } } } if (!isDef(ret)) { ret = [] } // 返回 VNode 数组 (ret: any)._isVList = true return ret}_m = renderStatic/src/core/instance/render-helpers/render-static.js/** * Runtime helper for rendering static trees. * 运行时负责生成动态树的 VNode 的帮忙程序,实现了以下两件事 * 1、执行 staticRenderFns 数组中指定下标的渲染函数,生成动态树的 VNode 并缓存,下次在渲染时从缓存中间接读取(isInFor 必须为 true) * 2、为动态树的 VNode 打动态标记 * @param { number} index 示意以后动态节点的渲染函数在 staticRenderFns 数组中的下标索引 * @param { boolean} isInFor 示意以后动态节点是否被包裹在含有 v-for 指令的节点外部 */ export function renderStatic ( index: number, isInFor: boolean): VNode | Array<VNode> { // 缓存,动态节点第二次被渲染时就从缓存中间接获取已缓存的 VNode const cached = this._staticTrees || (this._staticTrees = []) let tree = cached[index] // if has already-rendered static tree and not inside v-for, // we can reuse the same tree. // 如果以后动态树曾经被渲染过一次(即有缓存)而且没有被包裹在 v-for 指令所在节点的外部,则间接返回缓存的 VNode if (tree && !isInFor) { return tree } // 执行 staticRenderFns 数组中指定元素(动态树的渲染函数)生成该动态树的 VNode,并缓存 // otherwise, render a fresh tree. tree = cached[index] = this.$options.staticRenderFns[index].call( this._renderProxy, null, this // for render fns generated for functional component templates ) // 动态标记,为动态树的 VNode 打标记,即增加 { isStatic: true, key: `__static__${index}`, isOnce: false } markStatic(tree, `__static__${index}`, false) return tree}_c/src/core/instance/render.js/** * 定义 _c,它是 createElement 的一个柯里化办法 * @param {*} a 标签名 * @param {*} b 属性的 JSON 字符串 * @param {*} c 子节点数组 * @param {*} d 节点的规范化类型 * @returns VNode or Array<VNode> */vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)createElement/src/core/vdom/create-element.js/** * 生成组件或一般标签的 vnode,一个包装函数,不必管 * wrapper function for providing a more flexible interface * without getting yelled at by flow */export function createElement( context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean): VNode | Array<VNode> { if (Array.isArray(data) || isPrimitive(data)) { normalizationType = children children = data data = undefined } if (isTrue(alwaysNormalize)) { normalizationType = ALWAYS_NORMALIZE } // 执行 _createElement 办法创立组件的 VNode return _createElement(context, tag, data, children, normalizationType)}_createElement/src/core/vdom/create-element.js/** * 生成 vnode, * 1、平台保留标签和未知元素执行 new Vnode() 生成 vnode * 2、组件执行 createComponent 生成 vnode * 2.1 函数式组件执行本人的 render 函数生成 VNode * 2.2 一般组件则实例化一个 VNode,并且在其 data.hook 对象上设置 4 个办法,在组件的 patch 阶段会被调用, * 从而进入子组件的实例化、挂载阶段,直至实现渲染 * @param {*} context 上下文 * @param {*} tag 标签 * @param {*} data 属性 JSON 字符串 * @param {*} children 子节点数组 * @param {*} normalizationType 节点规范化类型 * @returns VNode or Array<VNode> */export function _createElement( context: Component, tag?: string | Class<Component> | Function | Object, data?: VNodeData, children?: any, normalizationType?: number): VNode | Array<VNode> { if (isDef(data) && isDef((data: any).__ob__)) { // 属性不能是一个响应式对象 process.env.NODE_ENV !== 'production' && warn( `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` + 'Always create fresh vnode data objects in each render!', context ) // 如果属性是一个响应式对象,则返回一个空节点的 VNode return createEmptyVNode() } // object syntax in v-bind if (isDef(data) && isDef(data.is)) { tag = data.is } if (!tag) { // 动静组件的 is 属性是一个假值时 tag 为 false,则返回一个空节点的 VNode // in case of component :is set to falsy value return createEmptyVNode() } // 检测惟一键 key,只能是字符串或者数字 // warn against non-primitive key if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.key) && !isPrimitive(data.key) ) { if (!__WEEX__ || !('@binding' in data.key)) { warn( 'Avoid using non-primitive value as key, ' + 'use string/number value instead.', context ) } } // 子节点数组中只有一个函数时,将它当作默认插槽,而后清空子节点列表 // support single function children as default scoped slot if (Array.isArray(children) && typeof children[0] === 'function' ) { data = data || {} data.scopedSlots = { default: children[0] } children.length = 0 } // 将子元素进行标准化解决 if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children) } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) } /** * 这里开始才是重点,后面的都不须要关注,基本上是一些异样解决或者优化等 */ let vnode, ns if (typeof tag === 'string') { // 标签是字符串时,该标签有三种可能: // 1、平台保留标签 // 2、自定义组件 // 3、不出名标签 let Ctor // 命名空间 ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) if (config.isReservedTag(tag)) { // tag 是平台原生标签 // platform built-in elements if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) { // v-on 指令的 .native 只在组件上失效 warn( `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`, context ) } // 实例化一个 VNode vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { // tag 是一个自定义组件 // 在 this.$options.components 对象中找到指定标签名称的组件构造函数 // 创立组件的 VNode,函数式组件间接执行其 render 函数生成 VNode, // 一般组件则实例化一个 VNode,并且在其 data.hook 对象上设置了 4 个办法,在组件的 patch 阶段会被调用, // 从而进入子组件的实例化、挂载阶段,直至实现渲染 // component vnode = createComponent(Ctor, data, context, children, tag) } else { // 不出名的一个标签,但也生成 VNode,因为思考到在运行时可能会给一个适合的名字空间 // unknown or unlisted namespaced elements // check at runtime because it may get assigned a namespace when its // parent normalizes children vnode = new VNode( tag, data, children, undefined, undefined, context ) } } else { // tag 为非字符串,比方可能是一个组件的配置对象或者是一个组件的构造函数 // direct component options / constructor vnode = createComponent(tag, data, context, children) } // 返回组件的 VNode if (Array.isArray(vnode)) { return vnode } else if (isDef(vnode)) { if (isDef(ns)) applyNS(vnode, ns) if (isDef(data)) registerDeepBindings(data) return vnode } else { return createEmptyVNode() }}createComponent/src/core/vdom/create-component.js/** * 创立组件的 VNode, * 1、函数式组件通过执行其 render 办法生成组件的 VNode * 2、一般组件通过 new VNode() 生成其 VNode,然而一般组件有一个重要操作是在 data.hook 对象上设置了四个钩子函数, * 别离是 init、prepatch、insert、destroy,在组件的 patch 阶段会被调用, * 比方 init 办法,调用时会进入子组件实例的创立挂载阶段,直到实现渲染 * @param {*} Ctor 组件构造函数 * @param {*} data 属性组成的 JSON 字符串 * @param {*} context 上下文 * @param {*} children 子节点数组 * @param {*} tag 标签名 * @returns VNode or Array<VNode> */export function createComponent( Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string): VNode | Array<VNode> | void { // 组件构造函数不存在,间接完结 if (isUndef(Ctor)) { return } // Vue.extend const baseCtor = context.$options._base // 当 Ctor 为配置对象时,通过 Vue.extend 将其转为构造函数 // plain options object: turn it into a constructor if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor) } // 如果到这个为止,Ctor 依然不是一个函数,则示意这是一个有效的组件定义 // if at this stage it's not a constructor or an async component factory, // reject. if (typeof Ctor !== 'function') { if (process.env.NODE_ENV !== 'production') { warn(`Invalid Component definition: ${String(Ctor)}`, context) } return } // 异步组件 // async component let asyncFactory if (isUndef(Ctor.cid)) { asyncFactory = Ctor Ctor = resolveAsyncComponent(asyncFactory, baseCtor) if (Ctor === undefined) { // 为异步组件返回一个占位符节点,组件被渲染为正文节点,但保留了节点的所有原始信息,这些信息将用于异步服务器渲染 和 hydration return createAsyncPlaceholder( asyncFactory, data, context, children, tag ) } } // 节点的属性 JSON 字符串 data = data || {} // 这里其实就是组件做选项合并的中央,即编译器将组件编译为渲染函数,渲染时执行 render 函数,而后执行其中的 _c,就会走到这里了 // 解析构造函数选项,并合基类选项,以避免在组件构造函数创立后利用全局混入 // resolve constructor options in case global mixins are applied after // component constructor creation resolveConstructorOptions(Ctor) // 将组件的 v-model 的信息(值和回调)转换为 data.attrs 对象的属性、值和 data.on 对象上的事件、回调 // transform component v-model data into props & events if (isDef(data.model)) { transformModel(Ctor.options, data) } // 提取 props 数据,失去 propsData 对象,propsData[key] = val // 以组件 props 配置中的属性为 key,父组件中对应的数据为 value // extract props const propsData = extractPropsFromVNodeData(data, Ctor, tag) // 函数式组件 // functional component if (isTrue(Ctor.options.functional)) { /** * 执行函数式组件的 render 函数生成组件的 VNode,做了以下 3 件事: * 1、设置组件的 props 对象 * 2、设置函数式组件的渲染上下文,传递给函数式组件的 render 函数 * 3、调用函数式组件的 render 函数生成 vnode */ return createFunctionalComponent(Ctor, propsData, data, context, children) } // 获取事件监听器对象 data.on,因为这些监听器须要作为子组件监听器解决,而不是 DOM 监听器 // extract listeners, since these needs to be treated as // child component listeners instead of DOM listeners const listeners = data.on // 将带有 .native 修饰符的事件对象赋值给 data.on // replace with listeners with .native modifier // so it gets processed during parent component patch. data.on = data.nativeOn if (isTrue(Ctor.options.abstract)) { // 如果是形象组件,则值保留 props、listeners 和 slot // abstract components do not keep anything // other than props & listeners & slot // work around flow const slot = data.slot data = {} if (slot) { data.slot = slot } } /** * 在组件的 data 对象上设置 hook 对象, * hook 对象减少四个属性,init、prepatch、insert、destroy, * 负责组件的创立、更新、销毁,这些办法在组件的 patch 阶段会被调用 * install component management hooks onto the placeholder node */ installComponentHooks(data) const name = Ctor.options.name || tag // 实例化组件的 VNode,对于一般组件的标签名会比拟非凡,vue-component-${cid}-${name} const vnode = new VNode( `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`, data, undefined, undefined, undefined, context, { Ctor, propsData, listeners, tag, children }, asyncFactory ) // Weex specific: invoke recycle-list optimized @render function for // extracting cell-slot template. // https://github.com/Hanks10100/weex-native-directive/tree/master/component /* istanbul ignore if */ if (__WEEX__ && isRecyclableComponent(vnode)) { return renderRecyclableComponentTemplate(vnode) } return vnode}resolveConstructorOptions/src/core/instance/init.js/** * 从构造函数上解析配置项 */export function resolveConstructorOptions (Ctor: Class<Component>) { // 从实例构造函数上获取选项 let options = Ctor.options if (Ctor.super) { const superOptions = resolveConstructorOptions(Ctor.super) // 缓存 const cachedSuperOptions = Ctor.superOptions if (superOptions !== cachedSuperOptions) { // 阐明基类的配置项产生了更改 // super option changed, // need to resolve new options. Ctor.superOptions = superOptions // check if there are any late-modified/attached options (#4976) // 找到更改的选项 const modifiedOptions = resolveModifiedOptions(Ctor) // update base extend options if (modifiedOptions) { // 将更改的选项和 extend 选项合并 extend(Ctor.extendOptions, modifiedOptions) } // 将新的选项赋值给 options options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions) if (options.name) { options.components[options.name] = Ctor } } } return options}resolveModifiedOptions/src/core/instance/init.js/** * 解析构造函数选项中后续被批改或者减少的选项 */function resolveModifiedOptions (Ctor: Class<Component>): ?Object { let modified // 构造函数选项 const latest = Ctor.options // 密封的构造函数选项,备份 const sealed = Ctor.sealedOptions // 比照两个选项,记录不统一的选项 for (const key in latest) { if (latest[key] !== sealed[key]) { if (!modified) modified = {} modified[key] = latest[key] } } return modified}transformModelsrc/core/vdom/create-component.js/** * 将组件的 v-model 的信息(值和回调)转换为 data.attrs 对象的属性、值和 data.on 对象上的事件、回调 * transform component v-model info (value and callback) into * prop and event handler respectively. */function transformModel(options, data: any) { // model 的属性和事件,默认为 value 和 input const prop = (options.model && options.model.prop) || 'value' const event = (options.model && options.model.event) || 'input' // 在 data.attrs 对象上存储 v-model 的值 ; (data.attrs || (data.attrs = {}))[prop] = data.model.value // 在 data.on 对象上存储 v-model 的事件 const on = data.on || (data.on = {}) // 已存在的事件回调函数 const existing = on[event] // v-model 中事件对应的回调函数 const callback = data.model.callback // 合并回调函数 if (isDef(existing)) { if ( Array.isArray(existing) ? existing.indexOf(callback) === -1 : existing !== callback ) { on[event] = [callback].concat(existing) } } else { on[event] = callback }}extractPropsFromVNodeData/src/core/vdom/helpers/extract-props.js/** * <comp :msg="hello vue"></comp> * * 提取 props,失去 res[key] = val * * 以 props 配置中的属性为 key,父组件中对应的的数据为 value * 当父组件中数据更新时,触发响应式更新,从新执行 render,生成新的 vnode,又走到这里 * 这样子组件中相应的数据就会被更新 */export function extractPropsFromVNodeData ( data: VNodeData, // { msg: 'hello vue' } Ctor: Class<Component>, // 组件构造函数 tag?: string // 组件标签名): ?Object { // 组件的 props 选项,{ props: { msg: { type: String, default: xx } } } // 这里只提取原始值,验证和默认值在子组件中解决 // we are only extracting raw values here. // validation and default values are handled in the child // component itself. const propOptions = Ctor.options.props if (isUndef(propOptions)) { // 未定义 props 间接返回 return } // 以组件 props 配置中的属性为 key,父组件传递下来的值为 value // 当父组件中数据更新时,触发响应式更新,从新执行 render,生成新的 vnode,又走到这里 // 这样子组件中相应的数据就会被更新 const res = {} const { attrs, props } = data if (isDef(attrs) || isDef(props)) { // 遍历 propsOptions for (const key in propOptions) { // 将小驼峰模式的 key 转换为 连字符 模式 const altKey = hyphenate(key) // 提醒,如果申明的 props 为小驼峰模式(testProps),但因为 html 不辨别大小写,所以在 html 模版中应该应用 test-props 代替 testProps if (process.env.NODE_ENV !== 'production') { const keyInLowerCase = key.toLowerCase() if ( key !== keyInLowerCase && attrs && hasOwn(attrs, keyInLowerCase) ) { tip( `Prop "${keyInLowerCase}" is passed to component ` + `${formatComponentName(tag || Ctor)}, but the declared prop name is` + ` "${key}". ` + `Note that HTML attributes are case-insensitive and camelCased ` + `props need to use their kebab-case equivalents when using in-DOM ` + `templates. You should probably use "${altKey}" instead of "${key}".` ) } } checkProp(res, props, key, altKey, true) || checkProp(res, attrs, key, altKey, false) } } return res}checkProp/src/core/vdom/helpers/extract-props.js/** * 失去 res[key] = val */function checkProp ( res: Object, hash: ?Object, key: string, altKey: string, preserve: boolean): boolean { if (isDef(hash)) { // 判断 hash(props/attrs)对象中是否存在 key 或 altKey // 存在则设置给 res => res[key] = hash[key] if (hasOwn(hash, key)) { res[key] = hash[key] if (!preserve) { delete hash[key] } return true } else if (hasOwn(hash, altKey)) { res[key] = hash[altKey] if (!preserve) { delete hash[altKey] } return true } } return false}createFunctionalComponent/src/core/vdom/create-functional-component.jsinstallRenderHelpers(FunctionalRenderContext.prototype)/** * 执行函数式组件的 render 函数生成组件的 VNode,做了以下 3 件事: * 1、设置组件的 props 对象 * 2、设置函数式组件的渲染上下文,传递给函数式组件的 render 函数 * 3、调用函数式组件的 render 函数生成 vnode * * @param {*} Ctor 组件的构造函数 * @param {*} propsData 额定的 props 对象 * @param {*} data 节点属性组成的 JSON 字符串 * @param {*} contextVm 上下文 * @param {*} children 子节点数组 * @returns Vnode or Array<VNode> */export function createFunctionalComponent ( Ctor: Class<Component>, propsData: ?Object, data: VNodeData, contextVm: Component, children: ?Array<VNode>): VNode | Array<VNode> | void { // 组件配置项 const options = Ctor.options // 获取 props 对象 const props = {} // 组件自身的 props 选项 const propOptions = options.props // 设置函数式组件的 props 对象 if (isDef(propOptions)) { // 阐明该函数式组件自身提供了 props 选项,则将 props.key 的值设置为组件上传递下来的对应 key 的值 for (const key in propOptions) { props[key] = validateProp(key, propOptions, propsData || emptyObject) } } else { // 以后函数式组件没有提供 props 选项,则将组件上的 attribute 主动解析为 props if (isDef(data.attrs)) mergeProps(props, data.attrs) if (isDef(data.props)) mergeProps(props, data.props) } // 实例化函数式组件的渲染上下文 const renderContext = new FunctionalRenderContext( data, props, children, contextVm, Ctor ) // 调用 render 函数,生成 vnode,并给 render 函数传递 _c 和 渲染上下文 const vnode = options.render.call(null, renderContext._c, renderContext) // 在最初生成的 VNode 对象上加一些标记,示意该 VNode 是一个函数式组件生成的,最初返回 VNode if (vnode instanceof VNode) { return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext) } else if (Array.isArray(vnode)) { const vnodes = normalizeChildren(vnode) || [] const res = new Array(vnodes.length) for (let i = 0; i < vnodes.length; i++) { res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext) } return res }}installComponentHooks/src/core/vdom/create-component.jsconst hooksToMerge = Object.keys(componentVNodeHooks)/** * 在组件的 data 对象上设置 hook 对象, * hook 对象减少四个属性,init、prepatch、insert、destroy, * 负责组件的创立、更新、销毁 */ function installComponentHooks(data: VNodeData) { const hooks = data.hook || (data.hook = {}) // 遍历 hooksToMerge 数组,hooksToMerge = ['init', 'prepatch', 'insert' 'destroy'] for (let i = 0; i < hooksToMerge.length; i++) { // 比方 key = init const key = hooksToMerge[i] // 从 data.hook 对象中获取 key 对应的办法 const existing = hooks[key] // componentVNodeHooks 对象中 key 对象的办法 const toMerge = componentVNodeHooks[key] // 合并用户传递的 hook 办法和框架自带的 hook 办法,其实就是别离执行两个办法 if (existing !== toMerge && !(existing && existing._merged)) { hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge } }}function mergeHook(f1: any, f2: any): Function { const merged = (a, b) => { // flow complains about extra args which is why we use any f1(a, b) f2(a, b) } merged._merged = true return merged}componentVNodeHooks/src/core/vdom/create-component.js// patch 期间在组件 vnode 上调用内联钩子// inline hooks to be invoked on component VNodes during patchconst componentVNodeHooks = { // 初始化 init(vnode: VNodeWithData, hydrating: boolean): ?boolean { if ( vnode.componentInstance && !vnode.componentInstance._isDestroyed && vnode.data.keepAlive ) { // 被 keep-alive 包裹的组件 // kept-alive components, treat as a patch const mountedNode: any = vnode // work around flow componentVNodeHooks.prepatch(mountedNode, mountedNode) } else { // 创立组件实例,即 new vnode.componentOptions.Ctor(options) => 失去 Vue 组件实例 const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ) // 执行组件的 $mount 办法,进入挂载阶段,接下来就是通过编译器失去 render 函数,接着走挂载、patch 这条路,直到组件渲染到页面 child.$mount(hydrating ? vnode.elm : undefined, hydrating) } }, // 更新 VNode,用新的 VNode 配置更新旧的 VNode 上的各种属性 prepatch(oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) { // 新 VNode 的组件配置项 const options = vnode.componentOptions // 老 VNode 的组件实例 const child = vnode.componentInstance = oldVnode.componentInstance // 用 vnode 上的属性更新 child 上的各种属性 updateChildComponent( child, options.propsData, // updated props options.listeners, // updated listeners vnode, // new parent vnode options.children // new children ) }, // 执行组件的 mounted 申明周期钩子 insert(vnode: MountedComponentVNode) { const { context, componentInstance } = vnode // 如果组件未挂载,则调用 mounted 申明周期钩子 if (!componentInstance._isMounted) { componentInstance._isMounted = true callHook(componentInstance, 'mounted') } // 解决 keep-alive 组件的异常情况 if (vnode.data.keepAlive) { if (context._isMounted) { // vue-router#1212 // During updates, a kept-alive component's child components may // change, so directly walking the tree here may call activated hooks // on incorrect children. Instead we push them into a queue which will // be processed after the whole patch process ended. queueActivatedComponent(componentInstance) } else { activateChildComponent(componentInstance, true /* direct */) } } }, /** * 销毁组件 * 1、如果组件被 keep-alive 组件包裹,则使组件失活,不销毁组件实例,从而缓存组件的状态 * 2、如果组件没有被 keep-alive 包裹,则间接调用实例的 $destroy 办法销毁组件 */ destroy (vnode: MountedComponentVNode) { // 从 vnode 上获取组件实例 const { componentInstance } = vnode if (!componentInstance._isDestroyed) { // 如果组件实例没有被销毁 if (!vnode.data.keepAlive) { // 组件没有被 keep-alive 组件包裹,则间接调用 $destroy 办法销毁组件 componentInstance.$destroy() } else { // 负责让组件失活,不销毁组件实例,从而缓存组件的状态 deactivateChildComponent(componentInstance, true /* direct */) } } }}createComponentInstanceForVnode/src/core/vdom/create-component.js/** * new vnode.componentOptions.Ctor(options) => 失去 Vue 组件实例 */export function createComponentInstanceForVnode( // we know it's MountedComponentVNode but flow doesn't vnode: any, // activeInstance in lifecycle state parent: any): Component { const options: InternalComponentOptions = { _isComponent: true, _parentVnode: vnode, parent } // 查看内联模版渲染函数 const inlineTemplate = vnode.data.inlineTemplate if (isDef(inlineTemplate)) { options.render = inlineTemplate.render options.staticRenderFns = inlineTemplate.staticRenderFns } // new VueComponent(options) => Vue 实例 return new vnode.componentOptions.Ctor(options)}总结面试官 问:一个组件是如何变成 VNode? ...

March 8, 2022 · 16 min · jiezi

关于vue.js:如何在-Vue-中使用-Chartjs-手把手教你搭可视化数据图表

本文首发:《如何在 Vue 中应用 Chart.js - 手把手教你搭可视化数据图表》 很多 Vue 我的项目中都须要 PDF 文件预览性能,比方合同 ERP,销售CRM,外部文档CMS管理系统,内置 PDF 文件在线预览性能。本文手把手教你搭建一套 PDF 预览组件嵌入到 Vue 我的项目中,实现 PDF 文件在线预览等 PDF 预览的所有常见性能。 追随本教程学习实现后,你会搭出以下 PDF 在线预览成果的 Vue PDF 预览组件 如果你正在搭建后盾管理工具,又不想解决前端问题,举荐应用卡拉云,卡拉云是新一代低代码开发工具,可一键接入常见数据库及 API ,无需懂前端,仅需拖拽即可疾速搭建属于你本人的后盾管理工具,一周工作量缩减至一天,详见本文文末。 扩大浏览《12 款最棒 Vue 开源 UI 库测评 - 特地针对国内应用场景举荐》 第 2 步 - 下载并配置 PDF.jsnpm install -g @vue/clivue create kalacloud-vue-pdfjs-viewercd kalacloud-vue-pdfjs-viewer接下来,咱们所有操作都在 kalacloud-vue-pdfjs-viewer 这个目录中实现。 第 2 步 - 下载并配置 PDF.js配置好 Vue 我的项目后,咱们先去 PDF.js 官网 下载最新的稳定版,PDF.js 是目前 PDF 在线预览中最好的开源解决方案之一。咱们把下载好的压缩包解压到 Vue 我的项目中的新建文件夹 public/lib 中。 而后在找一个 pdf 文件放在文件夹的 web 文件夹中,一会咱们用写的 pdf 预览组件来调用并预览这个 PDF 文件。 ...

March 8, 2022 · 3 min · jiezi

关于vue.js:petitevue源码剖析vfor重新渲染工作原理

在《petite-vue源码分析-v-if和v-for的工作原理》咱们理解到v-for在动态视图中的工作原理,而这里咱们将深刻理解在更新渲染时v-for是如何运作的。 逐行解析// 文件 ./src/directives/for.ts/* [\s\S]*示意辨认空格字符和非空格字符若干个,默认为贪心模式,即 `(item, index) in value` 就会匹配整个字符串。 * 批改为[\s\S]*?则为懈怠模式,即`(item, index) in value`只会匹配`(item, index)` */const forAliasRE = /([\s\S]*?)\s+(?:in)\s+([\s\S]*?)/// 用于移除`(item, index)`中的`(`和`)`const stripParentRE= /^\(|\)$/g// 用于匹配`item, index`中的`, index`,那么就能够抽取出value和index来独立解决const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/type KeyToIndexMap = Map<any, number>// 为便于了解,咱们假如只承受`v-for="val in values"`的模式,并且所有入参都是无效的,对入参有效性、解构等代码进行了删减export const _for = (el: Element, exp: string, ctx: Context) => { // 通过正则表达式抽取表达式字符串中`in`两侧的子表达式字符串 const inMatch = exp.match(forAliasRE) // 保留下一轮遍历解析的模板节点 const nextNode = el.nextSibling // 插入锚点,并将带`v-for`的元素从DOM树移除 const parent = el.parentElement! const anchor = new Text('') parent.insertBefore(anchor, el) parent.removeChild(el) const sourceExp = inMatch[2].trim() // 获取`(item, index) in value`中`value` let valueExp = inMatch[1].trim().replace(stripParentRE, '').trim() // 获取`(item, index) in value`中`item, index` let indexExp: string | undefined let keyAttr = 'key' let keyExp = el.getAttribute(keyAttr) || el.getAttribute(keyAttr = ':key') || el.getAttribute(keyAttr = 'v-bind:key') if (keyExp) { el.removeAttribute(keyExp) // 将表达式序列化,如`value`序列化为`"value"`,这样就不会参加前面的表达式运算 if (keyAttr === 'key') keyExp = JSON.stringify(keyExp) } let match if (match = valueExp.match(forIteratorRE)) { valueExp = valueExp.replace(forIteratorRE, '').trim() // 获取`item, index`中的item indexExp = match[1].trim() // 获取`item, index`中的index } let mounted = false // false示意首次渲染,true示意从新渲染 let blocks: Block[] let childCtxs: Context[] let keyToIndexMap: KeyToIndexMap // 用于记录key和索引的关系,当产生从新渲染时则复用元素 const createChildContexts = (source: unknown): [Context[], KeyToIndexMap] => { const map: KeyToIndexMap = new Map() const ctxs: Context[] = [] if (isArray(source)) { for (let i = 0; i < source.length; i++) { ctxs.push(createChildContext(map, source[i], i)) } } return [ctxs, map] } // 以汇合元素为根底创立独立的作用域 const createChildContext = ( map: KeyToIndexMap, value: any, // the item of collection index: number // the index of item of collection ): Context => { const data: any = {} data[valueExp] = value indexExp && (data[indexExp] = index) // 为每个子元素创立独立的作用域 const childCtx = createScopedContext(ctx, data) // key表达式在对应子元素的作用域下运算 const key = keyExp ? evaluate(childCtx.scope, keyExp) : index map.set(key, index) childCtx.key = key return childCtx } // 为每个子元素创立块对象 const mountBlock = (ctx: Conext, ref: Node) => { const block = new Block(el, ctx) block.key = ctx.key block.insert(parent, ref) return block } ctx.effect(() => { const source = evaluate(ctx.scope, sourceExp) // 运算出`(item, index) in items`中items的实在值 const prevKeyToIndexMap = keyToIndexMap // 生成新的作用域,并计算`key`,`:key`或`v-bind:key` ;[childCtxs, keyToIndexMap] = createChildContexts(source) if (!mounted) { // 为每个子元素创立块对象,解析子元素的子孙元素后插入DOM树 blocks = childCtxs.map(s => mountBlock(s, anchor)) mounted = true } else { // 更新渲染逻辑!! // 依据key移除更新后不存在的元素 for (let i = 0; i < blocks.length; i++) { if (!keyToIndexMap.has(blocks[i].key)) { blocks[i].remove() } } const nextBlocks: Block[] = [] let i = childCtxs.length let nextBlock: Block | undefined let prevMovedBlock: Block | undefined while (i--) { const childCtx = childCtxs[i] const oldIndex = prevKeyToIndexMap.get(childCtx.key) let block if (oldIndex == null) { // 旧视图中没有该元素,因而创立一个新的块对象 block = mountBlock(childCtx, newBlock ? newBlock.el : anchor) } else { // 旧视图中有该元素,元素复用 block = blocks[oldIndex] // 更新作用域,因为元素下的`:value`,`{{value}}`等都会跟踪scope对应属性的变动,因而这里只须要更新作用域上的属性,即可触发子元素的更新渲染 Object.assign(block.ctx.scope, childCtx.scope) if (oldIndex != i) { // 元素在新旧视图中的地位不同,须要挪动 if ( blocks[oldIndex + 1] !== nextBlock || prevMoveBlock === nextBlock ) { prevMovedBlock = block // anchor作为同级子元素的开端 block.insert(parent, nextBlock ? nextBlock.el : anchor) } } } nextBlocks.unshift(nextBlock = block) } blocks = nextBlocks } }) return nextNode}难点冲破上述代码最难了解就是通过key复用元素那一段了 ...

March 7, 2022 · 4 min · jiezi

关于vue.js:vue使用videojs

1.开发环境 vue22.电脑系统 windows10专业版3.废话不多说,间接上操作: npm i video.js -D3-1.在mian.js中增加如下代码: import videojs from 'video.js';import 'video.js/dist/video-js.css';Vue.prototype.$VideoJs = videojs;4.创立组件VideoPlayer.vue: <template> <div> <video ref="videoPlayer" id="videoplayer" class="video-js"></video> </div></template><script>export default { name: "VideoPlayer", props: { options: { type: Object, default () { return {}; } } }, data () { return { player: null }; }, created () { this.$nextTick( () => { this.player = this.$VideoJs(this.$refs.videoPlayer, this.options); const player = this.$VideoJs('videoplayer_html5_api'); player.ready(function () { let _this = this; _this.playbackRate( parseFloat(4)); }); }); }, mounted () { }, beforeDestroy () { if (this.player) { this.player.dispose(); } }};</script>5.在对应的文件中引入VideoPlayer组件: ...

March 7, 2022 · 1 min · jiezi

关于vue.js:大型项目数据状态管理摸索

讲一个悲伤的故事原本这篇文章应该是上周写完的。 故事产生在一周前,我在segmentfault在线编辑文章,写了差不多两个小时,在贴了一张图片失败之后,而后ctrl+z撤销了一步,后果整个文档被霎时清空了,编辑器还主动保留了清空态。 这一刻,有点心凉,好像忽然被浇了一桶冷水。 第一工夫,关上浏览器控制台,去翻缓存,后果localStorage外面空洞无物,过后就感觉心愿不大了,空想着他们服务端能保留某个时刻的记录。 微信分割了他们的客服小姐姐,晚饭后回复我:“技术人员上班了,今天帮忙查看,问我急不急?”。还能怎么?开发何必尴尬开发,过后就放过了她。 第二天,通过他们一番追踪,服务端没有任何记录...... 终局就是当初这样,我再从新写一遍。 通过这个事件,也能够看出,segmentfault的数据状态管理机制还是有缺点的,容错机制不太好,至多缓存机制不太行,共勉! 概述有个头痛的问题,什么是恋情? 噢噢,状态错乱了,“状态凌乱”就这个头痛的问题。 随着前端利用的规模越来越宏大,业务复杂度直线拉升。状态治理也成为了重中之重,DDD畛域驱动模型也逐步流行。 随着我的项目倒退,单元测试也变成了很重要的一个环节,随着业务逻辑复杂度的减少,问题关注点间接爆炸,人力心智累赘越来越高。 所以不论是 为了更好地开发模型 还是 更容易的单元测试,如何更好地治理数据状态都 成了一个 外围环节。 比方,畛域状态 要和 UI状态拆散;按照DDD畛域驱动模型构建 数据Store,而不是根据UI来构建。 状态数据构建 是 全局的还是 部分的,衍生数据 如何主动生成,副作用变更逻辑如何书写? 数据逻辑 如何 和 UI视图 解耦,又如何不便注入UI应用,怎么进行不便的通信? 社区有那么多套 所谓成熟的解决方案,咱们到底改用那套? 我的项目问题回归事实,聚焦到目前业务我的项目上。一个三年左右的我的项目了,技术栈是十分惯例的React框架搭建,初期是应用Redux做的的状态治理;起初React公布了Hook个性,看代码记录,大家果决摈弃了Redux,拥抱Hook,我的项目从某个节点开始,成了清一色的Function式。 好了,当初轻易找个我的项目开发者征询一下,我的项目存在什么技术问题,开发体验如何?霎时就能开启吐槽模式: 状态 凌乱 ,没有 畛域状态 UI状态辨别全局状态 和 部分状态 设计不合理数据流逻辑解决 耦合 UI视图 (CodeReview难,bug不好追踪,单元测试艰难)props drilling 而且传递过程 命名多变props 传递粒度 不够精密 (渲染频率减少)hook应用形式 近乎demo形式,极其不合理,无抽离,无封装Redux 样板代码较多;应用繁琐;性能产生了问题,频繁渲染,无奈追踪什么操作 导致变更开发方式 对人的要求较高,心智累赘较高看起来做了一系列性能优化,还不如vue不优化......不一一列举了那有解决方案吗?有,大家拉个会议一探讨,指定编码开发标准,严格CodeReview,该优化的优化。OK,问题解决了吗?没有,一段时间过后,代码格调对立了,可是这个真的不重要,问题还是那些问题,旧的代码能不动就不动,新代码就间接往上堆砌。 代码格调对立了,eslint跑过了,可是相比零碎架构而言,这些流于形式的货色权重就很低了,重要性微不足道。 如果不做演变改良,熵增持续,零碎势必走向解体。 这个时候,咱们必须思考,呈现问题的实质起因到底是什么?是历史遗留问题吗,是技术栈问题吗?都不是,思前想后,还是人的问题,开发者本身的问题。 适度的自在必然导致凌乱。 React设计理念 和 哲学思考都是很高的,自身是为了解决UI渲染问题;这就像一把神器,境界高的使用者如臂使指,用来所向无敌,事倍功半;境界不迭者,就会感觉深陷泥潭,拿不起来挥不动,终被反噬。 屠龙少年终成恶龙! 一个没什么开发教训,设计模式也不懂,框架立意也不清晰,读了几个api用来写我的项目的人,怎么能掌控好呢?React可能连开发多年的老鸟都不肯定能玩明确,上手难度高也不是空穴来风啊,大家可能只是习惯无脑的晓得大家这么说,哦,那就上手难度高。可是为什么上手难度高,到底难点在哪里?照着api开发,难吗,一点也不对吧 Vue技术栈为什么就没有这些问题?为什么很少呈现性能问题?尤雨溪尤大 设计框架的时候,面向的应用群体是那些人?框架里帮咱们解决了什么问题? 所以,实质起因是什么,这里就很明确了,然而对于”人“,十分难以掌控。 ...

March 7, 2022 · 1 min · jiezi

关于vue.js:petitevue源码剖析vif和vfor的工作原理

深刻v-if的工作原理<div v-scope="App"></div><script type="module"> import { createApp } from 'https://unpkg.com/petite-vue?module' createApp({ App: { $template: ` <span v-if="status === 'offline'"> OFFLINE </span> <span v-else-if="status === 'UNKOWN'"> UNKOWN </span> <span v-else> ONLINE </span> `, } status: 'online' }).mount('[v-scope]')</script>人肉单步调试: 调用createApp依据入参生成全局作用域rootScope,创立根上下文rootCtx;调用mount为<div v-scope="App"></div>构建根块对象rootBlock,并将其作为模板执行解析解决;解析时辨认到v-scope属性,以全局作用域rootScope为根底运算失去部分作用域scope,并以根上下文rootCtx为底本一起构建新的上下文ctx,用于子节点的解析和渲染;获取$template属性值并生成HTML元素;深度优先遍历解析子节点(调用walkChildren);解析<span v-if="status === 'offline'"> OFFLINE </span>解析<span v-if="status === 'offline'"> OFFLINE </span>书接上一回,咱们持续人肉单步调试: 辨认元素带上v-if属性,调用_if原指令对元素及兄弟元素进行解析;将附带v-if和跟紧其后的附带v-else-if和v-else的元素转化为逻辑分支记录;循环遍历分支,并为逻辑运算后果为true的分支创立块对象并销毁原有分支的块对象(首次渲染没有原分支的块对象),并提交渲染工作到异步队列。// 文件 ./src/walk.ts// 为便于了解,我对代码进行了精简export const walk = (node: Node, ctx: Context): ChildNode | null | void { const type = node.nodeType if (type == 1) { // node为Element类型 const el = node as Element let exp: string | null if ((exp = checkAttr(el, 'v-if'))) { return _if(el, exp, ctx) // 返回最近一个没有`v-else-if`或`v-else`的兄弟节点 } }}// 文件 ./src/directives/if.tsinterface Branch { exp?: string | null // 该分支逻辑运算表达式 el: Element // 该分支对应的模板元素,每次渲染时会以该元素为模板通过cloneNode复制一个实例插入到DOM树中}export const _if = (el: Element, exp: string, ctx: Context) => { const parent = el.parentElement! /* 锚点元素,因为v-if、v-else-if和v-else标识的元素可能在某个状态下都不位于DOM树上, * 因而通过锚点元素标记插入点的地位信息,当状态发生变化时则能够将指标元素插入正确的地位。 */ const anchor = new Comment('v-if') parent.insertBefore(anchor, el) // 逻辑分支,并将v-if标识的元素作为第一个分支 const branches: Branch[] = [ { exp, el } ] /* 定位v-else-if和v-else元素,并推入逻辑分支中 * 这里没有管制v-else-if和v-else的呈现程序,因而咱们能够写成 * <span v-if="status=0"></span><span v-else></span><span v-else-if="status === 1"></span> * 但成果为变成<span v-if="status=0"></span><span v-else></span>,最初的分支永远没有机会匹配。 */ let elseEl: Element | null let elseExp: string | null while ((elseEl = el.nextElementSibling)) { elseExp = null if ( checkAttr(elseEl, 'v-else') === '' || (elseExp = checkAttr(elseEl, 'v-else-if')) ) { // 从在线模板移除分支节点 parent.removeChild(elseEl) branches.push({ exp: elseExp, el: elseEl }) } else { break } } // 保留最近一个不带`v-else`和`v-else-if`节点作为下一轮遍历解析的模板节点 const nextNode = el.nextSibling // 从在线模板移除带`v-if`节点 parent.removeChild(el) let block: Block | undefined // 以后逻辑运算构造为true的分支对应块对象 let activeBranchIndex: number = -1 // 以后逻辑运算构造为true的分支索引 // 若状态发生变化导致逻辑运算构造为true的分支索引发生变化,则须要销毁原有分支对应块对象(蕴含停止旗下的副作用函数监控状态变动,执行指令的清理函数和递归触发子块对象的清理操作) const removeActiveBlock = () => { if (block) { // 从新插入锚点元素来定位插入点 parent.insertBefore(anchor, block.el) block.remove() // 解除对已销毁的块对象的援用,让GC回收对应的JavaScript对象和detached元素 block = undefined } } // 向异步工作对抗压入渲染工作,在本轮Event Loop的Micro Queue执行阶段会执行一次 ctx.effect(() => { for (let i = 0; i < branches.length; i++) { const { exp, el } = branches[i] if (!exp || evaluate(ctx.scope, exp)) { if (i !== activeBranchIndex) { removeActiveBlock() block = new Block(el, ctx) block.insert(parent, anchor) parent.removeChild(anchor) activeBranchIndex = i } return } } activeBranchIndex = -1 removeActiveBlock() }) return nextNode}上面咱们看看子块对象的构造函数和insert、remove办法 ...

March 7, 2022 · 5 min · jiezi

关于vue.js:Vue-源码解读10-编译器-之-生成渲染函数

当学习成为了习惯,常识也就变成了常识。 感激各位的 关注、点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 前言这篇文章是 Vue 编译器的最初一部分,前两局部别离是:Vue 源码解读(8)—— 编译器 之 解析、Vue 源码解读(9)—— 编译器 之 优化。 从 HTML 模版字符串开始,解析所有标签以及标签上的各个属性,失去 AST 语法树,而后基于 AST 语法树进行动态标记,首先标记每个节点是否为动态动态,而后进一步标记出动态根节点。这样在后续的更新中就能够跳过这些动态根节点的更新,从而进步性能。 这最初一部分讲的是如何从 AST 生成渲染函数。 指标深刻了解渲染函数的生成过程,了解编译器是如何将 AST 变成运行时的代码,也就是咱们写的类 html 模版最终变成了什么? 源码解读入口/src/compiler/index.js/** * 在这之前做的所有的事件,只有一个目标,就是为了构建平台特有的编译选项(options),比方 web 平台 * * 1、将 html 模版解析成 ast * 2、对 ast 树进行动态标记 * 3、将 ast 生成渲染函数 * 动态渲染函数放到 code.staticRenderFns 数组中 * code.render 为动静渲染函数 * 在未来渲染时执行渲染函数失去 vnode */export const createCompiler = createCompilerCreator(function baseCompile ( template: string, options: CompilerOptions): CompiledResult { // 将模版解析为 AST,每个节点的 ast 对象上都设置了元素的所有信息,比方,标签信息、属性信息、插槽信息、父节点、子节点等。 // 具体有那些属性,查看 options.start 和 options.end 这两个解决开始和完结标签的办法 const ast = parse(template.trim(), options) // 优化,遍历 AST,为每个节点做动态标记 // 标记每个节点是否为动态节点,而后进一步标记出动态根节点 // 这样在后续更新中就能够跳过这些动态节点了 // 标记动态根,用于生成渲染函数阶段,生成动态根节点的渲染函数 if (options.optimize !== false) { optimize(ast, options) } // 代码生成,将 ast 转换成可执行的 render 函数的字符串模式 // code = { // render: `with(this){return ${_c(tag, data, children, normalizationType)}}`, // staticRenderFns: [_c(tag, data, children, normalizationType), ...] // } const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns }})generate/src/compiler/codegen/index.js/** * 从 AST 生成渲染函数 * @returns { * render: `with(this){return _c(tag, data, children)}`, * staticRenderFns: state.staticRenderFns * } */export function generate( ast: ASTElement | void, options: CompilerOptions): CodegenResult { // 实例化 CodegenState 对象,生成代码的时候须要用到其中的一些货色 const state = new CodegenState(options) // 生成字符串格局的代码,比方:'_c(tag, data, children, normalizationType)' // data 为节点上的属性组成 JSON 字符串,比方 '{ key: xx, ref: xx, ... }' // children 为所有子节点的字符串格局的代码组成的字符串数组,格局: // `['_c(tag, data, children)', ...],normalizationType`, // 最初的 normalization 是 _c 的第四个参数, // 示意节点的规范化类型,不是重点,不须要关注 // 当然 code 并不一定就是 _c,也有可能是其它的,比方整个组件都是动态的,则后果就为 _m(0) const code = ast ? genElement(ast, state) : '_c("div")' return { render: `with(this){return ${code}}`, staticRenderFns: state.staticRenderFns }}genElement/src/compiler/codegen/index.js ...

March 7, 2022 · 13 min · jiezi

关于vue.js:Vue-响应式原理

Vue 最独特的个性之一,是非侵入式的响应零碎。数据模型仅仅是一般的 JavaScript 对象。而当你批改它们时,视图会进行更新。聊到 Vue 响应式实现原理,泛滥开发者都晓得实现的关键在于利用 Object.defineProperty , 但具体又是如何实现的呢,明天咱们来一探到底。 为了通俗易懂,咱们还是从一个小的示例开始: <body> <div id="app"> {{ message }} </div> <script> var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } })</script></body>咱们曾经胜利创立了第一个 Vue 利用!看起来这跟渲染一个字符串模板十分相似,然而 Vue 在背地做了大量工作。当初数据和 DOM 曾经被建设了关联,所有货色都是响应式的。咱们要怎么确认呢?关上你的浏览器的 JavaScript 控制台 (就在这个页面关上),并批改 app.message的值,你将看到上例相应地更新。批改数据便会自动更新,Vue 是如何做到的呢?通过 Vue 构造函数创立一个实例时,会有执行一个初始化的操作: function Vue (options) { this._init(options);}这个 _init初始化函数外部会初始化生命周期、事件、渲染函数、状态等等: initLifecycle(vm); initEvents(vm); initRender(vm); callHook(vm, 'beforeCreate'); initInjections(vm); initState(vm); initProvide(vm); callHook(vm, 'created');因为本文的主题是响应式原理,因而咱们只关注 initState(vm) 即可。它的要害调用步骤如下: function initState (vm) { initData(vm);}function initData(vm) { // data就是咱们创立 Vue实例传入的 {message: 'Hello Vue!'} observe(data, true /* asRootData */);}function observe (value, asRootData) { ob = new Observer(value);}var Observer = function Observer (value) { this.walk(value);}Observer.prototype.walk = function walk (obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { // 实现响应式要害函数 defineReactive$$1(obj, keys[i]); }};}咱们来总结一下下面 initState(vm)流程。初始化状态的时候会对利用的数据进行检测,即创立一个 Observer 实例,其构造函数外部会执行原型上的 walk办法。walk办法的次要作用便是 遍历数据的所有属性,并把每个属性转换成响应式,而这转换的工作次要由 defineReactive$$1 函数实现。 ...

March 6, 2022 · 3 min · jiezi

关于vue.js:Vue项目

vue我的项目增加菜单vue是基于组件:vm的components,一个 vue component (或者说 vue instance,两者只有细微差别)能够看成是 view 和 model 的联合,或者更间接一点:template + model。model 的属性(props)能够在 view 中援用时赋值,model 的数据(data)能够被 view 取用。还有其余一些 view 和 model 的调用关系。具体能够参考:https://www.jianshu.com/p/937... 所以针对于组建开发方式,咱们能够依照如下步骤: 1、增加组建index.js用于记录菜单路由: import { modName } from './vars'/** @type {import('vue-router').RouteConfig[]} */const routes = [ { path: `/${modName}`, meta: { title: '计费', icon: 'menu', permissionTag: 'bill' }, children: [ { path: 'recharge', component: () => import('./views/recharge/Index.vue'), meta: { title: '充值治理', permissionTag: 'bill_recharge' } } ] }]export default { routes }vars.js是配置的路由中变量export const modName = 'bill' ...

March 6, 2022 · 2 min · jiezi

关于vue.js:petitevue源码剖析从静态视图开始

代码库构造介绍examples 各种应用示例scripts 打包公布脚本tests 测试用例src directives v-if等内置指令的实现app.ts createApp函数block.ts 块对象context.ts 上下文对象eval.ts 提供v-if="count === 1"等表达式运算性能scheduler.ts 调度器utils.ts 工具函数walk.ts 模板解析若想构建本人的版本只需在控制台执行npm run build即可。 深刻了解动态视图的渲染过程动态视图是指首次渲染后,不会因UI状态变动引发从新渲染。其中视图不蕴含任何UI状态,和依据UI状态首次渲染后状态不再更新两种状况,本篇将针对前者进行解说。 示例: <div v-scope="App"></div><script type="module"> import { createApp } from 'https://unpkg.com/petite-vue?module' createApp({ App: { $template: ` <span> OFFLINE </span> <span> UNKOWN </span> <span> ONLINE </span> ` } }).mount('[v-scope]')</script>首先进入的就是createApp办法,它的作用就是创立根上下文对象(root context)、全局作用域对象(root scope)并返回mount,unmount和directive办法。而后通过mount办法寻找附带[v-scope]属性的孩子节点(排除匹配[v-scope] [v-scope]的子孙节点),并为它们创立根块对象。源码如下(基于这个例子,我对源码进行局部删减以便更容易浏览): // 文件 ./src/app.tsexport const createApp = (initialData: any) => { // 创立根上下文对象 const ctx = createContext() // 全局作用域对象,作用域对象其实就是一个响应式对象 ctx.scope = reactive(initialData) /* 将scope的函数成员的this均绑定为scope。 * 若采纳箭头函数赋值给函数成员,则上述操作对该函数成员有效。 */ bindContextMethods(ctx.scope) /* 根块对象汇合 * petite-vue反对多个根块对象,但这里咱们能够简化为仅反对一个根块对象。 */ let rootBlocks: Block[] return { // 简化为必然挂载到某个带`[v-scope]`的元素下 mount(el: Element) { let roots = el.hasAttribute('v-scope') ? [el] : [] // 创立根块对象 rootBlocks = roots.map(el => new Block(el, ctx, true)) return this }, unmount() { // 当节点卸载时(removeChild)执行块对象的清理工作。留神:刷新界面时不会触发该操作。 rootBlocks.forEach(block => block.teardown()) } }}代码尽管很短,但引出了3个外围对象:上下文对象(context)、作用域(scope)和块对象(block)。他们三的关系是: ...

March 4, 2022 · 3 min · jiezi

关于vue.js:vuecli3-项目-tokentypeendsWith-is-not-a-function-生产事故分析

事变形容一图解千愁事变造成的影响生产环境某些页面打不开,控制台报错,错误信息 token.type.endsWith is not a function事变起因剖析为什么生产环境引入这个包:@vue/cli-plugin-eslint?token.type.endsWith is not a function报错的起因是?为什么生产环境引入这个包:@vue/cli-plugin-eslint因为咱们我的项目的 lintOnSave 的配置默认就是true, 官网也说了, 如果是true的话, 会导致生产也会被启用以后我的我的项目的配置又刚好是把lintOnSave给开进去了!!!! token.type.endsWith is not a function报错的起因是谷歌把我指引到了 eslint-plugin-vue 的官网issues,官网的大佬说,这个问题是babel-eslint的问题!!!token.type.endsWith is not a function解决方案babel-eslint 变成8.2.2版本降级babel-eslint,官网babel-eslint 8.2.2 版本之后就修复了, 然而我我的项目的babel-eslint的版本是10.0.1啊,为啥还会有问题,想不通,网上也碰到很多人说10.0.1的有一堆问题,反正降级到8.2.2就好了babel-eslint 降级到8.2.2 npm install -D babel-eslint@8.2.2 降级 eslint-plugin-vue 到 8.3.0 npm install -D eslint-plugin-vue@8.2.2 如何重现这个bug?我的项目背景基于若依前后端拆散模板外面拷贝进去的我的项目概率性,我发现应用npm install 比拟容易呈现这个问题,我的重现步骤是1 vue.config.js lintOnSave 变成true2 以后我的项目的一些依赖,次要就是babel-eslint 10.0.1 在window不同电脑外面,有的电脑,同样的版本就是报错!!3 启动服务,报错信息如下4 打包我的项目,控制台会报token.type.endsWith is not a function,然而打包胜利,呈现这种状况,如果你不留神,就把包打上去,祝贺你,支付生产bug福利5 有谬误的包,间接丢生产:页面打不开,控制台报错,一脸懵逼的福利事变解决方案vue.config.js 生产环境把lintOnSave 关掉,就算babel-eslint 报错, 也不会导致页面开不进去 升高babel-eslint版本,杜绝呈现这种问题,引申问题为啥同样的包,不同window电脑,就会报错?结束语本文如有谬误,欢送斧正,非常感谢感觉有用的老铁,点个双击,小红心走一波欢送灌水,来呀,相互挫伤哈 o(∩_∩)o 哈哈参考资料TypeError: token.type.endsWith is not a function vue eslint 问题解决vue我的项目所有vue援用报token.type.endsWith is not a function谬误eslint-plugin-vue/issues

March 4, 2022 · 1 min · jiezi

关于vue.js:Vue-源码解读9-编译器-之-优化

当学习成为了习惯,常识也就变成了常识。 感激各位的 关注、点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 前言上一篇文章 Vue 源码解读(8)—— 编译器 之 解析 具体详解了编译器的第一局部,如何将 html 模版字符串编译成 AST。明天带来编译器的第二局部,优化 AST,也是大家常说的动态标记。 指标深刻了解编译器的动态标记过程 源码解读入口/src/compiler/index.js/** * 在这之前做的所有的事件,只有一个目标,就是为了构建平台特有的编译选项(options),比方 web 平台 * * 1、将 html 模版解析成 ast * 2、对 ast 树进行动态标记 * 3、将 ast 生成渲染函数 * 动态渲染函数放到 code.staticRenderFns 数组中 * code.render 为动静渲染函数 * 在未来渲染时执行渲染函数失去 vnode */export const createCompiler = createCompilerCreator(function baseCompile ( template: string, options: CompilerOptions): CompiledResult { // 将模版解析为 AST,每个节点的 ast 对象上都设置了元素的所有信息,比方,标签信息、属性信息、插槽信息、父节点、子节点等。 // 具体有那些属性,查看 start 和 end 这两个解决开始和完结标签的办法 const ast = parse(template.trim(), options) // 优化,遍历 AST,为每个节点做动态标记 // 标记每个节点是否为动态节点,而后进一步标记出动态根节点 // 这样在后续更新的过程中就能够跳过这些动态节点了 // 标记动态根,用于生成渲染函数阶段,生成动态根节点的渲染函数 if (options.optimize !== false) { optimize(ast, options) } // 从 AST 生成渲染函数,生成像这样的代码,比方:code.render = "_c('div',{attrs:{"id":"app"}},_l((arr),function(item){return _c('div',{key:item},[_v(_s(item))])}),0)" const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns }})optimize/src/compiler/optimizer.js/** * 优化: * 遍历 AST,标记每个节点是动态节点还是动静节点,而后标记动态根节点 * 这样在后续更新的过程中就不须要再关注这些节点 */export function optimize(root: ?ASTElement, options: CompilerOptions) { if (!root) return /** * options.staticKeys = 'staticClass,staticStyle' * isStaticKey = function(val) { return map[val] } */ isStaticKey = genStaticKeysCached(options.staticKeys || '') // 平台保留标签 isPlatformReservedTag = options.isReservedTag || no // 遍历所有节点,给每个节点设置 static 属性,标识其是否为动态节点 markStatic(root) // 进一步标记动态根,一个节点要成为动态根节点,须要具体以下条件: // 节点自身是动态节点,而且有子节点,而且子节点不只是一个文本节点,则标记为动态根 // 动态根节点不能只有动态文本的子节点,因为这样收益太低,这种状况下始终更新它就好了 markStaticRoots(root, false)}markStatic/src/compiler/optimizer.js/** * 在所有节点上设置 static 属性,用来标识是否为动态节点 * 留神:如果有子节点为动静节点,则父节点也被认为是动静节点 * @param {*} node * @returns */function markStatic(node: ASTNode) { // 通过 node.static 来标识节点是否为 动态节点 node.static = isStatic(node) if (node.type === 1) { /** * 不要将组件的插槽内容设置为动态节点,这样能够防止: * 1、组件不能扭转插槽节点 * 2、动态插槽内容在热重载时失败 */ if ( !isPlatformReservedTag(node.tag) && node.tag !== 'slot' && node.attrsMap['inline-template'] == null ) { // 递归终止条件,如果节点不是平台保留标签 && 也不是 slot 标签 && 也不是内联模版,则间接完结 return } // 遍历子节点,递归调用 markStatic 来标记这些子节点的 static 属性 for (let i = 0, l = node.children.length; i < l; i++) { const child = node.children[i] markStatic(child) // 如果子节点是非动态节点,则将父节点更新为非动态节点 if (!child.static) { node.static = false } } // 如果节点存在 v-if、v-else-if、v-else 这些指令,则顺次标记 block 中节点的 static if (node.ifConditions) { for (let i = 1, l = node.ifConditions.length; i < l; i++) { const block = node.ifConditions[i].block markStatic(block) if (!block.static) { node.static = false } } } }}isStatic/src/compiler/optimizer.js/** * 判断节点是否为动态节点: * 通过自定义的 node.type 来判断,2: 表达式 => 动静,3: 文本 => 动态 * 但凡有 v-bind、v-if、v-for 等指令的都属于动静节点 * 组件为动静节点 * 父节点为含有 v-for 指令的 template 标签,则为动静节点 * @param {*} node * @returns boolean */function isStatic(node: ASTNode): boolean { if (node.type === 2) { // expression // 比方:{{ msg }} return false } if (node.type === 3) { // text return true } return !!(node.pre || ( !node.hasBindings && // no dynamic bindings !node.if && !node.for && // not v-if or v-for or v-else !isBuiltInTag(node.tag) && // not a built-in isPlatformReservedTag(node.tag) && // not a component !isDirectChildOfTemplateFor(node) && Object.keys(node).every(isStaticKey) ))}markStaticRoots/src/compiler/optimizer.js/** * 进一步标记动态根,一个节点要成为动态根节点,须要具体以下条件: * 节点自身是动态节点,而且有子节点,而且子节点不只是一个文本节点,则标记为动态根 * 动态根节点不能只有动态文本的子节点,因为这样收益太低,这种状况下始终更新它就好了 * * @param { ASTElement } node 以后节点 * @param { boolean } isInFor 以后节点是否被包裹在 v-for 指令所在的节点内 */function markStaticRoots(node: ASTNode, isInFor: boolean) { if (node.type === 1) { if (node.static || node.once) { // 节点是动态的 或者 节点上有 v-once 指令,标记 node.staticInFor = true or false node.staticInFor = isInFor } if (node.static && node.children.length && !( node.children.length === 1 && node.children[0].type === 3 )) { // 节点自身是动态节点,而且有子节点,而且子节点不只是一个文本节点,则标记为动态根 => node.staticRoot = true,否则为非动态根 node.staticRoot = true return } else { node.staticRoot = false } // 以后节点不是动态根节点的时候,递归遍历其子节点,标记动态根 if (node.children) { for (let i = 0, l = node.children.length; i < l; i++) { markStaticRoots(node.children[i], isInFor || !!node.for) } } // 如果节点存在 v-if、v-else-if、v-else 指令,则为 block 节点标记动态根 if (node.ifConditions) { for (let i = 1, l = node.ifConditions.length; i < l; i++) { markStaticRoots(node.ifConditions[i].block, isInFor) } } }}总结面试官 问:简略说一下 Vue 的编译器都做了什么? ...

March 4, 2022 · 3 min · jiezi

关于vue.js:想摸鱼吗先掌握这-19-个-css-技巧

作者:Matt Maribojoc译者:前端小智起源:stackabuse有幻想,有干货,微信搜寻 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。 本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。 批改 placeholder 款式,多行文本溢出,暗藏滚动条,批改光标色彩,程度和垂直居中。这些相熟的场景啊! 前端开发者简直每天都会和它们打交道,这里有20个CSS技巧,让咱们一起来看看吧。 1. 解决 img 5px 间距的问题你是否常常遇到图片底部多出5px间距的问题?不必急,这里有4种办法能够解决。 计划1:设置父元素字体大小为 0 要害代码: .img-container{ font-size: 0;}事例地址:https://codepen.io/qianlong/p... 计划2:将 img 元素设置为 display: block 要害代码: img{ display: block;}事例地址:https://codepen.io/qianlong/p... 计划3:将 img 元素设置为 vertical-align: bottom 要害代码: img{ vertical-align: bottom;}事例地址:https://codepen.io/qianlong/p... 解决方案4:给父元素设置 line-height: 5px 要害代码: .img-container{ line-height: 5px;}事例地址:https://codepen.io/qianlong/p... 2. 元素的高度与 window 的高度雷同如何使元素与窗口一样高? 答案应用 height: 100vh; 事例地址:https://codepen.io/qianlong/p... 3. 批改 input placeholder 款式要害代码: .placehoder-custom::-webkit-input-placeholder { color: #babbc1; font-size: 12px;} 事例地址:https://codepen.io/qianlong/p... ...

March 4, 2022 · 2 min · jiezi

关于vue.js:视频组件vuevideoplayer的使用

1.开发环境 vue22.电脑系统 windows11专业版3.在开发的过程中,咱们可能会须要应用到视频的查看,上面我来分享一个视频组件vue-video-player4.废话不多说,间接上操作: npm i vue-video-player5.在main.js中增加如下代码: import VideoPlayer from 'vue-video-player'require('video.js/dist/video-js.css')require('vue-video-player/src/custom-theme.css')Vue.use(VideoPlayer)或import VideoPlayer from 'vue-video-player';import 'video.js/dist/video-js.css';import 'vue-video-player/src/custom-theme.css';Vue.use(VideoPlayer)6.在应用的文件中增加如下代码: <video-player class="video-player vjs-custom-skin" ref="videoPlayer" :playsinline="true" :options="playerOptions"></video-player>6-1.配置数据: playerOptions : { playbackRates: [0.7, 1.0, 1.5, 2.0], //播放速度 autoplay: false, //如果true,浏览器筹备好时开始回放。 muted: false, // 默认状况下将会打消任何音频。 loop: false, // 导致视频一完结就从新开始。 preload: 'auto', // 倡议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器抉择最佳行为,立刻开始加载视频(如果浏览器反对) language: 'zh-CN', aspectRatio: '16:9', // 将播放器置于晦涩模式,并在计算播放器的动静大小时应用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3") fluid: true, // 当true时,Video.js player将领有流体大小。换句话说,它将按比例缩放以适应其容器。 sources: [{ type: "", //这里的品种反对很多种:根本视频格式、直播、流媒体等 src: "" //url地址 }], poster: "", //你的封面地址 // width: document.documentElement.clientWidth, //播放器宽度 notSupportedMessage: '此视频暂无奈播放,请稍后再试', //容许笼罩Video.js无奈播放媒体源时显示的默认信息。 controlBar: { timeDivider: true, //以后工夫和持续时间的分隔符 durationDisplay: true, //显示持续时间 remainingTimeDisplay: true, //是否显示剩余时间性能 fullscreenToggle: true //全屏按钮 }}7.本期的分享到了这里就完结啦,心愿对你有所帮忙,让咱们一起致力走向巅峰。 ...

March 3, 2022 · 1 min · jiezi

关于vue.js:Vue调用接口的方式有哪些

调用接口的形式原生ajax基于jQuery的ajaxfetchaxios 异步1.JavaScript的执行环境是「单线程」 所谓单线程,是指JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能实现一项工作,这个工作执行完后能力执行下一个,它会「阻塞」其余工作。这个工作可称为主线程 2.异步模式能够一起执行多个工作 3.JS中常见的异步调用 定时任何ajax事件函数promise次要解决异步深层嵌套的问题promise 提供了简洁的API 使得异步操作更加容易 <script type="text/javascript"> /*1. Promise根本应用 咱们应用new来构建一个Promise Promise的构造函数接管一个参数,是函数,并且传入两个参数: resolve,reject, 别离示意异步操作执行胜利后的回调函数和异步操作执行失败后的回调函数*/ var p = new Promise(function(resolve, reject){ //2. 这里用于实现异步工作 setTimeout setTimeout(function(){ var flag = false; if(flag) { //3. 失常状况 resolve('hello'); }else{ //4. 异常情况 reject('出错了'); } }, 100); }); // 5 Promise实例生成当前,能够用then办法指定resolved状态和reject状态的回调函数 // 在then办法中,你也能够间接return数据而不是Promise对象,在前面的then中就能够接管到数据了 p.then(function(data){ console.log(data) },function(info){ console.log(info) }); </script>基于Promise发送Ajax申请 <script type="text/javascript"> /*基于Promise发送Ajax申请*/ function queryData(url) { //# 1.1 创立一个Promise实例 var p = new Promise(function(resolve, reject){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readyState != 4) return; if(xhr.readyState == 4 && xhr.status == 200) { // # 1.2 解决失常的状况 resolve(xhr.responseText); }else{ // # 1.3 解决异常情况 reject('服务器谬误'); } }; xhr.open('get', url); xhr.send(null); }); return p; } // # 留神: 这里须要开启一个服务 // # 在then办法中,你也能够间接return数据而不是Promise对象,在前面的then中就能够接管到数据了 queryData('http://localhost:3000/data') .then(function(data){ console.log(data) //# 1.4 想要持续链式编程上来 须要 return return queryData('http://localhost:3000/data1'); }) .then(function(data){ console.log(data); return queryData('http://localhost:3000/data2'); }) .then(function(data){ console.log(data) }); </script>作者:蔚完待旭链接:https://www.jianshu.com/p/bcc...起源:简书著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。 ...

March 3, 2022 · 1 min · jiezi

关于vue.js:Vue中使用watch同时监听多个值的实现方法

1.开发环境 vue22.电脑系统 windows11专业版3.在应用vue开发的过程中,咱们有时候须要应用到监听watch来获取对应的数据,接下来让咱们看一下应用办法和同时监听多个值的应用办法。4.废话不多说,间接上代码: watch:{ "tempUrl"(newValue,oldValue){ console.log("我是监听的新数据",newValue); console.log("我是监听的旧数据",oldValue); }} 这种写法能监听多数据的变动,当初感觉是没有问题的//当我须要监听多个值变动的时候watch:{"tempUrl"(newValue,oldValue){ console.log("我是监听的新数据",newValue); console.log("我是监听的旧数据",oldValue); },"tagNameLists"(newValue,oldValue){ console.log("我是视频标签显示新数据",newValue); console.log("我是视频标签显示旧数据",oldValue); // this.getRdata(newValue); }} //只触发了第一个监听的数据变动,第二个数据变动没有监听多,怎么解决呢?5.应用computed: computed:{ dataChange () { const {tempUrl, tagNameLists} = this; return {tempUrl, tagNameLists}; } }watch:{ dataChange:{ handler(newValue,oldValue) { console.log("监听到了数据的变动",newValue); }, deep: true } }, 这样就实现了监听多个数据变动6.本期的分享到了这里就完结啦,心愿对你有所帮忙,让咱们一起致力走向巅峰。

March 3, 2022 · 1 min · jiezi

关于vue.js:Vue-源码解读8-编译器-之-解析下

当学习成为了习惯,常识也就变成了常识。 感激各位的 关注、点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 非凡阐明因为文章篇幅限度,所以将 Vue 源码解读(8)—— 编译器 之 解析 拆成了两篇文章,本篇是对 Vue 源码解读(8)—— 编译器 之 解析(上) 的一个补充,所以在浏览时请同时关上 Vue 源码解读(8)—— 编译器 之 解析(上) 一起浏览。 processAttrs/src/compiler/parser/index.js/** * 解决元素上的所有属性: * v-bind 指令变成:el.attrs 或 el.dynamicAttrs = [{ name, value, start, end, dynamic }, ...], * 或者是必须应用 props 的属性,变成了 el.props = [{ name, value, start, end, dynamic }, ...] * v-on 指令变成:el.events 或 el.nativeEvents = { name: [{ value, start, end, modifiers, dynamic }, ...] } * 其它指令:el.directives = [{name, rawName, value, arg, isDynamicArg, modifier, start, end }, ...] * 原生属性:el.attrs = [{ name, value, start, end }],或者一些必须应用 props 的属性,变成了: * el.props = [{ name, value: true, start, end, dynamic }] */function processAttrs(el) { // list = [{ name, value, start, end }, ...] const list = el.attrsList let i, l, name, rawName, value, modifiers, syncGen, isDynamic for (i = 0, l = list.length; i < l; i++) { // 属性名 name = rawName = list[i].name // 属性值 value = list[i].value if (dirRE.test(name)) { // 阐明该属性是一个指令 // 元素上存在指令,将元素标记动静元素 // mark element as dynamic el.hasBindings = true // modifiers,在属性名上解析修饰符,比方 xx.lazy modifiers = parseModifiers(name.replace(dirRE, '')) // support .foo shorthand syntax for the .prop modifier if (process.env.VBIND_PROP_SHORTHAND && propBindRE.test(name)) { // 为 .props 修饰符反对 .foo 速记写法 (modifiers || (modifiers = {})).prop = true name = `.` + name.slice(1).replace(modifierRE, '') } else if (modifiers) { // 属性中的修饰符去掉,失去一个洁净的属性名 name = name.replace(modifierRE, '') } if (bindRE.test(name)) { // v-bind, <div :id="test"></div> // 解决 v-bind 指令属性,最初失去 el.attrs 或者 el.dynamicAttrs = [{ name, value, start, end, dynamic }, ...] // 属性名,比方:id name = name.replace(bindRE, '') // 属性值,比方:test value = parseFilters(value) // 是否为动静属性 <div :[id]="test"></div> isDynamic = dynamicArgRE.test(name) if (isDynamic) { // 如果是动静属性,则去掉属性两侧的方括号 [] name = name.slice(1, -1) } // 提醒,动静属性值不能为空字符串 if ( process.env.NODE_ENV !== 'production' && value.trim().length === 0 ) { warn( `The value for a v-bind expression cannot be empty. Found in "v-bind:${name}"` ) } // 存在修饰符 if (modifiers) { if (modifiers.prop && !isDynamic) { name = camelize(name) if (name === 'innerHtml') name = 'innerHTML' } if (modifiers.camel && !isDynamic) { name = camelize(name) } // 解决 sync 修饰符 if (modifiers.sync) { syncGen = genAssignmentCode(value, `$event`) if (!isDynamic) { addHandler( el, `update:${camelize(name)}`, syncGen, null, false, warn, list[i] ) if (hyphenate(name) !== camelize(name)) { addHandler( el, `update:${hyphenate(name)}`, syncGen, null, false, warn, list[i] ) } } else { // handler w/ dynamic event name addHandler( el, `"update:"+(${name})`, syncGen, null, false, warn, list[i], true // dynamic ) } } } if ((modifiers && modifiers.prop) || ( !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name) )) { // 将属性对象增加到 el.props 数组中,示意这些属性必须通过 props 设置 // el.props = [{ name, value, start, end, dynamic }, ...] addProp(el, name, value, list[i], isDynamic) } else { // 将属性增加到 el.attrs 数组或者 el.dynamicAttrs 数组 addAttr(el, name, value, list[i], isDynamic) } } else if (onRE.test(name)) { // v-on, 处理事件,<div @click="test"></div> // 属性名,即事件名 name = name.replace(onRE, '') // 是否为动静属性 isDynamic = dynamicArgRE.test(name) if (isDynamic) { // 动静属性,则获取 [] 中的属性名 name = name.slice(1, -1) } // 处理事件属性,将属性的信息增加到 el.events 或者 el.nativeEvents 对象上,格局: // el.events = [{ value, start, end, modifiers, dynamic }, ...] addHandler(el, name, value, modifiers, false, warn, list[i], isDynamic) } else { // normal directives,其它的一般指令 // 失去 el.directives = [{name, rawName, value, arg, isDynamicArg, modifier, start, end }, ...] name = name.replace(dirRE, '') // parse arg const argMatch = name.match(argRE) let arg = argMatch && argMatch[1] isDynamic = false if (arg) { name = name.slice(0, -(arg.length + 1)) if (dynamicArgRE.test(arg)) { arg = arg.slice(1, -1) isDynamic = true } } addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i]) if (process.env.NODE_ENV !== 'production' && name === 'model') { checkForAliasModel(el, value) } } } else { // 以后属性不是指令 // literal attribute if (process.env.NODE_ENV !== 'production') { const res = parseText(value, delimiters) if (res) { warn( `${name}="${value}": ` + 'Interpolation inside attributes has been removed. ' + 'Use v-bind or the colon shorthand instead. For example, ' + 'instead of <div id="{{ val }}">, use <div :id="val">.', list[i] ) } } // 将属性对象放到 el.attrs 数组中,el.attrs = [{ name, value, start, end }] addAttr(el, name, JSON.stringify(value), list[i]) // #6887 firefox doesn't update muted state if set via attribute // even immediately after element creation if (!el.component && name === 'muted' && platformMustUseProp(el.tag, el.attrsMap.type, name)) { addProp(el, name, 'true', list[i]) } } }}addHandler/src/compiler/helpers.js/** * 处理事件属性,将事件属性增加到 el.events 对象或者 el.nativeEvents 对象中,格局: * el.events[name] = [{ value, start, end, modifiers, dynamic }, ...] * 其中用了大量的篇幅在解决 name 属性带修饰符 (modifier) 的状况 * @param {*} el ast 对象 * @param {*} name 属性名,即事件名 * @param {*} value 属性值,即事件回调函数名 * @param {*} modifiers 修饰符 * @param {*} important * @param {*} warn 日志 * @param {*} range * @param {*} dynamic 属性名是否为动静属性 */export function addHandler ( el: ASTElement, name: string, value: string, modifiers: ?ASTModifiers, important?: boolean, warn?: ?Function, range?: Range, dynamic?: boolean) { // modifiers 是一个对象,如果传递的参数为空,则给一个解冻的空对象 modifiers = modifiers || emptyObject // 提醒:prevent 和 passive 修饰符不能一起应用 // warn prevent and passive modifier /* istanbul ignore if */ if ( process.env.NODE_ENV !== 'production' && warn && modifiers.prevent && modifiers.passive ) { warn( 'passive and prevent can\'t be used together. ' + 'Passive handler can\'t prevent default event.', range ) } // 标准化 click.right 和 click.middle,它们实际上不会被真正的触发,从技术讲他们是它们 // 是特定于浏览器的,但至多目前地位只有浏览器才具备右键和两头键的点击 // normalize click.right and click.middle since they don't actually fire // this is technically browser-specific, but at least for now browsers are // the only target envs that have right/middle clicks. if (modifiers.right) { // 右键 if (dynamic) { // 动静属性 name = `(${name})==='click'?'contextmenu':(${name})` } else if (name === 'click') { // 非动静属性,name = contextmenu name = 'contextmenu' // 删除修饰符中的 right 属性 delete modifiers.right } } else if (modifiers.middle) { // 两头键 if (dynamic) { // 动静属性,name => mouseup 或者 ${name} name = `(${name})==='click'?'mouseup':(${name})` } else if (name === 'click') { // 非动静属性,mouseup name = 'mouseup' } } /** * 解决 capture、once、passive 这三个修饰符,通过给 name 增加不同的标记来标记这些修饰符 */ // check capture modifier if (modifiers.capture) { delete modifiers.capture // 给带有 capture 修饰符的属性,加上 ! 标记 name = prependModifierMarker('!', name, dynamic) } if (modifiers.once) { delete modifiers.once // once 修饰符加 ~ 标记 name = prependModifierMarker('~', name, dynamic) } /* istanbul ignore if */ if (modifiers.passive) { delete modifiers.passive // passive 修饰符加 & 标记 name = prependModifierMarker('&', name, dynamic) } let events if (modifiers.native) { // native 修饰符, 监听组件根元素的原生事件,将事件信息寄存到 el.nativeEvents 对象中 delete modifiers.native events = el.nativeEvents || (el.nativeEvents = {}) } else { events = el.events || (el.events = {}) } const newHandler: any = rangeSetItem({ value: value.trim(), dynamic }, range) if (modifiers !== emptyObject) { // 阐明有修饰符,将修饰符对象放到 newHandler 对象上 // { value, dynamic, start, end, modifiers } newHandler.modifiers = modifiers } // 将配置对象放到 events[name] = [newHander, handler, ...] const handlers = events[name] /* istanbul ignore if */ if (Array.isArray(handlers)) { important ? handlers.unshift(newHandler) : handlers.push(newHandler) } else if (handlers) { events[name] = important ? [newHandler, handlers] : [handlers, newHandler] } else { events[name] = newHandler } el.plain = false}addIfCondition/src/compiler/parser/index.js/** * 将传递进来的条件对象放进 el.ifConditions 数组中 */export function addIfCondition(el: ASTElement, condition: ASTIfCondition) { if (!el.ifConditions) { el.ifConditions = [] } el.ifConditions.push(condition)}processPre/src/compiler/parser/index.js/** * 如果元素上存在 v-pre 指令,则设置 el.pre = true */function processPre(el) { if (getAndRemoveAttr(el, 'v-pre') != null) { el.pre = true }}processRawAttrs/src/compiler/parser/index.js/** * 设置 el.attrs 数组对象,每个元素都是一个属性对象 { name: attrName, value: attrVal, start, end } */function processRawAttrs(el) { const list = el.attrsList const len = list.length if (len) { const attrs: Array<ASTAttr> = el.attrs = new Array(len) for (let i = 0; i < len; i++) { attrs[i] = { name: list[i].name, value: JSON.stringify(list[i].value) } if (list[i].start != null) { attrs[i].start = list[i].start attrs[i].end = list[i].end } } } else if (!el.pre) { // non root node in pre blocks with no attributes el.plain = true }}processIf/src/compiler/parser/index.js/** * 解决 v-if、v-else-if、v-else * 失去 el.if = "exp",el.elseif = exp, el.else = true * v-if 属性会额定在 el.ifConditions 数组中增加 { exp, block } 对象 */function processIf(el) { // 获取 v-if 属性的值,比方 <div v-if="test"></div> const exp = getAndRemoveAttr(el, 'v-if') if (exp) { // el.if = "test" el.if = exp // 在 el.ifConditions 数组中增加 { exp, block } addIfCondition(el, { exp: exp, block: el }) } else { // 解决 v-else,失去 el.else = true if (getAndRemoveAttr(el, 'v-else') != null) { el.else = true } // 解决 v-else-if,失去 el.elseif = exp const elseif = getAndRemoveAttr(el, 'v-else-if') if (elseif) { el.elseif = elseif } }}processOnce/src/compiler/parser/index.js/** * 解决 v-once 指令,失去 el.once = true * @param {*} el */function processOnce(el) { const once = getAndRemoveAttr(el, 'v-once') if (once != null) { el.once = true }}checkRootConstraints/src/compiler/parser/index.js/** * 查看根元素: * 不能应用 slot 和 template 标签作为组件的根元素 * 不能在有状态组件的 根元素 上应用 v-for 指令,因为它会渲染出多个元素 * @param {*} el */function checkRootConstraints(el) { // 不能应用 slot 和 template 标签作为组件的根元素 if (el.tag === 'slot' || el.tag === 'template') { warnOnce( `Cannot use <${el.tag}> as component root element because it may ` + 'contain multiple nodes.', { start: el.start } ) } // 不能在有状态组件的 根元素 上应用 v-for,因为它会渲染出多个元素 if (el.attrsMap.hasOwnProperty('v-for')) { warnOnce( 'Cannot use v-for on stateful component root element because ' + 'it renders multiple elements.', el.rawAttrsMap['v-for'] ) }}closeElement/src/compiler/parser/index.js/** * 次要做了 3 件事: * 1、如果元素没有被解决过,即 el.processed 为 false,则调用 processElement 办法解决节点上的泛滥属性 * 2、让本人和父元素产生关系,将本人放到父元素的 children 数组中,并设置本人的 parent 属性为 currentParent * 3、设置本人的子元素,将本人所有非插槽的子元素放到本人的 children 数组中 */function closeElement(element) { // 移除节点开端的空格,以后 pre 标签内的元素除外 trimEndingWhitespace(element) // 以后元素不再 pre 节点内,并且也没有被解决过 if (!inVPre && !element.processed) { // 别离解决元素节点的 key、ref、插槽、自闭合的 slot 标签、动静组件、class、style、v-bind、v-on、其它指令和一些原生属性 element = processElement(element, options) } // 解决根节点上存在 v-if、v-else-if、v-else 指令的状况 // 如果根节点存在 v-if 指令,则必须还提供一个具备 v-else-if 或者 v-else 的同级别节点,避免根元素不存在 // tree management if (!stack.length && element !== root) { // allow root elements with v-if, v-else-if and v-else if (root.if && (element.elseif || element.else)) { if (process.env.NODE_ENV !== 'production') { // 查看根元素 checkRootConstraints(element) } // 给根元素设置 ifConditions 属性,root.ifConditions = [{ exp: element.elseif, block: element }, ...] addIfCondition(root, { exp: element.elseif, block: element }) } else if (process.env.NODE_ENV !== 'production') { // 提醒,示意不应该在 根元素 上只应用 v-if,应该将 v-if、v-else-if 一起应用,保障组件只有一个根元素 warnOnce( `Component template should contain exactly one root element. ` + `If you are using v-if on multiple elements, ` + `use v-else-if to chain them instead.`, { start: element.start } ) } } // 让本人和父元素产生关系 // 将本人放到父元素的 children 数组中,而后设置本人的 parent 属性为 currentParent if (currentParent && !element.forbidden) { if (element.elseif || element.else) { processIfConditions(element, currentParent) } else { if (element.slotScope) { // scoped slot // keep it in the children list so that v-else(-if) conditions can // find it as the prev node. const name = element.slotTarget || '"default"' ; (currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element } currentParent.children.push(element) element.parent = currentParent } } // 设置本人的子元素 // 将本人的所有非插槽的子元素设置到 element.children 数组中 // final children cleanup // filter out scoped slots element.children = element.children.filter(c => !(c: any).slotScope) // remove trailing whitespace node again trimEndingWhitespace(element) // check pre state if (element.pre) { inVPre = false } if (platformIsPreTag(element.tag)) { inPre = false } // 别离为 element 执行 model、class、style 三个模块的 postTransform 办法 // 然而 web 平台没有提供该办法 // apply post-transforms for (let i = 0; i < postTransforms.length; i++) { postTransforms[i](element, options) }}trimEndingWhitespace/src/compiler/parser/index.js/** * 删除元素中空白的文本节点,比方:<div> </div>,删除 div 元素中的空白节点,将其从元素的 children 属性中移出去 */function trimEndingWhitespace(el) { if (!inPre) { let lastNode while ( (lastNode = el.children[el.children.length - 1]) && lastNode.type === 3 && lastNode.text === ' ' ) { el.children.pop() } }}processIfConditions/src/compiler/parser/index.jsfunction processIfConditions(el, parent) { // 找到 parent.children 中的最初一个元素节点 const prev = findPrevElement(parent.children) if (prev && prev.if) { addIfCondition(prev, { exp: el.elseif, block: el }) } else if (process.env.NODE_ENV !== 'production') { warn( `v-${el.elseif ? ('else-if="' + el.elseif + '"') : 'else'} ` + `used on element <${el.tag}> without corresponding v-if.`, el.rawAttrsMap[el.elseif ? 'v-else-if' : 'v-else'] ) }}findPrevElement/src/compiler/parser/index.js/** * 找到 children 中的最初一个元素节点 */function findPrevElement(children: Array<any>): ASTElement | void { let i = children.length while (i--) { if (children[i].type === 1) { return children[i] } else { if (process.env.NODE_ENV !== 'production' && children[i].text !== ' ') { warn( `text "${children[i].text.trim()}" between v-if and v-else(-if) ` + `will be ignored.`, children[i] ) } children.pop() } }}帮忙到这里编译器的解析局部就完结了,置信很多人看的是云里雾里的,即便多看几遍可能也没有那么清晰。 ...

March 3, 2022 · 11 min · jiezi

关于vue.js:关于elementUI的table设置fixed后样式错乱问题

接口申请失去数据的时候,走一遍这个办法 this.$nextTick(()=>{ this.$refs.table.doLayout() // table那里加一个ref="table"})

March 2, 2022 · 1 min · jiezi

关于vue.js:antdv-Spin组件拓展

antdv 组件库种Spin组件未提供间接的函数式全局调用办法;在ajax申请,或者其余一些用纯js中须要调用的时候比拟麻烦。基于Spin拓展 util/decorator/spin import Vue from "vue";import { Spin } from "ant-design-vue";let instance = null;function getInstance() {  if (!instance) {    instance = new Vue({      data: {        show: false,      },      methods: {        loading() {          this.show = true;        },        close() {          this.show = false;        },      },      render(h, data) {        const fullscreenLoading = {          position: "fixed",          left: 0,          top: 0,          width: "100%",          height: "100%",          display: "flex",          justifyContent: "center",          alignItems: "center",        };        return this.show ? (          <div style={fullscreenLoading}>            <Spin />          </div>        ) : (          ""        );      },    });    const component = instance.$mount();    document.body.appendChild(component.$el);  }  return instance;}Spin.show = function () {  getInstance().loading();};Spin.hide = function () {  getInstance().close();};export default Spin;

March 2, 2022 · 1 min · jiezi

关于vue.js:Vue-源码解读8-编译器-之-解析上

当学习成为了习惯,常识也就变成了常识。 感激各位的 关注、点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 非凡阐明因为文章篇幅限度,所以将 Vue 源码解读(8)—— 编译器 之 解析 拆成了高低两篇,所以在浏览本篇文章时请同时关上 Vue 源码解读(8)—— 编译器 之 解析(下)一起浏览。 前言Vue 源码解读(4)—— 异步更新 最初说到刷新 watcher 队列,执行每个 watcher.run 办法,由 watcher.run 调用 watcher.get,从而执行 watcher.getter 办法,进入理论的更新阶段。这个流程如果不相熟,倡议大家再去读一下这篇文章。 当更新一个渲染 watcher 时,执行的是 updateComponent 办法: // /src/core/instance/lifecycle.jsconst updateComponent = () => { // 执行 vm._render() 函数,失去 虚构 DOM,并将 vnode 传递给 _update 办法,接下来就该到 patch 阶段了 vm._update(vm._render(), hydrating)}能够看到每次更新前都须要先执行一下 vm._render() 办法,vm._render 就是大家常常听到的 render 函数,由两种形式失去: 用户本人提供,在编写组件时,用 render 选项代替模版由编译器编译组件模版生成 render 选项明天咱们就来深入研究编译器,看看它是怎么将咱们平时编写的类 html 模版编译成 render 函数的。 ...

March 2, 2022 · 29 min · jiezi

关于vue.js:Vue-状态管理与与SSR详解

1、vuex简介1、定义在vue项⽬中,每个组件的数据都有其独⽴的作⽤域。当组件间须要跨层级或者同层之间频繁传递的时候,数据交互就会⾮常繁琐。vuex的次要作⽤就是集中管理所有组件的数据和状态以及标准数据批改的⽅式。 官网解释:Vuex 是⼀个专为 Vue.js 应⽤程序开发的状态管理模式。它采⽤集中式存储管理应⽤的所有组件的状态,并以相应的规定保障状态以⼀种可预测的⽅式发⽣变动。 2、应用场景⼀般来讲,是以项⽬中的数据交互复杂程度来决定的。具体包含以下场景: 项⽬组件间数据交互不频繁,组件数量较少:不使⽤状态治理项⽬组件间数据交互频繁,但组件数量较少:使⽤eventBus或者vue store解决项⽬组件间数据交互频繁,组件数量较多:vuex解决3、外围原理剖析 // a.vue<h1>{{ username }}</h1>// b.vue<h2> {{ username }}</h2>/*** 如果 username 须要在每个组件都获取一次,是不是很麻烦,尽管能够通过独特的父级传入,然而不都是这种现实状况*/复制代码Flux 架构次要思维是利用的状态被集中寄存到一个仓库中,然而仓库中的状态不能被间接批改,必须通过特定的形式能力更新状态。 vuex基于flux思维为vue框架定制,辨别同步和异步,定义两种行为,Actions 用来解决异步状态变更(外部还是调用 Mutations),Mutations 解决同步的状态变更,整个链路应该是一个闭环,单向的,完满符合 FLUX 的思维 「页面 dispatch/commit」-> 「actions/mutations」-> 「状态变更」-> 「页面更新」-> 「页面 dispatch/commit」... 2、vuex五大外围vue应用繁多状态树,繁多状态树让咱们可能间接地定位任一特定的状态片段,在调试的过程中也能轻易地获得整个以后利用状态的快照。用一个对象(骨干)就蕴含了全副的(分支)利用层级状态。每个利用将仅仅蕴含一个 store 实例对象(骨干)。每一个 Vuex 利用的外围就是 store(仓库)。“store”基本上就是一个容器,它蕴含着你的利用中大部分的状态 (state) 。Vuex 和单纯的全局对象有以下两点不同:Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。你不能间接扭转 store 中的状态。扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动,从而让咱们可能实现一些工具帮忙咱们更好地理解咱们的利用。1、State以后应⽤状态,能够了解为组件的data⽅法返回的Object import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({ state: { count: 0 }})new Vue({ store, //把store的实例注入所有的子组件,this.$store可拜访 render: h => h(App)}).$mount('#app')复制代码2、GettersGetter为state的计算属性,当须要反复对某个数据进⾏某种操作的时候能够封装在getter⾥⾯,当state中的数据扭转了当前对应的getter也会相应的扭转。 ...

March 1, 2022 · 11 min · jiezi

关于vue.js:Vue-源码解读7-Hook-Event

当学习成为了习惯,常识也就变成了常识。 感激各位的 关注、点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 前言Hook Event(钩子事件)置信很多 Vue 开发者都没有应用过,甚至没听过,毕竟 Vue 官网文档中也没有提及。 Vue 提供了一些生命周期钩子函数,供开发者在特定的逻辑点增加额定的解决逻辑,比方:在组件挂载阶段提供了 beforeMount 和 mounted 两个生命周期钩子,供开发者在组件挂载阶段执行额定的逻辑解决,比方为组件筹备渲染所需的数据。 那这个 Hook Event —— 钩子事件,其中也有钩子的意思,和 Vue 的生命周期钩子函数有什么关系呢?它又有什么用呢?这就是这边文章要解答的问题。 指标了解什么是 Hook Event ?明确其应用场景深刻了解 Hook Event 的实现原理什么是 Hook Event ?Hook Event 是 Vue 的自定义事件联合生命周期钩子实现的一种从组件内部为组件注入额定生命周期办法的性能。 应用场景假如当初有这么一个第三方的业务组件,逻辑很简略,就在 mounted 生命周期中调用接口获取数据,而后将数据渲染到页面上。 <template> <div class="wrapper"> <ul> <li v-for="item in arr" :key="JSON.stringify(item)"> {{ item }} </li> </ul> </div></template><script>export default { data() { return { arr: [] } }, async mounted() { // 调用接口获取组件渲染的数据 const { data: { data } } = await this.$axios.get('/api/getList') this.arr.push(...data) }}</script>而后在应用的发现这个组件有些瑕疵,比方最简略的,接口等待时间可能比拟长,我想在 mounted 生命周期开始执行的时候在控制台输入一个 loading ... 字符串,加强用户体验。 ...

March 1, 2022 · 3 min · jiezi

关于vue.js:最好用的-7-个-Vue-Tree-select-树形组件-卡拉云

本文首发:《最好用的 7 个 Vue Tree select 树形组件 - 卡拉云》 Vue 树形选择器(Vue tree select)组件在搭建 Vue 的 app 中特地罕用,Vue tree select 除了简略的树形构造外,还有十分多样的性能来配合不同场景的应用。比方搜寻过滤,前端增加删除树枝,前端编辑批改子树名,拖拽排序,对用户操作事件记录等。本文记录了我本人应用多年最好用的 7 款 Vue tree select 组件,每一款都通过我理论测试,举荐给大家。 如果你正在搭建后盾管理工具,又不想解决前端问题,举荐应用卡拉云,卡拉云是新一代低代码开发工具,可一键接入常见数据库及 API ,无需懂前端,仅需拖拽即可疾速搭建属于你本人的后盾管理工具,一周工作量缩减至一天,详见本文文末。 接下来介绍 7 款我本人罕用的 Vue tree select第三方组件,它们各有特色,心愿能帮你找到适合你的选择器 Vue JSTree - 全功能,树状单选多选,可拖拽,过滤搜寻Vue Draggable Nested Tree - 纯树形抉择,轻捷趁手Vue Tree List Component - 前端可编辑、删除,拖拽,界面敌对Vue Tree select - 根底款树形选择器,没有多余性能Vue Tree Chart - 传图树形选择器 UI 适宜展现树状关系Vue Liquor Tree - 挪动端敌对,可拖拽,灵便配置V-TreeView - 根底款树形选择器,可换 icon,可过滤搜寻1. Vue JSTree - 全功能,树状单选多选,可拖拽,过滤搜寻Vue JSTree 放在第一个举荐,因为它涵盖了大多数你须要的性能,单选多选,可更换 icon 简略的搜寻过滤,可任意拖拽子集到新汇合里。 ...

February 28, 2022 · 2 min · jiezi

关于vue.js:vue原理初探

目录构造├── benchmarks 性能、基准测试├── dist 构建打包的输入目录├── examples 案例目录├── flow flow 语法的类型申明├── packages 一些额定的包,比方:负责服务端渲染的包 vue-server-renderer、配合 vue-loader 应用的 vue-template-compiler,还有 weex 相干的│ ├── vue-server-renderer│ ├── vue-template-compiler│ ├── weex-template-compiler│ └── weex-vue-framework├── scripts 所有的配置文件的寄存地位,比方 rollup 的配置文件├── src vue 源码目录│ ├── compiler 编译器│ ├── core 运行时的外围包│ │ ├── components 全局组件,比方 keep-alive│ │ ├── config.js 一些默认配置项│ │ ├── global-api 全局 API,比方相熟的:Vue.use()、Vue.component() 等│ │ ├── instance Vue 实例相干的,比方 Vue 构造函数就在这个目录下│ │ ├── observer 响应式原理│ │ ├── util 工具办法│ │ └── vdom 虚构 DOM 相干,比方相熟的 patch 算法就在这儿│ ├── platforms 平台相干的编译器代码│ │ ├── web│ │ └── weex│ ├── server 服务端渲染相干├── test 测试目录├── types TS 类型申明Vue 的初始化过程(new Vue(options))都做了什么?1.解决组件配置项 ...

February 28, 2022 · 4 min · jiezi

关于vue.js:适合做外包项目的开源后台管理系统

梳理: 1.D2admin 开源地址:https://github.com/d2-project... 文档地址:https://d2.pub/zh/doc/d2-admin/ 成果预览:https://d2.pub/d2-admin/previ... 开源协定:MIT 2.vue-element-admin 开源地址:https://github.com/PanJiaChen... 文档地址:https://panjiachen.github.io/... 成果预览:https://panjiachen.gitee.io/v... 开源协定:MIT 3.JEECG-BOOT 开源地址:https://github.com/zhangdaisc... 文档地址:https://panjiachen.github.io/... 成果预览:http://boot.jeecg.com/ 开源协定:Apache-2.0 License 4.GIN-VUE-ADMIN 开源地址:https://github.com/flipped-au... 文档地址:https://www.gin-vue-admin.com/ 成果预览:http://demo.gin-vue-admin.com... 开源协定:Apache-2.0 License 5.vue-admin-beautiful 开源地址:https://github.com/chuzhixin/... 文档地址:https://www.gin-vue-admin.com/ 成果预览:https://vue-admin-beautiful.c... 开源协定:MPL-2.0 License 6.Dcat-admin 开源地址:https://github.com/jqhph/dcat... 文档地址:http://www.dcatadmin.com/ 成果预览:http://103.39.211.179:8080/admin 开源协定:MIT License 7.RuoYi 开源地址:https://gitee.com/y_project/R... 文档地址:https://doc.ruoyi.vip/ 成果预览:https://vue.ruoyi.vip/index 开源协定:MIT License 8.renren-fast-vue 开源地址:https://gitee.com/renrenio/re... 文档地址:https://www.renren.io/guide 成果预览:http://demo.open.renren.io/re... 开源协定:MIT License 9.ant-design-pro 开源地址:https://github.com/ant-design... 文档地址:https://pro.ant.design/index-cn/ 成果预览:https://pro.ant.design/ 开源协定:MIT License 10.iview-admin 开源地址:https://github.com/iview/ivie... 文档地址:https://lison16.github.io/ivi... 成果预览:https://admin.iviewui.com/home 开源协定:MIT License 11.material-dashboard ...

February 28, 2022 · 1 min · jiezi

关于vue.js:Vue-源码解读6-实例方法

当学习成为了习惯,常识也就变成了常识。 感激各位的 点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 前言上一篇文章 Vue 源码解读(5)—— 全局 API 具体介绍了 Vue 的各个全局 API 的实现原理,本篇文章将会具体介绍各个实例办法的实现原理。 指标深刻了解以下实例办法的实现原理。 vm.$setvm.$deletevm.$watchvm.$onvm.$emitvm.$offvm.$oncevm._updatevm.$forceUpdatevm.$destroyvm.$nextTickvm._render源码解读入口/src/core/instance/index.js该文件是 Vue 实例的入口文件,包含 Vue 构造函数的定义、各个实例办法的初始化。 // Vue 的构造函数function Vue (options) { // 调用 Vue.prototype._init 办法,该办法是在 initMixin 中定义的 this._init(options)}// 定义 Vue.prototype._init 办法initMixin(Vue)/** * 定义: * Vue.prototype.$data * Vue.prototype.$props * Vue.prototype.$set * Vue.prototype.$delete * Vue.prototype.$watch */stateMixin(Vue)/** * 定义 事件相干的 办法: * Vue.prototype.$on * Vue.prototype.$once * Vue.prototype.$off * Vue.prototype.$emit */eventsMixin(Vue)/** * 定义: * Vue.prototype._update * Vue.prototype.$forceUpdate * Vue.prototype.$destroy */lifecycleMixin(Vue)/** * 执行 installRenderHelpers,在 Vue.prototype 对象上装置运行时便当程序 * * 定义: * Vue.prototype.$nextTick * Vue.prototype._render */renderMixin(Vue)vm.&dollar;data、vm.&dollar;propssrc/core/instance/state.js这是两个实例属性,不是实例办法,这里简略介绍以下,当然其自身实现也很简略 ...

February 28, 2022 · 10 min · jiezi

关于vue.js:Vue2Vuex数据持久化

Vuex数据长久化在刷新页面时Vuex中的数据会重置,对于想要保留的状态同样也会重置在Storage中数据能够长久化存储,然而在存储了太多数据时不利于状态的对立治理 外围:commit时备份一份数据在本地 以登录验证token为例 1.Storage的简略封装// @/utiles/storage.jsconst Storage = class { constructor(isLocal) { this.isLocal = isLocal } has(key) { if (this.isLocal) { return Reflect.has(localStorage, key) } else { return Reflect.has(sessionStorage, key) } } set(key, value) { if (this.isLocal) { localStorage.setItem(key, JSON.stringify(value)) } else { sessionStorage.setItem(key, JSON.stringify(value)) } } get(key) { if (this.has(key)) { if (this.isLocal) { return JSON.parse(localStorage.getItem(key)) } else { return JSON.parse(sessionStorage.getItem(key)) } } return false } remove(key) { if (this.has(key)) { if (this.storageType) { localStorage.removeItem(key) } else { sessionStorage.removeItem(key) } return true } return false } clear() { if (this.isLocal) { localStorage.clear() } else { sessionStorage.clear() } }}const local = new Storage(true)const session = new Storage(false)export { Storage, local, session }2. Vuex// @/store/modules/user.jsimport {local,session} from '@/utils/storage.js'import { request } from '@/api/request.js' //接口封装const user = { namespaced:true, state:{ loginInfo:{ userId:'', pwd:'', }, token:'', }, mutations:{ SET_LOGININFO: (state,loginInfo)=>{ state.loginInfo = loginInfo local.set('loginInfo',loginInfo) }, SET_TOKEN:(state,token) => { state.token = token session.set('token',token) } }, actions:{ async Login({commit,state},loginInfo){ // 申请token let token =await request('login',loginInfo) commit('SET_LOGININFO',loginInfo) commit('SET_TOKEN',token) }, Logout({commit}){ commit('SET_TOKEN','') session.clear() } persistUser({commit}){ commit('SET_LOGININFO',local.get('loginInfo')) commit('SET_TOKEN',session.get('token')) } }}export default user// @/store/index.jsimport Vue from 'vue'import Vuex from 'vuex'import user form './modules.js'Vue.use(Vuex)const store = new Vuex.Store({ modules:{ user }})export default store3. 路由守卫// @/router/permission.jsimport {router} from './index.js'import store from '@/store/index.js'router.beforEach((to,from,next)=> { store.dispatch('user/persistUser') if(store.state.user.token){ if(to.path === '/login'){ next({path:'/'}) }else{ next() } }else { next(`/login?redirect=${to.fullPath}`) }})在actions或组件中commit 将申请或者要批改的数据保留至state以及本地刷新页面时在前置路由守卫或者组件挂载时dispatch 将本地的备份数据保留至state

February 28, 2022 · 2 min · jiezi

关于vue.js:如何搭建vue项目

首先,列出来咱们须要的货色: node.js环境(npm包管理器)vue-cli 脚手架构建工具cnpm npm的淘宝镜像 装置node.js从node.js官网下载并装置node,装置过程很简略,一路“下一步”就能够了(傻瓜式装置)。 装置实现之后,关上命令行工具,输出 node -v,如下图,如果呈现相应的版本号,则阐明装置胜利。 npm包管理器npm包管理器,是集成在node中的,所以,间接输出 npm -v就会如下图所示,显示出npm的版本信息。因为有些npm有些资源被屏蔽或者是国外资源的起因,常常会导致用npm装置依赖包的时候失败,所有我还须要npm的国内镜像---cnpm。 装置cnpm在命令行中输出 npm install -g cnpm --registry=http://registry.npm.taobao.org进行装置实现之后,就能够用cnpm代替npm来装置依赖包了。 装置vue-cli脚手架构建工具在命令行中运行命令 cnpm install -g vue-cli(留神,这里应用cnpm来代替npm,不然速度超级慢。)通过以上步骤,须要筹备的环境和工具都筹备好了,接下来就开始应用vue-cli来构建我的项目。 用vue-cli构建我的项目要创立我的项目,首先咱们要选定目录,而后再命令行中把目录转到选定的目录。在这里,我抉择桌面来寄存新建的我的项目,则咱们须要先把目录cd到桌面,如下图。在命令行中运行命令 vue init webpack firstVue,桌面就会生成我的项目目录 。解释一下这个命令,这个命令的意思是初始化一个我的项目,其中webpack是构建工具,也就是整个我的项目是基于webpack的。其中firstVue是整个我的项目文件夹的名称,如下图。运行初始化命令的时候回让用户输出几个根本的选项,如项目名称,形容,作者等信息,如果不想填间接回车默认就好。这就是整个我的项目的目录构造,其中,咱们次要在src目录中做批改。这个我的项目当初还只是一个构造框架,整个我的项目须要的依赖资源都还没有装置,如下图。 装置我的项目所需的依赖在我的项目目录下运行命令 cnpm install来进行依赖装置装置实现之后,会在的我的项目目录firstVue文件夹中多出一个node_modules文件夹,这里边就是我的项目须要的依赖包资源。 运行我的项目在我的项目目录运行命令 npm run dev npm run dev 命令中的“run”对应的是package.json文件中,scripts字段中的dev,也就是 node build/dev-server.js命令的一个快捷方式。运行胜利

February 25, 2022 · 1 min · jiezi

关于vue.js:工程化系列教你如何流程化搭建一个Vite-TS-ESlint-项目

一、创立我的项目局部应用 Vite 新建一个我的项目yarn create vite二、ESlint应用局部增加ESlintyarn add eslint --dev初始化ESlintyarn run eslint --init这个时候编写代码就会有 eslint 校验了,咱们来配置整个我的项目校验命令配置 package.json "scripts": { //... "lint": "eslint 'src/**/*.{js,jsx,vue,ts,tsx}' --fix" },执行校验yarn run lint这个时候就能看到校验不通过的中央了个别会提醒以下问题咱们来逐个解决 error The template root requires exactly one elementerror 'defineProps' is not defined解决Vue3模板根元素校验问题批改 .eslintrc.js ,把 plugin:vue/essential 替换为 plugin:vue/vue3-recommended extends: [ // 'plugin:vue/essential', 'plugin:vue/vue3-recommended', 'standard' ],解决 defineProps 定义问题批改 .eslintrc.js ,增加 globals 配置 globals: { defineProps: 'readonly', defineEmits: 'readonly', defineExpose: 'readonly', withDefaults: 'readonly' }保留在执行下 yarn run lint,这时能够看到所有校验都通过了附上此时 .eslintrc.js 配置 ...

February 25, 2022 · 2 min · jiezi

关于vue.js:Vue3-Vite2-TypeScript-PiniaVuexJSX-搭建企业级开发脚手架开箱即用

随着Vue3的遍及,曾经有越来越多的我的项目开始应用Vue3。为了疾速进入开发状态,在这里向大家举荐一套开箱即用的企业级开发脚手架,框架应用:Vue3 + Vite2 + TypeScript + JSX + Pinia(Vuex) + Antd。废话不多话,间接上手开撸。该脚手架依据应用状态库的不同分为两个版本Vuex版、Pinia版,上面是相干代码地址:Vuex版、Pinia版搭建需筹备Vscode : 前端人必备写码神器Chrome :对开发者十分敌对的浏览器(程序员标配浏览器)Nodejs & npm :配置本地开发环境,装置 Node 后你会发现 npm 也会一起装置下来 (V12+)应用npm装置依赖包时会发现十分慢,在这里举荐应用cnpm、yarn代替。脚手架目录构造├── src│   ├── App.tsx│ ├── api # 接口治理模块│ ├── assets # 动态资源模块│ ├── components # 公共组件模块│ ├── mock # mock接口模仿模块│ ├── layouts # 公共自定义布局│ ├── main.ts # 入口文件│ ├── public # 公共资源模块│ ├── router # 路由│ ├── store # vuex状态库│ ├── types # 申明文件│ ├── utils # 公共办法模块│ └── views # 视图模块├── tsconfig.json└── vite.config.js什么是Vite下一代前端开发与构建工具Vite(法语意为 "疾速的",发音 /vit/,发音同 "veet")是一种新型前端构建工具,可能显著晋升前端开发体验。它次要由两局部组成:一个开发服务器,它基于 原生 ES 模块 提供了 丰盛的内建性能,如速度快到惊人的 模块热更新(HMR)。一套构建指令,它应用 Rollup 打包你的代码,并且它是预配置的,可输入用于生产环境的高度优化过的动态资源。Vite 意在提供开箱即用的配置,同时它的 插件 API 和 JavaScript API 带来了高度的可扩展性,并有残缺的类型反对。 ...

February 25, 2022 · 5 min · jiezi

关于vue.js:Vue-源码解读5-全局-API

当学习成为了习惯,常识也就变成了常识。 感激各位的 点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 指标深刻了解以下全局 API 的实现原理。 Vue.useVue.mixinVue.componentVue.filterVue.directiveVue.extendVue.setVue.deleteVue.nextTick源码解读从该系列的第一篇文章 Vue 源码解读(1)—— 前言 中的 源码目录构造 介绍中能够得悉,Vue 的泛滥全局 API 的实现大部分都放在 /src/core/global-api 目录下。这些全局 API 源码浏览入口则是在 /src/core/global-api/index.js 文件中。 入口/src/core/global-api/index.js/** * 初始化 Vue 的泛滥全局 API,比方: * 默认配置:Vue.config * 工具办法:Vue.util.xx * Vue.set、Vue.delete、Vue.nextTick、Vue.observable * Vue.options.components、Vue.options.directives、Vue.options.filters、Vue.options._base * Vue.use、Vue.extend、Vue.mixin、Vue.component、Vue.directive、Vue.filter * */export function initGlobalAPI (Vue: GlobalAPI) { // config const configDef = {} // Vue 的泛滥默认配置项 configDef.get = () => config if (process.env.NODE_ENV !== 'production') { configDef.set = () => { warn( 'Do not replace the Vue.config object, set individual fields instead.' ) } } // Vue.config Object.defineProperty(Vue, 'config', configDef) /** * 裸露一些工具办法,轻易不要应用这些工具办法,解决你很分明这些工具办法,以及晓得应用的危险 */ Vue.util = { // 正告日志 warn, // 相似选项合并 extend, // 合并选项 mergeOptions, // 设置响应式 defineReactive } // Vue.set / delete / nextTick Vue.set = set Vue.delete = del Vue.nextTick = nextTick // 响应式办法 Vue.observable = <T>(obj: T): T => { observe(obj) return obj } // Vue.options.compoents/directives/filter Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) // 将 Vue 构造函数挂载到 Vue.options._base 上 Vue.options._base = Vue // 在 Vue.options.components 中增加内置组件,比方 keep-alive extend(Vue.options.components, builtInComponents) // Vue.use initUse(Vue) // Vue.mixin initMixin(Vue) // Vue.extend initExtend(Vue) // Vue.component/directive/filter initAssetRegisters(Vue)}Vue.use/src/core/global-api/use.js/** * 定义 Vue.use,负责为 Vue 装置插件,做了以下两件事: * 1、判断插件是否曾经被装置,如果装置则间接完结 * 2、装置插件,执行插件的 install 办法 * @param {*} plugin install 办法 或者 蕴含 install 办法的对象 * @returns Vue 实例 */Vue.use = function (plugin: Function | Object) { // 曾经装置过的插件列表 const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) // 判断 plugin 是否曾经装置,保障不反复装置 if (installedPlugins.indexOf(plugin) > -1) { return this } // 将 Vue 构造函数放到第一个参数地位,而后将这些参数传递给 install 办法 const args = toArray(arguments, 1) args.unshift(this) if (typeof plugin.install === 'function') { // plugin 是一个对象,则执行其 install 办法装置插件 plugin.install.apply(plugin, args) } else if (typeof plugin === 'function') { // 执行间接 plugin 办法装置插件 plugin.apply(null, args) } // 在 插件列表中 增加新装置的插件 installedPlugins.push(plugin) return this}Vue.mixin/src/core/global-api/mixin.js/** * 定义 Vue.mixin,负责全局混入选项,影响之后所有创立的 Vue 实例,这些实例会合并全局混入的选项 * @param {*} mixin Vue 配置对象 * @returns 返回 Vue 实例 */Vue.mixin = function (mixin: Object) { // 在 Vue 的默认配置项上合并 mixin 对象 this.options = mergeOptions(this.options, mixin) return this}mergeOptionssrc/core/util/options.js/** * 合并两个选项,呈现雷同配置项时,子选项会笼罩父选项的配置 */export function mergeOptions ( parent: Object, child: Object, vm?: Component): Object { if (process.env.NODE_ENV !== 'production') { checkComponents(child) } if (typeof child === 'function') { child = child.options } // 标准化 props、inject、directive 选项,不便后续程序的解决 normalizeProps(child, vm) normalizeInject(child, vm) normalizeDirectives(child) // 解决原始 child 对象上的 extends 和 mixins,别离执行 mergeOptions,将这些继承而来的选项合并到 parent // mergeOptions 解决过的对象会含有 _base 属性 if (!child._base) { if (child.extends) { parent = mergeOptions(parent, child.extends, vm) } if (child.mixins) { for (let i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm) } } } const options = {} let key // 遍历 父选项 for (key in parent) { mergeField(key) } // 遍历 子选项,如果父选项不存在该配置,则合并,否则跳过,因为父子领有同一个属性的状况在下面解决父选项时曾经解决过了,用的子选项的值 for (key in child) { if (!hasOwn(parent, key)) { mergeField(key) } } // 合并选项,childVal 优先级高于 parentVal function mergeField (key) { // strat 是合并策略函数,如何 key 抵触,则 childVal 会 笼罩 parentVal const strat = strats[key] || defaultStrat // 值为如果 childVal 存在则优先应用 childVal,否则应用 parentVal options[key] = strat(parent[key], child[key], vm, key) } return options}Vue.component、Vue.filter、Vue.directive/src/core/global-api/assets.js这三个 API 实现比拟非凡,然而原理又很类似,所以就放在了一起实现。 ...

February 25, 2022 · 8 min · jiezi

关于vue.js:智汀云盘开发指南android端存储池

1. 阐明存储池是存储池分区及文件操作的根基,只有存储池存在,能力操作存储池分区和文件夹,是数据存储的中央。 只有有闲置的存储池(硬盘),咱们就能够把它增加到现有的存储池或者是新的存储池,而后回到存储池列表就能够看到刷新的数据。 除了增加和查看存储池之外,咱们还能够批改存储池的名称、删除存储和增加存储池分区、查看存储池分区、辑存储池分区及删除存储池分区(存储池分区的操作将会在下一章节进行阐明)。 因为删除存储池须要肯定的工夫,所以存在存储池删除中的状态;删除存储池不肯定能删除胜利,所以存在删除存储池失败的状态。 注:只有家庭的拥有者能力操作存储池 1.1次要代码实现 1.2存储池列表和存储详情的model类 /** * 存储池列表 */public class StoragePoolListBean { private List<StoragePoolDetailBean> list; // tem为object,存储池 private PagerBean pager; // 分页数据 ...}/** * 存储池详情 */public class StoragePoolDetailBean implements MultiItemEntity, Serializable { public static final int POOL = 1; // 本人定义字段增加存储池时是存储 public static final int ADD = 2; // 本人定义字段增加存储池时是最初的加号图片 private int itemType; // 本人定义字段,用于辨别增加存储池时是存储还是最初的加号图片 private boolean selected; // 本人定义字段,用于示意存储增加存储池时是否抉择 private String id; private String name; // 名称 private double capacity; // 容量 private double use_capacity; //已用容量 /** * // 异步工作状态, 为空则没有异步工作, * TaskDelPool_0删除存储池失败,TaskDelPool_1删除存储池中, * TaskAddPool_0增加存储池失败,TaskAddPool_1增加存储池中, * TaskUpdatePool_0批改存储池失败,TaskUpdatePool_1批改存储池中, */ private String status; private String task_id; //异步工作ID private List<DiskBean> pv; // 物理分区:就是所谓硬盘 private List<DiskBean> lv; //逻辑分区:理论存储池分区 ...}/** * 硬盘和存储池分区 */public class DiskBean implements Serializable { /** * id : 4255 * name : 安但风张义 * capacity : 91.2 */ private String id; private String name; private long capacity; private long use_capacity; // 已用容量 private boolean selected; /** * 为空则没有工作状态, * TaskAddPartition_1增加存储池分区中,TaskAddPartition_0增加存储池分区失败, * TaskUpdatePartition_1批改存储池分区中,TaskUpdatePartition_0批改存储池分区失败, * TaskDelPartition_1删除存储池分区中,TaskDelPartition_0删除存储池分区失败 */ private String status; private String task_id; // 异步工作id ...}2.2 存储池列表及闲置硬盘 ...

February 24, 2022 · 4 min · jiezi

关于vue.js:ElectronVue-方法库

文件门路/src/utils/Tools.js所需依赖yarn add xlsx@0.17.3 # 解析解决 Excel 表格专用挂载为全局办法/src/main.jsimport Vue from 'vue'import App from './App.vue'// 全局办法挂载Vue.prototype.$Tools = new Tools()文件概述import xlsx from 'xlsx'const { dialog, getCurrentWindow } = require('electron').remoteexport default class Tools { constructor() {} // 具体方法写在此处...}具体方法获取输出值的具体数据类型/** * 获取输出值的具体数据类型 * @param {*} variable */getType(variable) { return Object.prototype.toString.call(variable).split(' ')[1].replace(']', '')}获取文件门路/** * 获取文件门路 * @param {string} file_type 文件抉择类型的整体形容 * @param {array} extensions 选中文件的具体扩展名组成数组 */getFilePath(file_type = 'All Files', extensions = ['*']) { return new Promise(async (resolve, reject) => { const { canceled, filePaths } = await dialog.showOpenDialog(getCurrentWindow(), { title: '请抉择一个目录', properties: ['openFile', 'dontAddToRecent'], buttonLabel: '抉择此文件', // 确认按钮的 label filters: [{ name: file_type, extensions }], }) canceled ? reject('用户已勾销对文件的抉择操作') : resolve(filePaths[0]) })}获取文件门路getDirPath() { return new Promise(async (resolve, reject) => { const { canceled, filePaths } = await dialog.showOpenDialog(getCurrentWindow(), { title: '请抉择一个目录', properties: ['openDirectory', 'dontAddToRecent'], buttonLabel: '抉择此文件夹', // 确认按钮的 label }) canceled ? reject('用户已勾销对文件夹的抉择操作') : resolve(filePaths[0]) })}获取文件保留的地位/** * 获取文件保留的地位 * @param {string} file_type 文件抉择类型的整体形容 * @param {array} extensions 选中文件的具体扩展名组成数组 */getFileSavePath(file_type = 'All Files', extensions = ['*']) { return new Promise(async (resolve, reject) => { const { canceled, filePath } = await dialog.showSaveDialog(getCurrentWindow(), { title: '请抉择文件的保留地位', properties: ['dontAddToRecent'], buttonLabel: '保留在此地位', // 确认按钮的 label filters: [{ name: file_type, extensions }], }) canceled ? reject('用户已勾销对文件的保留操作') : resolve(filePath) })}

February 24, 2022 · 1 min · jiezi

关于vue.js:性能优化-使用-esbuild-为你的构建提速-

背景最近发现我的项目(基于Vue2)构建比较慢, 一次上线公布须要 15 分钟, 效率低下。 现在这个时代,工夫就是金钱,效率就是生命。 于是这两天抽空对我的项目做了一次构建优化,线上(多国家)构建工夫, 从 10分钟 优化到 4分钟, 本地单次构建工夫, 从 300秒 优化到 90秒, 成果还不错。 明天把 具体的革新过程 和 相干 技术原理 整理出来分享给大家, 心愿对大家有所帮忙。 注释首先看一下摆在面前的问题: 能够显著看出: 整体构建环节耗时过长, 效率低下,影响业务的公布和回滚。 线上构建流程: 其中, Build base 和 Build Region 阶段存在优化空间。 Build base 阶段的优化, 和运维团队沟通过, 后续会减少缓存解决。 本次次要关注 Build Region 阶段。 初步优化后,达到成果如下: 上面介绍这次优化的细节。 我的项目优化实战面对耗时大这个问题,首先要做耗时数据分析。 这里引入 SpeedMeasurePlugin, 示例代码如下: # vue.config.jsconst SpeedMeasurePlugin = require("speed-measure-webpack-plugin");configureWebpack: (config) => { config.plugins.push(new SpeedMeasurePlugin());}失去后果如下: 失去: SMP ⏱ Loaderscache-loader, and vue-loader, and eslint-loader took 3 mins, 39.75 secs module count = 1894cache-loader, and thread-loader, and babel-loader, and ts-loader, and eslint-loader took 3 mins, 35.23 secs module count = 482cache-loader, and thread-loader, and babel-loader, and ts-loader, and cache-loader, and vue-loader took 3 mins, 16.98 secs module count = 941cache-loader, and vue-loader, and cache-loader, and vue-loader took 3 mins, 9.005 secs module count = 947mini-css-extract-plugin, and css-loader, and vue-loader, and postcss-loader, and sass-loader, and cache-loader, and vue-loader took 3 mins, 5.29 secs module count = 834modules with no loaders took 1 min, 52.53 secs module count = 3258mini-css-extract-plugin, and css-loader, and vue-loader, and postcss-loader, and cache-loader, and vue-loader took 27.29 secs module count = 25css-loader, and vue-loader, and postcss-loader, and cache-loader, and vue-loader took 27.13 secs module count = 25file-loader took 12.049 secs module count = 30cache-loader, and thread-loader, and babel-loader took 11.62 secs module count = 30url-loader took 11.51 secs module count = 70mini-css-extract-plugin, and css-loader, and postcss-loader took 9.66 secs module count = 8cache-loader, and thread-loader, and babel-loader, and ts-loader took 7.56 secs module count = 3css-loader, and // ...Build complete.fetch translationsen has been saved!id has been saved!sp-MX has been saved!vi has been saved!zh-TW has been saved!zh-CN has been saved!th has been saved!$ node ./script/copy-static-asset.js✨ Done in 289.96s.统计出耗时比拟大的几个loader: ...

February 24, 2022 · 5 min · jiezi

关于vue.js:如何在-Vue-中导出数据至-Excel-表格-卡拉云

本文首发:《如何在 Vue 中导出数据至 Excel 表格 - 卡拉云》 咱们常常须要在 Vue 搭建的后盾管理系统里导出数据到 Excel / CSV ,不便咱们将数据共享给其他同学或在另一个零碎里导入数据进行剖析。 本教程将率领大家一起应用 Vue 搭建一个导出性能页,将 JSON 数据 转化成 Excel 文件并导出。咱们会用到 bootstrap-vue 和 xlsx 这两个 JS 库。 咱们先来看一下导出性能页最终的成果。 导出 excel 数据简略罗唆,是不是很棒。请关上你的 Terminal ,追随本教程一起边学边练。 如果你对前端不是很相熟,举荐应用卡拉云,卡拉云是一套低代码开发工具,你无需写任何前端代码,简略拖拽即可疾速搭建后盾管理系统,迅速将你跑进去的数据,一键导出至 Excel / CSV / JSON 等多种格局。详见本文文尾。 接下来,咱们开始吧。 配置 Vue 环境应用 npm 装置 Vue 脚手架 vue-cli npm install -g @vue/cli 而后咱们创立一个 Vue 我的项目 kalacloud-vue-json-to-excel vue create kalacloud-vue-json-to-excel装置实现后,cd 进入 kalacloud-vue-json-to-excel 目录,接下来所有操作都在这个目录之下。 咱们先跑一下 Vue ,这是 vue 的默认状态 ...

February 24, 2022 · 2 min · jiezi

关于vue.js:Vue-源码解读4-异步更新

当学习成为了习惯,常识也就变成了常识。 感激各位的 点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 前言上一篇的 Vue 源码解读(3)—— 响应式原理 说到通过 Object.defineProperty 为对象的每个 key 设置 getter、setter,从而拦挡对数据的拜访和设置。 当对数据进行更新操作时,比方 obj.key = 'new val' 就会触发 setter 的拦挡,从而检测新值和旧值是否相等,如果相等什么也不做,如果不相等,则更新值,而后由 dep 告诉 watcher 进行更新。所以,异步更新 的入口点就是 setter 中最初调用的 dep.notify() 办法。 目标深刻了解 Vue 的异步更新机制nextTick 的原理源码解读dep.notify/src/core/observer/dep.js对于 dep 更加具体的介绍请查看上一篇文章 —— Vue 源码解读(3)—— 响应式原理,这里就不占用篇幅了。 /** * 告诉 dep 中的所有 watcher,执行 watcher.update() 办法 */notify () { // stabilize the subscriber list first const subs = this.subs.slice() // 遍历 dep 中存储的 watcher,执行 watcher.update() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() }}watcher.update/src/core/observer/watcher.js/** * 依据 watcher 配置项,决定接下来怎么走,个别是 queueWatcher */update () { /* istanbul ignore else */ if (this.lazy) { // 懒执行时走这里,比方 computed // 将 dirty 置为 true,能够让 computedGetter 执行时从新计算 computed 回调函数的执行后果 this.dirty = true } else if (this.sync) { // 同步执行,在应用 vm.$watch 或者 watch 选项时能够传一个 sync 选项, // 当为 true 时在数据更新时该 watcher 就不走异步更新队列,间接执行 this.run // 办法进行更新 // 这个属性在官网文档中没有呈现 this.run() } else { // 更新时个别都这里,将 watcher 放入 watcher 队列 queueWatcher(this) }}queueWatcher/src/core/observer/scheduler.js/** * 将 watcher 放入 watcher 队列 */export function queueWatcher (watcher: Watcher) { const id = watcher.id // 如果 watcher 曾经存在,则跳过,不会反复入队 if (has[id] == null) { // 缓存 watcher.id,用于判断 watcher 是否曾经入队 has[id] = true if (!flushing) { // 以后没有处于刷新队列状态,watcher 间接入队 queue.push(watcher) } else { // 曾经在刷新队列了 // 从队列开端开始倒序遍历,依据以后 watcher.id 找到它大于的 watcher.id 的地位,而后将本人插入到该地位之后的下一个地位 // 行将以后 watcher 放入已排序的队列中,且队列仍是有序的 let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1, 0, watcher) } // queue the flush if (!waiting) { waiting = true if (process.env.NODE_ENV !== 'production' && !config.async) { // 间接刷新调度队列 // 个别不会走这儿,Vue 默认是异步执行,如果改为同步执行,性能会大打折扣 flushSchedulerQueue() return } /** * 相熟的 nextTick => vm.$nextTick、Vue.nextTick * 1、将 回调函数(flushSchedulerQueue) 放入 callbacks 数组 * 2、通过 pending 管制向浏览器工作队列中增加 flushCallbacks 函数 */ nextTick(flushSchedulerQueue) } }}nextTick/src/core/util/next-tick.jsconst callbacks = []let pending = false/** * 实现两件事: * 1、用 try catch 包装 flushSchedulerQueue 函数,而后将其放入 callbacks 数组 * 2、如果 pending 为 false,示意当初浏览器的工作队列中没有 flushCallbacks 函数 * 如果 pending 为 true,则示意浏览器的工作队列中曾经被放入了 flushCallbacks 函数, * 待执行 flushCallbacks 函数时,pending 会被再次置为 false,示意下一个 flushCallbacks 函数能够进入 * 浏览器的工作队列了 * pending 的作用:保障在同一时刻,浏览器的工作队列中只有一个 flushCallbacks 函数 * @param {*} cb 接管一个回调函数 => flushSchedulerQueue * @param {*} ctx 上下文 * @returns */export function nextTick (cb?: Function, ctx?: Object) { let _resolve // 用 callbacks 数组存储通过包装的 cb 函数 callbacks.push(() => { if (cb) { // 用 try catch 包装回调函数,便于谬误捕捉 try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true // 执行 timerFunc,在浏览器的工作队列中(首选微工作队列)放入 flushCallbacks 函数 timerFunc() } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) }}timerFunc/src/core/util/next-tick.js// 能够看到 timerFunc 的作用很简略,就是将 flushCallbacks 函数放入浏览器的异步工作队列中let timerFuncif (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() // 首选 Promise.resolve().then() timerFunc = () => { // 在 微工作队列 中放入 flushCallbacks 函数 p.then(flushCallbacks) /** * 在有问题的UIWebViews中,Promise.then不会齐全中断,然而它可能会陷入怪异的状态, * 在这种状态下,回调被推入微工作队列,但队列没有被刷新,直到浏览器须要执行其余工作,例如解决一个计时器。 * 因而,咱们能够通过增加空计时器来“强制”刷新微工作队列。 */ if (isIOS) setTimeout(noop) } isUsingMicroTask = true} else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]')) { // MutationObserver 次之 // Use MutationObserver where native Promise is not available, // e.g. PhantomJS, iOS7, Android 4.4 // (#6466 MutationObserver is unreliable in IE11) let counter = 1 const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { // 再就是 setImmediate,它其实曾经是一个宏工作了,但依然比 setTimeout 要好 timerFunc = () => { setImmediate(flushCallbacks) }} else { // 最初没方法,则应用 setTimeout timerFunc = () => { setTimeout(flushCallbacks, 0) }}flushCallbacks/src/core/util/next-tick.jsconst callbacks = []let pending = false/** * 做了三件事: * 1、将 pending 置为 false * 2、清空 callbacks 数组 * 3、执行 callbacks 数组中的每一个函数(比方 flushSchedulerQueue、用户调用 nextTick 传递的回调函数) */function flushCallbacks () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 // 遍历 callbacks 数组,执行其中存储的每个 flushSchedulerQueue 函数 for (let i = 0; i < copies.length; i++) { copies[i]() }}flushSchedulerQueue/src/core/observer/scheduler.js/** * Flush both queues and run the watchers. * 刷新队列,由 flushCallbacks 函数负责调用,次要做了如下两件事: * 1、更新 flushing 为 ture,示意正在刷新队列,在此期间往队列中 push 新的 watcher 时须要非凡解决(将其放在队列的适合地位) * 2、依照队列中的 watcher.id 从小到大排序,保障先创立的 watcher 先执行,也配合 第一步 * 3、遍历 watcher 队列,顺次执行 watcher.before、watcher.run,并革除缓存的 watcher */function flushSchedulerQueue () { currentFlushTimestamp = getNow() // 标记当初正在刷新队列 flushing = true let watcher, id /** * 刷新队列之前先给队列排序(升序),能够保障: * 1、组件的更新程序为从父级到子级,因为父组件总是在子组件之前被创立 * 2、一个组件的用户 watcher 在其渲染 watcher 之前被执行,因为用户 watcher 先于 渲染 watcher 创立 * 3、如果一个组件在其父组件的 watcher 执行期间被销毁,则它的 watcher 能够被跳过 * 排序当前在刷新队列期间新进来的 watcher 也会按程序放入队列的适合地位 */ queue.sort((a, b) => a.id - b.id) // 这里间接应用了 queue.length,动静计算队列的长度,没有缓存长度,是因为在执行现有 watcher 期间队列中可能会被 push 进新的 watcher for (index = 0; index < queue.length; index++) { watcher = queue[index] // 执行 before 钩子,在应用 vm.$watch 或者 watch 选项时能够通过配置项(options.before)传递 if (watcher.before) { watcher.before() } // 将缓存的 watcher 革除 id = watcher.id has[id] = null // 执行 watcher.run,最终触发更新函数,比方 updateComponent 或者 获取 this.xx(xx 为用户 watch 的第二个参数),当然第二个参数也有可能是一个函数,那就间接执行 watcher.run() } // keep copies of post queues before resetting state const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice() /** * 重置调度状态: * 1、重置 has 缓存对象,has = {} * 2、waiting = flushing = false,示意刷新队列完结 * waiting = flushing = false,示意能够像 callbacks 数组中放入新的 flushSchedulerQueue 函数,并且能够向浏览器的工作队列放入下一个 flushCallbacks 函数了 */ resetSchedulerState() // call component updated and activated hooks callActivatedHooks(activatedQueue) callUpdatedHooks(updatedQueue) // devtool hook /* istanbul ignore if */ if (devtools && config.devtools) { devtools.emit('flush') }}/** * Reset the scheduler's state. */function resetSchedulerState () { index = queue.length = activatedChildren.length = 0 has = {} if (process.env.NODE_ENV !== 'production') { circular = {} } waiting = flushing = false}watcher.run/src/core/observer/watcher.js/** * 由 刷新队列函数 flushSchedulerQueue 调用,如果是同步 watch,则由 this.update 间接调用,实现如下几件事: * 1、执行实例化 watcher 传递的第二个参数,updateComponent 或者 获取 this.xx 的一个函数(parsePath 返回的函数) * 2、更新旧值为新值 * 3、执行实例化 watcher 时传递的第三个参数,比方用户 watcher 的回调函数 */run () { if (this.active) { // 调用 this.get 办法 const value = this.get() if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep ) { // 更新旧值为新值 const oldValue = this.value this.value = value if (this.user) { // 如果是用户 watcher,则执行用户传递的第三个参数 —— 回调函数,参数为 val 和 oldVal try { this.cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { // 渲染 watcher,this.cb = noop,一个空函数 this.cb.call(this.vm, value, oldValue) } } }}watcher.get/src/core/observer/watcher.js /** * 执行 this.getter,并从新收集依赖 * this.getter 是实例化 watcher 时传递的第二个参数,一个函数或者字符串,比方:updateComponent 或者 parsePath 返回的函数 * 为什么要从新收集依赖? * 因为触发更新阐明有响应式数据被更新了,然而被更新的数据尽管曾经通过 observe 察看了,然而却没有进行依赖收集, * 所以,在更新页面时,会从新执行一次 render 函数,执行期间会触发读取操作,这时候进行依赖收集 */ get () { // 关上 Dep.target,Dep.target = this pushTarget(this) // value 为回调函数执行的后果 let value const vm = this.vm try { // 执行回调函数,比方 updateComponent,进入 patch 阶段 value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } // 敞开 Dep.target,Dep.target = null popTarget() this.cleanupDeps() } return value }以上就是 Vue 异步更新机制的整个执行过程。 ...

February 24, 2022 · 6 min · jiezi

关于vue.js:Vue-30-简介

Vue 3.0 性能晋升次要是通过哪几方面体现的?响应式零碎的降级Vue 2.0 应用 defineProperty defineProperty 代理对象中的某个属性初始化时将 data 中的数据进行解决,如果属性时 object 类型还须要递归解决Vue 3.0 应用 Proxy 对象重写响应式零碎 Proxy 代理整个对象调用时递归能够监听动静新增的属性能够监听删除的属性能够监听数组的索引和 length 属性编译优化Vue 2.0 中通过标记动态根节点,优化 diff 过程Vue 3.0 中标记和晋升所有的动态根节点,diff 只须要比照动静节点内容 Fragments动态晋升Patch flag缓存事件处理函数源码体积优化Vue 3.0 中移除了一些不罕用的 API inline-tempalte、filterTree-shakingVue 3.0 所采纳的 Composition Api 与 Vue 2.x应用的Options Api 有什么区别?Vue 2.0 Options Api在一个 vue 文件中应用 data、methods、computed、watch 定义属性和办法,独特解决页面逻辑问题:当组件开始变得更大时,逻辑关注点的列表也会增长。尤其对于那些一开始没有编写这些组件的人来说,这会导致组件难以浏览和了解。 // src/components/UserRepositories.vueexport default { components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList }, props: { user: { type: String, required: true } }, data () { return { repositories: [], // 1 filters: { ... }, // 3 searchQuery: '' // 2 } }, computed: { filteredRepositories () { ... }, // 3 repositoriesMatchingSearchQuery () { ... }, // 2 }, watch: { user: 'getUserRepositories' // 1 }, methods: { getUserRepositories () { // 应用 `this.user` 获取用户仓库 }, // 1 updateFilters () { ... }, // 3 }, mounted () { this.getUserRepositories() // 1 }}Vue 3.0 Composition Api在 Composition API 中,代码是依据逻辑性能来组织的,一个性能的所有 API 会放在一起(高内聚,低耦合)// src/components/UserRepositories.vueimport { toRefs } from 'vue'import useUserRepositories from '@/composables/useUserRepositories'import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'import useRepositoryFilters from '@/composables/useRepositoryFilters'export default { components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList }, props: { user: { type: String, required: true } }, setup(props) { const { user } = toRefs(props) const { repositories, getUserRepositories } = useUserRepositories(user) const { searchQuery, repositoriesMatchingSearchQuery } = useRepositoryNameSearch(repositories) const { filters, updateFilters, filteredRepositories } = useRepositoryFilters(repositoriesMatchingSearchQuery) return { // 因为咱们并不关怀未经过滤的仓库 // 咱们能够在 `repositories` 名称下裸露过滤后的后果 repositories: filteredRepositories, getUserRepositories, searchQuery, filters, updateFilters } }}Proxy 绝对于 Object.defineProperty 有哪些长处?defineProperty 代理对象中的某个属性,Proxy 代理整个对象defineProperty 无奈监控到数组下标的变动, Proxy 能够Vue 3.0 在编译方面有哪些优化?Vue 2.0 中通过标记动态根节点,优化 diff 过程Vue 3.0 中标记和晋升所有的动态根节点,diff 只须要比照动静节点内容 ...

February 23, 2022 · 3 min · jiezi

关于vue.js:Day-31100-Vue3-项目使用CDN加速

(一)需要我的项目首屏加载慢,低峰时段8.7s,顶峰时段甚至有60s+ 以上的反馈。 (二)起因Vue-cli 自身是有对webpack 打包优化的。 慢,是因为我的项目做了负载平衡 + nginx 做了限流。 (起因是避免DDOS攻打,这里仍需优化)。 因为打包后是动态文件,所以想到了应用CDN来减速的方法。 (三)步骤1、批改vue.config.js文件导出文件门路之前服务器上应用的是相对路径。 再次打包后,须要将我的项目门路,增加上CDN的绝对路径。 module.exports = { publicPath: 'OSS门路', // 生产环境}2、打包并上传到OSS中上传除了index.html 文件的其余文件到OSS中。 3、上传index.html 文件到服务器对应门路下最终后果 成果还挺显著的,首屏加载从8.73秒到1.75s~ 哎,尽管leader不器重,但播种的是本人的。 Vue3 首屏加载还有其余的计划,之前我本人也做过一些优化,并不是我的次要问题。 感兴趣的同学能够看参考链接。 参考链接https://juejin.cn/post/691353...

February 23, 2022 · 1 min · jiezi

关于vue.js:开源一个-vue-版的脑图编辑器数据上已兼容-agiletc-的测试结果格式

原文由 陈恒捷 发表于TesterHome社区网站,原文链接背景因为公司测试平台前端框架用的是 vue ,且年初 agiletc 还不反对百度脑图原生反对的从 xmind 复制粘贴至脑图组件,快捷键也不够好用,且前端组件没开源。因而基于社区内其余已有的开源脑图组件,再仿照 agiletc 的脑图组件包了一层。 很早就确认了能够开源,但始终有别的事件忙没空弄。刚好这周末又有同学私聊我,所以花了点工夫把公司私库相干信息改为内部 npm 的,进行开源。 反对性能: 1、百度脑图原有性能(节点编辑、优先级设置、自定义标签设置)2、测试后果注销,数据格式和 agiletc 现有脑图组件的格局统一,即能够间接正确展现和编辑现有 agiletc 自带编辑器设置的测试后果3、以后选中节点个数统计,在顶部标题栏加了个以后选中节点个数统计数据的文字 相比 agiletc 少了的性能: 1、脑图自身自带的导入导出性能(agiletc 服务端有提供,所以前端就不提供了)2、增加图片(百度脑图自身就没有)3、实时上报及更新脑图数据 在此特别感谢 fudax、MeYoung 两位前辈的开源奉献,我只是站在大家的肩膀上补充了一些边角性能而已 开源地址https://github.com/chenhengjie123/vue-testcase-minder-editor 成果[](https://testerhome.com/upload...!large) 也能够本地跑起来体验,克隆完代码后,在根目录下: # 装置依赖npm --registry=https://registry.npm.taobao.org install# 本地运行npm run lib && npm run serve运行后,依照提醒的地址关上即可,例如 App running at:- Local: http://localhost:8081 示意通过 http://localhost:8081 能够关上 我的项目中应用装置本组件 npm --registry=https://registry.npm.taobao.org install vue-testcase-minder-editor在 main.js 中 import 'vue-testcase-minder-editor/lib/VueTestcaseMinderEditor.css'import VueTestcaseMinderEditor from 'vue-testcase-minder-editor'Vue.use(VueTestcaseMinderEditor)本组件依赖 vuex 进行局部全局配置管理。如果没有用 vuex ,可间接在 main.js 退出上面代码。 Vue.use(Vuex)const store = new Vuex.Store({ modules: { caseEditorStore: VueTestcaseMinderEditor.caseEditorStore }})如果有,能够仿照上面代码,动静注册对应 module ...

February 23, 2022 · 1 min · jiezi

关于vue.js:Vue-源码解读3-响应式原理

当学习成为了习惯,常识也就变成了常识。 感激各位的 点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 前言上一篇文章 Vue 源码解读(2)—— Vue 初始化过程 具体解说了 Vue 的初始化过程,明确了 new Vue(options) 都做了什么,其中对于 数据响应式 的实现用一句话简略的带过,而这篇文章则会具体解说 Vue 数据响应式的实现原理。 指标深刻了解 Vue 数据响应式原理。methods、computed 和 watch 有什么区别?源码解读通过上一篇文章的学习,置信对于 响应式原理 源码浏览的入口地位大家都曾经晓得了,就是初始化过程中解决数据响应式这一步,即调用 initState 办法,在 /src/core/instance/init.js 文件中。 initState/src/core/instance/state.js/** * 两件事: * 数据响应式的入口:别离解决 props、methods、data、computed、watch * 优先级:props、methods、data、computed 对象中的属性不能呈现反复,优先级和列出程序统一 * 其中 computed 中的 key 不能和 props、data 中的 key 反复,methods 不影响 */export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options // 解决 props 对象,为 props 对象的每个属性设置响应式,并将其代理到 vm 实例上 if (opts.props) initProps(vm, opts.props) // 解决 methos 对象,校验每个属性的值是否为函数、和 props 属性比对进行判重解决,最初失去 vm[key] = methods[key] if (opts.methods) initMethods(vm, opts.methods) /** * 做了三件事 * 1、判重解决,data 对象上的属性不能和 props、methods 对象上的属性雷同 * 2、代理 data 对象上的属性到 vm 实例 * 3、为 data 对象的上数据设置响应式 */ if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } /** * 三件事: * 1、为 computed[key] 创立 watcher 实例,默认是懒执行 * 2、代理 computed[key] 到 vm 实例 * 3、判重,computed 中的 key 不能和 data、props 中的属性反复 */ if (opts.computed) initComputed(vm, opts.computed) /** * 三件事: * 1、解决 watch 对象 * 2、为 每个 watch.key 创立 watcher 实例,key 和 watcher 实例可能是 一对多 的关系 * 3、如果设置了 immediate,则立刻执行 回调函数 */ if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } /** * 其实到这里也能看出,computed 和 watch 在实质是没有区别的,都是通过 watcher 去实现的响应式 * 非要说有区别,那也只是在应用形式上的区别,简略来说: * 1、watch:实用于当数据变动时执行异步或者开销较大的操作时应用,即须要长时间期待的操作能够放在 watch 中 * 2、computed:其中能够应用异步办法,然而没有任何意义。所以 computed 更适宜做一些同步计算 */}initPropssrc/core/instance/state.js// 解决 props 对象,为 props 对象的每个属性设置响应式,并将其代理到 vm 实例上function initProps (vm: Component, propsOptions: Object) { const propsData = vm.$options.propsData || {} const props = vm._props = {} // 缓存 props 的每个 key,性能优化 // cache prop keys so that future props updates can iterate using Array // instead of dynamic object key enumeration. const keys = vm.$options._propKeys = [] const isRoot = !vm.$parent // root instance props should be converted if (!isRoot) { toggleObserving(false) } // 遍历 props 对象 for (const key in propsOptions) { // 缓存 key keys.push(key) // 获取 props[key] 的默认值 const value = validateProp(key, propsOptions, propsData, vm) // 为 props 的每个 key 是设置数据响应式 defineReactive(props, key, value) // static props are already proxied on the component's prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. if (!(key in vm)) { // 代理 key 到 vm 对象上 proxy(vm, `_props`, key) } } toggleObserving(true)}proxy/src/core/instance/state.js// 设置代理,将 key 代理到 target 上export function proxy (target: Object, sourceKey: string, key: string) { sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition)}initMethods/src/core/instance/state.js/** * 做了以下三件事,其实最要害的就是第三件事件 * 1、校验 methoss[key],必须是一个函数 * 2、判重 * methods 中的 key 不能和 props 中的 key 雷同 * methos 中的 key 与 Vue 实例上已有的办法重叠,个别是一些内置办法,比方以 $ 和 _ 结尾的办法 * 3、将 methods[key] 放到 vm 实例上,失去 vm[key] = methods[key] */function initMethods (vm: Component, methods: Object) { // 获取 props 配置项 const props = vm.$options.props // 遍历 methods 对象 for (const key in methods) { if (process.env.NODE_ENV !== 'production') { if (typeof methods[key] !== 'function') { warn( `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` + `Did you reference the function correctly?`, vm ) } if (props && hasOwn(props, key)) { warn( `Method "${key}" has already been defined as a prop.`, vm ) } if ((key in vm) && isReserved(key)) { warn( `Method "${key}" conflicts with an existing Vue instance method. ` + `Avoid defining component methods that start with _ or $.` ) } } vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm) }}initDatasrc/core/instance/state.js/** * 做了三件事 * 1、判重解决,data 对象上的属性不能和 props、methods 对象上的属性雷同 * 2、代理 data 对象上的属性到 vm 实例 * 3、为 data 对象的上数据设置响应式 */function initData (vm: Component) { // 失去 data 对象 let data = vm.$options.data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} if (!isPlainObject(data)) { data = {} process.env.NODE_ENV !== 'production' && warn( 'data functions should return an object:\n' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm ) } /** * 两件事 * 1、判重解决,data 对象上的属性不能和 props、methods 对象上的属性雷同 * 2、代理 data 对象上的属性到 vm 实例 */ const keys = Object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (i--) { const key = keys[i] if (process.env.NODE_ENV !== 'production') { if (methods && hasOwn(methods, key)) { warn( `Method "${key}" has already been defined as a data property.`, vm ) } } if (props && hasOwn(props, key)) { process.env.NODE_ENV !== 'production' && warn( `The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) } else if (!isReserved(key)) { proxy(vm, `_data`, key) } } // 为 data 对象上的数据设置响应式 observe(data, true /* asRootData */)}export function getData (data: Function, vm: Component): any { // #7573 disable dep collection when invoking data getters pushTarget() try { return data.call(vm, vm) } catch (e) { handleError(e, vm, `data()`) return {} } finally { popTarget() }}initComputed/src/core/instance/state.jsconst computedWatcherOptions = { lazy: true }/** * 三件事: * 1、为 computed[key] 创立 watcher 实例,默认是懒执行 * 2、代理 computed[key] 到 vm 实例 * 3、判重,computed 中的 key 不能和 data、props 中的属性反复 * @param {*} computed = { * key1: function() { return xx }, * key2: { * get: function() { return xx }, * set: function(val) {} * } * } */function initComputed (vm: Component, computed: Object) { // $flow-disable-line const watchers = vm._computedWatchers = Object.create(null) // computed properties are just getters during SSR const isSSR = isServerRendering() // 遍历 computed 对象 for (const key in computed) { // 获取 key 对应的值,即 getter 函数 const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get if (process.env.NODE_ENV !== 'production' && getter == null) { warn( `Getter is missing for computed property "${key}".`, vm ) } if (!isSSR) { // 为 computed 属性创立 watcher 实例 watchers[key] = new Watcher( vm, getter || noop, noop, // 配置项,computed 默认是懒执行 computedWatcherOptions ) } if (!(key in vm)) { // 代理 computed 对象中的属性到 vm 实例 // 这样就能够应用 vm.computedKey 拜访计算属性了 defineComputed(vm, key, userDef) } else if (process.env.NODE_ENV !== 'production') { // 非生产环境有一个判重解决,computed 对象中的属性不能和 data、props 中的属性雷同 if (key in vm.$data) { warn(`The computed property "${key}" is already defined in data.`, vm) } else if (vm.$options.props && key in vm.$options.props) { warn(`The computed property "${key}" is already defined as a prop.`, vm) } } }}/** * 代理 computed 对象中的 key 到 target(vm)上 */export function defineComputed ( target: any, key: string, userDef: Object | Function) { const shouldCache = !isServerRendering() // 结构属性描述符(get、set) if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : createGetterInvoker(userDef) sharedPropertyDefinition.set = noop } else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : createGetterInvoker(userDef.get) : noop sharedPropertyDefinition.set = userDef.set || noop } if (process.env.NODE_ENV !== 'production' && sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( `Computed property "${key}" was assigned to but it has no setter.`, this ) } } // 拦挡对 target.key 的拜访和设置 Object.defineProperty(target, key, sharedPropertyDefinition)}/** * @returns 返回一个函数,这个函数在拜访 vm.computedProperty 时会被执行,而后返回执行后果 */function createComputedGetter (key) { // computed 属性值会缓存的原理也是在这里联合 watcher.dirty、watcher.evalaute、watcher.update 实现的 return function computedGetter () { // 失去以后 key 对应的 watcher const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { // 计算 key 对应的值,通过执行 computed.key 的回调函数来失去 // watcher.dirty 属性就是大家常说的 computed 计算结果会缓存的原理 // <template> // <div>{{ computedProperty }}</div> // <div>{{ computedProperty }}</div> // </template> // 像这种状况下,在页面的一次渲染中,两个 dom 中的 computedProperty 只有第一个 // 会执行 computed.computedProperty 的回调函数计算理论的值, // 即执行 watcher.evalaute,而第二个就不走计算过程了, // 因为上一次执行 watcher.evalute 时把 watcher.dirty 置为了 false, // 待页面更新后,wathcer.update 办法会将 watcher.dirty 从新置为 true, // 供下次页面更新时从新计算 computed.key 的后果 if (watcher.dirty) { watcher.evaluate() } if (Dep.target) { watcher.depend() } return watcher.value } }}/** * 性能同 createComputedGetter 一样 */function createGetterInvoker(fn) { return function computedGetter () { return fn.call(this, this) }}initWatch/src/core/instance/state.js/** * 解决 watch 对象的入口,做了两件事: * 1、遍历 watch 对象 * 2、调用 createWatcher 函数 * @param {*} watch = { * 'key1': function(val, oldVal) {}, * 'key2': 'this.methodName', * 'key3': { * handler: function(val, oldVal) {}, * deep: true * }, * 'key4': [ * 'this.methodNanme', * function handler1() {}, * { * handler: function() {}, * immediate: true * } * ], * 'key.key5' { ... } * } */function initWatch (vm: Component, watch: Object) { // 遍历 watch 对象 for (const key in watch) { const handler = watch[key] if (Array.isArray(handler)) { // handler 为数组,遍历数组,获取其中的每一项,而后调用 createWatcher for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { createWatcher(vm, key, handler) } }}/** * 两件事: * 1、兼容性解决,保障 handler 必定是一个函数 * 2、调用 $watch * @returns */function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object) { // 如果 handler 为对象,则获取其中的 handler 选项的值 if (isPlainObject(handler)) { options = handler handler = handler.handler } // 如果 hander 为字符串,则阐明是一个 methods 办法,获取 vm[handler] if (typeof handler === 'string') { handler = vm[handler] } return vm.$watch(expOrFn, handler, options)}/** * 创立 watcher,返回 unwatch,共实现如下 5 件事: * 1、兼容性解决,保障最初 new Watcher 时的 cb 为函数 * 2、标示用户 watcher * 3、创立 watcher 实例 * 4、如果设置了 immediate,则立刻执行一次 cb * 5、返回 unwatch * @param {*} expOrFn key * @param {*} cb 回调函数 * @param {*} options 配置项,用户间接调用 this.$watch 时可能会传递一个 配置项 * @returns 返回 unwatch 函数,用于勾销 watch 监听 */Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object): Function { const vm: Component = this // 兼容性解决,因为用户调用 vm.$watch 时设置的 cb 可能是对象 if (isPlainObject(cb)) { return createWatcher(vm, expOrFn, cb, options) } // options.user 示意用户 watcher,还有渲染 watcher,即 updateComponent 办法中实例化的 watcher options = options || {} options.user = true // 创立 watcher const watcher = new Watcher(vm, expOrFn, cb, options) // 如果用户设置了 immediate 为 true,则立刻执行一次回调函数 if (options.immediate) { try { cb.call(vm, watcher.value) } catch (error) { handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`) } } // 返回一个 unwatch 函数,用于解除监听 return function unwatchFn () { watcher.teardown() }}observe/src/core/observer/index.js/** * 响应式解决的真正入口 * 为对象创立观察者实例,如果对象曾经被察看过,则返回已有的观察者实例,否则创立新的观察者实例 * @param {*} value 对象 => {} */export function observe (value: any, asRootData: ?boolean): Observer | void { // 非对象和 VNode 实例不做响应式解决 if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { // 如果 value 对象上存在 __ob__ 属性,则示意曾经做过察看了,间接返回 __ob__ 属性 ob = value.__ob__ } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { // 创立观察者实例 ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob}Observer/src/core/observer/index.js/** * 观察者类,会被附加到每个被察看的对象上,value.__ob__ = this * 而对象的各个属性则会被转换成 getter/setter,并收集依赖和告诉更新 */export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $data constructor (value: any) { this.value = value // 实例话一个 dep this.dep = new Dep() this.vmCount = 0 // 在 value 对象上设置 __ob__ 属性 def(value, '__ob__', this) if (Array.isArray(value)) { /** * value 为数组 * hasProto = '__proto__' in {} * 用于判断对象是否存在 __proto__ 属性,通过 obj.__proto__ 能够拜访对象的原型链 * 但因为 __proto__ 不是规范属性,所以有些浏览器不反对,比方 IE6-10,Opera10.1 * 为什么要判断,是因为一会儿要通过 __proto__ 操作数据的原型链 * 笼罩数组默认的七个原型办法,以实现数组响应式 */ if (hasProto) { // 有 __proto__ protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } this.observeArray(value) } else { // value 为对象,为对象的每个属性(包含嵌套对象)设置响应式 this.walk(value) } } /** * 遍历对象上的每个 key,为每个 key 设置响应式 * 仅当值为对象时才会走这里 */ walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } /** * 遍历数组,为数组的每一项设置察看,解决数组元素为对象的状况 */ observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } }}defineReactive/src/core/observer/index.js/** * 拦挡 obj[key] 的读取和设置操作: * 1、在第一次读取时收集依赖,比方执行 render 函数生成虚构 DOM 时会有读取操作 * 2、在更新时设置新值并告诉依赖更新 */export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean) { // 实例化 dep,一个 key 一个 dep const dep = new Dep() // 获取 obj[key] 的属性描述符,发现它是不可配置对象的话间接 return const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // 记录 getter 和 setter,获取 val 值 const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { val = obj[key] } // 递归调用,解决 val 即 obj[key] 的值为对象的状况,保障对象中的所有 key 都被察看 let childOb = !shallow && observe(val) // 响应式外围 Object.defineProperty(obj, key, { enumerable: true, configurable: true, // get 拦挡对 obj[key] 的读取操作 get: function reactiveGetter () { const value = getter ? getter.call(obj) : val /** * Dep.target 为 Dep 类的一个动态属性,值为 watcher,在实例化 Watcher 时会被设置 * 实例化 Watcher 时会执行 new Watcher 时传递的回调函数(computed 除外,因为它懒执行) * 而回调函数中如果有 vm.key 的读取行为,则会触发这里的 读取 拦挡,进行依赖收集 * 回调函数执行完当前又会将 Dep.target 设置为 null,防止这里反复收集依赖 */ if (Dep.target) { // 依赖收集,在 dep 中增加 watcher,也在 watcher 中增加 dep dep.depend() // childOb 示意对象中嵌套对象的观察者对象,如果存在也对其进行依赖收集 if (childOb) { // 这就是 this.key.chidlKey 被更新时能触发响应式更新的起因 childOb.dep.depend() // 如果是 obj[key] 是 数组,则触发数组响应式 if (Array.isArray(value)) { // 为数组项为对象的项增加依赖 dependArray(value) } } } return value }, // set 拦挡对 obj[key] 的设置操作 set: function reactiveSetter (newVal) { // 旧的 obj[key] const value = getter ? getter.call(obj) : val // 如果新老值一样,则间接 return,不跟新更不触发响应式更新过程 /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } // setter 不存在阐明该属性是一个只读属性,间接 return // #7981: for accessor properties without setter if (getter && !setter) return // 设置新值 if (setter) { setter.call(obj, newVal) } else { val = newVal } // 对新值进行察看,让新值也是响应式的 childOb = !shallow && observe(newVal) // 依赖告诉更新 dep.notify() } })}dependArray/src/core/observer/index.js/** * 遍历每个数组元素,递归解决数组项为对象的状况,为其增加依赖 * 因为后面的递归阶段无奈为数组中的对象元素增加依赖 */function dependArray (value: Array<any>) { for (let e, i = 0, l = value.length; i < l; i++) { e = value[i] e && e.__ob__ && e.__ob__.dep.depend() if (Array.isArray(e)) { dependArray(e) } }}数组响应式src/core/observer/array.js/** * 定义 arrayMethods 对象,用于加强 Array.prototype * 当拜访 arrayMethods 对象上的那七个办法时会被拦挡,以实现数组响应式 */import { def } from '../util/index'// 备份 数组 原型对象const arrayProto = Array.prototype// 通过继承的形式创立新的 arrayMethodsexport const arrayMethods = Object.create(arrayProto)// 操作数组的七个办法,这七个办法能够扭转数组本身const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']/** * 拦挡变异办法并触发事件 */methodsToPatch.forEach(function (method) { // cache original method // 缓存原生办法,比方 push const original = arrayProto[method] // def 就是 Object.defineProperty,拦挡 arrayMethods.method 的拜访 def(arrayMethods, method, function mutator (...args) { // 先执行原生办法,比方 push.apply(this, args) const result = original.apply(this, args) const ob = this.__ob__ // 如果 method 是以下三个之一,阐明是新插入了元素 let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } // 对新插入的元素做响应式解决 if (inserted) ob.observeArray(inserted) // 告诉更新 ob.dep.notify() return result })})def/src/core/util/lang.js/** * Define a property. */export function def (obj: Object, key: string, val: any, enumerable?: boolean) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true })}protoAugment/src/core/observer/index.js/** * 设置 target.__proto__ 的原型对象为 src * 比方 数组对象,arr.__proto__ = arrayMethods */function protoAugment (target, src: Object) { /* eslint-disable no-proto */ target.__proto__ = src /* eslint-enable no-proto */}copyAugment/src/core/observer/index.js/** * 在指标对象上定义指定属性 * 比方数组:为数组对象定义那七个办法 */function copyAugment (target: Object, src: Object, keys: Array<string>) { for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i] def(target, key, src[key]) }}Dep/src/core/observer/dep.jsimport type Watcher from './watcher'import { remove } from '../util/index'import config from '../config'let uid = 0/** * 一个 dep 对应一个 obj.key * 在读取响应式数据时,负责收集依赖,每个 dep(或者说 obj.key)依赖的 watcher 有哪些 * 在响应式数据更新时,负责告诉 dep 中那些 watcher 去执行 update 办法 */export default class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () { this.id = uid++ this.subs = [] } // 在 dep 中增加 watcher addSub (sub: Watcher) { this.subs.push(sub) } removeSub (sub: Watcher) { remove(this.subs, sub) } // 像 watcher 中增加 dep depend () { if (Dep.target) { Dep.target.addDep(this) } } /** * 告诉 dep 中的所有 watcher,执行 watcher.update() 办法 */ notify () { // stabilize the subscriber list first const subs = this.subs.slice() if (process.env.NODE_ENV !== 'production' && !config.async) { // subs aren't sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort((a, b) => a.id - b.id) } // 遍历 dep 中存储的 watcher,执行 watcher.update() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }}/** * 以后正在执行的 watcher,同一时间只会有一个 watcher 在执行 * Dep.target = 以后正在执行的 watcher * 通过调用 pushTarget 办法实现赋值,调用 popTarget 办法实现重置(null) */Dep.target = nullconst targetStack = []// 在须要进行依赖收集的时候调用,设置 Dep.target = watcherexport function pushTarget (target: ?Watcher) { targetStack.push(target) Dep.target = target}// 依赖收集完结调用,设置 Dep.target = nullexport function popTarget () { targetStack.pop() Dep.target = targetStack[targetStack.length - 1]}Watcher/src/core/observer/watcher.js/** * 一个组件一个 watcher(渲染 watcher)或者一个表达式一个 watcher(用户watcher) * 当数据更新时 watcher 会被触发,拜访 this.computedProperty 时也会触发 watcher */export default class Watcher { vm: Component; expression: string; cb: Function; id: number; deep: boolean; user: boolean; lazy: boolean; sync: boolean; dirty: boolean; active: boolean; deps: Array<Dep>; newDeps: Array<Dep>; depIds: SimpleSet; newDepIds: SimpleSet; before: ?Function; getter: Function; value: any; constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // options if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync this.before = options.before } else { this.deep = this.user = this.lazy = this.sync = false } this.cb = cb this.id = ++uid // uid for batching this.active = true this.dirty = this.lazy // for lazy watchers this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { // this.getter = function() { return this.xx } // 在 this.get 中执行 this.getter 时会触发依赖收集 // 待后续 this.xx 更新时就会触发响应式 this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = noop process.env.NODE_ENV !== 'production' && warn( `Failed watching path: "${expOrFn}" ` + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ) } } this.value = this.lazy ? undefined : this.get() } /** * 执行 this.getter,并从新收集依赖 * this.getter 是实例化 watcher 时传递的第二个参数,一个函数或者字符串,比方:updateComponent 或者 parsePath 返回的读取 this.xx 属性值的函数 * 为什么要从新收集依赖? * 因为触发更新阐明有响应式数据被更新了,然而被更新的数据尽管曾经通过 observe 察看了,然而却没有进行依赖收集, * 所以,在更新页面时,会从新执行一次 render 函数,执行期间会触发读取操作,这时候进行依赖收集 */ get () { // 关上 Dep.target,Dep.target = this pushTarget(this) // value 为回调函数执行的后果 let value const vm = this.vm try { // 执行回调函数,比方 updateComponent,进入 patch 阶段 value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } // 敞开 Dep.target,Dep.target = null popTarget() this.cleanupDeps() } return value } /** * Add a dependency to this directive. * 两件事: * 1、增加 dep 给本人(watcher) * 2、增加本人(watcher)到 dep */ addDep (dep: Dep) { // 判重,如果 dep 曾经存在则不反复增加 const id = dep.id if (!this.newDepIds.has(id)) { // 缓存 dep.id,用于判重 this.newDepIds.add(id) // 增加 dep this.newDeps.push(dep) // 防止在 dep 中反复增加 watcher,this.depIds 的设置在 cleanupDeps 办法中 if (!this.depIds.has(id)) { // 增加 watcher 本人到 dep dep.addSub(this) } } } /** * Clean up for dependency collection. */ cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 } /** * 依据 watcher 配置项,决定接下来怎么走,个别是 queueWatcher */ update () { /* istanbul ignore else */ if (this.lazy) { // 懒执行时走这里,比方 computed // 将 dirty 置为 true,能够让 computedGetter 执行时从新计算 computed 回调函数的执行后果 this.dirty = true } else if (this.sync) { // 同步执行,在应用 vm.$watch 或者 watch 选项时能够传一个 sync 选项, // 当为 true 时在数据更新时该 watcher 就不走异步更新队列,间接执行 this.run // 办法进行更新 // 这个属性在官网文档中没有呈现 this.run() } else { // 更新时个别都这里,将 watcher 放入 watcher 队列 queueWatcher(this) } } /** * 由 刷新队列函数 flushSchedulerQueue 调用,实现如下几件事: * 1、执行实例化 watcher 传递的第二个参数,updateComponent 或者 获取 this.xx 的一个函数(parsePath 返回的函数) * 2、更新旧值为新值 * 3、执行实例化 watcher 时传递的第三个参数,比方用户 watcher 的回调函数 */ run () { if (this.active) { // 调用 this.get 办法 const value = this.get() if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep ) { // 更新旧值为新值 const oldValue = this.value this.value = value if (this.user) { // 如果是用户 watcher,则执行用户传递的第三个参数 —— 回调函数,参数为 val 和 oldVal try { this.cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { // 渲染 watcher,this.cb = noop,一个空函数 this.cb.call(this.vm, value, oldValue) } } } } /** * 懒执行的 watcher 会调用该办法 * 比方:computed,在获取 vm.computedProperty 的值时会调用该办法 * 而后执行 this.get,即 watcher 的回调函数,失去返回值 * this.dirty 被置为 false,作用是页面在本次渲染中只会一次 computed.key 的回调函数, * 这也是大家常说的 computed 和 methods 区别之一是 computed 有缓存的原理所在 * 而页面更新后会 this.dirty 会被从新置为 true,这一步是在 this.update 办法中实现的 */ evaluate () { this.value = this.get() this.dirty = false } /** * Depend on all deps collected by this watcher. */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } /** * Remove self from all dependencies' subscriber list. */ teardown () { if (this.active) { // remove self from vm's watcher list // this is a somewhat expensive operation so we skip it // if the vm is being destroyed. if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this) } let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false } }}总结面试官 问:Vue 响应式原理是怎么实现的? ...

February 23, 2022 · 17 min · jiezi

关于vue.js:VUE页面局部组件刷新

vue实现页面刷新在我的页面里有应用过三种页面刷新的办法,接下来挨个介绍下: 一、以后窗口刷新window.location.reload() //页面刷新 二、路由切换形式this.$router.push("须要刷新的页面地址"); //页面刷新 以上两种形式都能够有页面刷新的成果,然而毛病就是会呈现空白页,第三种形式就能够解决这一毛病,咱们一起来看看。。。 三、provide / inject 组合形式首先在你的App.vue页面增加v-if=“isRouterAlive”,如以下代码: <template> <div> <router-view v-if="isRouterAlive"></router-view> </div></template>export default { name: "app", data() { return { isRouterAlive: true, }; }, provide() { //提供 return { reload: this.reload, }; }, methods: { reload() { this.isRouterAlive = false; this.$nextTick(function () { this.isRouterAlive = true; }); }, },};最初在你须要加载的页面注入App.vue组件提供(provide)的 reload 依赖,而后间接用this.reload来调用就行,如以下代码: inject: ["reload"], //注入 和methods同级 methods: { onSubmit() { this.reload(); //部分刷新 } }

February 22, 2022 · 1 min · jiezi

关于vue.js:vue-接口监听-实现-跳转login

接口监听 跳转login import http from './modules/api/http';Vue.prototype.$axios = http;Vue.http.interceptors.push((request, next) => { if (request.method === 'GET') { request.headers.map["If-Modified-Since"] = ["0"]; //IEget缓存解决 } clearTimeout(loadingTimer); loadingTimer = setTimeout(() => { vm.$Loading.start(); }, 300); next((response) => { if (loadingTimer) { clearTimeout(loadingTimer); loadingTimer = null; } vm.$Loading.finish(); if (response.status === 401) { router.push('/login'); //没有权限弹出去 } if (String(response.status)[0] === '5') {//非本地测试敞开 // if( /(127.0.0.1)/.test(window.location.href) ){ // alert('服务器外部谬误'); //服务器外部谬误 // }; } });}

February 22, 2022 · 1 min · jiezi

关于vue.js:Vue-源码解读2-Vue-初始化过程

当学习成为了习惯,常识也就变成了常识。 感激各位的 点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 指标深刻了解 Vue 的初始化过程,再也不怕 面试官 的那道面试题:new Vue(options) 产生了什么? 找入口想晓得 new Vue(options) 都做了什么,就得先找到 Vue 的构造函数是在哪申明的,有两个方法: 从 rollup 配置文件中找到编译的入口,而后一步步找到 Vue 构造函数,这种形式 吃力通过编写示例代码,而后打断点的形式,一步到位,简略咱们就采纳第二种形式,写示例,打断点,一步到位。 在 /examples 目录下减少一个示例文件 —— test.html,在文件中增加如下内容:<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Vue 源码解读</title></head><body> <div id="app"> {{ msg }} </div> <script src="../dist/vue.js"></script> <script> debugger new Vue({ el: '#app', data: { msg: 'hello vue' } }) </script></body></html>在浏览器中关上控制台,而后关上 test.html,则会进入断点调试,而后找到 Vue 构造函数所在的文件 点击查看演示动图,动图地址:https://p1-juejin.byteimg.com... 失去 Vue 构造函数在 /src/core/instance/index.js 文件中,接下来正式开始源码浏览,带着指标去浏览。 在浏览过程中如遇到看不明确的中央,可通过编写示例代码,而后应用浏览器的调试性能进行一步步调试,配合了解,如果还是了解不了,就做个备注持续向后看,兴许你看到其它中央,就忽然明确这个中央在做什么,或者回头再来看,就会懂了,源码这个货色,肯定要多看,要想精通,一遍两遍必定是不够的源码解读 —— Vue 初始化过程Vue/src/core/instance/index.jsimport { initMixin } from './init'// Vue 构造函数function Vue (options) { // 调用 Vue.prototype._init 办法,该办法是在 initMixin 中定义的 this._init(options)}// 定义 Vue.prototype._init 办法initMixin(Vue)export default VueVue.prototype._init/src/core/instance/init.js/** * 定义 Vue.prototype._init 办法 * @param {*} Vue Vue 构造函数 */export function initMixin (Vue: Class<Component>) { // 负责 Vue 的初始化过程 Vue.prototype._init = function (options?: Object) { // vue 实例 const vm: Component = this // 每个 vue 实例都有一个 _uid,并且是顺次递增的 vm._uid = uid++ // a flag to avoid this being observed vm._isVue = true // 解决组件配置项 if (options && options._isComponent) { /** * 每个子组件初始化时走这里,这里只做了一些性能优化 * 将组件配置对象上的一些深层次属性放到 vm.$options 选项中,以进步代码的执行效率 */ initInternalComponent(vm, options) } else { /** * 初始化根组件时走这里,合并 Vue 的全局配置到根组件的部分配置,比方 Vue.component 注册的全局组件会合并到 根实例的 components 选项中 * 至于每个子组件的选项合并则产生在两个中央: * 1、Vue.component 办法注册的全局组件在注册时做了选项合并 * 2、{ components: { xx } } 形式注册的部分组件在执行编译器生成的 render 函数时做了选项合并,包含根组件中的 components 配置 */ vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { // 设置代理,将 vm 实例上的属性代理到 vm._renderProxy initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm // 初始化组件实例关系属性,比方 $parent、$children、$root、$refs 等 initLifecycle(vm) /** * 初始化自定义事件,这里须要留神一点,所以咱们在 <comp @click="handleClick" /> 上注册的事件,监听者不是父组件, * 而是子组件自身,也就是说事件的派发和监听者都是子组件自身,和父组件无关 */ initEvents(vm) // 解析组件的插槽信息,失去 vm.$slot,解决渲染函数,失去 vm.$createElement 办法,即 h 函数 initRender(vm) // 调用 beforeCreate 钩子函数 callHook(vm, 'beforeCreate') // 初始化组件的 inject 配置项,失去 result[key] = val 模式的配置对象,而后对后果数据进行响应式解决,并代理每个 key 到 vm 实例 initInjections(vm) // resolve injections before data/props // 数据响应式的重点,解决 props、methods、data、computed、watch initState(vm) // 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上 initProvide(vm) // resolve provide after data/props // 调用 created 钩子函数 callHook(vm, 'created') // 如果发现配置项上有 el 选项,则主动调用 $mount 办法,也就是说有了 el 选项,就不须要再手动调用 $mount,反之,没有 el 则必须手动调用 $mount if (vm.$options.el) { // 调用 $mount 办法,进入挂载阶段 vm.$mount(vm.$options.el) } }}resolveConstructorOptions/src/core/instance/init.js/** * 从组件构造函数中解析配置对象 options,并合并基类选项 * @param {*} Ctor * @returns */export function resolveConstructorOptions (Ctor: Class<Component>) { // 配置我的项目 let options = Ctor.options if (Ctor.super) { // 存在基类,递归解析基类构造函数的选项 const superOptions = resolveConstructorOptions(Ctor.super) const cachedSuperOptions = Ctor.superOptions if (superOptions !== cachedSuperOptions) { // 阐明基类构造函数选项曾经产生扭转,须要从新设置 Ctor.superOptions = superOptions // 查看 Ctor.options 上是否有任何前期批改/附加的选项(#4976) const modifiedOptions = resolveModifiedOptions(Ctor) // 如果存在被批改或减少的选项,则合并两个选项 if (modifiedOptions) { extend(Ctor.extendOptions, modifiedOptions) } // 选项合并,将合并后果赋值为 Ctor.options options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions) if (options.name) { options.components[options.name] = Ctor } } } return options}resolveModifiedOptions/src/core/instance/init.js/** * 解析构造函数选项中后续被批改或者减少的选项 */function resolveModifiedOptions (Ctor: Class<Component>): ?Object { let modified // 构造函数选项 const latest = Ctor.options // 密封的构造函数选项,备份 const sealed = Ctor.sealedOptions // 比照两个选项,记录不统一的选项 for (const key in latest) { if (latest[key] !== sealed[key]) { if (!modified) modified = {} modified[key] = latest[key] } } return modified}mergeOptions/src/core/util/options.js/** * 合并两个选项,呈现雷同配置项时,子选项会笼罩父选项的配置 */export function mergeOptions ( parent: Object, child: Object, vm?: Component): Object { if (process.env.NODE_ENV !== 'production') { checkComponents(child) } if (typeof child === 'function') { child = child.options } // 标准化 props、inject、directive 选项,不便后续程序的解决 normalizeProps(child, vm) normalizeInject(child, vm) normalizeDirectives(child) // 解决原始 child 对象上的 extends 和 mixins,别离执行 mergeOptions,将这些继承而来的选项合并到 parent // mergeOptions 解决过的对象会含有 _base 属性 if (!child._base) { if (child.extends) { parent = mergeOptions(parent, child.extends, vm) } if (child.mixins) { for (let i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm) } } } const options = {} let key // 遍历 父选项 for (key in parent) { mergeField(key) } // 遍历 子选项,如果父选项不存在该配置,则合并,否则跳过,因为父子领有同一个属性的状况在下面解决父选项时曾经解决过了,用的子选项的值 for (key in child) { if (!hasOwn(parent, key)) { mergeField(key) } } // 合并选项,childVal 优先级高于 parentVal function mergeField (key) { // strats = Object.create(null) const strat = strats[key] || defaultStrat // 值为如果 childVal 存在则优先应用 childVal,否则应用 parentVal options[key] = strat(parent[key], child[key], vm, key) } return options}initInjections/src/core/instance/inject.js/** * 初始化 inject 配置项 * 1、失去 result[key] = val * 2、对后果数据进行响应式解决,代理每个 key 到 vm 实例 */export function initInjections (vm: Component) { // 解析 inject 配置项,而后从祖代组件的配置中找到 配置项中每一个 key 对应的 val,最初失去 result[key] = val 的后果 const result = resolveInject(vm.$options.inject, vm) // 对 result 做 数据响应式解决,也有代理 inject 配置中每个 key 到 vm 实例的作用。 // 不不倡议在子组件去更改这些数据,因为一旦祖代组件中 注入的 provide 产生更改,你在组件中做的更改就会被笼罩 if (result) { toggleObserving(false) Object.keys(result).forEach(key => { /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { defineReactive(vm, key, result[key], () => { warn( `Avoid mutating an injected value directly since the changes will be ` + `overwritten whenever the provided component re-renders. ` + `injection being mutated: "${key}"`, vm ) }) } else { defineReactive(vm, key, result[key]) } }) toggleObserving(true) }}resolveInject/src/core/instance/inject.js/** * 解析 inject 配置项,从祖代组件的 provide 配置中找到 key 对应的值,否则用 默认值,最初失去 result[key] = val * inject 对象必定是以下这个构造,因为在 合并 选项时对组件配置对象做了标准化解决 * @param {*} inject = { * key: { * from: provideKey, * default: xx * } * } */export function resolveInject (inject: any, vm: Component): ?Object { if (inject) { // inject is :any because flow is not smart enough to figure out cached const result = Object.create(null) // inject 配置项的所有的 key const keys = hasSymbol ? Reflect.ownKeys(inject) : Object.keys(inject) // 遍历 key for (let i = 0; i < keys.length; i++) { const key = keys[i] // 跳过 __ob__ 对象 // #6574 in case the inject object is observed... if (key === '__ob__') continue // 拿到 provide 中对应的 key const provideKey = inject[key].from let source = vm // 遍历所有的祖代组件,直到 根组件,找到 provide 中对应 key 的值,最初失去 result[key] = provide[provideKey] while (source) { if (source._provided && hasOwn(source._provided, provideKey)) { result[key] = source._provided[provideKey] break } source = source.$parent } // 如果上一个循环未找到,则采纳 inject[key].default,如果没有设置 default 值,则抛出谬误 if (!source) { if ('default' in inject[key]) { const provideDefault = inject[key].default result[key] = typeof provideDefault === 'function' ? provideDefault.call(vm) : provideDefault } else if (process.env.NODE_ENV !== 'production') { warn(`Injection "${key}" not found`, vm) } } } return result }}initProvide/src/core/instance/inject.js/** * 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上 */export function initProvide (vm: Component) { const provide = vm.$options.provide if (provide) { vm._provided = typeof provide === 'function' ? provide.call(vm) : provide }}总结Vue 的初始化过程(new Vue(options))都做了什么? ...

February 22, 2022 · 5 min · jiezi

关于vue.js:vue的生命周期的详细图解

关键词:前端培训

February 21, 2022 · 1 min · jiezi

关于vue.js:Vue-源码解读1-前言

当学习成为了习惯,常识也就变成了常识。 感激各位的 点赞、珍藏和评论。 新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。 简介专栏的第一篇,次要介绍专栏的目标、布局、适用人群,以及筹备工作和扫盲的基础知识。 前言最近在筹备一些 Vue 系列的文章和视频,之前 Vue 的源码也读过好几遍,然而始终没写相干的文章,所以最近就打算写一写。 指标精通 Vue 技术栈的源码原理,这是这系列的文章最终目标。 首先会从 Vue 源码解读开,会产出一系列的文章和视频,从具体刨析源码,再到 手写 Vue 1.0 和 Vue 2.0。之后会产出周边生态相干库的源码剖析和手写系列,比方:vuex、vue-router、vue-cli 等。 置信通过这一系列的认真学习,大家都能够在本人的简历上写上这么一条:精通 Vue 技术栈的源码原理。 适宜人群纯熟应用 Vue 技术栈进行日常开发(增删改查)想深刻理解框架实现原理想跳槽 或 跟老板提涨薪的同学(增删改查不值钱)如何学习对于系列文章,程序学习天然最好,但如果你自身对源码有一些理解或者对某一部分特地感兴趣,也能够间接看相应对应的文章。 很多人习惯利用碎片化工夫去学习,对于快餐类的文章当然没有问题,然而如果你想深刻学习,还是倡议坐在电脑前用整块的工夫对照着文章亲自动手去学。 记住:光看不练假把式,所以在学习过程中肯定要勤入手,不动笔墨不读书,像笔记、思维导图、示例代码、为源码编写正文、debug 调试等,该上就上,相对不能偷懒。 如果你感觉该系列文章对你有帮忙,欢送大家 点赞、关注,也欢送将它分享给你身边的小伙伴。 筹备当初最新的 Vue 2 的版本号是 2.6.12,所以我就以以后版本的代码进行剖析和学习。 下载 Vue 源码git 命令git clone https://github.com/vuejs/vue.git去 github 手动下载而后解压装包执行 npm i 装置依赖,待装到端到端测试工具时可间接 ctrl + c 掉,不影响后续源码的研读。 source map在 package.json -> scripts 中的 dev 命令中增加 --sourcemap,这样就能够在浏览器中调试源码时查看以后代码在源码中的地位。 ...

February 21, 2022 · 2 min · jiezi

关于vue.js:Vue-30-UI-库-Naive-的使用

Naive 间接引入 的应用1. 惯例引入<template> <n-button>naive-ui</n-button></template><script> import { defineComponent } from 'vue' import { NButton } from 'naive-ui' export default defineComponent({ components: { NButton } })</script>2. 应用 setup script<template> <n-button>naive-ui</n-button></template><script> import { defineComponent } from 'vue' import { NButton } from 'naive-ui' export default defineComponent({ components: { NButton } })</script>

February 19, 2022 · 1 min · jiezi

关于vue.js:vue路由懒加载

1.开发环境 vue2.电脑系统 windows10专业版3.为什么须要应用懒加载? 在开发的过程中随着我的项目的业务越来越多,需要越来越多,体积越来越来,像vue这种单页面利用,如果没有应用懒加载,运行打包之后的文件会很大,进入首页时,须要加载内容很多,工夫过长,可能会呈现短暂白屏的状况,即便做了loading也不利于用户体验,而使用懒加载能够将页面进行划分,须要的时候再加载页面。上面我来分享三种懒加载的应用办法。4.办法一:vue异步组件技术(异步加载) vue-royer配置路由,应用vue的异步组件技术,能够实现按需加载,然而这种状况下,一个组件生成一个js文件{ path: '/my', name: 'my', component: resolve => require(['../views/my.vue'], resolve) }5.办法二:组件懒加载(应用import) const 组件名=()=>import('组件门路');//没有指定 webpackChunkName,每个组件打包成一个js文件const Home=()=>import('../views/home.vue')//指定了雷同的webpackChunkName,会合并打包成一个js文件//按组件划分const Home=()=>import(/* webpackChunkName: "home" */ '../views/home.vue')const About=()=>import(/* webpackChunkName: "home" */ '../views/home.vue')const My=()=>import(/* webpackChunkName: "home" */ '../views/home.vue'){ path: '/about', component: About }, { path: '/my', component: My }, { path: '/home', component: Home }6.办法三:webpack提供的require.ensure() vue-router配置路由,应用webpack的require.ensure技术,也能够实现按需加载。这种状况下,多个路由指定雷同的chunkName,会合并打包成一个js文件{ path: '/home', name: 'home', component: r => require.ensure([], () => r(require('../views/home.vue')), 'home') },{ path: '/index', name: 'Index', component: r => require.ensure([], () => r(require('../views/index.vue')), 'home') },{ path: '/about', name: 'about', component: r => require.ensure([], () => r(require('@/views/about.vue')), 'about') }// r就是resolveconst list = r => require.ensure([], () => r(require('../views/list.vue')), 'list');//这种是官网举荐的写的 按模块划分懒加载 ...

February 19, 2022 · 1 min · jiezi

关于vue.js:vue调用支付宝支付接口返回form

1.开发环境 vue2.电脑系统 windows10专业版3.在开发的过程中,咱们在领取的时候会抉择领取形式,上面我来分享一下调用后端接口(后端接口支付宝的领取接口)返回form,前端怎么解决呢?办法如下。4-1:在template中增加如下代码: <div class="Graphicdetailsfooter" v-html="this.ConfirmOrderobj.alipayPayObj.cont">{{ this.ConfirmOrderobj.alipayPayObj.cont }}</div>4-2.在抉择领取形式的办法中,增加如下代码: const div = document.createElement("divform"); div.innerHTML = this.ConfirmOrderobj.alipayPayObj.cont; document.body.appendChild(div); document.forms[0].acceptCharset = "GBK"; // 放弃与支付宝默认编码格局统一,如果不统一将会呈现:调试谬误,请回到申请起源地,从新发动申请,错误代码 invalid-signature 谬误起因: 验签出错,倡议查看签名字符串或签名私钥与利用公钥是否匹配 document.forms[0].submit();//留神:this.ConfirmOrderobj.alipayPayObj.cont 就是后端返回的支付宝的form5.成果如下:6.本期的分享到了这里就完结啦,心愿对你有所帮忙,让咱们一起致力走向巅峰。

February 19, 2022 · 1 min · jiezi

关于vue.js:vue插件总结

在开发的过程中,咱们常常会应用到很多的插件,上面我来分享一下我应用到的和我发现的前端一些插件,心愿对你有所帮忙1.UI组件库: 1.element饿了么出品的vue2的web ui框架2.mint-ui vue2的挪动ui元素3.iview 基于vuejs的开源ui组件库4.Keen-ui 轻量级的根本UI组件合集5.vue-material通过vue material和Vue2建设精美的app利用6.muse-ui三端款式统一的响应式UI库7.vuetify为挪动端而生vue2组件框架8.vonic疾速构建挪动端单页利用9.vue-blu帮忙你轻松创立web利用10.vue-multiselect vue抉择框解决方案11.vueCircleMenu丑陋的vue圆环菜单12.vue-chat vue和vuex及wenpack的聊天示例13.radio-ui疾速开发产品的vue组件库14.vue-waterfall vue的瀑布流布局插件15.vue-carbon 基于vue开发MD格调的挪动端库16.vue-beauty 由vue和ant design 创立的柔美UI组件17.bootstrap-vue 利用于vue2的Twitter的Bootstrap 4 组件18.vueAdmin 基于vue2和element的简略的管理员模板19.vue-ztree 用vue写的树层级组件20.vue-tree vue树视图组件21.vue-tabs多tab页轻型框架2.slider: 1.vue-awesome-swiper vue触摸滑动组件2.vue-slick 实现晦涩轮播框的 vue组件3.vue-swipe vue触摸滑块4.vue-swiper 易于应用的滑块组件5.vue-images 显示一组图片的lightbox组件6.vue-carousel-3d vue的3d轮播组件7.vue-slide vue轻量级滑动组件8.vue-slider vue滑动组件9.vue-m-carousel vue挪动端轮播组件库10.dd-vue-component 订单来了的公共组件库11.vue-easy-slider vue的滑块组件3.图表: 1.vue-table 简化数据表格2.vue-chartjs vue中的Charts的封装3.vue-charts 轻松渲染一个图表4.vue-chart 弱小的高速的vue图表解析5.vue-highcharts HighCharts组件6.charts vue Bulma的chartjs组件7.vue-chartkik vue一行代码实现柔美图表4.日历: 1.vue-calendar 琪琪抉择插件2.vue-datepicker 日期和工夫抉择组件3.vue-datetime-picker 日期工夫抉择控件4.vue2-calendar 反对lunar和日期事件的日期选择器5.vue-fullcalendar 基于vue的全日历组件6.vue-datapicker 丑陋的vue日期选择器组件7.datepicker 基于flatpickr的工夫抉择组件8.vue-timepicker 下拉工夫选择器9.vue-data-picker vue日期选择器组件10.vue-datepicker-simple 基于vue的日期选择器5.地址抉择: 1.vue-city 城市选择器2.vue-region-picker 抉择中国的省份市和地区6.地图: 1.vue-amap 基于vue2和高德地图的地图组件2.vue-google-maps 带有双向数据绑定 Google 地图组件3.vue-baidu-map基于vue的百度地图组件库4.vue-cmap Vue China map 可视化组件7.播放器: 1.vue-video-player Vue视频及直播播放器2.vue-video Vue的HTML5视频播放器3.vue-music-master vue手机端网页音乐播放器8.滚动scroll: ...

February 19, 2022 · 1 min · jiezi

关于vue.js:vue3全局挂载和使用

1.开发环境 vue3.02.电脑系统 windows10专业版3.在应用vue开发的过程中,咱们会有一些专用的属性和办法,咱们个别为了方便使用会这个属性和办法挂载到全局,上面我来分享一下4.vue2挂载办法 Vue.prototype.$http = http//在对应的组件中应用this.$http//这种写法置信小火们很相熟了,那么在vue3中怎么写呢?4-1.vue3挂载并应用 // 全局挂载const app = createApp(App)app.config.globalProperties.$Methods = Methods;//在对应的组件中应用import { defineComponent, ref, getCurrentInstance, onMounted, reactive,} from "vue";//因为vue3是组合API,所以要引入对应的(getCurrentInstance)// setup//一个json数组去重const { proxy }: any = getCurrentInstance();//要害代码 const $Methods = proxy.$Methods;//要害代码 const jsonarrreduce = reactive([ { id: "1", name: "李白" }, { id: "2", name: "杜甫" }, { id: "3", name: "白居易" }, { id: "4", name: "项羽" }, { id: "5", name: "小米" }, { id: "1", name: "红米" }, { id: "1", name: "诺基亚" }, { id: "2", name: "真我" }, ]); onMounted(() => { console.log($Methods.JsonArrReduce(jsonarrreduce, "id")); });5.本期的分享到了这里就完结啦,心愿对你有所帮忙,让咱们一起致力走向巅峰。 ...

February 19, 2022 · 1 min · jiezi

关于vue.js:vue创建项目使用yarn包管理工具

1.开发环境 vue2.电脑系统 windows10专业版3.在应用vue开发的过程中,咱们在创立我的项目的过程中,咱们之前抉择的都是npm包,然而咱们之后创立我的项目想要应用yarn包管理工具,怎么做呢?4.装置yarn npm install -g yarn5.在C盘找到 //删除选中的文件删除之后,再次执行 vue create xxx,会提醒你抉择对应的包管理工具5-1.对于.vuerc6.扩大:如果你刚装置node,之后想要yarn进行治理,怎么搞呢? 首先全局装置 yarn npm install -g yarn6-1.配置yarn全局 //新建 yarn_global文件夹yarn config set global-folder "D:\node\node-v16.10.0-win-x64\yarn_global"yarn config set cache-folder "D:\node\node-v16.10.0-win-x64\yarn_cache"6-2.yarn常用命令 yarn global add @vue/cli 全局装置vuecliyarn add axios 装置axios到生产环境yarn add ant-design-vue -D 装置ant-design-vue到开发环境yarn remove element-ui 卸载element-ui6-3.yarn配置环境变量 找到yarn全局装置的目录:D:\node\node-v16.10.0-win-x64\yarn_global\node_modules\.bin //把D:\node\node-v16.10.0-win-x64\yarn_global\node_modules\.bin配置为环境变量就能够了7.本期的分享到了这里就完结啦,心愿对你有所帮忙,让咱们一起致力走向巅峰。

February 19, 2022 · 1 min · jiezi

关于vue.js:vueelement上传文件图片使用和解析后端返回二进制文件图片视频

1.开发环境 vue2.电脑系统 windows10专业版3.在开发的过程中,咱们常常会应用到element进行文件的上传和二进制文件的解析,上面我来分享一下。4.element图片上传,代码如下: // template中代码如下:<el-upload list-type="picture-card" action="" :limit="1" :auto-upload="false" //要害 代码 :on-change="onchangeMainpicture" //文件上传时触发函数 > <i slot="default" class="el-icon-plus"></i> <div slot="file" slot-scope="{ file }"> <img class="el-upload-list__item-thumbnail" :src="file.url" alt="" /> <span class="el-upload-list__item-actions"> <span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)" > <i class="el-icon-zoom-in"></i> </span> <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleDownload(file)"> <i class="el-icon-download"></i> </span> <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleRemove(file)"> <i class="el-icon-delete"></i> </span></span> </div></el-upload>return 中代码如下:uploadimgurl: { // 主图 Mainpictureurl: "", // 主题内容 Mainimagecontenturl: "", // 视频 videourl: "",},// methods中增加如下代码onchangeMainpicture(file, fileList) { console.log(file); this.uploadimgurl.Mainpictureurl = file.raw;},5.视频上传 // template代码<el-upload list-type="picture-card" :on-change="onchangevideo" action="" :limit="1" :auto-upload="false"> <i slot="default" class="el-icon-plus"></i> <div slot="file" slot-scope="{ file }"> <video :src="file.url" class="avatar" controls="controls"> </video> </div></el-upload>// return 代码如上// methods办法 如下:onchangevideo(file, fileList) { // console.log(event); // console.log(file); // console.log(fileList); this.uploadimgurl.videourl = file.raw;},6.提交代码 ...

February 19, 2022 · 3 min · jiezi

关于vue.js:vue使用antdesignvue中TreeSelect树选择异步加载数据

1.开发环境 vue2.电脑系统 windows10专业版3.在应用ant-design-vue开发的过程中,咱们在应用树形抉择的时候,因为数据量过大,咱们个别会点击父节点来申请子节点的数据,上面我来分享一下办法.4.废话不多说,间接上代码: <a-tree-select v-model="ruleForm.StoreCategoryobj.value2" tree-data-simple-mode style="width: 100%" :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" :tree-data="ruleForm.StoreCategoryobj.options" placeholder="Please select" :load-data="StoreCategoryload" //异步加载数据的办法 @focus="StoreCategoryobjfocus" // 获取焦点的办法 :showSearch="true" //显示搜寻框 />StoreCategoryobj: { value2: "", options: [ { id: 1, pId: 0, value: "1", title: "Expand to load" }, { id: 2, pId: 0, value: "2", title: "Expand to load" }, { id: 3, pId: 0, value: "3", title: "Tree Node", isLeaf: true }, ], id: "",},5.在methods中增加如下代码: //获取焦点的时候,把绑定的值赋值为后端获取的数据StoreCategoryobjfocus() { console.log("获取焦点事件"); treetable({ pid: this.account.rootTypeId, storeId: this.account.storeId, }).then((res) => { console.log("我是树形构造的表格接口"); console.log(res); console.log(this.$Cmethods.treelist(res.result)); this.ruleForm.StoreCategoryobj.options = this.$Cmethods.treelist( res.result ); // this.ruleForm.StoreCategoryobj.options = this.$Cmethods.treelist( // res.result // ); // console.log(this.$Cmethods.treelist(res.result)); // this.ruleForm.StoreCategoryobj.options = this.$Cmethods.treelist( // res.result // ); });// 点击父节点的时候,申请子节点的数据StoreCategoryload(treeNode) { console.log(treeNode); console.log(treeNode.dataRef); return treetable({ pid: treeNode.dataRef.id, storeId: this.account.storeId, }).then((res) => { console.log("我是树形构造的表格接口"); console.log(res); console.log(res.result); console.log(this.$Cmethods.treelist(res.result)); console.log(this.ruleForm.StoreCategoryobj.options); treeNode.dataRef.children = this.$Cmethods.treelist(res.result); //把获取的子节点的数据,赋值为父节点中children });},6.全局树形过滤办法: ...

February 19, 2022 · 1 min · jiezi

关于vue.js:vueelement中table树形结构懒加载

1.开发环境 vue+element2.电脑系统 windows10专业版3.在开发的过程中,咱们会遇到树形构造的表格,因为数据量十分的多,所以咱们须要点击父元素把对应的id传给后端,那到子元素的数据,造成树形,上面我来分享一下如何实现。4.废话不多说,间接上效果图:5.第一次申请后端返回的数据结构: // isParent :是否有子节点6.需要: // 点击父节点,把父节点的pid传给后端,后端返回子节点的数据7.实现办法,应用element的table的懒加载解决,代码如下: <el-table ref="multipleTable" :data="tableData" tooltip-effect="dark" style="width: 100%" @selection-change="handleSelectionChange" border :tree-props="{ children: 'children', hasChildren: 'isParent' }" //要害代码 row-key="id" lazy :load="load" //要害代码 > <el-table-column type="selection" width="55" fixed align="center"></el-table-column> <el-table-column prop="name" :label="$t('table.level')" align="center"></el-table-column> <el-table-column prop="isLeaf" :label="$t('table.upperlevel')" align="center" > <template slot-scope="scope"> <p v-if="!scope.row.isLeaf">111</p> </template> </el-table-column> <el-table-column prop="Upperlevelpro" :label="$t('table.Upperlevelpro')" align="center" ></el-table-column> <el-table-column prop="show" :label="$t('table.Whetherenable')" align="center" > <template slot-scope="scope"> <el-switch v-model="scope.row.show" active-color="#13ce66" inactive-color="#ff4949" active-value="0" inactive-value="1" ></el-switch> <!-- <el-button size="mini" @click="handleEdit(scope)">Edit</el-button> --> </template> </el-table-column> <el-table-column prop="Sort" :label="$t('table.Sort')" align="center"></el-table-column> <el-table-column prop="picture" :label="$t('table.picture')" align="center" > <template slot-scope="scope"> <img :src="scope.row.picture" alt="" class="picture" /> <!-- <el-button size="mini" @click="handleEdit(scope)">Edit</el-button> --> </template> </el-table-column> <el-table-column prop="name" :label="$t('table.operate')" width="220" align="center" > <template slot-scope="scope"> <el-button size="mini" @click="tableadd(scope)">{{ $t("tablebtn.add") }}</el-button> <el-button size="mini" @click="handleEdit(scope)">{{ $t("tablebtn.edit") }}</el-button> <el-button size="mini" type="danger" @click="handleDelete(scope)">{{ $t("tablebtn.delete") }}</el-button> </template> </el-table-column></el-table>load(row, treeNode, resolve) { console.log(row); treetable({ pid: row.id, storeId: this.account.storeId, }).then((res) => { console.log("我是树形构造的表格接口"); console.log(res); resolve(res.result); //应用懒加载的resolve办法 });},8.本期的分享到了这里就完结啦,心愿对你有所帮忙,让咱们一起致力走向巅峰。 ...

February 19, 2022 · 1 min · jiezi

关于vue.js:vue使用tinymce和tinymcevue实现富文本

1.开发环境 vue2.电脑系统 windows10专业版3.在开发的过程中,咱们常常会应用到富文本性能,上面我来分享一下。4.富文本比拟: UEditor:百度前端的开源我的项目,功能强大,基于 jQuery,但曾经没有再保护,而且限定了后端代码,批改起来比拟吃力bootstrap-wysiwyg:微型,易用,小而美,只是 Bootstrap + jQuery...kindEditor:功能强大,代码简洁,须要配置后盾,而且好久没见更新了wangEditor:轻量、简洁、易用,然而降级到 3.x 之后,不便于定制化开发。不过作者很怠惰,狭义上和我是一家人,打个callquill:自身性能不多,不过能够自行扩大,api 也很好懂,如果能看懂英文的话...summernote:没深入研究,UI挺丑陋,也是一款小而美的编辑器,可是我须要大的在有这么参考的状况下,我最终还是抉择了 tinymce 1. GitHub 上星星很多,性能也齐全;2. 惟一一个从 word 粘贴过去还能放弃绝大部分格局的编辑器;3. 不须要找后端人员扫码改接口,前后端拆散;5.我应用的版本6.Vue引入TinyMCE富文本组件时遇到的问题 版本兼容问题因为 @tinymce/tinymce-vue@4.0.0 仅针对Vue@3.x版本,须要降级。7.装置 npm install @tinymce/tinymce-vue@3 -Dnpm install tinymce@5.7.0 -D8.去官网下载语言包: (https://www.tiny.cloud/get-tiny/language-packages/)https://www.tiny.cloud/get-ti...[语言包]9.下载语言包之后,复制到我的项目中,我放在了assets上面:10.在对应的.vue文件中应用 //导入// 导入富文本import tinymce from "tinymce/tinymce";import Editor from "@tinymce/tinymce-vue";import "tinymce/skins/ui/oxide/skin.min.css"; //富文本款式import "tinymce/icons/default"; //富文本款式// 配置富文本import "tinymce/themes/silver/theme.min.js"; //引入富文本的次要脚本//注册components: { Editor },// template<editor id="tinymce" v-model="tinymceHtml" :init="einit"></editor><div v-html="tinymceHtml"></div>// return tinymceHtml: "请输出内容", einit: { skin_url: require("@/assets/skins/ui/oxide/skin.min.css"), language_url: require("@/assets/langs/zh_CN.js"), language: "zh_CN", height: 300, },// mountedtinymce.init({});10-1.全局注册,在mian.js中增加如下代码: import tinymce from "tinymce/tinymce";import Editor from "@tinymce/tinymce-vue";import "tinymce/icons/default"; import "tinymce/themes/silver/theme.min.js";Vue.component('editor', Editor); //注册全局组件10-2.引入注意事项: ...

February 19, 2022 · 1 min · jiezi

关于vue.js:vue使用libflexible和postcsspxtorempx转rem进行适配

1.开发环境 vue2.电脑系统 windows10专业版3.在开发的过程中,咱们须要做适配,上面我来分享一下办法,心愿对你有所帮忙。4.废话不多说,间接上操作: //装置 lib-flexiblenpm install lib-flexible --save-dev5.批改lib-flexible配置 // 文件在node_modules>lib-flexible>flexible5-1.在mian.ts中引入lib-flexible // 导入 lib-flexibleimport "lib-flexible";6.装置postcss-pxtorem(px转rem) npm install postcss-pxtorem -D6-1.在根目录下新建.postcssrc.js(和package同级) module.exports = { "plugins": { "autoprefixer": {}, 'postcss-pxtorem': { rootValue: 19.2, // 75示意750设计稿,37.5示意375设计稿 propList: ['*'] } }}6-2.你可能会遇到以下报错6-3.剖析起因 //最高版本是6,找不到8的版本6-4.解决办法如下: npm install postcss-pxtorem@5 -D7.本期的分享到了这里就完结啦,心愿对你有所帮忙,让咱们一起致力走向巅峰。

February 19, 2022 · 1 min · jiezi

关于vue.js:vue使用rem适配

1.开发环境 vue2.电脑系统 windows10专业版3.在应用vue开发挪动端的过程中,咱们会因为兼容性而头疼,上面我来分享分享上面vue应用rem自适配,心愿对你有所帮忙。4.废话不多说,间接上操作: //装置 postcss-pxtoremnpm i postcss-pxtorem -S5.在src目录新建rem文件夹,上面新建rem.js,增加如下代码: //基准大小const baseSize = 37.5// 设置 rem 函数function setRem() { const salepro = document.documentElement.clientWidth / 750 // 以后页面宽度绝对于 750 宽的缩放比例,可依据本人须要批改. // 设置页面根节点字体大小 document.documentElement.style.fontSize = (baseSize * Math.min(salepro, 2)) + 'px'}// 初始化setRem()// 扭转窗口大小时从新设置 remwindow.onresize = function () { setRem()}6.在我的项目根目录新建 .postcssrc.js,增加代码如下: module.exports = { "plugins": { "postcss-pxtorem": { "rootValue": 37.5, "propList": ["*"] } }}留神:我在配置中,比例是1:1,也就是设计图宽是750px,你在css中间接写width:750px;就能够啦,不必进行换算,是不是很棒。7.在main.js中引入 import '@/rem/rem.js'8.在vue模板中应用,css中增加如下代码: <style lang="scss" scoped>.about { width: 750px; height: 100vh; box-sizing: border-box; background-color: blue !important; .kk { width: 350px; height: 350px; background-color: red; }}</style>9.效果图如下:10.本期的分享到了这里就完结啦,心愿对你有所帮忙,让咱们一起致力走向巅峰。 ...

February 19, 2022 · 1 min · jiezi

关于vue.js:前端开发常用单位

1.开发环境 vue+vant2.电脑系统 windows10专业版3.在开发的过程中,咱们常常会应用到一些单位,比方px,em,rem,vw,vh上面我来分享他们之间的区别,心愿对你有所帮忙。4.相对单位------px 1.什么是像素(Pixel)? 在前端开发中视口的程度方向和垂直方向是由很多小方格组成的, 一个小方格就是一个像素。 例如div尺寸是100 x 100, 那么程度方向就占用100个小方格, 垂直方向就占用100个小方格 2.像素特点 不会随着视口大小的变动而变动, 像素是一个固定的单位(相对单位)5.绝对单位------百分比 1.什么是百分比? 百分比是前端开发中的一个动静单位, 永远都是以以后元素的父元素作为参考进行计算 例如父元素宽高都是200px, 设置子元素宽高是50%, 那么子元素宽高就是100px。 2.百分比特点 2.1子元素宽度是参考父元素宽度计算的 2.2子元素高度是参考父元素高度计算的 2.3子元素padding无论是程度还是垂直方向都是参考父元素宽度计算的 2.4子元素margin无论是程度还是垂直方向都是参考父元素宽度计算的 2.5不能用百分比设置元素的border论断: 百分比是一个动静的单位, 会随着父元素宽高的变动而变动(绝对单位)6.绝对单位--------em 1.什么是em? em是前端开发中的一个动静单位, 是一个绝对于元素字体大小的单位 例如font-size: 12px; ,那么1em就等于12px 2.em特点 2.1以后元素设置了字体大小, 那么就绝对于以后元素的字体大小 2.2以后元素没有设置字体大小, 那么就相当于第一个设置字体大小的先人元素的字体大小 2.3如果以后元素和所有先人元素都没有设置大小, 那么就相当于浏览器默认的字体大小论断: em是一个动静的单位, 会随着参考元素字体大小的变动而变动(绝对单位)7.绝对单位--------rem 1.什么是rem? rem就是root em, 和em是前端开发中的一个动静单位, rem和em的区别在于, rem是一个绝对于根元素字体大小的单位 例如 根元素(html) font-size: 12px; ,那么1em就等于12px 2.rem特点: 2.1除了根元素以外, 其它先人元素的字体大小不会影响rem尺寸 2.2如果根元素设置了字体大小, 那么就绝对于根元素的字体大小 2.3如果根元素没有设置字体大小, 那么就绝对于浏览器默认的字体大小论断: rem是一个动静的单位, 会随着根元素字体大小的变动而变动(绝对单位)8.绝对单位--------vw/vh 1.什么是vw(Viewport Width)和vh(Viewport Height)? 1.1vw和vh是前端开发中的一个动静单位, 是一个绝对于网页视口的单位 1.2零碎会将视口的宽度和高度分为100份,1vw就占用视口宽度的百分之一, 1vh就占用视口高度的百分之一 1.3vw和vh和百分比不同的是, 百分比永远都是以父元素作为参考 而vw和vh永远都是以视口作为参考 论断: vw/vh是一个动静的单位, 会随着视口大小的变动而变动(绝对单位) 2.什么是vmin和vmax? vmin: vw和vh中较小的那个 vmax: vw和vh中较大的那个 应用场景: 保障挪动开发中屏幕旋转之后尺寸不变留神:如果是做挪动端开发,须要应用百分比,倡议应用vh。```9.本期的分享到了这里就完结啦,心愿对你有所帮忙,让咱们一起致力走向巅峰。

February 19, 2022 · 1 min · jiezi