在基础的响应式数据和事件函数之后,我们再来说说 computed 计算属性 和 watch 监听吧。

初探 Vue 3.0 的组装式 API(一)
初探 Vue 3.0 的组装式 API(二)

(三)组件属性、计算属性与监听

1. 组件属性

通过数据和事件处理的几个例子,大家或许发现了 Vue3 的两个基本变化思路:

  • 开发人员自己动手组装响应式数据;
  • 事件处理对象方法降级为普通函数。

而这两者都避免了再将各种不同层级的属性、方法绑定到 $vm 上,从而避免了混乱的 this 指向问题。

以至于我们在 Vue3 的例子里基本没再用到过 this

对于父级组件传入的属性值,以前都是通过 this.<属性名> 访问的,在 Vue3 的 setup() 中怎么获取组件属性呢?

很简单,setup() 函数收到的第一个参数就是传入的属性值了:

1
2
3
4
5
6
7
8
9
10
export default {
props: {
name: String,
},
setup(props) {
const { name } = props;

return { name };
},
};

不过由于传入的 props 是一个响应式数据,为了确保传入的非引用型数据发生变化时,页面内状态能动态更新,我们还得用 toRefs 从里面解构取值:

1
2
3
4
5
6
7
8
9
10
11
12
import { toRefs } from 'vue';

export default {
props: {
name: String,
},
setup(props) {
const { name } = toRefs(props);

return { name };
},
};

2. 计算属性

对于间接通过其它数据再计算出来的计算属性,通过 Vue3 的组装 API 实现也很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// sells.js
import { ref, toRefs, computed } from 'vue';

export default {
props: {
// 库存数量
totalCount: Number,
// 售出数量
sellCount: Number,
},
setup(props) {
const {
totalCount = ref(0),
sellCount = ref(0),
} = toRefs(props);
// 剩余数量
const remainCount = computed(() => totalCount.value - sellCount.value);

return { totalCount, sellCount, remainCount };
},
};

除了实际的业务逻辑之外,computed() 通常还可以用于多个 className 的计算合成,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default {
props: {
// 是否禁用状态
disabled: Boolean,
},
setup(props) {
const { disabled } = toRefs(props);
const className = computed(() => [
'sells-list',
{
'is-disabled': disabled,
},
]);
},
};

3. 监听

3.1 watch

当某个响应数据发生变化时,执行相关处理逻辑,我们就会用到 watch() 了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const { disabledIds } = toRefs(props);
const selectedId = ref(null);

// 监听某个数据
watch(selectedId, (val, oldVal) => {
// do something
});
// 监听多个数据
watch([selectedId, disabledIds], (val, oldVal) => {
// 此时传入的 val 和 oldVal 将会是多个数据的数组
});

// 监听某个复合条件的结果
watch(() => selectedId === disabledIds, (val, oldVal) => {
// 当其中相关值发生改变,但结果不变时,这里不会被触发
});

我们还可以使用 watch() 返回的停止器来结束监听:

1
2
3
4
5
const selectedId = ref(null);

const stopWatch = watch(selectedId, (val) => { });

stopWatch();

3.2 watchEffect

watch 不同,watchEffect() 不需要指明监听目标,在它接收一个 effect 函数后,将会立刻执行并分析其中依赖的响应数据,在它们发生变化时再次执行这个 effect 函数。:

1
2
3
4
5
6
const sellsCount = ref(0);

watchEffect(() => {
console.log(`sellsCount: ${sellsCount.value}`);
});
// 将会立刻看到输出日志,之后数据变化时再次打印日志

3.3 回收处理

如果需要在监听停止的同时,做一些额外的回收处理(比如解除 DOM 事件监听器、清理其它数据等),可以用到 onInvalidate 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const sellsCount = ref(0);

watch(sellsCount, (val, oldVal, onInvalidate) => {
console.log(`sellsCount: ${val}`);
onInvalidate(() => {
// 回收处理
});
});

watchEffect((onInvalidate) => {
console.log(`sellsCount: ${sellsCount.value}`);
onInvalidate(() => {
// 回收处理
});
});