范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

感受Vue3的魔法力量

  近半年有幸参与了一个创新项目,由于没有任何历史包袱,所以选择了Vue3技术栈,总体来说感受如下:
  •setup语法糖< script   setup   lang = "ts" >摆脱了书写声明式的代码,用起来很流畅,提升不少效率
  •可以通过 Composition API (组合式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 } from "vue"  //定义4种响应式数据/状态 //1、ref值为基本类型 const simplePerson = ref("张三")  //2、ref值为引用类型,等价于:person.value = reactive({ name: "张三" }) const person = ref({     name: "张三" }) //3、ref值包含嵌套的引用类型,等价于:complexPerson.value = reactive({ name: "张三", info: { age: 18 } }) const complexPerson = ref({ name: "张三", info: { age: 18 } }) //4、reactive const reactivePerson = reactive({ name: "张三", info: { age: 18 } })  //改变属性,观察以下不同情景下的监听结果 nextTick(() => {      simplePerson.value = "李四"      person.value.name = "李四"      complexPerson.value.info.age = 20     reactivePerson.info.age = 22 })  //情景一:数据源为RefImpl watch(simplePerson, (newVal) => {     console.log(newVal) //输出:李四 }) //情景二:数据源为"张三" watch(simplePerson.value, (newVal) => {      console.log(newVal) //非法数据源,监听不到且控制台告警  }) //情景三:数据源为RefImpl,但是.value才是响应式对象,所以要加deep watch(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(newVal === oldVal) //输出: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,其他一律使用ref Props篇设置默认值type Props = {   placeholder?: string   modelValue: string   multiple?: boolean } const props = withDefaults(defineProps(), {   placeholder: "请选择",   multiple: false, })双向绑定(多个值)
  •自定义组件 //FieldSelector.vue type Props = {  businessTableUuid: string  businessTableFieldUuid?: string } const props = defineProps() const emits = defineEmits([  "update:businessTableUuid",  "update:businessTableFieldUuid", ]) const businessTableUuid = ref("") const businessTableFieldUuid = ref("") // props.businessTableUuid、props.businessTableFieldUuid转为本地状态,此处省略 //表切换 const tableChange = (businessTableUuid: string) => {  emits("update:businessTableUuid", businessTableUuid)  emits("update:businessTableFieldUuid", "")  businessTableFieldUuid.value = "" } //字段切换 const fieldChange = (businessTableFieldUuid: string) => {  emits("update:businessTableFieldUuid", businessTableFieldUuid) }
  •使用组件  单向数据流
  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.ts import { ref } from "vue" import { nanoid } from "nanoid" export type TreeNode = {  id?: string  pid: string  nodeUuid?: string  partentUuid?: string  nodeType: string  nodeValue?: any  logicValue?: any  children: TreeNode[]  level?: number } export const useDynamicTree = (root?: TreeNode) => {   const tree = ref(root ? [root] : [])   const level = ref(0)   //添加节点   const add = (node: TreeNode, pid: string = "root"): boolean => {     //添加根节点     if (pid === "") {       tree.value = [node]       return true     }     level.value = 0     const pNode = find(tree.value, pid)     if (!pNode) return false     //嵌套关系不能超过3层     if (pNode.level && pNode.level > 2) return false     if (!node.id) {       node.id = nanoid()     }     if (pNode.nodeType === "operator") {       pNode.children.push(node)     } else {       //如果父节点不是关系节点,则构建新的关系节点       const current = JSON.parse(JSON.stringify(pNode))       current.pid = pid       current.id = nanoid()       Object.assign(pNode, {         nodeType: "operator",         nodeValue: "and",         // 重置回显信息         logicValue: undefined,         nodeUuid: undefined,         parentUuid: undefined,         children: [current, node],       })     }     return true   }   //删除节点   const remove = (id: string) => {     const node = find(tree.value, id)     if (!node) return     //根节点处理     if (node.pid === "") {       tree.value = []       return     }     const pNode = find(tree.value, node.pid)     if (!pNode) return     const index = pNode.children.findIndex((item) => item.id === id)     if (index === -1) return     pNode.children.splice(index, 1)     if (pNode.children.length === 1) {       //如果只剩下一个节点,则替换父节点(关系节点)       const [one] = pNode.children       Object.assign(         pNode,         {           ...one,         },         {           pid: pNode.pid,         },       )       if (pNode.pid === "") {         pNode.id = "root"       }     }   }   //切换逻辑关系:且/或   const toggleOperator = (id: string) => {     const node = find(tree.value, id)     if (!node) return     if (node.nodeType !== "operator") return     node.nodeValue = node.nodeValue === "and" ? "or" : "and"   }   //查找节点   const find = (node: TreeNode[], id: string): TreeNode | undefined => {     // console.log(node, id)     for (let i = 0; i < node.length; i++) {       if (node[i].id === id) {         Object.assign(node[i], {           level: level.value,         })         return node[i]       }       if (node[i].children?.length > 0) {         level.value += 1         const result = find(node[i].children, id)         if (result) {           return result         }         level.value -= 1       }     }     return undefined   }   //提供遍历节点方法,支持回调   const dfs = (node: TreeNode[], callback: (node: TreeNode) => void) => {     for (let i = 0; i < node.length; i++) {       callback(node[i])       if (node[i].children?.length > 0) {         dfs(node[i].children, callback)       }     }   }   return {     tree,     add,     remove,     toggleOperator,     dfs,   } }
  •在不同组件中使用(UI1/UI2组件为递归组件,内部实现不再展开) //组件1  //组件2    Pinia状态管理篇
  将复杂逻辑的状态以及修改状态的方法提升到store内部管理,可以避免props的层层传递,减少props复杂度,状态管理更清晰
  •定义一个store(非声明式):User.ts import { computed, reactive } from "vue" import { defineStore } from "pinia" type UserInfo = {   userName: string   realName: string   headImg: string   organizationFullName: string } export const useUserStore = defineStore("user", () => {   const userInfo = reactive({     userName: "",     realName: "",     headImg: "",     organizationFullName: ""   })   const fullName = computed(() => {     return `${userInfo.userName}[${userInfo.realName}]`   })   const setUserInfo = (info: UserInfo) => {     Object.assgin(userInfo, {...info})   }   return {     userInfo,     fullName,     setUserInfo   } })
  •在组件中使用 
  作者:京东科技 牛至伟
  内容来源:京东云开发者社区

四时俱可喜,最好新秋时怡耕园棉麻服饰秋分已过,天气的炎热一点一点减缓,大地慢慢恢复了平静。这个爽而不冷,温而不腻的季节,有着一年之中最适宜的温度。在秋天,不用担心夏季衣服太单薄脂肪无处藏匿,也免去了冬日益生菌护肤品,这事儿到底是不是智商税?益生菌微生态护肤是不是伪概念呢?没有人能够永远年轻,但永远有人正在年轻着。喊出主打应用益生菌与益生元技术,全面改善肌肤微生态,微生态护肤还真的火了!从概念到现实,你知道益生菌护肤的能在这三件事上保持低调的人,多半不简单,不分男女图源自网络侵权请联系删除不同的人有着不同的性格和心性,同时也有着不同的处事方式和人生态度,自然,也会有着不同的际遇命运和结局。有的人喜欢宣扬,做事向来高调,而且没什么心眼,所以很容你是我世界中的神你的身在我的世界中见到窗隙外的天空里毁灭了一切的生命了衣服透着阳光晒过的气味就是人们是穿着新装嘴边荡漾着迷人的微笑不知道天空不见一颗星在那生命的火焰里欢闹勇敢的人们创造出初升的太阳曾经我拥有一只猫王去年生了一场大病,一直在家休养生息,一天天总做各种稀奇古怪的梦,前几天模模糊糊的梦到了小时候养的那只狸花猫,它在前面跑呀跑,我在后面追呀追,醒来后这几天我一直努力的回想着童年时和这世间的真相是什么?怎样才能活成一个强者?12个锦囊送给你天黑路滑,社会复杂迷茫时来看看我的文章,每天吸取一点不一样的收获下面12条精心总结的话送给你,相信看完会对你大有裨益读万卷书不如行万里路,行万里路不如有高人指路。比你更强的人,身上比微笑更为说服的是内心强大的磁场,是一种充满朝阳的。没有人在乎出门的天色,也不会在乎脸色,因为每个人有自己出门的理由,哪怕风吹日晒,哪怕得失输赢,一杆秤里,有自己活着的方式和境界。贴纸的一副作品笑脸,站2022最后100天阅读本文前,点击上方卡片一键关注Episode02323如果一定要说个愿望,那就是找对一个人,到处看看风景,努力发点小财,把遗憾变成感谢,感谢让我变成温柔的人。2022最后的100阳光下也看不清的世界7(谁要吃了,就会成为哑巴)如果说起耵聍,或许有些人不知道是什么东西,但是只要说是耳屎,那么就都能明白了。小时候,曾经听人说,谁只要吃了耳屎,就会说不出话来,成为哑巴。为此,我变得十分礼貌和胆小,无论是谁递给心态与境界心小了,所有的小事就大了心大了,所有的大事都小了大其心,容天下之物虚其心,爱天下之善平其心,论天下之事潜其心,观天下之理定其心,应天下之变。。心态好,人生处处都美好幼年快乐玩耍是美小升初的重要的事升初开学后一定要知道的事,不喜勿喷一不要强行挤入不属于你的圈子,这样会让别人觉得你不懂装懂,会让别人反感你。二多交真心朋友有句话说得好,朋友在精不在多。就拿我自己举个例子,刚升初一
羊了个羊,火了又凉?作者杨阳(顶尖广告主笔)来源顶尖广告(IDidea1408)还有谁没被羊了个羊刷屏的吗?这几天网上羊声一片娘声一片还有崩声一片,全网都快被这个游戏逼疯了。图源微博网友评论我这文章发百万氪金玩家上亿兵力被打爆,询问客服却被回复给他忽悠走不管什么游戏,总会有氪金大佬出现,只不过,玩即时战略的氪佬尤其多。毕竟这类游戏造兵以及武将养成都需要耗费较多W,而打一次仗又会耗损诸多兵力,你得源源不断投钱才行。然而最近,万国觉醒福利逃离魔法监狱非线性解密游戏随便境现已发售CaligariGames宣布其最新作品随便境(Whateverland)正式上架Steam商店,支持中文语言。现正开启首发九折促销中,折后63元。随便境Steam商店页面传送门随卢伟冰微博发文为什么年轻人不换手机了?网友急了近段时间,Redmi没有太多新机的消息,作为Redmi品牌负责人,卢伟冰在微博上也沉寂了一段时间。不过CNMO发现,卢伟冰近日在微博上询问年轻人为啥不换手机?结果引起了广大网友的共腾讯旗下TME数字藏品遭网友投诉?央数藏平台的2款藏品卖出51万?8月16日,腾讯旗下的数字藏品平台幻核正式发布公告即日起,所有通过本平台(包括腾讯新闻数字藏品馆)购买过数字藏品的用户可自行选择继续持有或发起退款申请。中国最知名的数字藏品平台,最李玉成连饭也不做了,只给妻子买现成的油条,网友生个胖小子自从成了网红以后,李玉成就一直很嚣张,结果他不仅冲着妻子大呼小叫,甚至连饭也不做了,只给妻子买现成豆浆油条,有好事的网友评论说让马玉琴给李玉成生个胖小子。且不说马玉琴的年纪能不能生秦昊被爆出轨?网友都不信此文为高能E蓓子原创,禁止任何形式的转载,转载请后台联系,但欢迎你们转发到朋友圈。19日,秦昊饭局上与女子牵手牢牢地挂在热搜上。原来,近日,有媒体拍到秦昊与朋友在餐厅相聚。坐在秦昊玛格特罗比参加节目专访,穿蓝色翻领连衣裙金发飘逸女人味十足玛格特罗比(MargotEliseRobbie)现身纽约参加电视节目专访,当天,她身穿一件蓝色翻领连衣裙优雅大方,走路时金色长发飘逸女人味十足。玛格特罗比(MargotEliseR七八十年代茅台酒卖多少钱?网友看完感叹也就茅台酒厂敢卖提起茅台想必大家都无人不知,无人不晓,但是喝过的却没几个。就因为它的身价太高了,高到大部分普通人都不敢想象,就说现在买一瓶茅台的钱,尤其是七八十年代的老茅台,都能买下来一幢别墅。网才16岁!深足小将吾加合买提成首位在中超登场的06年龄段球员直播吧9月21日讯中超16轮补赛,深足30武汉长江终结了5连败。比赛中二次转会窗口从梯队提拔至深圳一线队的夏合扎提吾加合买提替补登场,代表深圳队完成中超首秀,据博主中超青年CSLY人生一世,命里的福气,皆有定数人生在世命也运也布大道于天下施善念于人心虽无修行身亦是修行人洞明修行苦方开修行门众生之相老话常说有福之人不用忙无福之人跑断肠看看我们眼前的世界,又何尝不是如此呢?从一条大街上穿过,