前端数据范式化

2017-09-29 fishedee 前端

1 概述

前端数据范式化,最近无聊时看到了一遍关于《前端范式化》的文章,挺有意思的。

2 问题

首先,举个例子,这个例子所表现的问题可能过于严苛了。

Screen Shot 2017-09-29 at 10.25.28 P

这是食谱列表页面,有显示总的收藏和评论数目,以及当前用户是否对该用户点赞的状态。

Screen Shot 2017-09-29 at 10.36.29 P

然后点进去详情页面,查看该食谱的详细信息,顶部标题和初始信息都是空的。顶部标题甚至出现了undefined的字样。

Screen Shot 2017-09-29 at 10.27.34 P

闪烁一下,页面拉到后台信息后,信息刷新,页面正常显示。这时候,当前用户对该食谱点了一个赞。

Screen Shot 2017-09-29 at 10.34.31 P

然后返回到再列表页面,点赞的按钮开始时是灰色的,然后页面会闪烁一下,拉到后台信息后,信息更新,点赞按钮变红。

好了,我们总结一下问题:

  • 详情页的标题信息滞后,按道理说,在列表页时app已经知道了该食谱的标题,用户,描述,图片等信息了,为什么在进入详情页面时仍然会显示undefined,或者没有概要信息的显示。当然,第二次进入就没有这个问题了,因为app做了全局store的保存。
  • 列表页的点赞信息滞后,按道理说,在详情页就已经知道用户点赞了这个食谱,为啥在列表页仍然需要拉后台来更新这个信息,然后出现了一个闪烁的显示。

换句话说,前端保存的各个model之间数据没有互通,某个model数据更新时,其他model的数据也没有更新,导致数据滞后,只能通过重新拉取后台数据来刷新。

function like(){
    this.recipeDetailModel.update()
    this.recipeListModel.update()
}

一个很明显的解决方案是,在详情页更新recipeDetailModel的信息后,也去更新recipeListModel的信息。

function like(){
    recipeDetailModel.update()
    recipeListModel.update()
    timelineModel.update()
    serachModel.update()
    collectModel.update()
    ...
}
function likeCount(){
    recipeDetailModel.update()
    recipeListModel.update()
    timelineModel.update()
    serachModel.update()
    collectModel.update()
    ...
}

但这样做的问题也很明显,每次需要同时更新的model太多了,收藏页,搜索页,时间线页等等。同时,不仅需要更新like信息,还需要更新likeCount,commentCount等等信息。也就是说,不仅需要同步很多个model,还需要同步很多个字段。瞬间有了一种拆东墙补西墙,填了旧坑埋了新坑的感觉。

3 解决

其实这个问题不仅前端会出现,后端也会出现呀。你向后端like了一个食谱后,你在后端拉取收藏页,搜索页,时间线的信息也会可能出现滞后的问题。可是,这种现象就从来没有出现过,这又是为什么呢?

Screen Shot 2017-09-29 at 10.58.36 P
Screen Shot 2017-09-29 at 10.59.04 P

如果你拿着这个问题去问后端的程序员,后端的程序员肯定会说“你是不是傻呀,因为数据放在数据库中都是尽可能满足三大范式的”。例如,时间线的信息在后端中仅仅保存contentId的信息,不会保存整个recipe的信息,只有在吐出到前端时才会组合在一起吐出去的。这样在修改recipe信息时,只修改一处的地方就可以了,不需要说既要修改recipeDetail,也要修改timeline。这个问题在后端可谓是一个人尽皆知的基础问题了。

recipe:[
    "10001":{},
    "10002":{},
],
timeline:[
    "10003":{title:"xxx",desc:"xxxx"},
    "10004":{title:"xxx",desc:"xxxx"},
]
recipe:[
    "10001":{},
    "10002":{},
],
timeline:[
    "10003":{recipeId:"10001"},
    "10004":{recipeId:"10002"},
]

所以,按照后端的想法,前端的数据也应该扁平化和范式化。也就是尽可能将model设计为无冗余的,而且少嵌套的。这样,like时只需要修改一个recipe的信息就可以了,timeline的信息会自动刷新。

而且,由于使用非常扁平的结构,你甚至不再需要immutable,因为你可以简单的拼数据就可以了,不用处理嵌套修改的问题。

4 局限

render(){
    return (
        <List data=timelineModel.data/>
    )
}
render(){
    var recipeData = recipeModel.data; 
    var timelineData = timelineModel.data.map((key,value)=>{
        return recipeData[value];
    })
    return (
        <List data=timelineData/>
    )
}

当然,这样做也是需要付出代价的,在render时需要像后端一样将各个model的数据组合起来才能用,这麻烦了一点,而且耗费性能。

Screen Shot 2017-09-29 at 11.12.52 P
timeline:[
    "10003":{title:"xxx",desc:"xxxx"},
    "10004":{title:"xxx",desc:"xxxx"},
]

而且,后端吐出来的数据本来就是组合好的,到了前端数据层,你又把它分拆出来范式化了,这样做前端得写到吐血(当然,有个很流行的库专门解决这个问题,写起来会省事很多),而且后端程序猿看到你这样做会怎么想呢=,=。如果你要让后端程序猿也改为吐出已经范式化的数据,呃,那拉一个列表页面得分多少次接口才能拉齐数据呢。

5 总结

就像没有银弹一样,这个问题的解决方案依然需要付出代价,如果你的app没有多个model交叉同步信息时,使用范式化数据就是没事找事干。而反之,如果你的app存在多个model交叉同步信息的大型结构时,范式化数据就是很好的选择了,毕竟手工同步信息简直要命。全球微博鼻祖twitter最近就用react+redux重构了整个前端栈,他们权衡已久后,最终使用的正是数据范式化的方案,看这里。注意,twitter可是一个超大的SPA架构。

其实,在以前的web多页面架构时,就没有这样的问题,毕竟多网页信息就难以同步和共享,用户也就没有这么苛刻要求信息要快速对齐并且无刷新。但是,在web逐渐转向SPA趋势的情况下,用户也越来越苛求,希望web就像app一样,快速无滞后。

相关文章