vue3.0使用流程和变化

安装流程

1.安装vite脚手架

npm install -g create-vite-app
# or
yarn add -g create-vite-app

2.项目初始化

yarn create vite-app <project-name>

3.集成TS

yarn add --dev typescript

4.项目根目录创建配置文件:tsconfig.json:

{
  "include": ["./**/*.ts"],
  "compilerOptions": {
    "jsx": "react",
    "target": "es2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
    "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
    // "lib": ["es2017.object"] /* Specify library files to be included in the compilation. */,
    // "declaration": true /* Generates corresponding '.d.ts' file. */,
    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
    "sourceMap": true /* Generates corresponding '.map' file. */,
    // "outFile": "./",                       /* Concatenate and emit output to single file. */
    "outDir": "./dist" /* Redirect output structure to the directory. */,

    "strict": true /* Enable all strict type-checking options. */,
    "noUnusedLocals": true /* Report errors on unused locals. */,
    "noImplicitReturns": true /* Report error when not all code paths in function return a value. */,

    "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
    "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
  }
}

5.集成eslint(如果不想安装,可以忽略5,6)

yarn add --dev eslint eslint-plugin-vue

6.项目根目录创建配置文件.eslintrc.js:

module.exports = {
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: '@typescript-eslint/parser', // Specifies the ESLint parser
    ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
    sourceType: 'module', // Allows for the use of imports
    ecmaFeatures: {
      // tsx: true, // Allows for the parsing of JSX
      jsx: true,
    },
  },
  // settings: {
  //   tsx: {
  //     version: "detect" // Tells eslint-plugin-react to automatically detect the version of React to use
  //   }
  // },
  extends: [
    'plugin:vue/vue3-recommended',
    'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
    'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
    'plugin:prettier/recommended', // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
  ],
  rules: {
    // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
    // e.g. "@typescript-eslint/explicit-function-return-type": "off",
  },
};

7.集成pritter

yarn add --dev prettier eslint-config-prettier eslint-plugin-prettier

8.项目根目录创建配置文件:.prettierrc.js:

module.exports = {
  semi: true,
  trailingComma: "all",
  singleQuote: true,
  printWidth: 100,
  tabWidth: 2,
  endOfLine:"auto"
};

到这一步,一个Vue3+TSX的项目就搭建起来了,以上配置文件的具体内容就不做解释了。
9.修改入口文件

因为默认项目模板是以src/main.js为入口的,我们需要把它修改为src/main.ts。
在根目录的index.html中修改入口文件的引用即可:

... ...
<body>
  ... ...
  <script type="module" src="/src/main.ts"></script>
</body>
</html>

10.优化TS类型推断

在src目录下,创建shim.d.ts、source.d.ts
shim.d.ts: (这个其实不太需要,因为项目中全是通过tsx开发的)

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

source.d.ts: (优化编译器提示,声明静态资源文件)

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

11.集成vue-router

yarn add --dev vue-router@4.0.0-beta.2

这里可以去npm官网查找最新版本
在src目录下,新建router文件夹,并在文件夹内创建index.ts index.ts:

import { RouteRecordRaw, createRouter, createWebHistory } from 'vue-router';

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    component: import('../views/Home'),
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('../views/About'),
  },
];

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
});

export default router;

这里创建router的方式与之前不同,在vue3中,结合TS的类型推断,开发效率会高很多。

12.集成vuex

yarn add --dev vuex@4.0.0-beta.4

在src目录下,新建store文件夹,并在文件夹内创建index.ts
index.ts:

import { state } from './state';
import { createStore } from 'vuex';

export default createStore({
  state,
  mutations: {},
  actions: {},
  modules: {},
});

state.js:

export interface State {
  title: string;
}

export const state: State = {
  title: 'Vue(v3) 与 tsx 的结合~',
};

13.main.ts

最终main.ts中引入store、router:

import { createApp } from 'vue';
import Root from './App.vue';
import router from './router';
import store from './store';

const app = createApp(Root);
app.use(router);
app.use(store);
app.mount('#app');

export default app;

以上安装流程来自Vue3全家桶 + Vite + TS + TSX尝鲜,先人一步!

vue3.0发生的变化

vue3现在没有this,官方的解释:在 setup() 内部,this 不会是该活跃实例的引用,因为 setup() 是在解析其它组件选项之前被调用的,所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同。这在和其它选项式 API 一起使用 setup() 时可能会导致混淆。那么问题来了没有this我们如何使用router,store,emit, nextTick这些方法呢?下面会有案例来说明如何使用。

1.创建方式改变

vue创建方式

老版本

var app = new Vue({
  el: '#app',
  data: {}
})

新版本

import { createApp } from 'vue';
createApp(Root).mount('#app')

vuex创建方式

老版本

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

新版本

import { state } from './state.js';
import { createStore } from 'vuex';

export default createStore({
  state,
  mutations: {},
  actions: {},
  modules: {},
});

//使用方式
<script type="ts">
import {} from 'vue';
import { useStore } from 'vuex';
import axios from '../../../axios/index';
export default {
  name: 'NavMenu',
  props: {
  },
  setup(props, context) {
    const store = useStore();
    const getMenu = () => {
      const url = 'xxxx';
      axios('post', url).then((res) => {
        routerList.value = res;
        store.commit('setRouterList', res)
      });
    };
    return {
    };
  },
};
</script>

vue-router创建方式

老版本

import VueRouter from 'vue-router';
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User,
      children: [
        {
          path: 'profile',
          component: UserProfile
        },
        {
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

新版本

import { RouteRecordRaw, createRouter, createWebHashHistory } from 'vue-router';
const routes: RouteRecordRaw[] = [{
 path: '/',
    name: '首页',
    component: getComponentsPath('Layout'),
    children: [{
      { path: '/', redirect: '/subassembly/base-subassembly' },
      {
        path: '/subassembly',
        name: '组件',
        component: view(),
        children: [
          {
            path: '/subassembly/base-subassembly',
            name: '基础组件',
            component: getViewPath('subassembly/baseSubassembly'),
          },
          {
            path: '/subassembly/complexity-subassembly',
            name: '复杂组件',
            component: getViewPath('subassembly/complexitySubassembly'),
          },
        ],
      },
    }]
}]
const router = createRouter({
  history: createWebHashHistory(process.env.BASE_URL),//可以换成History 模式
  routes,
});

export default router;

2.写法的改变
新版中需要注意的点都在注释中

老版本data函数 => 新版本setup函数

老版本

<template>
  <div>{{num}}</div>
</template>

<script>
  export default {
  data() {
    return {
      num:0
    };
  },
}
</script>

<style lang='less' scoped>

</style>

新版本

<template>
  <div>{{num}}</div>
</template>

<script>
import { ref, reactive, toRef } from 'vue';
export default {
  setup() {
    //vue2.x中的data函数
    const num = 0; //不具备响应式特性
    const cunst = ref(0); //单个属性具备响应式特性
  //如果想让引用数据类型的值有响应式状态就需要
  //const books = reactive([ref('Vue 3 Guide')]),下面的这种写法
    const checkList = reactive([]);
    //多个属性具备响应式特性
    const state = reactive({
      name: '张三',
      age: 18,
    });


    return {
      num,
      cunst,
      //因为setup本身是返回一个对象的如果有多个返回可以使用ES6的展开运算符,
      //但是reactive方法有bug对于直接把state放入return中会丢失响应式特效所以在返回之前用toRef方法包裹
      //toRef:简单来讲就是让reactive中的值每个都具有响应式特性
      ...toRef(state),
    };
  },
};
</script>

<style lang='less' scoped>
</style>

生命周期
老版本

<template>
  <div>
  </div>
</template>

<script>
  export default {
  created() {

  },
  mounted() {

  },
  methods: {

  },
}
</script>

<style lang='less' scoped>

</style>

新版本

//生命周期函数,vue3.0删除掉两个生命周期
//beforeCreate -> use setup()
//created -> use setup()

<template>
  <div></div>
</template>

<script>
import { ref, reactive, onMounted, onUpdated, onUnmounted, toRef } from 'vue';
export default {
  setup() {
    onMounted(() => {
      console.log('mounted!');
    });
    onUpdated(() => {
      console.log('updated!');
    });
    onUnmounted(() => {
      console.log('unmounted!');
    });

    return {
    };
  },
};
</script>

<style lang='less' scoped>
</style>

3.注册全局组件
老版本

import Vue from 'vue';
import content from "./content.vue";
Vue.component("my-content ", content );

新版本

import myInput from './Input/index.vue';
import myRadio from './Radio/index.vue';
import myCheckbox from './Checkbox/index.vue';
export default {
  install: (Vue: any) => {
    Vue.component("MyComponentInput",myInput);
    Vue.component("MyComponentRadio",myRadio);
    Vue.component("MyComponentCheckbox",myCheckbox);
  },
};

main文件中引入

import components from './components/components';
const app = createApp(Root);
app.use(components);
app.mount('#app');

其实按照vue3的文档上来看的话创建全局组件和vue2时没有变化,但是我用的时候报错,原因没有找到,下面是vue3文档的写法

const app = Vue.createApp({...})

app.component('my-component-name', {
  /* ... */
})

4.Vue3.0配置全局属性的方法
老版本
Vue 2.0之前我们封装axios和message等通用属性的时候是这么写的:

Vue.prototype.$api = api
Vue.prototype.$http = http

新版本

const app=createApp(App);

app.use(store);
app.use(router);
app.use(Antd);
app.mount('#app');

// 配置全局属性
app.config.globalProperties.$message = message;
app.config.globalProperties.$http=http;
app.config.globalProperties.$api=api;

//使用方式
import { getCurrentInstance } from "vue";
在setUp函数中通过 
const { ctx } = getCurrentInstance(); //获取上下文实例,ctx=vue2的this   
ctx.$api ()
//或者
const { proxy } = getCurrentInstance(); 
proxy.http();  

vue3中使用Provide/Inject依赖注入,将替代vue2中在原型链上挂载一些属性

 //APP.vue
   //在app.vue内注册
    import { provide ,ref ,readonly} from "vue";
    setup() {
    //用ref包裹可使location变为响应式
        let location=ref('location')
        const updataLocation=()=>{
           location.value='变身后的大Location'
        }
        /*readonly包裹后可以在组件内引用时不被改变值。
        否则在组件内可以直接通过location.value=***将值改变,
        包裹后只能通过updataLocation()方法改变*/
        provide('location',readonly(location))
        provide('updataLocation',updataLocation)
       }

    //组件内使用
    import { inject} from "vue";
    setup(){
        const userLocation = inject('location')
        const updataLocation = inject('updataLocation')
        return{
        userLocation,updataLocation
        }
    }

5.vue3封装input

//对于所有不带参数的 v-model,请确保分别将 prop 和 event 命名更改为 modelValue 和 update:modelValue
//详情查看https://vue-docs-next-zh-cn.netlify.app/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5

<template>
  <div>
    <input
      :class="dynamic"
      :type="type"
      :maxlength="maxlength"
      :minlength="minlength"
      :placeholder="placeholder"
      :disabled="disabled"
      @input="emitValue"
      :value="modelValue"
    />
  </div>
</template>

<script>
import { onMounted, reactive, computed, toRefs } from 'vue';
export default {
  name: 'MyComponentInput',
  props: {
    type: {
      tyep: String,
      default: () => 'text',
    },
    maxlength: {
      tyep: Number,
      default: 255,
    },
    minlength: {
      tyep: Number,
      default: 0,
    },
    placeholder: {
      tyep: String,
      default: '',
    },
    disabled: {
      tyep: Boolean,
      default: false,
    },
    size: {
      tyep: String,
      default: 'small',
    },
    modelValue: {
      tyep: String,
      default: '',
    },
  },
  setup(props, { emit }) {
    const size = props.size;
    const dynamic = computed(() => {
      let c = '';
      switch (size) {
        case 'large':
          c = 'large';
          break;
        case 'small':
          c = 'small';
          break;
        case 'mini':
          c = 'mini';
          break;
      }
      return c;
    });
    onMounted(() => {
    });
    const emitValue = (e) => {
      let value = e.target.value;
      emit('update:modelValue', value);
    };
    return {
      dynamic,
      emitValue,
    };
  },
};
</script>

<style lang='less' scoped>
input {
  background-color: #fff;
  background-image: none;
  border-radius: 4px;
  border: 1px solid #dcdfe6;
  box-sizing: border-box;
  color: #606266;
  display: inline-block;
  font-size: inherit;
  height: 40px;
  line-height: 40px;
  outline: none;
  padding: 0 15px;
  transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
  width: 100%;
}
.large {
  height: 36px;
  line-height: 36px;
}
.small {
  height: 32px;
  line-height: 32px;
}
.mini {
  height: 28px;
  line-height: 28px;
}
</style>

可以看出来现在不需要在用model来设置props和event,外面也不需要在用.snyc来接值

6.如何使用路由跳转和vue内置方法

setup函数中有两个参数,props,context
context中有attrs,slots,emit

<template>
  <div></div>
</template>

<script>
import { } from 'vue';
export default {
  setup(props,context) {

    return {
    };
  },
};
</script>

<style lang='less' scoped>
</style>

使用emit方法

<template>
  <div></div>
</template>

<script>
import {} from 'vue';
export default {
  setup(props, { emit }) {
    emit('add', 123);
    return {};
  },
};
</script>

<style lang='less' scoped>
</style>

使用nextTick等方法

<template>
  <div>{{ num }}</div>
</template>

<script>
import { rref, onMounted, onUpdated, onUnmounted,nextTick } from 'vue';
export default {
  name: 'base-form',
  setup() {
    const num = ref(0);
    onMounted(() => {
      console.log('mounted!');
    });
    onUpdated(() => {
      console.log('updated!');
    });
    onUnmounted(() => {
      console.log('unmounted!');
    });
    nextTick(() => {
      console.log('nextTick!');
    })
    return {
      num,
    };
  },
};
</script>

<style lang='less' scoped>
</style>

7.在组件内如何跳转路由

<template>
  <div>
    <div @click="toPage">跳转</div>
  </div>
</template>

<script>
import {} from 'vue';
import { useRouter } from 'vue-router';
export default {
  setup(props, { emit }) {
    const router = useRouter();
    const toPage = () => {
      router.push('/home');
    };
    return { toPage };
  },
};
</script>

<style lang='less' scoped>
</style>

更多新的不同会持续更新

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345