关键词:this
、context.refs
前面已经提到,setup的参数context替代了this
的作用,而setup
内部的this
直接指向了undefined
。这么设计是为了避免一些常见的this
的错误用法(其实主要是function的作用域导致的,箭头函数就不会有这些问题)。上文还提到,需要通过context来访问以前通过this
访问的内容,比如$refs
、$store
、$router
。
但是this
真的不能获取吗?文档里虽然说了setup里不能访问当前实例,但目前就这个插件来看(并非规范),还是有几个地方可以获取到有实际意义的this
的(通过function调用,而非箭头函数):
- 生命周期函数
- watch的getter
- computed,但这里的
this
指向的是一个由插件自己创建的Vue实例。
再次强调,这个并不是规范用法,甚至有可能是插件的“遗漏”,绝对不要基于这些编写代码,尤其不要用到生产环境中。
我个人感觉,把context分离出来有一个额外的好处,这个我在开头就提到过:方便逻辑的组合和复用;这也正是Vue3的设计初衷。分离出来之后,可能会更多地让人思考如何进行逻辑上的抽象和提取,而且从体验上来说比mixin好。按照尤大的原话来说,解决的是这些问题:
- 模版中的数据来源不清晰。举例来说,当一个组件中使用了多个 mixin 的时候,光看模版会很难分清一个属性到底是来自哪一个 mixin。HOC 也有类似的问题。
- 命名空间冲突。由不同开发者开发的 mixin 无法保证不会正好用到一样的属性或是方法名。HOC 在注入的 props 中也存在类似问题。
- 性能。HOC 和 Renderless Components 都需要额外的组件实例嵌套来封装逻辑,导致无谓的性能开销。
比如有这么一个应用场景:几个组件(视图层面的组件)都需要获取鼠标当前位置的逻辑。这时候,把这个监听、返回鼠标当前位置的过程抽取出一个逻辑组件,明显就比mixin好。
到目前为止说的东西看起来跟refs没什么关系。事实上,refs跟2.x里的用法并没有太大区别;refs从一开始设计出来,就是为了在特殊情况下访问子组件的(从体验上来说,像是直接操作DOM):
关于 ref 注册时间的重要说明:因为 ref 本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们 - 它们还不存在!
$refs
也不是响应式的,因此你不应该试图用它在模板中做数据绑定。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问
$refs
。
之所以单独把refs拿出来说,是因为在3.0里用的时候有点小坑,可能不那么符合使用的认知。事实上,这也是我不完全理解的地方。我们先看一段jQuery代码:
<script>
function useClick(el, cb) {
$(function() {
// not works!
el.addEventListener('click', cb);
});
}
var appEl = document.getElementById('app');
useClick(appEl)
</script>
<div id="app"></div>
这段代码能跑吗?显然是不能的,在useClick
执行之前,#app还没有被渲染。其实refs,包括整个setup,也是这么回事。之所以在setup里不能直接访问context.refs,原因和2.x一样,refs指向的目标还没有被渲染。所以,这种用法是不行的:
useElementKeypress("u", context.refs.elementKeypress, () => {
console.log("clicked!");
});
正确的用法是通过函数传递:
function useElementKeypress(key, getElement, callback) {
onMounted(() => {
getElement().addEventListener("keydown", onKeydown);
})
}
useElementKeypress("keydown", () => context.refs.someElement, () => {
console.log("clicked!");
});
因为Vue不是React,所以没有useRef,还是要入乡随俗(笑)。但是这个写法确实不是那么方便……
此外,还有一点值得一提。我们可以通过refs操作子组件,是毫无疑问的,想必大家都用过;但是在Vue3里,操作子组件的属性有几点需要注意:
一是只能操作子组件通过setup返回值暴露出来的属性,这个应该没什么疑问;
二是通过refs获得的子组件的属性,不是包装对象,是值本身。
假设我们要访问一个ref="foo"
的组件的一个属性,定义为const bar = value('bar');
。下面这段代码可以解释上面所说的:
context.refs.foo.bar // 'bar'
context.refs.foo.bar.value // undefined
并不是获得了子组件,我们就进入了子组件的作用域。子组件定义了但是没暴露出来的变量(类似于私有变量吧),也是获取不到的。
另外,关于refs可以看看dalao们的讨论:Refs are undefined in setup() outside of lifecycle method
目录
Vue 3.0 function-based API尝鲜(一):前言
Vue 3.0 function-based API尝鲜(二):配置与启动
Vue 3.0 function-based API尝鲜(三):包装对象
Vue 3.0 function-based API尝鲜(四):值得一提的watch