let data = reactive([])但是我万万没想到的是,这样使用是有问题的。因为当我从接口里面获得一个新数据的时候,想要直接用新的列表覆盖初始列表,结果居然没有什么好的办法能让这种覆盖生效!!!
// 接口请求成功之后执行,数据失去响应 data = result.data然后我就只能这么写
// 定义 const res = reactive({ list: [] }) // 接口请求成功之后调用 res.list = result.data我这个组件只有一个响应式数据的时候,就贼难受,所以我就想着法又加了一个,这样就舒服一点了
const res = reactive({ list: [], show: false })这样处理之后呢,我想着用的时候,就很自然的想着用解构语法来使用吧。但是呢,响应性又丢失了...
const {lsit, show} = reactive({ list: [], show: false })这样也不行
const data = reactive({ list: [], show: false }) const {list, show} = data必须要引入一个新的 api 来解决这个问题 toRefs
let data = reactive({ list: [], open: false }) const {list, open} = toRefs(data)然后我就含泪看着我的 reactive 被强行变成了 ref. 这其实我还勉强可以接受,最令我崩溃的是,由于 list 和 open 都被转化成 ref,因此使用的时候,我必须这样用,把 .value 的尾巴加回来...
list.value = [ {message: 'hello'}, {message: 'world'} ]所以 reactive 一个符合语义的响应式 api,给我的使用感受就是,在设计上就是非常失败。但是一个不太符合语义的的响应式 api ref 被处理得还相对好一些。在这样的情况之下,也就不得不更多的在项目中使用 ref。但是使用 ref 的时候,除了不符合语义化之外,还不符合一致性。因为在 script 中使用,我们必须加上 .value 来处理。但是呢,template 中又不用... 甚至如果我为了一致性在模板中用了还会出问题。
<h1 v-if="awesome">Vue is awesome!</h1>等价于
awesome && <h1>Vue is awesome!</h1>但是这个机制就由此就导致了在父组件使用自定义组件时,往子组件传参就变得非常复杂。因为在子组件内部就没办法统一接收属性参数了。因为有的属性呢,他是自定义指令,是不应该往下传的,但是有的指令呢,又需要往下传。
<button v-on:click="greet">Greet</button>这个时候我们在学习的时候,就必须的保证区分如下几种传参:
<h1 v-if="awesome">Vue is awesome!</h1> <h1 v-else>Oh no 😢</h1>一种是函数类型的参数,多为事件回调函数
<MyComponent @some-event="callback" />一种是数据绑定类型的,这种被 Vue 官方成为动态类型。
<BlogPost :title="post.title" />还有一种就是正常的参数传递,这种被 Vue 官方文档称为静态参数类型。
<BlogPost title="My journey with Vue" />然后这里有一个很魔性的约定,如果你参数的 key 值使用横杠的方式,如下
<MyComponent greeting-message="hello" />到子组件接收的时候,它居然强制把这个key 的写法改了 。
defineProps({ greetingMessage: String })还有一个对我来说,误导性更大的一种情况。就是当我试图使用静态参数类型传递一个静态对象时,你猜怎么着?传不了!我只能改成动态的绑定写法,才能正常传递。这里为啥误导性很强呢,因为在我看来,虽然我声明的是一个对象,但是他就是一个静态的数据,也不是一个响应式数据
// 我觉得这是一个静态数据 const a = { message: 'hello' } <HelloWrold :message='a' />所以这个就给我干懵了。没办法,虽然我已经知道怎么用了,但是我到现在也不太确定官方文档说的动态属性表达的准确定义是啥。然后完了之后呢,还有一种参数类型,叫做透传 Attributes,比如像这种,他可以直接在内部元素贴上去生效
<MyButton class="large" />所以我个人的感受就是,不仅设计得复杂,还有一些我觉得不够合理的地方。有人说,这个学习成本低,我是不太信的。
defineProps({ msg: String })当我这样写的时候,可以直接在 template 中使用 msg
<template> <h1>{{ msg }}</h1> </template>但是在 <scirpt> 中就用不了... 不一致的表现让我觉得非常难受。然后另外一个让我觉得非常难受的语法设计就是对于事件回调函数的处理。例如我想要通过 @click 传递一个回调函数到子组件,但是这个时候,子组件怎么接收这个回调函数呢?他的接收逻辑,又跟 props 的逻辑完全不一样了。
<script setup> defineEmits(['inFocus', 'submit']) </script>而且调用的逻辑我也觉得有点懵... 这是啥?连回调函数的执行都不见了...
<script setup> const emit = defineEmits(['inFocus', 'submit']) function buttonClick() { emit('submit') } </script>我宁愿这样传
<HelloWrold :onclick='clickhandler' />这样接收和使用,更符合我的一致性的想法。
<script setup> const props = defineProps({ msg: String, onclick: Function }) </script> <template> <h1 @click='onclick' text-center'>{{ msg }}</h1> </template>四、watch 的怪异行为
const obj = reactive({ count: 0 }) // 错误,因为 watch() 得到的参数是一个 number watch(obj.count, (count) => { console.log(`Count is: ${count}`) })我必须额外提供一个 getter 函数才能做到
// 提供一个 getter 函数 watch( () => obj.count, (count) => { console.log(`Count is: ${count}`) } )这又给我偏执的想要使用 reactive 增加了不舒服的感觉... 难顶啊。当然有的人比较喜欢用 watchEffect ,但是这里有一个非常重要的问题,就是他会自动追踪所有能访问到的响应式属性。很明显这是一种简单粗暴,并且也存在冗余监听风险的一种方式。他的响应性依赖关系并不明确,所以我并不是很喜欢使用它。
无论是从语法设计的角度来考虑,还是从设计模式的方向来考虑,基于类似 signal 的底层实现,语法表现上明显更适合设计为面向对象。我们可以基于装饰器和依赖注入来完整底层逻辑的设想,例如
<script setup> @Inject class HelloWorld extends Vue { @reactive counter = 0 ... } </script> <template> // 堆代码 duidaima.com </template>
如果能通过解析省掉 script 和 template 标签就更好了。这样设计之后,就完全不需要担心任何响应性丢失的问题。从而极大的降低了学习成本和使用心智负担。深度使用之后给我整体感受就是,Vue3 拥抱函数式,拥抱得很勉强。一方面是上手难度提高了,另外一方面是使用过程中的心智负担也变重了。所以,从 Vue2 切换到 Vue3,绝非有的人认为的那么平滑,甚至可以说是重新学了另外一个框架。甚至我认为,React 开发者到 Vue3 才是平滑的切换,他们比 Vue2 开发者更容易接受 Vue3。