感受Vue3的魔法力量
近半年有幸参与了一个创新项目,由于没有任何历史包袱,所以选择了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也可以监听到 })
总结:
Props篇
设置默认值
type Props = { placeholder?: string modelValue: string multiple?: boolean } const props = withDefaults(defineProps<Props>(), { placeholder: '请选择', multiple: false, })
双向绑定(多个值)
//FieldSelector.vue type Props = { businessTableUuid: string businessTableFieldUuid?: string } const props = defineProps<Props>() 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) }
<template> <FieldSelector v-model:business-table-uuid="stringFilter.businessTableUuid" v-model:business-table-field-uuid="stringFilter.businessTableFieldUuid" /> </template> <script setup lang="ts"> import { reactive } from 'vue' const stringFilter = reactive({ businessTableUuid: '', businessTableFieldUuid: '' }) </script>
单向数据流
逻辑/UI解耦篇
利用Vue3的Composition/组合式API,将某种逻辑涉及到的状态,以及修改状态的方法封装成一个自定义hook,将组件中的逻辑解耦,这样即使UI有不同的形态或者调整,只要逻辑不变,就可以复用逻辑。下面是本项目中涉及的一个真实案例-逻辑树组件,UI有2种形态且可以相互转化。
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<TreeNode[]>(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, } }
//组件1 <template> <UI1 :logic="logic" :on-add="handleAdd" :on-remove="handleRemove" :toggle-operator="toggleOperator" </UI1> </template> <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 <template> <UI2 :logic="logic" :on-add="handleAdd" :on-remove="handleRemove" :toggle-operator="toggleOperator" </UI2> </template> <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复杂度,状态管理更清晰
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<UserInfo>({ userName: '', realName: '', headImg: '', organizationFullName: '' }) const fullName = computed(() => { return `${userInfo.userName}[${userInfo.realName}]` }) const setUserInfo = (info: UserInfo) => { Object.assgin(userInfo, {...info}) } return { userInfo, fullName, setUserInfo } })
<template> <div class="welcome" font-JDLangZheng> <el-space> <el-avatar :size="60" :src="userInfo.headImg ? userInfo.headImg : avatar"> </el-avatar> <div> <p>你好,{{ userInfo.realName }},欢迎回来</p> <p style="font-size: 14px">{{ userInfo.organizationFullName }}</p> </div> </el-space> </div> </template> <script setup lang="ts"> import { useUserStore } from '@/stores/user' import avatar from '@/assets/avatar.png' const { userInfo } = useUserStore() </script>

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
在WPS表格里制作连接到openGauss的实时刷新报表
前言 其实我的数据库启蒙,是在一家甲方公司。 当时一进这家公司,就见到了通过连接数据库自动刷新的excel表。当时学会了这招就一发不可收拾,制作出各种自动刷新的报表。 想象一下,有些高管不喜欢打开各种复杂的业务系统或者报表系统,一上班就直接打开桌面上的可刷新表格文件,就能看到昨日的报表数据以及今日的实时数据。 当年智能手机还未普及,没有移动端报表,每晚的值班经理需要查询当日数据编辑短信发送给高管,也是在电脑上用EXCEL直接刷出数据,而且提前在EXCEL里写好了公式,拼接成了短信文本内容,复制粘贴到飞信就把业绩短信发出去了,多少年来一直都是这么用的,只是后来改成了粘贴到微信发送。 在当时,这也算是极低成本的半自动化了,好不惬意! 当时连接的主要是Oracle数据库,现在突然想起,是不是我们的国产数据库也可以这样连接呢? 原理 其实原理很简单,就是在windows的odbc数据源中配置好对应的数据库连接,然后在excel或者wps表格中选择导入数据/odbc数据源,选择需要的表及字段,或者直接写个sql查询也行,就可以把数据返回到表格中。当数据库中的数据发生变化时,只需要在表格中点击刷新...
- 下一篇
Squirrel状态机-从原理探究到最佳实践
作者:京东物流 郑朋辉 1 简介 Squirrel状态机是一种用来进行对象行为建模的工具,主要描述对象在它的生命周期内所经历的状态,以及如何响应来自外界的各种事件。比如订单的创建、已支付、发货、收获、取消等等状态、状态之间的控制、触发事件的监听,可以用该框架进行清晰的管理实现。使用状态机来管理对象生命流的好处更多体现在代码的可维护性、可测试性上,明确的状态条件、原子的响应动作、事件驱动迁移目标状态,对于流程复杂易变的业务场景能大大减轻维护和测试的难度。 2 基本概念 2.1 Squirrel状态机定义 Squirrel状态机是一种有限状态机,有限状态机是指对象有一个明确并且复杂的生命流(一般而言三个以上状态),并且在状态变迁存在不同的触发条件以及处理行为。 2.2 Squirrel状态机要素 Squirrel状态机可归纳为4个要素,即现态、条件、动作、次态。“现态”和“条件”是因,“动作”和“次态”是果。 现态:是指当前所处的状态。 条件:又称为事件。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧...
相关文章
文章评论
共有0条评论来说两句吧...