vue hook API 可能存在的问题

关于最近 Vue 社区的激烈讨论,不知道大家是否有所耳闻。其主要原因是 Evan You 前几天发布了一个新的关于 Vue3 的 RFC,主要描述了 hook API,通过 setup 方法,我们可以不需要再区分书写 datacomputedwatch 等属性来创建一个 vue 组件。

对于这个提案,社区的反应却非常激烈,甚至有很多的批评声,很多人觉得这个新的组件书写方式完全改变了老的组件开发模式,导致社区以前的积累都白费来。也有人觉得这个 API 完全就是抄袭 react 的,对于 vue 来说并不是特别需要。

在这里我简单讲一下我的见解,vue 的这个提案本质上跟 react 的 hook API 确实是非常像的,同时他们解决的也是同一个问题。这个问题其实是现在的组件化框架都存在的,简单概括就是:组件化可以很好得分解功能,却对于一些没有那么明确分块的逻辑无能为力。

所以即便这个方案是 react 先出的,你要说抄袭,那也没有这么夸张,毕竟是实际能解决问题的,而不是为了提供 react 相同的功能而去做的。

具体关于这块我不在这里继续深入,这篇文章主要是想讲一个可能比较严重的问题,如果这个问题无法解决,那么 vue 的 hook API 可能前途并不明朗。

这个问题非常简单,假设我们有如下组件:

{
  props: ['data']
  template: `<span>{{num}}</span>`,
  setup(props) {
    const data = props.data

    const num = computed(() => data.num + 1)

    return { num }
  }
}

我们在使用的时候可以这么做:

{
  template: `<Comp :data="data" />`,
  setup() {
    const num = value(0)

    const data = computed(() => ({ num, /* 其他数据。。。 */ }))

    // 其他设置

    return { data }
  }
}

以上的代码是非常自然的,有可能会出现在平时写页面和组件的时候。但是这里就出现了一个问题,我们在修改父组件的num的时候,子组件的num是无法更新的。

会出现这个问题也是非常好理解的,vue 中的 computed 的实现原理是他会执行一次传入的方法,在执行方法的时候,根据执行到某个数据获取的时候,把当前的 computed 作为一个依赖放入到数据的根性流程中。

到这个例子里面,我们执行 const num = computed(() => data.num + 1) 这句代码的时候,发现我们读取了 data.num,那么这个 computed 就会作为 data.num 的依赖,在后续 data.num 更新之后,会再次执行这个 computed 并更新 num

那么为什么在这个例子中最终子组件的 num 没有更新呢?因为在这里父组件改变了整个data,而不是 data.num。要解决这个问题也非常简单,我们使用 props.data.num + 1 作为 computed 的返回就可以。这两种方式的区别是后者把 props.data 也作为依赖,所以他改变的话也会引起更新。

但是,需要注意的是,对于一个函数来说,我们使用上面的写法是完全没有问题的。这个问题对于不那么理解 vue 的响应式原理的同学来说,不是那么明了的。这其实就跟 react 的 hooks API 不能在判断条件中调用一样,是一个使用 hook API 的先觉条件。我们完全可以通过所有的 props 调用都直接在 props 上直接操作,不预先读取某个对象然后再操作,来规避这个问题,但不免会显得有点啰嗦。

同时这个问题可能比 react 的问题更为严重,因为他很难消除也很难监控,你很难通过一个 eslint 插件来监控这种问题,而一旦这个问题出现了,在代码较为复杂的情况下,你也是很难查找问题的。

所以以上是我在尝试 vue 的 hook API 的时候遇到的第一个问题。介于毕竟目前能够使用的只是在 vue2 基础上实现的模拟 API,我并不能下定论说这个问题是存在的,可能在 vue3 正式发布的时候已经解决了,所以这里也只是抛砖引玉,跟大家分享一下。同时我也把这个也在 vue 的 issue 列表里反馈了一下,虽然好像没怎么引起注意就是了。issue 在这

上面 demo 的运行结果可以看这里

另外模拟 API 的库是这个,大家有兴趣可以自己去用用看。

最后,个人其实是非常喜欢这个 API 的模式的,但是不得不说,需要解决的问题还是挺多的。另外使用函数式的 API 却依然要写静态模版来绑定总感觉还是有点怪。但不论如何,让我们一起期待 vue3 带来的变化吧。

订阅技术周报