VUE 3.0 学习探索入门系列 - 用几个 demo 认识 vue3(3)
1 说明
- vue 3.0 运行环境必须支持
ES2015+语法(比如用高级浏览器) - vue 3.0 目前暂时不支持
IE11(后续应该会推出支持的版本) - 本示例的 vue 版本
3.0.0-alpha.8
先看一个 vue 3.0 结合了 vue-router 和 伪 vuex 的效果:
2 一个简单的 html 页面
记得 Evan You 好像在哪里说过,Vue 一定不会像某些框架一样,一定要依赖一个编译器,源码需要编译后才能运行在浏览器上。相反,Vue 一定会支持一个完整独立的 js,支持使用 CDN,可以直接在 html 中单独引入 js 使用。
所以,我们的第一个示例就是一个最简单的 html 页面。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue3.0 Demo</title>
<meta content="一个最简单的示例" name="description">
<script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>
</head>
<body>
<div id="app">
{{ message }}
</div>
<script>
const { createApp, ref } = Vue;
const App = {
setup() {
const message = ref('Hello Vue!');
return {
message
}
}
};
createApp(App).mount('#app');
</script>
</body>
</html>
3 稍微带一些交互逻辑的示例
示例参考地址:vue-composition-api-rfc.netlify.com/#basic-exam…
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue3.0 Demo</title>
<meta content="稍微带一些交互的示例" name="description">
<script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>
</head>
<body>
<div id="app">
<button @click="increment">
Count is: {{ state.count }}, double is: {{ state.double }}
</button>
</div>
<script>
const { createApp, reactive, computed } = Vue;
const App = {
setup() {
const state = reactive({
count: 0,
double: computed(() => state.count * 2)
});
function increment() {
state.count++
};
return {
state,
increment
};
}
};
createApp(App).mount('#app');
</script>
</body>
</html>
4 一个工程化的集成 webpack 的简单示例
在实际大型项目中,我们一般都是工程化的思路在架构前端,所以很少简单的写一个 html 页面,引入 vue 脚本,而是需要依赖编译器,一般是 webpack。
示例参考地址:github.com/vuejs/vue-n…
源码:点击下载
目录结构:
.
├── 1 index.html
├── 2 webpack.config.js
├── src
│ ├── 3 App.vue
│ └── 4 main.js
└── 5 package.json
1、index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Vue3.0 Demo</title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
2、webpack.config.js
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = (env = {}) => ({
mode: env.prod ? 'production' : 'development',
devtool: env.prod ? 'source-map' : 'cheap-module-eval-source-map',
entry: path.resolve(__dirname, './src/main.js'),
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/'
},
resolve: {
alias: {
// this isn't technically needed, since the default `vue` entry for bundlers
// is a simple `export * from '@vue/runtime-dom`. However having this
// extra re-export somehow causes webpack to always invalidate the module
// on the first HMR update and causes the page to reload.
'vue': '@vue/runtime-dom'
}
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.png$/,
use: {
loader: 'url-loader',
options: { limit: 8192 }
}
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: './index.html',
filename: 'index.html'
})
],
devServer: {
inline: true,
hot: true,
stats: 'minimal',
contentBase: __dirname,
overlay: true,
publicPath: '/',
historyApiFallback: true
}
})
3、src/App.vue
<template>
<h1>Hello Vue 3!</h1>
<button @click="inc">Clicked {{ count }} times.</button>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
const inc = () => {
count.value++
}
return {
count,
inc
}
}
}
</script>
<style scoped>
h1 {
font-family: Arial, Helvetica, sans-serif;
}
</style>
4、src/main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
5、package.json
{
"private": true,
"scripts": {
"dev": "webpack-dev-server",
"build": "webpack --env.prod"
},
"dependencies": {
"vue": "^3.0.0-alpha.8"
},
"devDependencies": {
"@vue/compiler-sfc": "^3.0.0-alpha.8",
"css-loader": "^3.4.0",
"file-loader": "^5.0.2",
"html-webpack-plugin": "^3.2.0",
"style-loader": "^1.1.3",
"url-loader": "^3.0.0",
"vue-loader": "^16.0.0-alpha.1",
"webpack": "^4.41.4",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1"
}
}
所有文件准备就绪后,执行以下命令即可:
npm i && npm run dev
源码:点击下载
5 尝试引入 vue2.x 中的 vue-router 和 vuex 的示例
经过一翻折腾,将 vue-router 成功引入到项目中,但是 vuex 由于目前官网还没有适配 vue 3.0,所以只能换一种新的思路了。
以下是目前发现的问题清单(vue3 生态还有待完善):
- vue-router 适配 vue3.0,查看 这个ISSUE
- vue-router 暂时不支持异步加载组件
lazy-loaded,查看 这个ISSUE - vuex 暂时未适配 vue3.0,查看 这个ISSUE
- 换一种思路在 vue3.0 上实现 vuex 效果,查看 这篇文章
源码:点击下载
目录结构:
.
├── 1 index.html
├── 2 webpack.config.js
├── src
│ ├── 3 App.vue
│ ├── 4 main.js
│ ├── router
│ │ └── 5 index.js
│ ├── store
│ │ └── 6 index.js
│ └── views
│ ├── 7 Page1.vue
│ └── 8 Page2.vue
└── 9 package.json
相比上一个示例,本示例增加了3个目录 router、store 和 views,同时修改了 main.js 和 App.vue。
1、index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Vue3.0 Demo</title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
2、webpack.config.js
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = (env = {}) => ({
mode: env.prod ? 'production' : 'development',
devtool: env.prod ? 'source-map' : 'cheap-module-eval-source-map',
entry: path.resolve(__dirname, './src/main.js'),
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/'
},
resolve: {
alias: {
// this isn't technically needed, since the default `vue` entry for bundlers
// is a simple `export * from '@vue/runtime-dom`. However having this
// extra re-export somehow causes webpack to always invalidate the module
// on the first HMR update and causes the page to reload.
'vue': '@vue/runtime-dom'
}
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.png$/,
use: {
loader: 'url-loader',
options: { limit: 8192 }
}
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: './index.html',
filename: 'index.html'
})
],
devServer: {
inline: true,
hot: true,
stats: 'minimal',
contentBase: __dirname,
overlay: true,
publicPath: '/',
historyApiFallback: true
}
})
3、src/App.vue
<template>
<h1>{{ message }}</h1>
<p>count:{{count}}</p>
<router-link to="/">Home</router-link>
<router-link to="/page1">Page1</router-link>
<router-link to="/page2">Page2</router-link>
<router-view></router-view>
</template>
<script>
import { toRefs } from 'vue'
import store from './store';
export default {
setup() {
let data = store.getState();
// WARNING:这里无法直接修改属性 readonly
data.count++;
return {
...toRefs(data)
}
}
}
</script>
<style scoped>
h1 {
font-family: Arial, Helvetica, sans-serif;
}
a + a {
margin-left: 10px;
}
</style>
4、src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App)
.use(router)
.mount('#app')
5、src/router/index.js
import { createRouter, createHistory } from '@posva/vue-router-next'
import Page1 from '../views/Page1.vue'
import Page2 from '../views/Page2.vue'
const routes = [
{
path: '/page1',
name: 'page1',
component: Page1
},
{
path: '/page2',
name: 'page2',
// lazy-loaded doesn't seem to be implemented yet
// https://github.com/vuejs/vue-next/issues/777
component: Page2
}
]
export const routerHistory = createHistory()
export const router = createRouter({
history: routerHistory,
base: process.env.BASE_URL,
routes
})
router.beforeEach((to, from, next) => {
console.log('beforeEach', to.name);
next()
})
export default router
6、src/store/index.js
import {reactive, readonly } from 'vue';
let store = reactive({
message: 'Hello Vue3!',
count: 1
});
export default {
getState() {
return readonly(store);
},
updateCnt() {
console.log('updateCnt', store.count);
store.count++;
}
}
7、src/views/Page1.vue
<template>
<h2>Page1</h2>
</template>
<script>
import store from '../store';
export default {
name: 'page2',
setup() {
// 更新 visit 全局变量
store.updateCnt();
}
}
</script>
<style scoped>
</style>
8、src/views/Page2.vue
<template>
<h2>Page2</h2>
</template>
<script>
import store from '../store';
export default {
name: 'page1',
setup() {
// 更新 visit 全局变量
store.updateCnt();
}
}
</script>
<style scoped>
</style>
9、package.json
{
"private": true,
"scripts": {
"dev": "webpack-dev-server",
"build": "webpack --env.prod"
},
"dependencies": {
"@posva/vue-router-next": "^4.0.0-alpha.0",
"vue": "^3.0.0-alpha.8"
},
"devDependencies": {
"@vue/compiler-sfc": "^3.0.0-alpha.8",
"css-loader": "^3.4.0",
"file-loader": "^5.0.2",
"html-webpack-plugin": "^3.2.0",
"style-loader": "^1.1.3",
"url-loader": "^3.0.0",
"vue-loader": "^16.0.0-alpha.1",
"webpack": "^4.41.4",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1"
}
}
所有文件准备就绪后,执行以下命令即可:
npm i && npm run dev
源码:点击下载
最终效果:

6 使用 Typescript 语法创建一个工程化的示例
本示例参考:dev.to/lmillucci/b…
相比上一个示例,本示例大致的修改:
- 新增2个包:
typescript和ts-loader -
webpack.config.js增加ts-loader -
.js文件修改为.ts文件 -
.vue文件中export对象变 Ts 对象,使用 vue3 内置方法defineComponent -
<script>变为<script lang="ts"> - 新增文件
shims-vue.d.ts,不然 IDE 会解析报错 - 新增文件
tsconfig.json,TS 配置文件
源码:点击下载
文件清单:
.
├── 1 index.html
├── 2 webpack.config.js
├── src
│ ├── 3 App.vue
│ ├── 4 main.ts
│ ├── 5 shims-vue.d.ts
│ ├── router
│ │ └── 6 index.ts
│ ├── store
│ │ └── 7 index.ts
│ └── views
│ ├── 8 Page1.vue
│ └── 9 Page2.vue
├── 10 tsconfig.json
└── 11 package.json
1、index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Vue3.0 Demo</title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
2、webpack.config.js
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = (env = {}) => ({
mode: env.prod ? 'production' : 'development',
devtool: env.prod ? 'source-map' : 'cheap-module-eval-source-map',
entry: path.resolve(__dirname, './src/main.ts'),
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/'
},
resolve: {
extensions: ['.ts', '.js', '.vue', '.json'],
alias: {
// this isn't technically needed, since the default `vue` entry for bundlers
// is a simple `export * from '@vue/runtime-dom`. However having this
// extra re-export somehow causes webpack to always invalidate the module
// on the first HMR update and causes the page to reload.
'vue': '@vue/runtime-dom'
}
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.ts$/,
loader: 'ts-loader',
options: {
appendTsSuffixTo: [/\.vue$/],
}
},
{
test: /\.png$/,
use: {
loader: 'url-loader',
options: { limit: 8192 }
}
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: './index.html',
filename: 'index.html'
})
],
devServer: {
inline: true,
hot: true,
stats: 'minimal',
contentBase: __dirname,
overlay: true,
publicPath: '/',
historyApiFallback: true
}
})
3、src/App.vue
<template>
<h1>{{ message }}</h1>
<p>Ts count:{{count}}</p>
<router-link to="/">Home</router-link>
<router-link to="/page1">Page1</router-link>
<router-link to="/page2">Page2</router-link>
<router-view></router-view>
</template>
<script lang="ts">
import { defineComponent, toRefs } from 'vue'
import store from './store';
export default defineComponent({
setup() {
let data = store.getState();
// WARNING:这里无法直接修改属性 readonly
// data.count++;
return {
...toRefs(data)
}
}
})
</script>
<style scoped>
h1 {
font-family: Arial, Helvetica, sans-serif;
}
a + a {
margin-left: 10px;
}
</style>
4、src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App)
.use(router)
.mount('#app')
5、src/shims-vue.d.ts
这个文件只需要放到 src 目录下就可以了,为什么?
declare module '*.vue' {
import { defineComponent } from 'vue';
const Component: ReturnType<typeof defineComponent>;
export default Component;
}
6、src/router/index.ts
import { createRouter, createHistory } from '@posva/vue-router-next'
import Page1 from '../views/Page1.vue'
import Page2 from '../views/Page2.vue'
const routes = [
{
path: '/page1',
name: 'page1',
component: Page1
},
{
path: '/page2',
name: 'page2',
// lazy-loaded doesn't seem to be implemented yet
// https://github.com/vuejs/vue-next/issues/777
component: Page2
}
]
export const routerHistory = createHistory()
export const router = createRouter({
history: routerHistory,
routes
})
router.beforeEach((to, from, next) => {
console.log('beforeEach', to.name);
next()
})
export default router
7、src/store/index.ts
import {reactive, readonly } from 'vue';
let store = reactive({
message: 'Hello Ts Vue3!',
count: 1
});
export default {
getState() {
return readonly(store);
},
updateCnt() {
console.log('updateCnt', store.count);
store.count++;
}
}
8、src/views/Page1.vue
<template>
<h2>Ts Page1</h2>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import store from '../store';
export default defineComponent({
name: 'page2',
setup() {
// 更新 visit 全局变量
store.updateCnt();
}
})
</script>
<style scoped>
</style>
9、src/views/Page2.vue
<template>
<h2>Ts Page2</h2>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import store from '../store';
export default defineComponent({
name: 'page1',
setup() {
// 更新 visit 全局变量
store.updateCnt();
}
})
</script>
<style scoped>
</style>
10、tsconfig.json
{
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"declaration": false,
"esModuleInterop": true,
"experimentalDecorators": true,
"module": "es2015",
"moduleResolution": "node",
"noImplicitAny": false,
"noLib": false,
"sourceMap": true,
"strict": true,
"strictPropertyInitialization": false,
"suppressImplicitAnyIndexErrors": true,
"target": "es2015",
"baseUrl": "."
},
"exclude": [
"./node_modules"
],
"include": [
"./src/**/*.ts",
"./src/**/*.vue"
]
}
11、package.json
{
"private": true,
"scripts": {
"dev": "webpack-dev-server",
"build": "webpack --env.prod"
},
"dependencies": {
"@posva/vue-router-next": "^4.0.0-alpha.0",
"vue": "^3.0.0-alpha.8"
},
"devDependencies": {
"@vue/compiler-sfc": "^3.0.0-alpha.8",
"css-loader": "^3.4.0",
"file-loader": "^5.0.2",
"html-webpack-plugin": "^3.2.0",
"style-loader": "^1.1.3",
"ts-loader": "^6.2.1",
"typescript": "^3.8.3",
"url-loader": "^3.0.0",
"vue-loader": "^16.0.0-alpha.1",
"webpack": "^4.41.4",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1"
}
}
所有文件准备就绪后,执行以下命令即可:
npm i && npm run dev
源码:点击下载
最终效果:
到此为止,相信大家搭建 vue 3.0 的工程应该就没啥大问题了。后续我们就结合这现有的工程,详细说明 vue3 的一些新特性,欢迎大家关注和点赞。
(本文完)