@开篇
本文尽量以口语化的方式去写作,至于为啥,你懂的~ 🤪🤪🤪
@说一说你最近的项目;
- 记叙文的六要素:时间、人物、地点、起因、经过、结果;
- 时间:立项时间,研发周期;
- 人物:团队成员、分工、我负责哪几个模块;
- 起因:项目背景、项目的用户是谁、用户能用这个App干啥、基本业务逻辑描述;
- 经过:我负责哪几个模块,主用的技术栈是...,
- 结果:1.0耗时多久上线、目前处于何种状态;
- 项目亮点:最好有并且输出10分钟,例如组件封装、逻辑复用、性能优化、权限控制等;
@权限专题
@你的项目是怎么做的权限管理的?(背之~)
- 使用经典的 RBAC策略,也就是所谓的基于角色的权限控制策略去做;
- 分四个层面去做权限控制,分别是接口层、菜单层、路由层、组件层;
- 接口层主要通过axios的响应拦截器去做;
- 当用户访问一个未授权的服务端接口时,通常服务端会返回一个401或403的状态码,我们在axios响应拦截器中可以做相应处理,例如返回一个毁约态的Promise对象,或者直接帮应用程序跳转登录页或者未授权页等;
- 如果说想在前端直接做一些过滤,帮后台减轻一些访问负担的话,我们可以在axios的请求拦截器中判断当前用户是否有对当前接口的访问权限,如果没有也返回一个毁约态的Promise对象或者暴力跳转登录页或者未授权页也可以——最好还是交给应用层自己去跳转,或者至少以配置的方式告知axios该往哪里跳,以便视图层和数据层的解耦;
- 这是所谓接口层权限控制;
- 接下来是菜单层:当用户登录以后,可以由服务端返回一个对应的权限菜单,然后我们在主页中来动态渲染这个权限菜单;
- 我有封装过一个递归菜单组件,所以做起来还算比较方便!
- 如果用户在没有菜单项的情况下暴力输入路由地址的话,会直接跳转404页面,具体实现由路由层权限来加以控制;
- 路由权限在四层权限控制里相对复杂一些,可以分为全路由表策略和半路由表策略两种实现方式——名字是我自己起的,不好意思~
- 所谓全路由表策略,就是直接在前端挂载全部用户可能用到的路由,然后通过路由守卫去控制访问权限;
- 在Vue中实现起来很方便,因为Vue自带路由守卫机制,我们只需要在每个路由的声明里加一个meta信息,标记当前路由的授权访问角色列表就行了,没权限的时候一脚踹到登录页或者404页面都可以;
- React里的路由守卫需要自己封装,我的做法是基于
react-router-dom
的Route
组件做二次封装形成一个名为AuthedRoute
的组件,除了接收Route组件需要的path
和component
属性以外,还额外接收一个roles
(肉丝儿)属性用以声明当前路由的授权访问角色列表,再另外接收一个roleGetter
属性,值是一个函数,调用一下就能获取当前登录用户的角色,最终在AuthedRoute组件挂载的时候,调用roleGetter函数去获取当前登录用户的角色,然后跟roles肉丝儿列表进行比对,有访问权限就渲染跳转目标Route,没权限就渲染一个跳转404的Route; - 这是所谓全路由表策略;
- 另外一种就是所谓的半路由表策略,程序加载的一开始只挂载几个公共路由,登录页、关于页什么的;
- 用户登录成功以后,后台返回一个权限路由表的JSON数据,然后前端解析一下,根据解析结果把权限路由一个一个地
router.addRoute
进去,在路由表的最后挂载一个通配的404路由; - 这样当用户暴力在地址栏输入非法路由时,无法命中任何一个公共路由或权限路由,就会最终命中404!
- 这就是我所谓的半路由表策略;
- 最后是组件级别的权限控制,比如一个按钮什么的,最简单粗暴的做法就是使用条件渲染,if有权限就显示,否则就不显示;
- 如果要封装一下的话,我的实际操作是,在Vue中封装一个
v-auth
指令,放在要鉴权的组件身上,值是一个对象,内含roles
肉丝儿可访问的角色列表,外加一个roleGetter
函数,在组件或者指令挂载的时候调用roleGetter获取当前用户的登录角色与roles
肉丝儿进行比对,如果有权限就什么都不做,也就是正常渲染当前组件,否则就是没权限,直接el.remove()
把当前元素给删除,用户就什么都看不见了! - 在React当中我的做法是封装一个Auth包装器组件,将要鉴权的组件放在
Auth
组件的肚子里,Auth
组件同样接收roles
肉丝儿和roleGetter
两个props,权限匹配就正常返回chilren
这个JSX
,没权限就返回空气 / 幽灵标签,用户实际就什么都看不到了!
@封装专题
@组件封装整体思路(背之~)
- 以终为始,想想用户怎么用起来是最趁手的,我们的组件就怎么去设计;
- 无非管理好几样东西:
- 父组件需要告诉子组件一些什么信息,无非数据长啥样,配置长啥样;
- 子组件需要回传哪些信息给父组件,比如用户点击了确定,父组件我作为儿子我把这个情报告诉你了,具体怎么处置您看着办,例如用户在儿子身上点击了某个item,作为儿子的我把这个item的序号和负载的数据都告诉父亲大人您了,您看着处置吧——管理好回调和子父通信;
- 写在组件肚子里的模板/JSX,不管是叫插槽还是叫children,通常都是一部分需要特殊排版的DOM结构,组件拿过来要怎么渲染一下;
- 最后组件需要暴露哪些特定的API出来给父组件,以便父组件做整体的调度;
@万用表格封装思路(背之~)
- 以终为始,先构思好用户怎么用着爽,我的props和相应API就怎么去设计
- 就表格而言,用户需要传入数据
- 用一个props(formatter)描述一些特殊的字段要如何显示,例如头像avatar显示为一个a标签嵌套img来显示,再例如用户昵称也显示为一个a标签嵌套昵称的文本
- 用一个数组告诉表格哪些字段可以支持排序
- 这样表格逻辑大致就是:
- 首先使用列表渲染将data数组映射为一组div即表格行
- 行内再继续列遍历一个item的所有字段,一一映射为列div,也就是表格的单元格了
- 每个单元格内默认显示对应字段的值
- 但如果formatter/props告知这个字段要渲染为a标签或img或其它用户自定义的JSX形式,就按用户告知的来
- 哪些字段支持排序,就在点击对应的列表头时使用arr.sort对数据进行排序,然后直接定位回第一页
- 主要的state有dataArr为总数据,pageArr为当前页数据,currentPage即当前页码
- 使用useEffect监听currentPage的变化,然后从dataArr中截取对应页的数据去更新pageArr
- 表格本质显示的是pageArr
- 最后表格对外暴露出setCurrentPage/API,供外界去调用实现翻页,主要的调用者其实是分页器组件
@万用表单封装思路(背之~)
- 核心props肯定是表单要显示的数据对象了
- 表单标题、表单宽度、点击确定后的回调,也都由父组件以props形式注入
- 既然叫万用表单,言下之意就是用户可以自定以每个数据字段在表单中的展示和编辑方式
- 我是用一个名叫dataFrame也就是数据框架的props来接收用户对表单布局的描述
- 这个props的标准格式是一个二维数组
- 每个内层数组描述表单的一行
- 每个内层数组item描述一个单元格里要展示的表单控件
- 该item的核心属性包括label、type也就是何种控件(核心类型无非是text、password、select、textarea),如果type是select的话还需要额外带一个optionValues属性例如东南西北,核心属性还有key,代表当前表单控件显示formData的哪个key
- item的核心属性还有rowSpan,colSpan描述跨行跨列排版配置,根据这个二维数组的每个最内层item的描述,就可以绘制一个自定义排版的table表格,每个单元格内都是一组label+表单元素组合
- 当type是textarea时,展示控件为一个富文本编辑器
- 我的万用表单还支持type是form类型,也就是说该万能表单是支持递归的——万用表单的JSX里还有万用表单
- 如果父组件给一个editable=false,那么该表单仅仅只是一个数据展示组件,所有表单元素都有disabled属性,用户不可编辑,表单的最后一行也只有一个返回按钮
- 如果父组件给的editable=true,则传入的formData在表单内会形成一个state,各表单元素的onChange事件都会形成一次setState或setFormData
- 在可编辑模式下,当用户最终点击了确定按钮时,会回调父组件传入的onConfirm函数,将最新修改的表单数据state通知父组件
- 对于text、textarea和password组件,onChange事件是加过防抖优化的
- 所以最终我这个万用表单可以展示或编辑任意数据,并且以任意格式去排版,并且用户的最新修改会回调通知给父组件进行任意的后续操作
@递归菜单封装思路(背之~)
- 递归菜单组件接收的核心props就是一个menu数组
- 每个item代表一个菜单项,有icon,text,path也就是跳转路由地址
- 根据这个item的描述就可以轻而易举地绘制一个不可展开的基础菜单项,点击时跳转对应路由
- 如果这个item中有一个submenu属性,就意味着当前菜单项是可以展开的
- 就继续递归submenu中的二级item,渲染时缩进一个层级
- 以此类推,可以做出无限层级的递归菜单
- 说说icon解决方案:
- icon的值是个字符串,会渲染出一个MyIcon组件,接收一个name,然后从font-awesome经典图标库中检索出对应图标字体
- 再说说纵横折叠解决方案:
- 菜单接收一个singleExpand属性,默认为true,也就是说允许同时展开所有子菜单,设置为false时,每次只允许展开一个子菜单,其余自动折叠
- 具体实现方式为:用一个currentExpand/state来描述当前展开的是第几项,如果一级菜单项的index不为currentIndex时,则菜单收起
- 水平折叠方式为,宽度通过transition动画逐渐收缩为5rem,收缩过程中的overflow为hidden,收缩动画完成后切换到折叠模式,菜单项都变为各自的图标即可
@大文件上传组件封装思路(背之~)
- 组件布局一个type等于file的input元素,外加一个上传按钮就OK了;
- 当用户选择了文件以后,用一个state把用户选好的文件
e.target.files[0]
存起来; - 这个state是一个
File类型
的数据,本质就是一个字节数组,后续可以直接对它进行切片; - 选好文件,点击上传,就开始进入切片上传逻辑;
- 通过
file.slice
将文件切割为一堆分片/chunk
,分片的类型自然还是字节数组; - 将分片数组映射为Promise数组,使用axios向服务器接收地址发送POST请求,通过手动构造的
FormData
数据携带每个分片的字节数据和序号,此时所有分片已经开始了上传动作; - 通过
Promise.allSettled
以刚刚的Promise数组为入参,分别获得每个切片的上传结果; - 对于上传成功的分片,文件上传的整体进度+1,对于失败的分片进行重传;
- 服务端接收接口是父组件以props形式传入的;
- 服务端的操作无非就是逐个接收,最后再重新拼接为文件;
- 大致如此,难度还好~
@页头封装思路(等我写完你就背之~)
即将来袭
@分页器封装思路(等我写完你就背之~)
即将来袭
@筛选器封装思路(等我写完你就背之~)
即将来袭
@超级表格封装思路(等我写完你就背之~)
即将来袭
@弹窗封装思路(等我写完你就背之~)
即将来袭
@axios的封装思路;
参见 有没有封装过axios
@某hook的封装思路;
以【获取页面组件的实时滚动位置钩子useScroll】为例:
- 该组件能实时获取页面的滚动位置,即调用useScroll()会到响应式数据scrollTop;
- hook内要定义响应式输入const scrollTop = ref(0);
- 需要将真正在滚动的元素作为参数传递给该hook:const scrollTop = useScroll(scrollingElement)
- 组件挂载时对其根元素添加scroll事件,组件卸载时移除该DOM事件监听器,以避免内存泄露;
- 在scroll事件监听器中,实时获取根元素的scrollToP,同步给ref数据scrollTop:
const scrollHandler = (e) => {
scrollTop.value = root.scrollTop
}
- 最终调用该hook的组件得到的就是其根元素实时滚动的位置这一响应式数据,其余的什么都不用管;
@某指令的封装思路;
- v-change指令可以在元素被鼠标覆盖的时候改变样式,鼠标移出的时候恢复默认样式;
- 使用方式:
<button v-change="{color:'cyan'}">Click Me</button>
- 指令作用的目标元素所在的组件被挂载时,创建mouseover事件侦听,在其内部将应用binding.value定义的所有样式,在应用该样式之前,要先缓存在元素变化之前的样式在指令对象的defaultStyle中;
- 同时注册该元素的mouseout事件侦听,在其中恢复事先缓存好的该元素的默认样式;
- 指令作用的目标元素所在的组件被卸载时,移除该元素的mouseover与mouseout事件侦听;
@性能专题
@你的项目在性能方面有哪些优化措施?
参见 Vue性能优化
@如何量化分析性能问题(背之~)
- 用浏览器的Lighthouse跑分
- 根据Lightouse提示的
oppotunites(机会)
和diagnotics(诊断信息)
去进行针对性的优化,特别是它的estimated savings(预期节约时间)
特别多的点,去进行针对性的优化; - 说几个特别典型的场景:
- 例如他告诉说
remove unused javascript(去除未用的JS)
,就说明很多打包好的chunk是引而未用的,我们就要考虑是不是把包拆的更细致一点,是不是有些能异步加载的包或者组件可以从主包中分离一下; - 再比如提示
Minimize main-thread work / 最小化主线程工作
,说明主线程中做的运算量太大了,有些能分配给Worker线程
去做的工作就分配出去,等它做完了再回调结果给主线程就可以了; - 再比如提示
Serve static assets with an efficient cache policy(为静态资源提供缓存)
,可以在浏览器控制台里看看CacheControl响应头
、Etag响应头
、LastModified响应头
是不是都有,如果没有就需要和服务端联调一下,把这几个响应头配上,同时Nginx配置里也要同步开启这些设置; - 再比如提示
Reduce JavaScript execution time / 减少js的执行时间
,我们可能需要检查一下对应的代码包中是不是有死循环、大文件的读写操作等,死循环肯定是要绝对避免的,大文件读写肯定不能同步,得写成异步; - 再比如提示
Avoid enormous network payloads / 避免大量的网络负载
,说明网络耗时太久了,能不能把任务拆分一下,该缓存的地方是不是已经缓存过了,纯本地的(状态管理或LocalStorage)以及能走浏览器304的,都要检查一下; - 果然是巨量静态数据,可以考虑用
indexedDB
,我以前临时参与过一个出海的项目(类似非洲版微信),那些国际化中外文对照我就是缓存在indexedDB
里的; - 再比如提示
Eliminate render-blocking resources / 消除资源的渲染阻止
,说明外部link的css或者外部link的js太大太久,可能需要检查一下Webpack有没有开启JS压缩和CSS压缩,有些能内联或内嵌的样式可以考虑走内联或内嵌,不要直接在页面上引lodash/jquery这种大家伙,比如lodash基本上我个人除了防抖节流以外其它的用的不是特别多,防抖节流我都是写在自己的小轮子里; - 再比如
Image elements do not have explicit width and height / 图片没设置宽高
,浏览器解析的很辛苦,设一下就行了; - 在浏览器的 Network选项卡 或 Coverage选项卡 中查看当前页面各个包的实时加载情况,有时会发现加载chunk的实际体积比打包报告所描述的体积要大很多,可以看一下Webpack是不是开启了资源压缩,可以看一看Nginx的gzip压缩是不是开启了;
@主要的性能指标(背之~)
按优先级排序,以下是浏览器性能指标的详细解释:
- FCP(First Contentful Paint):FCP表示从页面加载开始到浏览器首次渲染第一块内容的时间点。它体现了页面加载速度,较快的FCP可以提供更快的反馈和更好的用户体验。
- LCP(Largest Contentful Paint):LCP衡量的是渲染页面上最大的可见内容所需的时间。它突出显示了页面关键内容的呈现速度,较快的LCP可以让用户迅速看到重要信息。
- FID(First Input Delay):FID衡量用户首次与页面进行交互时的延迟。它表示了页面对用户输入响应的速度,较低的FID意味着页面能够快速响应用户操作。
- TTFB(Time to First Byte):TTFB指从浏览器发出请求到接收到第一个字节的时间。它关注网络连接和服务器响应速度,较小的TTFB表示网络和服务器的性能更佳。
- TTI (Time to Interactive) :可交互时间指标用于标记应用已进行视觉渲染并能可靠响应用户输入的时间点
- TBT (Total Blocking Time) :页面阻塞总时长TBT汇总所有加载过程中阻塞用户操作的时长,在FCP和TTI之间任何long task中阻塞部分都会被汇总
- SI (Speed Index) :速度指数,指标用于显示页面可见部分的显示速度, 单位是时间
- CLS(Cumulative Layout Shift):CLS测量页面上发生的意外布局变化的总和。它考虑到页面稳定性,较小的CLS值意味着页面更稳定,减少了用户的混淆和误触。
这些指标可以帮助开发人员识别和解决浏览器性能问题,从而提升用户的浏览体验。优先关注FCP、LCP和FID这些直接影响用户感知的指标,然后考虑TTFB和CLS等其他指标来综合优化页面加载和交互性能。
@如何分析页面卡顿和白屏问题?(背之~)
- 性能问题,成因可能很多样,可以借助浏览器的
LightHouse+Coverage+Performance
三大面板联合排查; - 其中
LightHouse面板
直接帮我们性能跑分并给出优化建议; Coverage面板
让我们直观看到哪些chunk
的代码利用率较低;- 这类又大利用率又低的
chunk
通常是第三方包,典型的例如echarts
、各种组件库等; - 解决方案通常不外乎:分包分的细致一些、参考组件库的官方文档做按需加载而非完整加载、使用CDN方式在页面中引入第三方库;
- 具体的配置方式我记不清了,是有点麻烦的,但是我的笔记里有收藏;
- PS:实际请参考这篇文章:Web性能优化
Performance面板
可以帮助我们记录 页面加载全过程 中 各个时间节点下 各种资源、线程、任务 的实际开销,还能帮我们定位源码;- 用好这三个面板,基本上性能问题的排查就有 抓手 可用了!
@经验CHECK专题
@开发周期怎样?团队有几个人?
- 202205~202211共计6个月;项目组前3后6,自己任项目组前端组长/核心开发人员;
@讲一讲这个项目研发的全流程?
- 立项:产品经理产出
需求原型
(一套非常简陋的页面雏形+跳转关系,产品经理开发原型的工具:PC端Axure,手机端墨刀),美术产出页面效果图
; - 前后对接接口:前后端主程内部磋商,最后由服务端提供
接口文档
(doc),包含所有接口的:接口名 + 服务端路由地址 + 访问权限(角色数组) + 请求方法 + 请求参数(GET/DELETE的查询参数 + POST/PUT请求体与格式) + 返回数据格式(通常是JSON,通常有code + msg + data + err) - 前后分头开发:前端使用自建的mock数据和mock服务器;常见的mock解决方案:纯GET使用json-server,如果有POST/DELETE/PUT,最常用的是fast-mock,也可以使用自行构建的mock方案,比如mongo-expresser;
- 前端创建
工程+仓库
:脚手架拉取代码模板,模板为一套自动集成了[Vue3+TS+vite+axios+vuex+elementplus+常用路由表+常用hook+常用指令
]的工程,创建master+dev+test分支,后续在dev分支上合并特性分支,在test分支上做测试, 在master分支上合并test分支打包上线; - 专人开发MVC里面的M层,即axios的业务层;
- 典型的页面开发流程:静态页布局 + 拆分为组件 + AJAX通信获取数据 + 渲染页面 + 开发交互 + 封装整理形成可复用组件/hook/指令等;
- 合龙提交测试:开发人员不断开发+自测,一轮需求开发完毕后将各个特性分支合并到dev分支,然后用test分支合并dev分支,在test分支上打包,丢于测试服务器,通知测试人员测试;
- 递归修改BUG:测试人员将BUG提交于
禅道/Jira等BUG管理系统
(超级简单,无需担心),指派给前端组长,由前端组长转派给组员,组员修复后标记为已修改; - 打包上线:测试通过后(至少要解决
致命+严重+一般
三个级别的所有BUG,不严重
的可以马虎点,测试组长要在特定版本号上签字画押),然后将测试完毕的test分支合并到master分支,(可打tag),打包上传到生产服务器与用户见面; - 上线后维护:产品团队给出新一轮需求,同时生产环境下用户反馈了新的BUG,同时前后端的日志共同收集上来的BUG,共同促成版本升级的需要,重新执行接口-开发-测试-上线流程;
- 特别介绍自动集成
CI/CD
(ContinuousDeveloping持续开发 + ContinuousIntegration持续集成),一旦master分支有新的提交,GitHook会自动触发运维脚本git pull + npm build + ftp dist目录 生产服务器项目部署目录进行覆盖,用户不知不觉就被安排了,这一套体系CTO/运维负责人所搭建,与你无关,你只是一个被动的渣渣使用者;
@开发过程遇到哪些坑?
- 坑指数2/10:代码正确但界面就是不配合,重启开发服务器;
- 坑指数10/10:组件做切换动画时,有一个组件的根布局是ul,里面的item是根据数据动态渲染的,正常需要做切换动画的组件需要唯一根元素,但这里的Ul元素明明是一个根元素,但只要
<transition name="xx" mode="out-in">...</transiton>
一给,该页面就白屏并且同时所有其它参与切换的页面也失效,最终解决方式,在ul外再套一层div,解决方案百度了也没有,猜测可能是ul反复被重新差量渲染状态不稳定,因此加套一层相对稳定的div元素在外面就解决了该问题; - 坑指数4/10:早些年在配置Nginx代理的时候给代理前缀
/api
没有加结尾的/,导致前端路由无效,最终加上收尾的/即/api/
即解决问题; - 坑指数3/10:keepalive缓存组件,导致详情页每次都渲染同一个影片数据,最终解决方案:给keepalive加excluede='[Detail]'将详情页排除出keepalive的管理栈;
- 坑指数10/10:在Vue3拿到网络返回数据的时候,无法在实例对象上挂载同名数据,即
this.cities = cities
无效,在Vue2中有效; - uniapp + Vue2,模板的v-for中不支持对item直接解构...
@之前团队的工作流程
立项阶段
- 老板+产品+技术总监一起论证某产品的可行性
- 产品出《需求文档》+《需求原型》
*PS:需求原型的形态通常是网页上的简单交互与跳转,或手机App上的简单交互与跳转,产品使用的原型制作工具(Axure/墨刀等)能自动生成一个临时的App二维码,扫码即可安装 开发阶段
- 美术按照需求原型设计界面,生成《效果图》
- 与此同时前后端共同协商,生成前后端通信的《接口文档》
- 前后端根据《接口文档》分头开发
- 前端组长创建工程并推送远程,添加开发者账号,组员克隆项目
- 组员在各自的分支上开发不同功能(通常按页面分工最容易衡量各自的责权利)
- 前端根据《效果图》制作静态页面
- 前端将静态页拆分为组件化结构
- 前端自己部署mock数据(json/json-server/fast-mock等皆可)
- 前端与自己的mock-server进行网络通信,完成数据交互与其它业务功能
- 后端的接口开发完成后,前端将网络框架中的BASE_URL由mock-server改为真实后端服务器地址
- 前后端共同联调直至前后端功能开发完毕 测试阶段
- 各功能分支合并到dev分支,再基于dev分支拉出一个test分支,打包到测试环境服务器,提交测试
- 测试人员提交BUG到【禅道/Jira】等BUG管理平台上
- 组长将BUG分配给不同的个人进行修复
- 持续修复BUG并提交测试
- 【致命】+【严重】+【一般】级别的BUG确认修复完毕后(测试人员需要签字),可以考虑上线 上线阶段
- 手动上线流程:master分支合并dev分支,打包并上传到生产服务器上的Nginx部署目录下,上线完成
- 持续集成(CI/CD)上线流程:master分支只要一提交,即自动触发运维脚本(运维同学所写),该脚本执行
npm run build
并将产出目录上传覆盖到Nginx的部署目录,上线完成 升级/维护阶段 - 重复上述流程,即:产出新需求 + 产出新界面 + 产出新接口 + 产出新代码 + 测试通过 + 再次上线
@前端开发人员的日常工作与产出
- 例会:周例会,日例会,时间可长可短,无非各自通报一下进展与问题;
- 日报/周报/月报:日报或有或无,周报与月报通常都有,总结一下本周完成,规划一下下周计划(好好写,写漂亮点)
- 日常工作:早上到公司同步一下最新的代码,开发,晚上下班前例行提交一下今天的代码(哪怕有BUG),每次提交都尽量写详实一点
- 产出:代码 + 接口文档(可选) + 技术文档(例如特定类库的使用方法,可选)+ 打好的包
代码提交模板
新功能:xxx
修复BUG:xxx
依赖变化:xxx
目录变化:xxx
备注:xxx
@使用过哪些开发工具
PS:掘金搜索:"前端 工具"、"VSCode插件",几万吨工具将会迎面袭来
以下随便罗列一些:
大工具
- MarkDown编辑器:Typora + 掘金编辑文章
- 文本编辑器:notepad++ + editPlus + sublime
- 思维导图:FreeMind + XMind
- 数据库可视化:MongoDB/Compass + MySQL/SQLyog(海豚)
- BUG管理:Jira,禅道
VSCode插件
- 正则大全:VSCode插件any-rule
- AI代码提示:Tabnine
- JSON转TS类型定义:JSON to TS
- 随机生成假数据:vscode-faker
- 即时翻译:VSCode Google Translate
在线文档
- NPM包的使用方式:npmjs.com
- 图标字体:Iconfont
- 各种官方文档
浏览器插件
- Vue调试:devtools浏览器扩展程序
- 前端性能调试:lighthouse
在线工具类
- 在线JSON格式化:json.cn
- 在线翻译:百度翻译
- 在线作图:Process On
- 在线二维码生成:草料二维码
- 在线雪碧图生成:sprite-generator
- 在线压图:TinyPNG
- 在线调色板: sojson.com
- 在线工具大全:tool.lu sojson.com
命令行工具
- Node版本切换/源切换:nvm/nrm
- mock数据:json-server,fast-mock
- 兼容性检查:CanIuse + caniuse-cmd
npm install -g caniuse-cmd
caniuse fetch
- 删除文件/文件夹: rimraf
npm i -g rimraf
rimraf ./node_modules
@使用过哪些第三方库
即将来袭
@重点功能专题
@大文件上传
- 组件布局一个type等于file的input元素,外加一个上传按钮就OK了;
- 当用户选择了文件以后,用一个state把用户选好的文件
e.target.files[0]
存起来; - 这个state是一个
File类型
的数据,本质就是一个字节数组,后续可以直接对它进行切片; - 选好文件,点击上传,就开始进入切片上传逻辑;
- 通过
file.slice
将文件切割为一堆分片/chunk
,分片的类型自然还是字节数组; - 将分片数组映射为Promise数组,使用axios向服务器接收地址发送POST请求,通过手动构造的
FormData
数据携带每个分片的字节数据和序号,此时所有分片已经开始了上传动作; - 通过
Promise.allSettled
以刚刚的Promise数组为入参,分别获得每个切片的上传结果; - 对于上传成功的分片,文件上传的整体进度+1,对于失败的分片进行重传;
- 服务端接收接口是父组件以props形式传入的;
- 服务端的操作无非就是逐个接收,最后再重新拼接为文件;
- 大致如此,难度还好~
@电子签名
即将来袭
@海量数据/虚拟列表优化
- 当移动端需要加载大量数据,形成一个巨长的列表时,比如说一万条数据分为一千页去加载,如果真的一次性加载到内存中的话,一定会造成明显的页面卡顿;
- 一方面原因是因为内存吃紧,更重要的是可视区域外,用户看不见的地方,页面进行了大量的渲染操作——直接把主线程给干爆了;
- 优化的方式就是使用所谓“虚拟列表”优化,大致原理是:
- 用户滚动页面的时候我们加一个防抖,抓到用户不再滚动的时间点;
- 我们通过scrollTop结合滚动内容的完整height,计算出当前页面所滚动到的位置比例;
- 比如内容高一万像素,当前scrollTop我五千像素,就说明滚动位置恰好为整体的一半;
- 我们根据这个比例,去到完整数据列表二分之一的位置,开始截取10条数据(假设一屏恰好显示10条数据);
- 把这10条数据同步到页面的state中来,例如名叫pageArr;
- 此时pageArr的长度为一万,前五千条数据为null,[5001,5010] 区段为刚刚截取过来的数据,5010条往后又全部都是null;
- 也就是说虽然pageArr的长度为一万,但真正有数据的,就是当前屏的10条数据!
- 所以根据数据驱动视图原理,框架只需要对当前屏的10条数据进行渲染,其余条目全部为一个复用的骨架屏item;
- 这样无论数据层面还是渲染层面,真正的开销就仅仅只有一屏数据了!——本例中实际资源开销为优化前的万分之十!
- 现在还有一个问题就是,当用户开始缓缓向上或向下滚动屏幕查看数据时,当前屏的上下区域都没有数据加载,用户看到的是骨架屏,这体验很不好;
- 所以我们实际的操作是:当滚动停止时,我们并不仅仅只加载当前屏的数据,而是一次性加载三屏数据,即 当前屏+上一屏+下一屏;
- 这样我们就能以30条数据的代价模拟出一万条数据的效果了,而且性能体验俱佳!
@维护专题
@项目上线后如何排查BUG
即将来袭