Vue3 目前已经进入到了 Beta 阶段,距离正式版也不远了(兴奋的搓手手)。相信很多开发者已经开始自己试用起来了,但是在搭建过程中可能会出现一些问题,此文仅仅作为记录一下自己创建第一个 Hello World 过程。
注:该文章中 Vue 版本为 3.0.0-beta.15
准备阶段
本次搭建是在 vue-cli3 的基础上进行搭建的(人懒没办法)。
- 安装 vue-cli3
npm i -g @vue/cli
- 正常创建项目
vue create vue3
反正只是简单的起个项目,所以我把Eslint、Router、Vuex、Typescript都关了。此处无图,都开始看 3 了,创建项目的步骤就略去了。
- 添加 Vue3
因为目前 Vue3 还没有发布正式版,目前名称为 vue-next,所以添加的时候名称不要写错了。
vue add vue-next
- 启动项目
npm run serve
到这一步,恭喜你已经启动了一个报错的 Vue3 项目!
解决报错
首先看下浏览器中的报错

h is not a function 这个 h 是指的 src/main.js 中 render 函数的报错
// 报错代码
createApp({
render: function (h) { return h(App) },
}).mount('#app')
// 解决办法
createApp(App).mount('#app')
原因:此处直接接受 component 组件,不用自己在去写render函数了。
// 源码位置:packages/runtime-core/src/apiCreateApp.ts
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
// 省略大段代码
mount(rootContainer: HostElement, isHydrate?: boolean): any {
const vnode = createVNode(rootComponent as Component, rootProps)
// 省略大段代码
// 看,这儿已经帮你render了
render(vnode, rootContainer)
}
}
}
此处解决后,浏览器应该是显示出了正确的页面,因为 Vue3 是同时接受 Vue2 的配置式写法的。
折腾阶段
src/App.vue 源文件
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
钩子函数(或是叫生命周期)
我们知道 Vue3 中新增了一个 setup api,那么其执行顺序是什么呢?
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
},
beforeCreate() {console.log('beforecreate')},
created() {console.log('created')},
beforeMount() {console.log('beforeMount')},
mounted() {console.log('mounted')},
setup() {console.log('setup')}
}
测试结果:setup 是最先执行的。其余的钩子函数执行顺讯和 Vue2 并无区别。

data/watch/computed
Vue3 中的各个 api 都已经独立了出来,可以根据自己的需求来引入。
监测基本类型
使用 ref 进行数据的监测,一般来说,使用 ref 进行基本数据的监测。
<template>
<div id="app">
<button @click="changeMsg1">msg1: {{msg1}}</button>
<div>msg2: {{msg2}}</div>
<div>msg3: {{msg3}}</div>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import { ref, watch, computed } from 'vue'
export default {
mounted() {
console.log(this);
this.msg3 = 'mounted changed value'
},
setup() {
const msg1 = ref(1);
const changeMsg1 = () => {
msg1.value += 1;
};
watch(msg1, (val, oldVal) => {
console.log(`new val: ${val}, old val: ${oldVal}`);
});
const msg2 = computed(() => msg1.value * 2);
const msg3 = ref();
return {
msg1,
changeMsg1,
msg2
};
}
};
</script>
看一下该段代码中的疑问:
- msg1.value 是个啥?
// 源码位置: packages/reactivity/src/ref.ts
export interface Ref<T = any> {
[isRefSymbol]: true
value: T
}
我们打印下 msg1:

由上面的接口可以看出 ref 对要监测的数据变成了一个 Object,如果要取值的话,需要使用 msg1.value 来设置/获取值。在 template 中可以直接使用 msg1。
- 如何 watch
// 源码位置: packages/runtime-core/src/apiWatch.ts
// 注意,源码中使用了函数重载,此处只写了一个
export function watch<T, Immediate extends Readonly<boolean> = false>(
source: WatchSource<T>,
cb: WatchCallback<T, Immediate extends true ? (T | undefined) : T>,
options?: WatchOptions<Immediate>
): StopHandle
使用方法:
watch(msg1, (val, oldVal) => {
console.log(`new val: ${val}, old val: ${oldVal}`);
});
watch 实际上与 Vue2 并无区别,只不过将其拆了出来。
- 如何 computed
使用方法:
const msg2 = computed(() => msg1.value * 2);
computed 也是和 Vue2 一样,使用一个函数的返回值作为其值,故 computed 需要return。
- return 的是什么?
此处 return 的是被监测的值。简单来说就是:如果你需要在模板文件或其他hook函数中使用这个值,那么你就需要将其 return。故 watch 不需要 return;computed 需要 return,因为你要使用其返回的值。
- 如何在其他生命周期中修改被监测的值呢?
上面第四条已经说过如果要使用该值,那么你就需要将其 return。
我们试着在其他 hook 函数中打印下 this:

这时候我们可以看到 this 是个 Proxy 对象,而我们 return 的值已经被代理了,那么我们在使用或修改该值的时候,直接 this.msg3 = 'modify' 就可以了。
这时候可能就有人要问:为啥我在 setup 中修改需要使用 this.msg.value ,在 hook 函数中只需要 this.msg 就行了呢?
那是因为在 setup 中的可以确定返回的就是一个对象,而将其 return 后,他被挂载到了 proxy 对象上,在获取的时候就可以正常获取了。(这儿没去看源码,个人猜的)
监测对象
<template>
<div id="app">
<button @click="changeMsg1">msg1: {{msg1.val}}</button>
<div>msg2: {{msg1.msg2}}</div>
</div>
</template>
<script>
import { reactive, watch, computed } from 'vue'
export default {
mounted() {
},
setup() {
const msg1 = reactive({
val: 1,
msg2: computed(() => msg1.val * 2)
});
console.log(msg1);
watch(msg1, (val, oldVal) => {
console.log(val === oldVal); // true
console.log(`new val: ${val.val}, old val: ${oldVal.val}`);
});
const changeMsg1 = () => {
msg1.val += 1;
};
return {
msg1,
changeMsg1
};
}
};
</script>
解释一下该段代码:
- msg1 也就是我们监测的对象。由于 computed 已经可以单独使用了,所以我们把它拆出来写到 reactive 里面。
- 看一下 msg1 是个啥

可以看到 msg1 是一个 Proxy 对象,而被 computed 的 msg2 是一个 ref 对象。
- 如果要 watch 的话,和 ref 的使用方法一样,我们打印一下这两个对象

这儿我们可以看到一个新值和老值。桥豆麻袋,老值和新值怎么相等了……额,这是bug吧o(╥﹏╥)o,github 上问一下,等有结果了后续更新。
- 没了吧。
component
那么如何引入 components 呢?答:和之前没有任何区别。
<template>
<div id="app">
<HelloWorld />
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld'
export default {
components: {
HelloWorld
}
};
</script>
结束语
虽然内容很简单,还是希望能够帮助一些不知道如何建项目的同学吧。