感受Vue3的魔法力量
近半年有幸参与了一个创新项目,由于没有任何历史包袱,所以选择了Vue3技术栈,总体来说感受如下:
setup语法糖scriptsetuplangts摆脱了书写声明式的代码,用起来很流畅,提升不少效率
可以通过CompositionAPI(组合式API)封装可复用逻辑,将UI和逻辑分离,提高复用性,view层代码展示更清晰
和Vue3更搭配的状态管理库Pinia,少去了很多配置,使用起来更便捷
构建工具Vite,基于ESM和Rollup,省去本地开发时的编译步骤,但是build打包时还是会编译(考虑到兼容性)
必备VSCode插件Volar,支持Vue3内置API的TS类型推断,但是不兼容Vue2,如果需要在Vue2和Vue3项目中切换,比较麻烦
当然也遇到一些问题,最典型的就是响应式相关的问题响应式篇
本篇主要借助watch函数,理解ref、reactive等响应式数据状态,有兴趣的同学可以查看Vue3源代码部分加深理解,
watch数据源可以是ref(包括计算属性)、响应式对象、getter函数、或多个数据源组成的数组import{ref,reactive,watch,nextTick}fromvue定义4种响应式数据状态1、ref值为基本类型constsimplePersonref(张三)2、ref值为引用类型,等价于:person。valuereactive({name:张三})constpersonref({name:张三})3、ref值包含嵌套的引用类型,等价于:complexPerson。valuereactive({name:张三,info:{age:18}})constcomplexPersonref({name:张三,info:{age:18}})4、reactiveconstreactivePersonreactive({name:张三,info:{age:18}})改变属性,观察以下不同情景下的监听结果nextTick((){simplePerson。value李四person。value。name李四complexPerson。value。info。age20reactivePerson。info。age22})情景一:数据源为RefImplwatch(simplePerson,(newVal){console。log(newVal)输出:李四})情景二:数据源为张三watch(simplePerson。value,(newVal){console。log(newVal)非法数据源,监听不到且控制台告警})情景三:数据源为RefImpl,但是。value才是响应式对象,所以要加deepwatch(person,(newVal){console。log(newVal)输出:{name:李四}},{deep:true必须设置,否则监听不到内部变化})情景四:数据源为响应式对象watch(person。value,(newVal){console。log(newVal)输出:{name:李四}})情景五:数据源为张三watch(person。value。name,(newVal){console。log(newVal)非法数据源,监听不到且控制台告警})情景六:数据源为getter函数,返回基本类型watch(()person。value。name,(newVal){console。log(newVal)输出:李四})情景七:数据源为响应式对象(在Vue3中状态都是默认深层响应式的)watch(complexPerson。value。info,(newVal,oldVal){console。log(newVal)输出:Proxy{age:20}console。log(newValoldVal)输出:true})情景八:数据源为getter函数,返回响应式对象watch(()complexPerson。value。info,(newVal){console。log(newVal)除非设置deep:true或info属性被整体替换,否则监听不到})情景九:数据源为响应式对象watch(reactivePerson,(newVal){console。log(newVal)不设置deep:true也可以监听到})
总结:
1。在Vue3中状态都是默认深层响应式的(情景七),嵌套的引用类型在取值(get)时一定是返回Proxy响应式对象
2。watch数据源为响应式对象时(情景四、七、九),会隐式的创建一个深层侦听器,不需要再显示设置deep:true
3。情景三和情景八两种情况下,必须显示设置deep:true,强制转换为深层侦听器
4。情景五和情景七对比下,虽然写法完全相同,但是如果属性值为基本类型时是监听不到的,尤其是ts类型声明为any时,ide也不会提示告警,导致排查问题比较费力
5。所以精确的ts类型声明很重要,否则经常会出现莫名其妙的watch不生效的问题
6。ref值为基本类型时通过getset拦截实现响应式;ref值为引用类型时通过将。value属性转换为reactive响应式对象实现;
7。deep会影响性能,而reactive会隐式的设置deep:true,所以只有明确状态数据结构比较简单且数据量不大时使用reactive,其他一律使用refProps篇设置默认值typeProps{placeholder?:stringmodelValue:stringmultiple?:boolean}constpropswithDefaults(definePropsProps(),{placeholder:请选择,multiple:false,})双向绑定(多个值)
自定义组件FieldSelector。vuetypeProps{businessTableUuid:stringbusinessTableFieldUuid?:string}constpropsdefinePropsProps()constemitsdefineEmits(〔update:businessTableUuid,update:businessTableFieldUuid,〕)constbusinessTableUuidref()constbusinessTableFieldUuidref()props。businessTableUuid、props。businessTableFieldUuid转为本地状态,此处省略表切换consttableChange(businessTableUuid:string){emits(update:businessTableUuid,businessTableUuid)emits(update:businessTableFieldUuid,)businessTableFieldUuid。value}字段切换constfieldChange(businessTableFieldUuid:string){emits(update:businessTableFieldUuid,businessTableFieldUuid)}
使用组件templateFieldSelectorvmodel:businesstableuuidstringFilter。businessTableUuidvmodel:businesstablefielduuidstringFilter。businessTableFieldUuidtemplate单向数据流
1。大部分情况下应该遵循【单向数据流】原则,禁止子组件直接修改props,否则复杂应用下的数据流将变得混乱,极易出现bug且难排查
2。直接修改props会有告警,但是如果props是引用类型,修改props内部值将不会有告警提示,因此应该有团队约定(第5条除外)
3。如果props为引用类型,赋值到子组件状态时,需要解除引用(第5条除外)
4。复杂的逻辑,可以将状态以及修改状态的方法,封装成自定义hooks或者提升到store内部,避免props的层层传递与修改
5。一些父子组件本就紧密耦合的场景下,可以允许修改props内部的值,可以减少很多复杂度和工作量(需要团队约定固定场景)逻辑UI解耦篇
利用Vue3的Composition组合式API,将某种逻辑涉及到的状态,以及修改状态的方法封装成一个自定义hook,将组件中的逻辑解耦,这样即使UI有不同的形态或者调整,只要逻辑不变,就可以复用逻辑。下面是本项目中涉及的一个真实案例逻辑树组件,UI有2种形态且可以相互转化。
hooks部分的代码:useDynamicTree。tsimport{ref}fromvueimport{nanoid}fromnanoidexporttypeTreeNode{id?:stringpid:stringnodeUuid?:stringpartentUuid?:stringnodeType:stringnodeValue?:anylogicValue?:anychildren:TreeNode〔〕level?:number}exportconstuseDynamicTree(root?:TreeNode){consttreerefTreeNode〔〕(root?〔root〕:〔〕)constlevelref(0)添加节点constadd(node:TreeNode,pid:stringroot):boolean{添加根节点if(pid){tree。value〔node〕returntrue}level。value0constpNodefind(tree。value,pid)if(!pNode)returnfalse嵌套关系不能超过3层if(pNode。levelpNode。level2)returnfalseif(!node。id){node。idnanoid()}if(pNode。nodeTypeoperator){pNode。children。push(node)}else{如果父节点不是关系节点,则构建新的关系节点constcurrentJSON。parse(JSON。stringify(pNode))current。pidpidcurrent。idnanoid()Object。assign(pNode,{nodeType:operator,nodeValue:and,重置回显信息logicValue:undefined,nodeUuid:undefined,parentUuid:undefined,children:〔current,node〕,})}returntrue}删除节点constremove(id:string){constnodefind(tree。value,id)if(!node)return根节点处理if(node。pid){tree。value〔〕return}constpNodefind(tree。value,node。pid)if(!pNode)returnconstindexpNode。children。findIndex((item)item。idid)if(index1)returnpNode。children。splice(index,1)if(pNode。children。length1){如果只剩下一个节点,则替换父节点(关系节点)const〔one〕pNode。childrenObject。assign(pNode,{。。。one,},{pid:pNode。pid,},)if(pNode。pid){pNode。idroot}}}切换逻辑关系:且或consttoggleOperator(id:string){constnodefind(tree。value,id)if(!node)returnif(node。nodeType!operator)returnnode。nodeValuenode。nodeValueand?or:and}查找节点constfind(node:TreeNode〔〕,id:string):TreeNodeundefined{console。log(node,id)for(leti0;inode。length;i){if(node〔i〕。idid){Object。assign(node〔i〕,{level:level。value,})returnnode〔i〕}if(node〔i〕。children?。length0){level。value1constresultfind(node〔i〕。children,id)if(result){returnresult}level。value1}}returnundefined}提供遍历节点方法,支持回调constdfs(node:TreeNode〔〕,callback:(node:TreeNode)void){for(leti0;inode。length;i){callback(node〔i〕)if(node〔i〕。children?。length0){dfs(node〔i〕。children,callback)}}}return{tree,add,remove,toggleOperator,dfs,}}
在不同组件中使用(UI1UI2组件为递归组件,内部实现不再展开)组件1templateUI1:logiclogic:onaddhandleAdd:onremovehandleRemove:toggleoperatortoggleOperatorUI1template组件2templateUI2:logiclogic:onaddhandleAdd:onremovehandleRemove:toggleoperatortoggleOperatorUI2templatePinia状态管理篇
将复杂逻辑的状态以及修改状态的方法提升到store内部管理,可以避免props的层层传递,减少props复杂度,状态管理更清晰
定义一个store(非声明式):User。tsimport{computed,reactive}fromvueimport{defineStore}frompiniatypeUserInfo{userName:stringrealName:stringheadImg:stringorganizationFullName:string}exportconstuseUserStoredefineStore(user,(){constuserInforeactiveUserInfo({userName:,realName:,headImg:,organizationFullName:})constfullNamecomputed((){return{userInfo。userName}〔{userInfo。realName}〕})constsetUserInfo(info:UserInfo){Object。assgin(userInfo,{。。。info})}return{userInfo,fullName,setUserInfo}})
在组件中使用templateelspaceelavatar:size60:srcuserInfo。headImg?userInfo。headImg:avatarelavatarp你好,{{userInfo。realName}},欢迎回来pstylefontsize:14px{{userInfo。organizationFullName}}elspacetemplate
作者:京东科技牛至伟
内容来源:京东云开发者社区
许昌实施扩大内需战略推动经济高质量发展高质量发展是全面建设社会主义现代化国家的首要任务,要坚持以推动高质量发展为主题,把实施扩大内需战略同深化供给侧结构性改革有机结合起来,增强国内大循环内生动力和可靠性,提升国际循环质
我的世界22w44a你敢信么?末影龙刷怪蛋,无限熔岩加入mcMinecraft本周快照22w44a一个注定载入史册的快照版本,末影龙刷怪蛋加入到了我的世界中,mc玩家看到眼睛都直了!一加入了末影龙凋灵BOSS级刷怪蛋!(爷青结)Minecr
收费的配音软件千篇一律,好用的配音软件独领风骚,懂我意思吧?相信大家在挑选配音软件时,都会遇到一个问题,那就是不知道选哪个好,好像这个不错,用着用着又收费或者不好用,又要换下一个,找软件是个比较浪费时间的,而又心累的过程,还有就是容易被忽悠
击败小米和OPPO,蝉联中端机性能第一名,双11仅需1599元起手游厂商们想要获取更大的利益,就不能让手游的门槛太高,如果这个手游需要极强的性能才能带得动,那么注定玩这个游戏的玩家少,而这就是目前手游的现状,对手机性能的要求其实并不高,因此手机
美媒华为开始和美企说再见了大家都知道,三年前华为手机业务之所以一蹶不振,有三方面的原因,分别是谷歌停止向华为提供GMS服务台积电不能为海思代工生产麒麟芯片和美企无法继续向华为提供5G射频等元器件。要知道,没
1500元左右性能最强的五款手机流畅体验比肩旗舰,当下换机首选随着科技产品的迭代和新鲜资讯的吸收,低性能的手机已经无法满足日常所需,而在有限的预算里,处理器的提升往往是巨大的,目前1500这个价位段里,虽然大部分都是中低端处理器,但依旧有很多
资本主义的马老师,也是马老师文蔡垒磊马老师收购twitter的事情大家都清楚,前段时间的欲拒还迎,在今天看来,都成了一种谈判策略。那么马老师收了twitter后的第一件事是什么?开人,开很多很多的人。twit
马斯克全球性野心马斯克为何要拿出440亿美元购买社交网络推特?从经济角度看,推特的营业额与另一家社交网络脸书比微不足道,它比后者少25倍。当马斯克宣布要买下推特时,普遍很震惊。为什么全球首富要掌控
白皮书北斗系统是中国首个实现全球组网运行的航天系统新华社北京11月4日电(记者温竞华胡喆)国务院新闻办公室4日发布的新时代的中国北斗白皮书说,北斗系统星间星地一体组网,是中国首个实现全球组网运行的航天系统,显著提升了中国航天科研生
(国际)埃及举办第五届金字塔国际跳伞节当日,第五届金字塔国际跳伞节在埃及首都开罗附近的吉萨金字塔景区开幕,吸引了来自世界各地的100余名专业跳伞运动员参加。11月2日,在埃及吉萨金字塔景区,人们拍摄运动员跳伞表演。新华
来中国旅游的外国游客中北欧人很少,是什么原因?近年来,中国的国际实力不断提高,中国在国际上的地位在全世界也是有目共睹的。许多发达的国家都想来中国参观中国的名胜古迹,了解中国的文化习俗,尤其是西方欧美国家,他们纷纷来到中国。这些