最新要闻
- 每日短讯:真刺鸡战场!西安一景区设免费抓鸡活动:人鸡比例10:1
- AMD YES!来自小厂的迷你主机 把友商按在地上摩擦
- 世界看点:你敢坐吗?日产汽车联手日立:通过电动汽车为电梯临时供电
- 湖南以前叫什么名字?湖南旅游十大必去景区
- 南菱嫣盛霆旭是什么小说?2023年言情小说推荐
- 创造营2019全部成员有哪些?创造营2019出道成员
- 雪见是哪个电视剧的人物?雪见是哪个演员扮演的角色?
- 不要抛下绮绮是什么意思?王者荣耀流行梗有哪些?
- 【当前热闻】《流浪地球2》周边众筹已超4500万!最初目标仅仅10万
- 中国“宁王”成功出海 宁德时代首座海外工厂投产
- 春节假期有车走应急车道 视频车拍照举报还得数百元红包奖励
- 环球百事通!最勤劳“小兔子”行驶1500米、数据940GB!玉兔二号传回新玉照
- 当前热讯:中国玩家的电子阳痿:被日本“老中医”彻底治好了
- 【天天快播报】男子与女友吵完架开车2分钟扣22分 逆行、闯红灯等:网友看完害怕
- 最新:男子春节逆向旅游深圳承包整片沙滩:通过房价得出判断
- 中国春节档电影市场重焕活力:总票房破67亿 列历史第2
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
热头条丨感受 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、reactiveconst reactivePerson = reactive({ name: "张三", info: { age: 18 } })//改变属性,观察以下不同情景下的监听结果nextTick(() => { simplePerson.value = "李四" person.value.name = "李四" complexPerson.value.info.age = 20 reactivePerson.info.age = 22})//情景一:数据源为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(newVal === oldVal) //输出:true}) //情景八:数据源为getter函数,返回响应式对象watch( () => complexPerson.value.info, (newVal) => { console.log(newVal) //除非设置deep: true或info属性被整体替换,否则监听不到 })//情景九:数据源为响应式对象watch(reactivePerson, (newVal) => { console.log(newVal) //不设置deep: true也可以监听到 })
总结:
在Vue3中状态都是默认深层响应式的(情景七),嵌套的引用类型在取值(get)时一定是返回Proxy响应式对象
watch数据源为响应式对象时(情景四、七、九),会隐式的创建一个深层侦听器,不需要再显示设置deep: true
情景三和情景八两种情况下,必须显示设置deep: true,强制转换为深层侦听器
情景五和情景七对比下,虽然写法完全相同,但是如果属性值为基本类型时是监听不到的,尤其是ts类型声明为any时,ide也不会提示告警,导致排查问题比较费力
所以精确的ts类型声明很重要,否则经常会出现莫名其妙的watch不生效的问题
ref值为基本类型时通过get\set拦截实现响应式;ref值为引用类型时通过将.value属性转换为reactive响应式对象实现;
deep会影响性能,而reactive会隐式的设置deep: true,所以只有明确状态数据结构比较简单且数据量不大时使用reactive,其他一律使用ref
Props篇
设置默认值
type Props = { placeholder?: string modelValue: string multiple?: boolean}const props = withDefaults(defineProps(), { placeholder: "请选择", multiple: false,})
双向绑定(多个值)
• 自定义组件
//FieldSelector.vuetype 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)}
• 使用组件
<script setup lang="ts">import { reactive } from "vue"const stringFilter = reactive({ businessTableUuid: "", businessTableFieldUuid: ""})</script>
单向数据流
大部分情况下应该遵循【单向数据流】原则,禁止子组件直接修改props,否则复杂应用下的数据流将变得混乱,极易出现bug且难排查
直接修改props会有告警,但是如果props是引用类型,修改props内部值将不会有告警提示,因此应该有团队约定(第5条除外)
如果props为引用类型,赋值到子组件状态时,需要解除引用(第5条除外)
复杂的逻辑,可以将状态以及修改状态的方法,封装成自定义hooks或者提升到store内部,避免props的层层传递与修改
一些父子组件本就紧密耦合的场景下,可以允许修改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 <script setup lang="ts"> import { useDynamicTree } from "@/hooks/useDynamicTree" const { add, remove, toggleOperator, tree: logic, dfs } = useDynamicTree() const handleAdd = () => { //添加条件 } const handleRemove = () => { //删除条件 } const toggleOperator = () => { //切换逻辑关系:且、或 }</script>
//组件2 <script setup lang="ts"> import { useDynamicTree } from "@/hooks/useDynamicTree" const { add, remove, toggleOperator, tree: logic, dfs } = useDynamicTree() const handleAdd = () => { //添加条件 } const handleRemove = () => { //删除条件 } const toggleOperator = () => { //切换逻辑关系:且、或 } </script>
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 }})
• 在组件中使用
你好,{{ userInfo.realName }},欢迎回来
{{ userInfo.organizationFullName }}
<script setup lang="ts"> import { useUserStore } from "@/stores/user" import avatar from "@/assets/avatar.png" const { userInfo } = useUserStore()</script>
热头条丨感受 Vue3 的魔法力量
每日短讯:真刺鸡战场!西安一景区设免费抓鸡活动:人鸡比例10:1
AMD YES!来自小厂的迷你主机 把友商按在地上摩擦
世界看点:你敢坐吗?日产汽车联手日立:通过电动汽车为电梯临时供电
湖南以前叫什么名字?湖南旅游十大必去景区
南菱嫣盛霆旭是什么小说?2023年言情小说推荐
创造营2019全部成员有哪些?创造营2019出道成员
雪见是哪个电视剧的人物?雪见是哪个演员扮演的角色?
不要抛下绮绮是什么意思?王者荣耀流行梗有哪些?
大金空调是哪个国家的品牌?大金空调不制热怎么回事?
环球快消息!【如何提高IT运维效率】深度解读京东云基于NLP的运维日志异常检测AIOps落地实践
环球实时:别再写狗屎代码了,推荐这 5 款 IDEA 插件,让你的代码质量直接起飞!
将实体光盘制作成光盘映像iso文件
输入法切换不了是什么原因?输入法切换不了怎么解决?
小米6x什么时候发布的?小米6x详细参数
qq游戏大厅在哪里打开?qq游戏大厅怎么多开?
注册表编辑器是干什么的?注册表编辑器怎么恢复默认设置?
【当前热闻】《流浪地球2》周边众筹已超4500万!最初目标仅仅10万
中国“宁王”成功出海 宁德时代首座海外工厂投产
全球头条:springboot~logback按level添加不同的颜色
春节假期有车走应急车道 视频车拍照举报还得数百元红包奖励
环球百事通!最勤劳“小兔子”行驶1500米、数据940GB!玉兔二号传回新玉照
当前热讯:中国玩家的电子阳痿:被日本“老中医”彻底治好了
聚焦:读Java8函数式编程笔记03_高级集合类和收集器
【天天快播报】男子与女友吵完架开车2分钟扣22分 逆行、闯红灯等:网友看完害怕
最新:男子春节逆向旅游深圳承包整片沙滩:通过房价得出判断
中国春节档电影市场重焕活力:总票房破67亿 列历史第2
头条焦点:AX9000安装使用Docker
天天看点:理想L9高速“失灵” 追尾“自杀式并线”车引争议:车主自找的?
车企年度销量目标完成率:比亚迪一枝独秀 长城、长安惨不忍睹
今日热议:唯一/普通索引的选择?change buffer
焦点消息!AMD RX 400/500老显卡尴尬不能跑新游戏:同时代N卡却没问题
当前视点!暗物质:宇宙中最神秘的物质之一 已经逼疯科学家了
环球观天下!RabbitMQ介绍
图省钱去开电动汽车:在美国根本不存在
天天快资讯丨玩法BT!真人版《鱿鱼游戏》出意外:多人受伤
世界速看:MQ的相关概念
Python字符串
当前关注:女子过年练车坠河:一家3人不幸遇难 还是大学生
环球快播:学习笔记——安卓的下载路径;创建一个空的安卓project;Android中的日志工具划分
滚动:史上最好Windows系统!微软要对Win11首个正式版强制升级22H2了
【全球报资讯】马斯克称中国竞争对手最努力最聪明:最有可能仅次于特斯拉
每日快讯!Cybertruck又跳票了!特斯拉首款电动皮卡量产要等到2024年
2022手机战事骁龙精彩收官:新的好戏要开场了!
全球微头条丨Codeforces Round #601 (Div. 2) A-E
世界快报:敏感肌适用 露得清氨基酸洗面奶19.9元白菜价:3.3折狂促
全球要闻:POJ 1185 炮兵阵地
每日动态![概率论与数理统计]笔记:4.3 常用的统计分布
通讯!刘德华吴京《流浪地球2》电影里重回20岁 吴京:没有被年轻俊美吓到吧
【天天新要闻】2G/3G退网 怎就这么难
【环球速看料】[NOIP2016提高组] 愤怒的小鸟
焦点速看:33.98万元起买吗?理想L7内部空间图公布:感受一下到底有多能装
男子花20多万三亚度假遭遇节约型爸妈 网友热议:过度节约才是更大浪费
全球热点!操作系统的概念、功能和目标
今日热讯:阿里回应将在新加坡建“第一高楼”当全球总部:出生在杭州 生长在杭州 发展在杭州
中国团队开发液体机器人成功越狱:复刻《终结者2》T-1000名场面
密钥封装和公钥加密的联系和区别?
环球观热点:将awk脚本写在文件里:一种高效的awk循环循环方式
【世界播资讯】男子在鱼疗池睡着“生吞”小鱼 网友:鱼生从未体验的味道
要闻速递:国航航班颠簸下坠乘客录视频遗言以防不测:全飞机的人都在尖叫
学习笔记——redis数据类型(ZSet)
世界微速讯:这几个月的二手车 可能是最香的
Hexo 修改默认文章路径
【世界热闻】-53℃的漠河启动i9-13900K、RTX 4090!魔幻一幕出现
当前热点-男孩逛景区遇现实版“鹈鹕灌顶”:小心确实有攻击性
Educational Codeforces Round 1
动态焦点:女儿返程点千元外卖塞满父母冰箱:感恩双亲 过去他们把我行李箱装满
【世界新视野】顽皮狗总监:《神秘海域》不会再出了
环球速看:Windows开发的瑞士军刀,NewSPYLite发布
当前最新:学习笔记——redis中的数据类型(List、Set、Hash)
信息:【算法训练营day27】LeetCode39. 组合总和 LeetCode40. 组合总和II LeetCode131. 分割回文串
【环球新要闻】windows2003 的安装以及安装时遇到的问题
世界焦点!大规模实测199颗i9-13900KS:6GHz的秘密找到了!真神仙
天天信息:PS5 Edge手柄续航差原因找到了:电池容量缩水1/3
首款支持NVIDIA RTX I/O秒进游戏的大作终于来了!但是平均帧率降了10%
每日速看!家长带三胞胎爬五指山 三大三小全被困:21小时才救出来
全球微资讯!希捷搞定50+TB硬盘!但还得等3年
《流浪地球2》火爆 吴京恳请大家不要再宣传300亿票房:会觉得内疚
20年前 1个啤酒瓶能卖5毛钱 为什么在没人收了?
焦点速读:狗狗走丢一个月回家疯狂撞门:为啥土狗能找到回家的路 宠物狗却不能?
门店359元:鸿星尔克腾蛇2.0老爹鞋149元大促
读Java8函数式编程笔记02_流
苹果在三大战场向谷歌发起“无声战斗”:让iOS远离Android
全球动态:支付宝接口的数字签名
全球头条:连Intel都难逃寒冬:美国加州部门裁员数百人
自行车数量比人口还多 荷兰水下自行车库启用:比汽车车库还豪华
世界简讯:孩子抱走小狗还给大狗磕了3个头 剧情反转没血缘关系:网友调侃白磕
iPhone 15全系USB-C!iOS 17曝光:几乎没变化 苹果开始“摆烂”
每日信息:比燃油车更污染 吐槽新能源车愚蠢!丰田换帅 扩大纯电动汽车销售
直击近地行星2023BU与地球擦肩而过:卡车大小、时速高达5.4万公里
43英寸Mini-LED 144Hz高刷屏:三星宣布奥德赛Neo G7显示器进军全球市场
环球即时:AMD官方发布RX 7000/6000系显卡实测:老显卡更具性价比
每日焦点!Linux笔记03: Linux常用命令_3.5权限管理命令
焦点速讯:ASP.NET Core+Element+SQL Server开发校园图书管理系统(二)
长沙游客吐槽网红店排队4538桌 全家吃泡面引热议:春节被挤爆 网友感慨都是人
11.6英寸大屏+中置单摄模组:一加首款平板现身海外
最新版 Proteus 8.15 Professional 图文安装教程 [ 附安装包 ]
焦点要闻:假期剩5天作业剩7本 女孩崩溃大哭表示不想活了:网友笑趴太真实
我们没偷票房、幽灵场等!《满江红》被质疑抄袭《龙门镖局》宁财神回应
每日简讯:用状态机实现通用多字节SPI接口模块