1前言Vmo是我在2018年发布的一个快速创建数据模型的工具库。当时,我写了一篇文章《Vmo 前端数据模型设计》,得到了一段时间的关注。当时从事的是3D装修相关的项目。在图形和海量复杂数据的背景下,前端自然会衍生出数据处理、分析、消费的技术方案,这也为我的数据模型概念埋下了种子。举个简单的例子:3D装修的房子需要分析哪些数据?房屋(Hourse)、地板(Layer)、房间(Room)、墙壁(Wall)、墙面(WallSpace)、墙角(Corner)、天花板、踢脚线、地板(withthickness)、地板空间(FloorSpace)、门、窗。并且会延伸出大量的变型,比如飘窗、直角飘窗、弧形窗、墙洞、楼梯等等。解析这些数据有很多相互关系和计算,比如房间需要和墙关联,墙需要和墙关联,墙最多关联2个房间,角落关联到多个房间,角落与多面墙相关联。面对如此庞大复杂的数据,只消费一次API请求的结果显然不可取。先不说能不能正确解析数据,再来说说如何维护数据。保存的时候如何收集所有的数据并反序列化到后端是一个很头疼的问题。当然,这些问题在我们当时抽象出的各种数据模型中都得到了解决。如果想了解详情,可以查看我之前的文章。今天想说的是我加入阿里以来一直在思考的关于数据模型的两个问题:数据模型这种东西对于常规项目来说是没有使用场景和价值的吗?套路,比如一些数据查询,或者填写一些数据提交。是否有必要为此需求使用任何抽象类或数据模型?为什么在前端圈子里很少见到,现在前端圈子大部分都在往函数化、组合化等方向发展,对吧?这个路径有问题吗?找了2年,在Lazada商户端主导产品发布页面重构的时候,似乎找到了一些答案。2.商品模型首先,用户在添加商品的过程中,实际上是以客户端的形式在做一组商品数据。从常规的前端来看,就是提交一个“JSON”。编辑就是通过API拉取这个“JSON”,解析成表单,让用户编辑,然后提交这个“JSON”。那么粗略的将数据抽象成模型会是这样的:嗯,到目前为止我们所做的一切感觉就像是在脱裤子放屁。哈哈哈,各位评委暂时不要喷,也不要急。那么为什么我们需要将这些数据抽象成一个类呢?我举几个案例来说明一下:1请求数据&单元测试很多时候,前端把数据的请求和处理写在组件里,可能更好的封装在某个cluster或者某个Hook里,state和data打电话时可以很容易地获得。请求商品等数据的方式有很多种:从草稿中获取,从编辑中获取,从某个类目中获取(不同的类目有不同的商品属性)。每种获取方式请求的接口和参数组合可能不同,但前端消费的产品是相同的。根据策略模式,采用不同的策略得到一个产品模型,但产品是一致的。无论消费者调用哪种方法,得到的结果都是一个可靠的Product模型类。有经验的前端都知道,很多时候,一个项目经过一轮又一轮的迭代之后,我们的接口数据中往往会有一些数据需要前端进行处理或者转换。面对这样的数据处理,放在组件或者Hook中是不合适的,可能会给我们在做单元测试或者数据消费的时候带来一些阻力。在我看来,调试一个数据问题最好的方式就是写一个单元测试来调试单元测试的预期结果,这往往比在浏览器中mock一段数据来调试数据效率更高,后者更重要为了以后的稳定。也比较有帮助。安全感,数据消费,一个类,一段JSON,带给开发者完全不一样的安全感和爽快感。消费过数据模型或者稍微接触过Interface的朋友,相信对这一点是非常认同的。哈哈,说到这里,可能有朋友要问了,我们可以用Interface来实现和你说的一样的效果。好,我们继续……2计算消费数据什么是计算消费数据,简单的说,比如:classPerson1{fistName="Wang";lastName="Yee";getfullName(){return`${this.lastName}${this.fistName}`;//YeeWang}getfullNameCN(){return`${this.fistName}${this.lastName}`;//WangYee}}上面的例子很经典也很清楚,element的数据可能只是一些基础数据,但很多时候,前端需要根据不同的场景组装元数据。过去,这些数据往往被封装成各种方法,或者作为模板写在组件中,散落在各个角落,每当使用到这些数据时,可能会根据场景重新组装。往往这时候会出现需求不足,比如在某种情况下,需要把之前消费的fullName全部改成小写。谈到产品发布,计算消费数据的应用场景有哪些?在此之前先解释一下SKU数据模型,其核心元数据为:value:Map根据上表所示,可以看到商品一共有6个SKU,对应的SKU模型数据第一个SKU应该是:classSKU{value=newMap([[newSKUProperty({id:1,label:"ColorFamily"}),newSKUValue({id:101,label:"Red"}),],[newSKUProperty({id:2,label:"Size"}),newSKUValue({id:201,label:"33"}),],]);price:string;}对于这样的SKUModel,它的元数据已经可以清晰描述当前SKU,通过SKU的扩展方法可以获取很多有用的数据,比如:getProperties()获取SKU的所有属性Attributes比如:ColorFamily,Size。getValues()获取这个SKU的所有Values,比如:红色,33。isEqual(anotherSKU:SKU):boolean比较一个SKU是否和当前SKU一模一样,在后续的数据合并中非常有用。getValueByPropertyId(id:string)通过PropertyId获取一个SKUValue。与仅仅一个Object对象相比,数据模型可以带来大量的数据处理和数据扩展能力。当需要消费数据在某种情况下产生的计算消耗数据时,可以方便地扩展使用,对数据结构有更好的预期和控制。结合数据模型的单元测试,可以清晰快速的开发数据层。当数据层可靠时,视图层的消费就会变得顺畅、得心应手。单元测试的一个例子:it("aliasskuequal",()=>{constdata=[{text:"300MB",value:2988,name:"p-1",},{text:"Blue",value:2888,别名:“Blue1”,名称:“p-2”,},];constsku=SKU.fromData(数据);期望(sku.isEqual(SKU.fromData([{文本:“300MB”,值:2988,名称:“p-1”,},{文本:“Blue”,值:2888,别名:“Blue2”,名称:“p-2”,},]))。toBeFalsy();});这种SKU是一种特殊类型的SKU,里面会有别名字段。当有这样一个字段时,在做SKU对比时,不仅要对比SKUProperty和SKUValue的ID,还要对比SKUValue别名字段的ID。所以根据上面的单测,结果应该是false,因为两个数据中的别名是不一样的。没办法,这是业务需求。如果在视图层做数据对比时使用纯数据对比,很有可能会漏掉这部分逻辑,导致项目捉襟见肘,补西。反正当消费者层遇到大量的数据处理或者判断的时候,可以把这部分能力交给数据模型来保证数据的稳定性。3数据关系使用数据模型还可以帮助您清晰地管理数据关系,例如商品与SKU、SKU与SKUProperty、SKUValue的关系。举一个具体的案例:这是编辑产品时对笛卡尔积进行分组的过程。当我们的SKU属性被用户添加或修改时,会触发笛卡尔商品的重新计算,得到最新的排列组合结果。.例如,当用户添加尺寸为35时,笛卡尔积将产生另外两个组合结果。同理,如果在维度上增加一列,比如增加物料维度,会生成更多的SKU结果。以前前端开发者总是把这部分计算过程封装成一个数学方法,在utils中随时调用,好像没什么问题。如果把这个过程看成一个SKUCollection数据模型的构建过程,那么一切都会变得顺理成章:test('skucalculatewhethervalid',()=>{constskuCellection=SKUCollection.fromData({'p-3xxxx':[{text:'300MB',value:2,},{text:'128GB',value:3,},],'p-4xxxx':[{text:'Blue',value:3,},{text:'Red',value:15,},{text:'Green',value:1,},],});expect(skuCellection.value).toEqual(//6SKUModel);});有了这样一个数据模型结构之后,就可以通过数据模型清晰的调用它的相关数据和计算数据。另外,不同的数据模型虽然相互依赖,但在数据分析和计算数据上是相互独立的,可以独立使用和单元测试。三变态模型商品发布本质上是一个比较复杂的表单提交页面。由于领域多、交互复杂,很多领域在产品设计过程中被拆分成不同的模块,以减轻用户的心理负担。比如会有:基本信息、商品属性、详细描述、运费等。在填写过程中,会有一些前端验证+后端验证的场景。在数据提交或者其他数据写入的过程中,后台也会进行字段校验。当后端发现某个字段填写错误时,服务端会返回错误信息和错误字段信息。为了更好的交互体验,前端会根据返回获取字段信息,定位到对应的字段位置,显示错误信息并上报红色,同时还需要根据当前字段判断所属模块并上报错误。另一种情况是:服务端第一层验证通过,调用其他商品的上游链接时抛出异常。此时上行链路可能丢失了字段信息。面对这样的异常数据,前端需要在表单顶部显示,并提供traceId来跟踪定位异常。此类异常数据通常需要与后台反复确认,以确认不同情况下的表现。有些异常甚至很难出现一次。在迭代过程中,我们经常会因为一些组件变更或者逻辑变更而失去这部分数据消费能力。对于产品发布来说,明显的“保存”动作是一个需要处理异常的情况,所以我们会在提交的地方写很多后端返回异常的处理逻辑。当某一天,又有一个迭代需要写的时候,也会出现异常情况。当这些异常情况再次处理时,就会有很多数据转换和错误显示的逻辑。如果接收到后端返回的数据,将其转化为异常数据模型,然后交给视图层进行消费。这将允许重用所有异常模型下需要处理的逻辑,以避免交互逻辑丢失。当然,视图层如何更巧妙地消费数据模型是另外一个有趣的设计,这里暂且不表。后面会写一篇文章,专门介绍产品发布时视图层的状态管理设计。4.总结在产品发布中,除了上面提到的数据模型之外,其实还构建了一些其他类型的数据模型,比如:运费模型、产品质量分类模型、品类推荐模型等...那么这些多个子模型组合成产品模型。在消费这样的数据模型时,开发者并不会太在意需要请求什么API,返回什么样的数据,是否需要对其返回进行处理、转换和兼容。同时,这样高质量的数据模型不依赖于视图层的框架。可以将其抽取出来作为一个独立的包进行管理和维护,然后在其他页面引入使用。比如产品领域可能会遇到:产品管理、产品选型、运费编辑、产品质量预览等……回到最开始,我提到的问题:数据模型真的没有使用场景和价值吗对于常规项目?套路,比如一些数据查询,或者填写一些数据提交。是否有必要为此需求使用任何抽象类或数据模型?为什么在前端圈子里很少见到,现在前端圈子大部分都在往函数化、组合化等方向发展,对吧?这个路径有问题吗?首先,在我使用的过程中,数据模型确实非常清晰,强大,坚定的解决了我面临的业务问题,所以很有价值。至于常规需求,我该用什么?哈哈,这个问题有流氓回答。孩子只考虑自己想要什么,不想要什么,而大人什么都想要。没有技术是非黑即白的。Vite只能在Vue项目中使用吗?适合用什么?简单的数据查询展示不需要这么精细的数据处理。当然也可以直接使用。这是解决业务问题的好方法!至于CompositionAPI,其实在产品发布的重构过程中,大部分都是采用这种设计思路来实现的。这样的设计确实可以让我们清楚的区分每个方法是干什么的,会不会影响交互,这个交互是干什么的?每个交互都在一个地方维护和处理。后面会单独写介绍。在实践过程中,我发现数据模型和CompositionAPI并不冲突。一个用来处理数据层,一个用来处理视图层。它们相辅相成,结合一些订阅模式的设计,会让整个项目的分工非常清晰。我强烈建议大家在以后遇到更复杂的单点项目时,使用这套思路来解决业务问题!