> ## Content Index
> Fetch the complete content index at: https://iiong.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Vue3 体验
- URL: https://iiong.com/vue3-use-notes/
- Published: 2020-09-17T13:30:00.000Z
- Updated: 2026-06-22T15:27:58.000Z
- Description: 从去年开始就有 Vue3 各种消息，一直比较期待 V3 的版本，因为 V2 针对 TypeScript 不是太完善，支持不是太好，一直没用上。其次针对 React 来比， V2 又显然太死板，并且在大项项目构架上来看，复用性很差。
- Author: 淮城一只猫
- Tags: 编程技术, Vue.js

### 前言

经过几个月后，Vue3 慢慢成长逐渐成熟，今天进入 `RC` 阶段，意味着核心 `Api` 稳定下来了。查看周边的生态也慢慢丰富了，所以抽出点时间查阅相关资料。当我阅读 [V3 文档](https://v3.vuejs.org/guide/introduction.html?ref=iiong.com) 的时候发现很多写法和 `V2` 差不多，过渡没有多大问题，当然在脚手架开发中不好说了。所以简单弄个脚手架项目。

最近整合 `Vue3` 项目可以参考：[https://github.com/JaxsonWang/Vue3-Ant-Design-Admin-Pro](https://github.com/JaxsonWang/Vue3-Ant-Design-Admin-Pro?ref=iiong.com)

### 新建项目

新建 `vue3` 目前有俩种，分别是 `vite` 和 `@vue/cli`，`vite` 下面有描述，这边依然采用 `@vue/cli` 脚手架来新建项目。首先需要安装最新的 `@vue/cli` 来新建一个 `vue3` 项目：

```bash
npm install @vue/cli
vue create vue3-demo

# 输出如下
Vue CLI v4.5.6
? Please pick a preset: (Use arrow keys)
  Default ([Vue 2] babel, eslint)
> Default (Vue 3 Preview) ([Vue 3] babel, eslint)
  Manually select features

```

选择第二个进行创建 `Vue3` 项目，里面只包含最基础的 `Babel` 和 `Vue3` 的项目，如果你需要支持路由状态管理器和样式处理器需要自己额外安装。下面将踩坑体验下其中的过程。

### Router、Vuex 和 SASS 支持

查看下项目发现和 `vue2` 改动不是很大，除了一些细节上的要点，剩下和`v2`没什么区别了，下面就直接按照 `Router` 、`Vuex` 和 `SASS` 来支持：

```bash
yarn add vue-router@next vuex@4.0.0-beta.4
yarn add sass sass-loader -D

```

按照好了再进行相关配置就可以基本能正常开发一个 `Vue3` 项目了。

首先配置路由，在 `src/router` 文件夹下新建 `index.js` 写入：

```javascript
import { createRouter, createWebHashHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import(/* webpackChunkName: "home" */ '../views/Home.vue')
  },
  {
    path: '/about-vue',
    name: 'About-Vue',
    component: () => import(/* webpackChunkName: "about-vue" */ '../views/About.vue')
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes: routes
})

export default router

```

这边新建路由实例和以前版本不一样，其他没啥区别了，细节上的差别可以查阅下文档。

至于 `Vuex` 和老版本完全一样，没有不一样的地方，这边就不详细解说了：

```javascript
import { createStore } from 'vuex'
import getters from './getters'
import app from './modules/app'
import user from './modules/user'

const store = createStore({
  getters,
  modules: {
    app,
    user
  }
})

export default store

```

学习源码：[vue3-router-vuex](https://github.com/JaxsonWang/Golden-House/tree/master/2020/0917/Vue3/vue3-router-vuex?ref=iiong.com)

### JSX 的支持

对于复杂的 `UI` 界面使用 `template` 去编写不合适了，例如类似递归渲染，比如菜单，使用 `JSX` 更合适，在这个项目里可以一部分用 `template` 编写也可以一部分使用 `JSX` 编写，可是相当舒服，`Vue3` 在社区采纳 `JSX` 很多方案，最终确定由 `ant-design-vue` 团队定制的 `JSX` 方案，大致看了下还不错，安装也相当简单：

```bash
yarn install @vue/babel-plugin-jsx -D

```

在 `babel.config.js` 添加规则：

```javascript
plugins: [
  '@vue/babel-plugin-jsx'
]

```

然后新建个 `jsx` 文件可以进行编写了，我这边编写一个 `jsx` 渲染的页面，定义一个路由：

```javascript
const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import(/* webpackChunkName: "home" */ '../views/Home.vue')
  },
  {
    path: '/about-vue',
    name: 'About-Vue',
    component: () => import(/* webpackChunkName: "about-vue" */ '../views/About.vue')
  },
  {
    path: '/about-jsx',
    name: 'About-JSX',
    component: () => import(/* webpackChunkName: "about-jsx" */ '../views/About.jsx')
  }
]

```

`About.jsx` 文件如下：

```jsx
import { mapGetters } from 'vuex'

const styles = {
  about: {
    border: '1px solid #ffaaee'
  }
}

const About = {
  name: 'About',
  computed: {
    ...mapGetters([
      'systemName'
    ])
  },
  methods: {
    onInputChange(event) {
      this.$store.dispatch('app/setSystemName', event.target.value)
    }
  },
  render() {
    return <>
      <div>
        <h3>这是 JSX 页面</h3>
        <label>
          <input type="text" placeholder="系统名称" style={styles.about} value={this.systemName} onInput={this.onInputChange}/>
        </label>
      </div>
    </>
  }
}

export default About

```

后面被人请教 `jsx` 的组件事件调用，使用方法也相当简单，先编写个组件传递事件：

```jsx
import { defineComponent } from 'vue'

const HelloVue = defineComponent({
  props: {
    msg: {
      require: false,
      type: String,
      default: ''
    }
  },
  methods: {
    emitClick(event) {
      this.$emit('hello-vue', event)
    }
  },
  render() {
    return <>
      <h3 onClick={this.emitClick}>{ this.msg }</h3>
    </>
  }
})

export default HelloVue

```

在父类接受事件回调：

```jsx
import { mapGetters } from 'vuex'
import HelloVue from '@/components/HelloVue.jsx'

const styles = {
  about: {
    border: '1px solid #ffaaee'
  }
}

const About = {
  name: 'About',
  components: {
    HelloVue
  },
  computed: {
    ...mapGetters([
      'systemName'
    ])
  },
  methods: {
    onInputChange(event) {
      this.$store.dispatch('app/setSystemName', event.target.value)
    },
    helloVueAction(event) {
      alert('我被点击了！')
      console.log(event)
    }
  },
  render() {
    return <>
      <div>
        <h3>这是 JSX 页面</h3>
        <hello-vue msg="这是一个被调用的 JSX 的组件，你可以点击试试看！" onHello-vue={this.helloVueAction}/>
        <label>
          <input type="text" placeholder="系统名称" style={styles.about} value={this.systemName} onInput={this.onInputChange}/>
        </label>
      </div>
    </>
  }
}

export default About

```

大致就像这样一个效果，更多用法请参考文档：[Babel Plugin JSX for Vue 3.0](https://github.com/vuejs/jsx-next?ref=iiong.com)

学习源码：[vue3-router-vuex-jsx](https://github.com/JaxsonWang/Golden-House/tree/master/2020/0917/Vue3/vue3-router-vuex-jsx?ref=iiong.com)

### TypeScript支持

既然 `JSX` 都用上了，那么对 `TypeScript` 支持不能少，虽然所在的公司几乎用不上，不过多掌握一门技术对自己没有坏处。

在 `vue create vue3-demo` 选项选择最后一个然后添加 `typescript` 支持就行了，我是后面才发觉，不然上面那些安装方法直接省略...`ts` 的写法无法就是加上数据类型支持，不过我接触的比较少，所以用起来不是太熟练，`typescript` 愣生生被我用成 `anyscript` 了，当然一套下来感觉还是没 `React` 爽快。

学习源码：[Vue3 + Vue Router + Vuex + TSX](https://github.com/JaxsonWang/Golden-House/blob/master/StudyNotes/Vue3/vue3-router-vuex-tsx?ref=iiong.com)

### 参考文档

- [Vue 3 官方中文参考文档](https://vue-docs-next-zh-cn.netlify.app/?ref=iiong.com)
- [Vue 3 Jsx 文档](https://github.com/vuejs/jsx-next?ref=iiong.com)

---

### 以下皆为历史记录

### Vite

在 `Vue-Cli` 使用中，发现热更新和编译页面非常慢，所以作者放弃基于 `Webpack` 开发的脚手架，全新开发新的脚手架：[Vite](https://github.com/vitejs/vite?ref=iiong.com) ，诸多新特征查阅相关文档，这边不做详述，但对于老版本的脚手架来比，上手几乎没有任何难点，参考的 `Api` 和老版本一致，新建新的 `Vue3` 项目很简单：

```bash
npm init vite-app hello-vue3

```

就会生成比较简单的基础 `Vue3` 项目，运行 `npm run dev` 即可看到项目内容。

### 安装 TypeScript

下面把项目转换成 `TSX` 的项目，首先安装相关依赖：

```bash
yarn add typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-plugin-vue -D

```

配置 `tsconfig.json` 文件：

```json
{
  "include": [
    "./**/*.ts"
  ],
  "compilerOptions": {
    "jsx": "react",
    "target": "es2020",
    "module": "commonjs",
    "sourceMap": true,
    "strict": true,
    "noUnusedLocals": true,
    "noImplicitReturns": true,
    "moduleResolution": "node",
    "esModuleInterop": true
  }
}

```

配置语法校验规则 `.eslintrc.js` 文件：

```js
module.exports = {
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: '@typescript-eslint/parser',
    ecmaVersion: 2020,
    sourceType: 'module',
    ecmaFeatures: {
      tsx: true,
      jsx: true,
    },
  },
  extends: [
    'plugin:vue/vue3-recommended',
    'plugin:@typescript-eslint/recommended'
  ],
  rules: {
  }
}

```

在 `src/` 目录下新建 `typescript` 类型推断优化配置：

`source.d.ts` 静态资源优化：

```typescript
declare const React: string
declare module '*.json'
declare module '*.png'
declare module '*.jpg'

```

`shim.d.ts` ：

```typescript
declare module '*.vue' {
  import Vue from 'vue';
  export default Vue;
}

```

修改 `index.html` 文件入口：

```typescript
<script type="module" src="/src/main.ts"></script>

```

### 转换 TSX 项目

修改项目入口文件 `main.ts` :

```typescript
import { createApp } from 'vue'
import App from './App'
import './index.css'

createApp(App).mount('#app')

```

然后这边报错，是因为 `App` 类型推断错误导致，所以继续修改 `App.vue` 文件，将文件转换成 `App.tsx` ：

```tsx
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'App',
  setup() {
    return () =>
      <>
        <div class="container">
          Hello World
        </div>
      </>
  }
})

```

然后重新启动服务即可看到全新的 `TSX` 项目。

### 编写组件

本来在 `components` 文件夹下新建组件，无奈引入的时候一直报错，搜查资料也没有搜到相关资料，所以这边就在当前目录下随意折腾。编写 `tsx` 组件没有想象中那么复杂，比如新建个 `Title.tsx` 组件：

```tsx
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'Title',
  setup() {
    return () =>
      <>
        <h1 class="title">
           This is title.
        </h1>
      </>
  }
})

```

然后在 `App.tsx` 引入调用即可：

```tsx
import { defineComponent } from 'vue'
import Title from './Title'

export default defineComponent({
  name: 'App',
  setup() {
    return () =>
      <>
        <div class="container">
          <Title/>
          Hello World
        </div>
      </>
  }
})

```

如果组件开放 `props ` 属性给父组件传入参数也很简单，只需要把 `Title.tsx` 修改成：

```tsx
import { defineComponent } from 'vue';
export default defineComponent({
  name: 'Title',
  props: {
    title: {
      type: String,
      require: false,
      default: 'This is title.'
    }
  },
  setup(props) {
    return () =>
      <>
        <h1 class="title">
          { props.title }
        </h1>
      </>
  }
})

```

组件的 `props` 声明和之前没有什么区别，在 `tsx` 写法需要暴露 `props` 在 `render` 使用，然后在父组件只需要 `<Title title="Hey!This my title!" />` 使用即可。

**子组件事件传递**

如果需要将子组件的事件传递到父组件也简单，只需要用到 `setup` 的第二个参数对象中的 `emit` 使用方式和 `vue2` 一样：

```tsx
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'Title',
  props: {
    title: {
      type: String,
      require: false,
      default: 'This is title.'
    }
  },
  setup(props, context) {
    return () =>
      <>
        <h1 class="title" onClick={() => context.emit('data')}>
          { props.title }
        </h1>
      </>
  }
})

```

但目前不知道在父组件如何调用...这边暂时就到这里吧。

---

在插件的支持下，传递事件如下：

```tsx
import { defineComponent, PropType } from 'vue'

export default defineComponent({
  name: 'Title',
  props: {
    title: {
      type: String,
      require: false,
      default: 'This is title.'
    },
    onTitleClick: Function as PropType<(event: MouseEvent) => void>
  },
  setup (props, context) {
    return () =>
      <>
        <h1 class="title" onClick={(event: MouseEvent) => context.emit('title-click', event)}>
          { props.title }
        </h1>
      </>
  }
})

```

注意 `props` 要声明事件， 然后外部调用：

```tsx
import { defineComponent } from 'vue'
import Title from './components/HelloWorld'

export default defineComponent({
  name: 'App',
  setup () {
    const onTitleClick = (event: any) => {
      console.log(event)
    }
    return () =>
      <>
        <div class="container">
          <Title title="这是一个例子" onTitleClick={onTitleClick} />
          Hello World
        </div>
      </>
  }
})

```

下面是历史文章，可以无视\~

### 前言

从去年开始就有 `Vue3` 各种消息，一直比较期待 `V3` 的版本，因为 `V2` 针对 `TypeScript` 不是太完善，支持不是太好，一直没用上。其次针对 `React` 来比， `V2` 又显然太死板，并且在大项项目构架上来看，复用性很差。所以在之前的 `Vue3 PPT` 讲到诸多特性，知道前几天的 `Beta` 版本出现，虽然 `Alpha` 版本尝鲜过，无奈问题太多。所以趁此机会折腾一波，前置学习下，趁机弯道超过各位大佬（#滑稽脸）。

### 建立

最近的 `Beta` 版本不需要建立 `Webpack` 项目，所以直接基于 `Vue-CLI` 脚手架生成的项目，直接注入相关插件就支持 `Vue3` 项目了。

使用 `Vue Cli` 建立项目：

```bash
vue create vue3-demo

Vue CLI v4.3.1
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS, CSS Pre-processors, Lin
ter
? Use class-style component syntax? No
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfi
lls, transpiling JSX)? No
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported 
by default): Sass/SCSS (with dart-sass)
? Pick a linter / formatter config: Standard
? Pick additional lint features: Lint on save, Lint and fix on commit
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated confi
g files
? Save this as a preset for future projects? No

vue add vue-next # 安装插件

```

安装上去发现 `package.json` 添加几个依赖以及 `App.vue` 发生改变，但还需要进行进一步配置：

- 删除 `shims-tsx.d.ts` 文件，控制台一直显示错误，新版本存在兼容性问题，等官方说明，删掉不影响使用；
- 修改 `App.vue` 内容：

```typescript
<template>
  <div class="home">
    <img src="./assets/logo.png" alt="logo">
    <h1>Hello Vue 3!</h1>
    <button @click="inc">Clicked {{ count }} times.</button>
    <div class="todo-area">
      <todo-list/>
    </div>
  </div>
</template>a

<script lang="ts">
import { ref } from 'vue'
import TodoList from '@/components/TodoList.vue'

export default {
  setup () {
    const count = ref(0)
    const inc = () => {
      count.value++
    }
    return {
      count,
      inc
    }
  },
  components: {
    TodoList
  }
}
</script>

<style lang="scss" scoped>
.home {
  img {
    width: 200px;
  }

  h1 {
    color: #41b983;
    font-family: Arial, Helvetica, sans-serif;
  }
}
</style>

```

`@/components/TodoList.vue` 随便填充点内容，启动服务 `yarn dev` 就可以看到新版本的页面了。

### Router / Vuex 整合

未完待续

### 实战

打算利用新的 `Api` 做个简单的 `Todo List` 例子：

```typescript
<template>
  <div class="todo-list">
    <h3>Todo List</h3>
    <div class="add-todo-area">
      <label>
        <input v-model="addTodoName"/>
      </label>
      <label>
        <button @click="addTodoAction">新增清单</button>
      </label>
    </div>
    <div class="todo-area">
      <h3>任务清单</h3>
      <ul class="todo-list">
        <li
           v-for="item in undoneTodoList"
           :key="item.id"
           class="todo-item"
        >
          {{ item.name }}
          <button @click="doneTodo(item)">已完成</button>
          <button @click="delTodoAction(item, true)">删除</button>
        </li>
      </ul>
    </div>
    <div class="done-todo-area">
      <h3>已完成的任务清单</h3>
      <ul class="todo-list">
        <li
           v-for="item in completedTodoList"
           :key="item.id"
           class="todo-item"
        >
          {{ item.name }}
          <button @click="delTodoAction(item, false)">删除</button>
        </li>
      </ul>
    </div>
  </div>
</template>

<script lang="ts">
import { ref, reactive } from 'vue'

import { TodoList } from './types/TodoList'

export default {
  setup () {
    const addTodoName = ref<string>('')
    const undoneTodoList: TodoList[] = reactive([]) // 清单列表
    const completedTodoList: TodoList[] = reactive([]) // 已完成的清单列表

    const addTodoAction = () => {
      const obj = {
        id: new Date().valueOf(),
        name: addTodoName
      }
      undoneTodoList.push(JSON.parse(JSON.stringify(obj)))
      addTodoName.value = ''
    }

    const delTodoAction = (item: TodoList, todo: boolean) => {
      if (todo) {
        undoneTodoList.splice(undoneTodoList.findIndex(i => i.id === item.id), 1)
      } else {
        completedTodoList.splice(completedTodoList.findIndex(i => i.id === item.id), 1)
      }
    }

    const doneTodo = (item: TodoList) => {
      undoneTodoList.splice(undoneTodoList.findIndex(i => i.id === item.id), 1)
      completedTodoList.push(item)
    }

    return {
      addTodoName,
      addTodoAction,
      delTodoAction,
      doneTodo,
      undoneTodoList,
      completedTodoList
    }
  }
}
</script>

<style lang="scss" scoped>
</style>

```

### 参考

[Vue Composition API RFC](https://vue-composition-api-rfc.netlify.app/?ref=iiong.com)

[Github vue-cli-plugin-vue-next](https://github.com/vuejs/vue-cli-plugin-vue-next?ref=iiong.com)

[Github vue-next](https://github.com/vuejs/vue-next?ref=iiong.com)

[Github vue-next-webpack-preview](https://github.com/vuejs/vue-next-webpack-preview?ref=iiong.com)