关键词:配置、setup()
开始用之前,得先配置一下环境。这个的“环境”配置起来也简单,只需要在先通过npm/yarn安装依赖,然后在main.js(或者main.ts)里加上这么一小段就行了:
// main.js
import { plugin } from 'vue-function-api'
Vue.use(plugin) // 别漏了这句
说起来简单,不过要是忘了引入,可能会出现属性/方法未定义的报错(也就是Property or method is not defined on the instance but referenced during render这个报错),导致后续的工作都无法开展。尤其是这里必须显式地使用Vue.use()
导入,这一点容易被忽略。虽然文档上已经很明确地写了:
您必须显式地通过
Vue.use()
来安装vue-function-api
。
值得一提的是,这个库同样支持在<script>
标签里直接引入,就跟Vue本体一样是个UMD库,这个让我吃惊不小;不过想想也挺正常的。
<script src="https://unpkg.com/vue-function-api/dist/vue-function-api.umd.js"></script>
通过全局变量
window.vueFunctionApi
来使用。
至于具体使用么,在每个单文件组件SFC的<script>
标签里import,然后使用就好了。不过用法上稍微有点区别,第一个是API全部换成函数式的了,另一个就是没有了data()
,可响应属性的初始化以及所有的生命周期钩子都要放到新加的setup()
里。这是官方的例子:
<script>
import Vue from 'vue';
import { value, computed, watch, onMounted } from 'vue-function-api'
export default {
setup() {
// reactive state
const count = value(0);
// computed state
const plusOne = computed(() => count.value + 1);
// method
const increment = () => {
count.value++;
};
// watch
watch(
() => count.value * 2,
val => {
console.log(`count * 2 is ${val}`);
}
);
// lifecycle
onMounted(() => {
console.log(`mounted`);
});
// expose bindings on render context
return {
count,
plusOne,
increment,
};
},
};
</script>
可以看到,变化还是挺大的。里面的具体内容后续会单独提出来说,目前需要强调的是写法上的变化:可响应的数据和需要调用的方法(对应于之前的data()
和methods
)变成了setup()
的返回值,不然无法在模板里调用。
看起来可能确实不太习惯,不过这里改用setup()
有一个额外的好处:更符合写JavaScript的习惯。
看看官方文档咋说的:
setup(props: Props, context: Context):
Object|undefined
组件现在接受一个新的
setup
选项,在这里我们利用函数 api 进行组件逻辑设置。
setup()
中不可以使用this
访问当前组件实例, 我们可以通过setup
的第二个参数context
来访问 vue2.x API 中实例上的属性。const MyComponent = { props: { name: String }, setup(props, context) { console.log(props.name); // context.attrs // context.slots // context.refs // context.emit // context.parent // context.root } }
这里的context
取代了this
,成为了实例的“指针”;而this
直接指向了undefined
。至于为什么要这么设计,按照官方说法,是为了避免错误使用this:
setup() {
function onClick() {
this // 这里的this指向并不会是预期中的调用者
}
}
这里的this指向谁?我也不知道,完全取决于在什么context下调用这个函数。通常情况下,如果是在setup里调用,会是undefined
;如果是用v-on绑定,然后等待触发,因为Vue会自己进行代理,通常情况下,这个函数会绑定到不同的context(从调用栈来分析,似乎一般是null,比如绑定了click事件;或者一个Proxy对象,比如绑定了按键事件,因为Vue3是基于Proxy的),反正不可能是触发事件的那个DOM元素。
文档里也提到,setup()
中不能使用 this
访问当前组件实例,所以很多在2.x里用到的基于this
的用法都会发生改变,比如$refs
、$store
、$router
。这些之前绑定在根实例上的内容,需要通过context.root
获取。这些后面会提到。
另外,setup()
只会在创建时被调用一次。
还有一个,这里的props和context都是可响应的,会确保和修改同步。按照尤大的原话来说:
需要留意的是这里传进来的
props
对象是响应式的 —— 它可以被当作数据源去观测,当后续 props 发生变动时它也会被框架内部同步更新。但对于用户代码来说,它是不可修改的(会导致警告)。
不过这个“数据源”说得实在是有点模糊。事实上,props是不能作为watch的数据源使用的,因为watch的文档里提到,只能观测函数、包装对象和包含前两者的数组。我觉得,尤大在这里想表达的意思,应该是和2.x的文档里一致的:
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
也就是说,是一个可以动态响应的属性。我感觉Vue的核心思想还是数据驱动,对数据流的控制是很重视的。虽然实现上是双向绑定,但是数据流最好还是单向的。正如文档里提到的那样,实际使用的时候其实也没有必要修改props。如果确实需要修改传过来的props,也是保存到组件内部,作为组件本身的属性进行修改。
至于包装对象呢,放到后面说。
目录
Vue 3.0 function-based API尝鲜(一):前言
Vue 3.0 function-based API尝鲜(三):包装对象
Vue 3.0 function-based API尝鲜(四):值得一提的watch
Vue 3.0 function-based API尝鲜(五):生命周期