前几天花了点时间仔细阅读了一下vue3的rfcs,总的来说,相比于vue2,我对vue3的兴趣是要明显大很多的。我一直相对于vue更喜欢react的原因是react明显更适合工程。
vue的一大吸引点就是.vue文件的开发模式,这种类似html的结构,以及非常明确的单文件即单组件的开发模式,非常的直观,所以这也是vue对于初中级开发者比较友好的方面,再结合好用的vue-cli,操作符代理带来的直接变量修改方式,都是对新手非常友好的方式,而这也是vue能快速开疆扩土的原因所在。(当然官方中文也是一个很大的原因)
但是.vue文件开发模式其实是有很多限制的,比如:
- 你export的必须是一个对象组件,并且你不可以封装他,比如
export default someFun({}) - 你不能在文件种声明两个组件,因为你的模板只有一份,这就限制vue组件的粒度只能是文件级的
而vue通过操作符代理来修改state也会在大型应用的复杂数据交互种,让人很难找到到底我在什么时候修改了哪个state,或者有个state被修改了,但是我完全不知道他在啥时候被修改的。
同时vue的props必须被声明之后才能在组件内通过this.xxx来使用,这也限制了HOC这种开发组件化开发种非常好用的组件增强方法在vue种非常难实现。你能想象你要扩展一个组件的时候,需要先把这个组件的所有props先声明一遍带来的痛苦吗?当然这个问题现在可以通过组合$props和$attrs来进行动态处理,但这毕竟是比较高级的技巧了,可能你问10个vue开发者,有6个是不知道的。
这些问题的出现其实并不是说vue团队意识不到这些问题,而更多时候其实是为了方便性而牺牲了很多灵活性。毫无疑问jsx的灵活和全面性肯定是胜过.vue文件很多的,template这种静态解析的模板肯定比不上纯函数的能力。但是你问10个人jsx和.vue哪个好学,那可能9个人都会说后者。这也是vue的生存之道。
那么以上我说了这么多的问题,其实是想说,vue3是有在改这些问题的,或者说vue3的一些特性,让我们可以选择很好得避过上面的问题,甚至因为setup的加入,我们可以像React那样通过纯函数组件来构建应用。

vue3自身也在rfcs里面提到了现在你可以直接使用函数来作为组件:
function a(props, { slots, attrs }) {
return h('div')
}
而这也意味着你不在需要为每个组件声明props了,所有传递过来的参数都是props,这就和React很像了,毫无疑问这也变得更灵活了,让我们写HOC也变得可能了。
结果后来我又看到了一条:
对于所有on开头的props,如果你没有在组件的props列表里面声明,那么他会直接被当作事件绑定到组件的根节点上
既然如此,你为啥要支持纯函数组件呢?想象一下某一天你用纯函数写了一个组件,然后你想给你组件里面的某个节点绑定一个onClick事件,并且你会触发外部传给你的onClick,好了,这时候你发现你的纯函数不够用了,你必须要声明这个props。
当然这不是最大的缺陷,毕竟函数组件你任然是可以声明props的:
{
functional: true,
props: {},
setup() {}
}
但是这种默认处理确实是会造成一些误解的。首先我们需要明确一点,不是所有人都会看rfc,在使用开源组件的时候,很多人也并不会认真阅读文档。在这种前提下,作为组件开发者,我们是希望能够尽可能的让使用者按照正确的使用方式来使用我们的组件,并且非常希望我们的组件实现没有歧义,导致一些使用者的困惑感。
那么什么叫歧义呢?我们来看一个例子:
{
props: {
onChange: Function
},
render() {
return <input onInput={this.handleChange} />
},
methods: {
handleChange(e) {
// do something with data
if (xxx) {
this.onChange(data)
}
}
}
}
在这样一个组件中,我们希望使用者通过传入onChange函数来获取输入的变化。因为我们并不是每次输入都会触发onChange,这是一个带有一些逻辑处理的输入框。那么正常情况下,这个组件是没什么问题的,因为我们没有定义onInput这个props,按照正常逻辑来说,使用者给我们传onInput是没有任何作用的,他会立马发现问题。
但是在vue3里面,歧义就这样出来了。使用者任然可以传入onInput,并且这个onInput还能正常生效,并且跟onChange很多情况下的表现行为是一致的。是想我们的onChange只是处理输入框的输入数字不能超过两位,那么在使用者传入的是onChange的时候他是正常的。但是如果他传入的是onInput,那么结果就是任何输入都会被接收,这时候使用者可能还会觉得组件开发者实现得不对,或者这是一个BUG。
我觉得这是作为框架的vue不应该期望出现的问题,这种替用户做决定的方式简直是有点睿智。对于组件开发者,如果我们确实希望在组件的跟节点上做某些事件监听,那么实现者完全可以自己去做这件事情,vue3的默认行为则变成了一种组件开发者完全无法掌控的事情,我总不能把每个可能的事件都注册成props吧?
同时我无法理解这个需求到底解决了什么问题呢?作为组件开发者,我如果不提供你onClick事件props,那么应该说明我就是希望这个组件是无法点击的,如果我要实现一个可点击的组件,我就应该实现onClick,这个组件的行为应该由组件开发者来定义,而不是由组件使用者来定义,因为组件使用者无法获知我增加了这个事件监听,会不会带来其他的副作用(比如组件内部如果阻止了事件冒泡呢?)
我可以从一点猜测尤老师为什么要这么做。在vue3中对于vnode的抽象方式变了,所有props都作为一级属性进行传递,也就是不像vue2中,对于事件我们需要通过:
{
on: {
click: () => {}
},
props: {
a: '123'
}
}
这样子来进行传递,而是事件绑定跟其他props放在来一级:
{
onClick: () => {},
a: '123'
}
vue希望抽离事件和其他props的区别,则直接把onXxx作为事件绑定。
但是这依然无法解释为什么要绑定到组件的跟节点,vue完全可以对于组件不执行这种默认行为,而对于原生DOM节点采用这种策略。通过createElement的第一个参数是字符串还是对象或者函数来区分是DOM节点还是组件。
总之,vue3最终还是这么来实现,那是我是非常失望的。为了所谓的便捷性而失去了灵活性甚至带来歧义,让我觉得vue在设计上抄React抄到了表面,却在最重要的API设计层面上输得一塌糊涂。