js 刷新div_vue.js 会是那颗银弹吗?

vue.js 双向绑定 DOM 和 View Model

我们从这段代码开始

App.vue

<template>
    <div>
        <h3>Todo</h3>
        <ul>
            <li v-for="item in items">{{ item }}</li>
        </ul>
        <input type="text" v-model="newItem"/>
        <button @click="addNewItem">add</button>
    </div>
</template>
<script lang="ts">
    export default {
        data() {
            return {
                items: [],
                newItem: ''
            }
        },
        methods: {
            addNewItem() {
                this.items.push(this.newItem)
                this.newItem = ''
            }
        }
    }
</script>

vue.js 提供的价值是

  • 当 HTML 页面上的输入框变化的时候,同步其内容到 newItem
  • 当代码里更新了 newItem 的内容的时候,同步反映到 HTML 页面上

这种双向绑定,使得“业务逻辑”代码 addNewItem 与具体的输入输出技术无关。在这里,其外部呈现是一个 HTML 页面,我们同样可以让相同的代码绑定到 Android 的 UI 上。从 DDD 到六边形架构,从六边形架构到洋葱架构,从洋葱架构到Clean Architecture,各位老程序员孜孜以求的就是这样的架构属性,让“业务逻辑”与特定的输入输出技术无关。

99b5da117db94c1e7764a7c7b3d94a00.png

通过双向绑定,用 ViewModel 完全代表外部的 HTML DOM,参与到业务逻辑的交互中来。

“世界”随之更新

我们再来看第二段代码

<template>
    <div>
        <h3>Todo: {{ items.length }}</h3>
        <ul>
            <li v-for="item in items">{{ item }}</li>
        </ul>
        <input type="text" v-model="newItem"/>
        <button @click="addNewItem">add</button>
    </div>
</template>
<script lang="ts">
    export default {
        data() {
            return {
                items: [],
                newItem: ''
            }
        },
        methods: {
            addNewItem() {
                this.items.push(this.newItem)
                this.newItem = ''
            }
        }
    }
</script>

与上面那段代码的区别是增加了 Todo: {{ items.length }}。在 addNewItem 更新了 this.items 之后

  • 列表会被刷新,反映最新的 items 情况
  • 标题会被刷新,反映 items 的总数

vue.js 提供的价值在于 “世界”都随之更新。你不用管这个世界如何运转,你只管做你想做的,世界会自己更新自己。为什么我们会觉得这样的编程体验很爽?因为懒。这种写法更符合人的认知习惯。

3de20428c76187484cbbde01d2f71eeb.png

“全局”状态刷新

把这种爽的编程体验更往上推进一层的是 mobx + vue。vue 提供的是对单个组件的响应式绑定。mobx 提供的是全局的响应式绑定。当全局状态更新的时候,所有绑定在其上的组件都会随之更新。

限于篇幅这里就不贴全部的源代码了(taowen/toolchain)

<template>
    <div>
        <TitleBar :state="domainModel"/>
        <ItemsList :state="domainModel"/>
        <NewItemPanel :state="newItemPanelViewModel"/>
    </div>
</template>
<script lang="ts">
    import { observer } from "mobx-vue";
    import DomainModel from "./DomainModel.ts";
    import NewItemPanelViewModel from "./NewItemPanelViewModel.ts"
    import TitleBar from "./TitleBar";
    import ItemsList from "./ItemsList";
    import NewItemPanel from "./NewItemPanel"
    export default observer({
        components: {TitleBar, ItemsList, NewItemPanel},
        data() {
            let domainModel = new DomainModel();
            let newItemPanelViewModel = new NewItemPanelViewModel(domainModel);
            return { domainModel, newItemPanelViewModel }
        }
    })
</script>

其原理和前面的代码是一样的。只是把一个完整的组件,拆分成了 TitleBar,ItemsList 和 NewItemPanel 三个组件而已。

4023a599f986c4f2d9a6c28404fe8b27.png

Mobx + Vue 打破了 Vue 对状态绑定的限制,从而使得我们可以彻底地把业务逻辑和输入输出技术分开。

e98b1b7236e53093d55b9f0a3090fccd.png

vue.js 会是那颗银弹吗?

听起来 vue.js 已经完美解决了老一辈程序员毕生奋斗去简化编程体验的目标。当然仅仅是“听起来”。故事远没有那么简单。首先我们回顾一下什么叫“银弹”

在 Fred Brooks http://worrydream.com/refs/Brooks-NoSilverBullet.pdf 中,提到的银弹是有明确定义的:

there is no single development, in either technology or management technique, which by itself promises even one order-of-magnitude improvement within a decade in producitivity, in reliability, in simplicity.

简单来说就是可以把开发效率提升10倍的工具或者管理手段。

Fred Brooks 本人在 2007 年的 OOPSLA 会议上,参与了一个由 Martin Fowler 等人参与的高端圆桌会议。他回顾了 no silver bullet 这篇文章

I made a bold statement in the paper: no single technique will cause an order of magnitude improvement in productivity in the next ten years (1986-1996). This has turned out true. If there is one technique that has made a significant improvement, it is OO programming.

也就是他语言的 1986 ~ 1996 这个时间段内,整个行业确实是没有技术提供了10倍的效率改进。

所以不要望文生义了,银弹不是说要把成本降为0,它有一个明确的又随意的指标,10倍。

问题的核心在于现在的总耗时里面,哪部分是有最大水分,最有提高空间的。我认为由复杂性引发的程序员本人的认知困难,进而因为认知困难引起的团队沟通困难是最有改进空间的地方。其本质在于,我们对于“程序员认知软件行为”这个过程是缺乏认知的。

我在 陶文:如何提高代码的可读性 中,从这个角度对程序员的认知行为做过一些分析。在这里,重复几个关键的观点。

ea9305a2ca7e701db364f6acba1ad00a.png

我认为,人在思考计算机行为的时候,其底层操作系统是真实世界模拟。我们在脑海中假设自己是一只小白鼠,去探索一个迷宫。这个对于“我”的认知,就映射为指向当前程序所执行到的位置的那个指针。而“我”只有一个,所以我们的思维始终是单线程的。

eed04bcaf723c2a0f298b8692aacb1bf.png

第二个关键点是“我”与“世界”。人类会把自己放在一个环境中去思考。想象你在一个公园里面漫步,你对这个公园会由一个地图的概念。如果只有“我”,没有“世界”。或者“世界”的今天和明天差别很大,那就会颠倒三观,感到认知遇到困难。

6392901c387046ebcea0985257479b78.png

第三个关键点是我们预期这个世界有其底层运行规律。比如,图中的秋千你碰了它,它就会摆动。我们需要去安排每个原子如何运行么?不需要,我们只需要参与其中,每个原子都会根据物理规律自己安排自己的命运。就是,我们只管干我们自己的事情,这个世界会自己运转。

30cb341d19301c464ff8ffa538856b5e.png

也就是说,你有你自己的agenda,你会有一个原因到结果的 causality line,因果关系线索。其他人,其他事物,也各自有各自的agenda,它们有自己的 causality line。

所以什么叫符合人类的认知习惯?就是分开描述因果关系。每个事物各自都有自己的因果关系线。因为这是这个物理世界的运作机制,也是人类的认知能力的来源。从控制复杂度的角度来说,分开描述,每条因果关系先自身可以做到很简单。比如牛顿的三大定律,就描述了普适而又简单的因果关系。

vue.js 做得好的地方恰恰是这一点。它构建了一个世界,世界本身有自己的运作机制(绑定关系)。然后 UI 事件触发的 method,就像一个小老鼠,在这个世界的迷宫里进行探索,与这个世界发生相互作用。这种与人类认知模式的深层契合,是其力量的根本源泉。

与 vue.js 相对应的就是 jquery 命令式的逻辑表达方式。我们要知道输入,然后一个命令一个命令地去更新多个地方,以维护“业务规则的平衡”。比如 ul 列表里的 li 数量,要和标题上的 todo 个数对应。回到这个隐喻

ea9305a2ca7e701db364f6acba1ad00a.png

我们不再是那只轻盈的小老鼠去探索这个世界。而是把整个世界扛在了自己背包里,一路走,一路去把世界装进背包,又从背包里还原这个世界。

734bf99a71853c4ff5a01905d9039a58.png

更本质上来说,就是把状态带着走,然后“沿路”更新的模式导致了很高的认知负担(cognitive load)。那么问题来了,为什么要把状态带在身上走呢?这就要回到文章的标题,vue.js 是不是那颗银弹。为什么 vue.js 没有完全解决这个问题。这个模型在推广到更广的场景里会遇到什么困难。

一切都是有原因的

如果这个问题简单,早就被解决了。一切都是有原因的。

a2d80437b68ca32eeeac14489a59e225.png

我们知道这个模式很有效果。那么把它推广到与后端交互过程中去呢?

20736be369c69ecc75b50b6bf9d9108b.png

或者按照六边形架构的画图艺术

1e87f4c6f6040de4e119097efdf67767.png

为什么不能用双向绑定来屏蔽这些特定的输入输出技术?原因如下:

  • 内存限制:我们不可能把整个数据库映射成内存中的对象。那么内存中的对象都是全局状态的一个选择性的投影。
  • I/O速度限制:不可能想更新的时候就更新。必须在一次 round trip 里尽可能多地完成状态地交换。
  • I/O不会永远成功:与 vue.js 更新显示器的像素不同,我们不能假设所有的更新都一定会成功。除了偶然的网路错误,还有两个人并发更新了同一条数据库记录这样的情况。那么这种需要在 I/O 操作后面根据不同的结果有不同的后续逻辑,怎么表达?
  • 为了效率,需要点对点的直连:理论来讲可以把所有的两个I/O外设的交互都通过中间的Domain Model来进行,从而像外汇结算那样都先兑换成美元。但是我们不可能用这种方式来实现两个 mysql 表的拷贝,这样太慢了。为了效率,点对点的直连仍然是必须的。这个时候,model 与 model 的映射关系,需要借助编译技术来表达为特定 I/O 技术到特定 I/O 技术之间的直连逻辑表达。

这些实际的问题都是 vue.js 没有解决的。只是 vue.js 所处的浏览器的泡泡是一个很诗情画意很田园的泡泡。在那个环境里,我们可以简化很多事情。

所以实际中,这个 Domain Model 不会是六边形架构的图中画得这么美好。

33d05a1f7201f5afa745d193754850c1.png

服务端的 Model 和客户端的 Model 要通过一个狭窄逼仄地管道挤过去。随着微服务地普遍流行,这样地管道会越来越普遍,把你的Model切得支离破碎。同时因为数据库无法完整做双向绑定到Domain Model上,那么必然需要通过用类似 SQL 的技术,去选择性地绑定双边的状态。但是这个“选择性”意味着不通用。一不小心,业务逻辑就会通过SQL的形式外泄到binding这一层去。

在这些 I/O 技术的约束下,我们会越来越放弃构建一个model的想法,而是以command control为中心。也就是放弃构建一个世界,而是把所有的状态放在小老鼠的背包里。让小老鼠扛着这个世界负重前行。这才是 transaction script 相对于 rich domain model 的本质区别。在于我们是有多个 causality line 来表达这个世界,还是把所有的业务逻辑,都合并到一个 transaction script 的单线条里描述,从而导致复杂度的组合爆炸,从而引起程序员的认知困难,从而引起沟通的灾难。

所以最本质的做法就是要对业务逻辑进行 divide and conqur。领域模型也好,六边形架构也好,vue.js双向绑定也好都是a means to the end,只是一个手段。最本质的是对于所有要描述的因果关系,我们是不是能够做到分而治之,用多个并行独立的causality line来描述它们。

比如说,当我们有一个新需求,需要在展示货物的时候,与结账的时候更添加一些交互逻辑,前后有“因果关系”。我们是在所谓“订单”上添加一个字段来表达这样的前后因果关系。还是添加一个新的流程单据,来承载这个因果关系。是在订单的处理代码里写所有的业务逻辑,还是在新的流程单据的流程代码里独立写这个因果关系。能不能做到分而治之,这才是决定了最后,我们这些小白鼠们,能不能从迷宫里走出来的关键之处。

结论

vue.js 不是银弹,没有提供 10 倍的开发效率提升。但是指明了一个很好的方向。

求前端人才

我们为顶尖工程师提供了与之相配的技术发挥空间、无后顾之忧的宽松工作环境以及有竞争力的薪酬福利。同时也为高潜质的行业新人提供充分的学习和成长机会。
这里,没有996,崇尚高效。
这里,话语权不靠职级和任命,靠的是代码的说服力。
这里,不打鸡血,我们用理性和内驱力去征服各种挑战。
这里,也会有项目排期,但不怕delay,我们有充足的时间,做到让自己更满意。
工作地点在北京西二旗,薪酬待遇见招聘链接:https://www.zhipin.com/job_detail/?query=%E4%B9%98%E6%B3%95%E4%BA%91&city=101010100&industry=&position=

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值