阅读 3929

VUE 3.0 学习探索入门系列 - 用几个 demo 认识 vue3(3)

1 说明

  1. vue 3.0 运行环境必须支持 ES2015+ 语法(比如用高级浏览器)
  2. vue 3.0 目前暂时不支持 IE11(后续应该会推出支持的版本)
  3. 本示例的 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/vue@3.0.0-alpha.8/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/vue@3.0.0-alpha.8/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…

相比上一个示例,本示例大致的修改:

  1. 新增2个包:typescriptts-loader
  2. webpack.config.js 增加 ts-loader
  3. .js 文件修改为 .ts 文件
  4. .vue 文件中 export 对象变 Ts 对象,使用 vue3 内置方法 defineComponent
  5. <script> 变为 <script lang="ts">
  6. 新增文件 shims-vue.d.ts,不然 IDE 会解析报错
  7. 新增文件 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 的一些新特性,欢迎大家关注和点赞。

(本文完)

关注下面的标签,发现更多相似文章
评论