Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
sunmaobin opened this issue Apr 21, 2021 · 0 comments
Open

Comments

@sunmaobin
Copy link
Owner

1 说明

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

先看一个 vue 3.0 结合了 vue-router 和 伪 vuex 的效果:

img

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

源码:点击下载

最终效果:

![img](data:image/svg+xml;utf8,)

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

源码:点击下载

最终效果:

img

到此为止,相信大家搭建 vue 3.0 的工程应该就没啥大问题了。后续我们就结合这现有的工程,详细说明 vue3 的一些新特性,欢迎大家关注和点赞。

(本文完)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
前端
Awaiting triage
Development

No branches or pull requests

1 participant