1. Vue/cli脚手架介绍
vue-cli是vue官网提供的一个脚手架,这个构建工具大大降低了webpack的使用难度,支持热更新,有webpack-dev-server的支持,相当于启动了一个请求服务器,给你搭建了一个测试环境,只关注开发就OK
1.1 安装vue-cli创建Vue2.x项目
1 2 3 4 5 6 7 8 9 10 11
| vue inspect > output.js
https:
npm run serve
npm run build
|
1.2 windows(32位) npm装不上vue/cli的问题
1.3 vue.config.js配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
|
const path = require('path'); const debug = process.env.NODE_ENV !== 'production'
module.exports = { publicPath: !debug ? '/cli-study/dist' : '/', outputDir: 'dist', assetsDir: 'static', filenameHashing: true, lintOnSave: false, runtimeCompiler: true, transpileDependencies: [], productionSourceMap: false,
configureWebpack: config => { Object.assign(config, { resolve: { alias: { '@': path.resolve(__dirname, './src'), 'vue$': 'vue/dist/vue.esm.js' } } }) },
chainWebpack: (config) => { if (debug) { } else { } },
css: { }, devServer: { open: true, host: '127.0.0.1', port: 3000, https: false, hotOnly: false, proxy: null, }, pluginOptions: {} };
|
1.4 Eslint格式化.vue文件
ESLint是一个用来识别 ECMAScript 并且按照规则给出报告的代码检测工具,使用它可以避免低级错误和统一代码的风格。
你可能有疑问,在.vue 文件中你怎么检验你的代码,因为它不是 JavaScript。我们假设你使用 ESLint (如果你没有使用话,你应该去使用!)。
你还需要官方的 eslint-plugin-vue,它支持同时检查你.vue文件中的模板和脚本。
https://vue-loader-v14.vuejs.org/zh-cn/workflow/linting.html
eslint配置(rules规则)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| "quotes": [0, "single"],
"no-multi-spaces": 0,
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'prefer-promise-reject-errors': 0, 'space-unary-ops': 0, 'no-unused-expressions': 0, 'no-useless-return': 0, 'standard/no-callback-literal': 0, 'import/first': 0, 'import/export': 0, 'no-mixed-operators': 0, 'no-use-before-define': 0,
'semi': [2, 'never'],
'eqeqeq': 0,
'indent': 2,
'no-tabs': 0,
'space-before-function-paren': [2, 'never'],
'padded-blocks': 0,
'one-var': 0,
'no-cond-assign': [2, 'except-parens'],
'no-constant-condition': 2,
'curly': [2, 'multi-line'],
'no-var': 2,
'no-multi-spaces': ['error', { ignoreEOLComments: true }], 'camelcase': 0,
'key-spacing': 2,
'no-else-return': 2,
'no-magic-numbers': [0, { ignoreArrayIndexes: true }],
'no-redeclare': [2, { builtinGlobals: true }],
'wrap-iife': [2, 'inside'],
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'dot-location': [2, 'property'],
'block-spacing': [2, 'always'],
'guard-for-in': 0,
'brace-style': [2, '1tbs', { 'allowSingleLine': true }],
'comma-spacing': [2, { 'before': false, 'after': true }],
'no-multiple-empty-lines': [2, { 'max': 1, 'maxEOF': 2 }],
'arrow-parens': 0,
'generator-star-spacing': [2, { 'before': false, 'after': true }],
'lines-around-comment': [2, { 'beforeBlockComment': true, 'afterBlockComment': false, 'beforeLineComment': true, 'afterLineComment': false }]
"off"或者0,不启用这个规则 "warn"或者1,出现问题会有警告 "error"或者2,出现问题会报错
|
https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/README.md
注意:”extends”: “plugin:vue/recommended” 会同时检查js和模板的语法错误
1 2
| vscode中通过 npm run lint会自动进行eslint的校验和修正 npm run lint --fix
|
2. Vue3.0介绍
官方文档:https://vue-docs-next-zh-cn.netlify.app/guide/installation.html
2.1 Vue3.0 项目创建
创建:在命令窗口输入指令
2.2 Vue composition API (函数式api)
vue 3.0 的 Composition API 带来的最大的特性,就是函数组件。通过函数组件,我们可以体会到类似 react 编程的愉悦。
使用传统的option配置方法写组件的时候问题,随着业务复杂度越来越高,代码量会不断的加大;由于相关业务的代码需要遵循option的配置写到特定的区域(data、methods、computed),导致后续维护非常的复杂,同时代码可复用性不高,而composition-api就是为了解决这个问题而生的
vue3.0 侧重于解决代码组织与逻辑复用问题
2.2.1 defineComponent
在结合了 TypeScript 的情况下,传统的 Vue.extend 等定义方法无法对组件给出正确的参数类型推断,这就需要引入 defineComponent() 组件包装函数
2.2.2 setup
vue3.0将组件的逻辑都写在了函数内部,setup()会取代vue2.x的data()函数,返回一个对象暴露给模板。
setup函数的特性:
- 使用Composition API 的入口
- 在beforeCreate之前调用
- 在setup中没有this (函数组件)
- 可以返回一个对象,这个对象的属性被合并到渲染上下文,并可以在模板中直接使用;
- 接收props对象作为第一个参数,接收来的props对象,可以通过watch监视其变化。
- 接受context对象作为第二个参数,这个对象包含attrs,slots,emit三个属性。
setup 函数是个新的入口函数,相当于 vue2.x 中 beforeCreate 和 created,在 beforeCreate 之后 created 之前执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| setup() { const msg = "张晓明"; let age = 18; function add() { age += 1; console.log(age) } return { msg, age, add }; }, <div class="home"> 姓名:{{msg}} 年龄:{{age}} <button @click="add">点我年龄加1</button> </div>
|
2.2.3 ref
ref可以让某一个变量具备响应式的能力
ref 注意点:
1 2 3 4 5 6 7 8 9
| import { defineComponent, ref } from "vue"; setup() { const msg = ref("张晓明"); let age = ref(18); function add() { age.value += 1; } return { msg, age, add }; },
|
2.2.4 reactive
经过reactive函数处理后的对象能变成响应式的对象,类似于option api里面的data属性的值,它主要是处理你的对象让它经过 Proxy
的加工变为一个响应式的对象。
注意点:
如果想要保持对象内容的响应式能力,在 return 的时候必须把整个 reactive()
对象返回出去
在引用的时候也必须对整个对象进行引用而无法解构,否则这个对象内容的响应式能力将会丢失。
Vue2.x版本中 Vue 使用了 Object.defineProperty()
来劫持数据变化,Vue3.0中使用 Proxy
来劫持数据变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <template> <div class="home"> 名字:{{ state.name }} 价格:{{ state.price }} 购买数量:{{state.count}} <button @click="add">点我加1</button> </div> </template>
<script lang="ts"> import { defineComponent, ref, reactive } from "vue";
export default defineComponent({ name: "Home", components: { }, setup() { const state = reactive({ count: 0, name:"商品1", price:100 }); function add() { state.count += 1; } return { state, add }; }, }); </script>
|
2.2.5 toRefs
但是在具体的业务中,如果无法使用解构取出 reactive()
对象的值,每次都需要通过 state.
操作符访问它里面的属性会是非常麻烦的,所以官方提供了 toRefs()
函数来为我们填好这个坑。只要使用 toRefs()
把 reactive()
对象包装一下,就能够通过解构单独使用它里面的内容了,而此时的内容也依然维持着响应式的特性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <template> <div class="home"> 名字:{{ name }} 价格:{{ price }} 购买数量:{{count}} <button @click="add">点我加1</button> </div> </template>
<script lang="ts"> import { defineComponent, ref, reactive,toRefs } from "vue";
export default defineComponent({ name: "Home", components: { }, setup() { const state = reactive({ count: 0, name:"商品1", price:100 }); function add() { state.count += 1; } return { ...toRefs(state), add }; }, }); </script>
|
2.2.6 watch和computed
computed:该函数用来创造计算属性,和过去一样,它返回的值是一个ref对象。里面可以传方法,或者一个对象,对象中包含set()、get()方法
watch:属性监听,支持传三个参数
- 想监听的数据,可以是数组形势,传入多个
- 监听数据的回调函数,该回调的参数有两个,前一个是newValue,后一个是oldValue,每一个都可以是一个数组,与之前的多个监听对应
- 监听的配置参数 是一个对象,如{ immediate: true },这样可以让初次挂在时执行watch的回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| <template> <div class="home"> 名字:{{ name }} 价格:{{ price }} 购买数量:{{count}} 双倍数量:{{double}} Title:{{title}} <button @click="add">点我加1</button> <button @click="title = '123'">修改title</button> </div> </template>
<script lang="ts"> import { defineComponent, ref, reactive,toRefs,computed,watch } from "vue";
export default defineComponent({ name: "Home", components: { }, setup() { const state = reactive({ count: 0, name:"商品1", price:100 }); const title = ref("标题")
const double = computed(() => state.count * 2)
watch(()=>state.count,(newCount,oldCount)=>{ console.log(newCount,oldCount) },{ immediate: true }) watch(title,()=>{
}) watch([title,()=>state.count],([newTitle,newCount],[oldTitle,oldCount])=>{ console.log(newTitle,newCount) })
function add() { state.count += 1; } return { ...toRefs(state),double, add,title }; }, }); </script>
|
2.2.7 props 和 context
在 Vue2.0
中我们可以使用 props
属性值完成父子通信,在这里我们需要定义 props
属性去定义接受值的类型,然后我们可以利用 setup
的第一个参数获取 props
使用。
a) 父传子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <template> <div class="home"> <HelloWorld :msg="title"></HelloWorld> <button @click="title = '123'">修改title</button> </div> </template>
<script lang="ts"> import { defineComponent, ref, reactive, toRefs, computed, watch, onMounted } from "vue"; import HelloWorld from '@/components/HelloWorld.vue';
export default defineComponent({ name: "Home", components: { HelloWorld }, setup() { const title = ref("标题"); return { title }; }
}); </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <div class="hello"> <h1>{{ msg }}</h1> </div> </template>
<script lang="ts"> import { defineComponent, ref,watch } from "vue";
export default defineComponent({ name: "HelloWorld", props: { msg: String, }, setup(props) { watch(()=>props.msg,(newVal)=>{ console.log(newVal) }) }, }); </script>
|
b) 子传父
1 2 3 4 5 6 7 8 9 10 11
| setup() { const title = ref("标题");
const receiveFromChild = (msg: any) => { title.value = msg; }; return { title, receiveFromChild }; }, <HelloWorld :msg="title" @func="receiveFromChild"></HelloWorld>
|
1 2 3 4 5 6 7 8 9 10 11 12
| <button @click="send">向父亲传递数据</button>
setup(props,context) { const send = ()=>{ context.emit("func","孩子的数据") } return {send} },
|
c) provide和inject
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { defineComponent,provide } from "vue"; setup() { provide("list",['张三','李四','王五']) return { }; }
import { defineComponent, inject } from "vue"; setup(props, context) { const list = inject("list"); console.log(list); return { }; }
|
2.2.8 过滤器、自定义指令
a) 过滤器
vue3.0不再支持过滤器,官方建议我们用methods、computed来替换他们
b) 自定义指令
1 2 3 4 5 6 7 8
| const app = Vue.createApp({}) app.directive('focus', { mounted(el) { el.focus() } })
|
1 2 3 4 5 6 7 8 9
| directives: { focus: { mounted(el) { el.focus() } } }
|
2.2.9 ref引用页面元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <template> <div class="home"> <input type="text" ref="inputRef" /> <button @click="showRef">点我显示input内容</button> </div> </template>
<script lang="ts"> import { defineComponent, ref } from "vue"; import HelloWorld from "@/components/HelloWorld.vue";
export default defineComponent({ name: "Home", components: { HelloWorld, }, setup() { const inputRef = ref(); const showRef = () => { console.log(inputRef.value && inputRef.value.value); }; return { inputRef, showRef }; }, }); </script>
|
2.2.10 vue3.0生命周期钩子
1 2 3
| onMounted(()=>{ console.log("onMounted") })
|
2.3 vue-router的使用
2.3.1 基本使用
https://router.vuejs.org/zh/guide/essentials/passing-props.html
router/index.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' import Home from '../views/Home.vue'
const routes: Array<RouteRecordRaw> = [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', component: () => import( '../views/About.vue') } ]
const router = createRouter({ history: createWebHashHistory(), routes })
export default router
|
1 2 3 4 5 6 7 8 9 10 11
| createApp(App).use(store).use(router).mount('#app')
<template> <div id="nav"> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> </div> <router-view/> </template>
|
2.3.2 动态路由参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| const routes: Array<RouteRecordRaw> = [ { path: '/', name: 'Home', component: Home }, { path: '/about/:id', props: true, name: 'About', component: () => About } ]
<template> <div id="nav"> <router-link to="/">Home</router-link> | <router-link to="/about/123">About</router-link> </div> <router-view/> </template>
<template> <div class="about"> <h1>This is an about page {{$route.params.id}}</h1> </div> </template>
<script> export default { props:['id'], created(){ console.log(this.id) } } </script>
|
2.3.3 路由传参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| const routes: Array<RouteRecordRaw> = [ { path: "/", name: "Home", component: Home }, { path: "/about", props: route => ({ query: route.query.q, query2: route.query.m }), name: "About", component: () => About } ];
<template> <div id="nav"> <router-link to="/">Home</router-link> | <router-link to="/about?q=vue&m=123">About</router-link> </div> <router-view/> </template>
<script> export default { props:['query','query2'], created(){ console.log(this.query,this.query2) } } </script>
|
2.3.4 编程式导航
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <template> <div class="about"> <h1>This is an about page</h1> <button @click="goHome">点我跳转页面</button> </div> </template>
<script> import { defineComponent, ref } from "vue";
import { useRouter } from "vue-router"; export default { setup() { const router = useRouter(); const goHome = () => { router.push("/"); }; return { goHome }; }, }; </script>
|
2.3.5 导航钩子函数
a) 路由级的导航钩子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const routes: Array<RouteRecordRaw> = [ { path: "/", name: "Home", component: Home }, { path: "/about", name: "About", component: () => About, beforeEnter:(to,from,next)=>{ console.log("beforeEnter",to,from,next) next(); } } ];
|
b) 全局的导航钩子
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const router = createRouter({ history: createWebHashHistory(), routes });
router.beforeEach((to, from, next) => { console.log("全局前置 beforeEach"); next(); }) router.afterEach( route => { console.log("全局后置 afterEach"); })
|
c) 组件级的导航钩子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <script> import { defineComponent, onMounted, ref } from "vue";
import { onBeforeRouteLeave, onBeforeRouteUpdate, useRouter } from "vue-router"; export default { setup() { const router = useRouter(); const goHome = () => { router.push("/"); };
onBeforeRouteLeave((to,from,next) => { console.log("onBeforeRouteLeave",to,from,next); next(); }); onBeforeRouteUpdate((to,from,next) => { console.log("onBeforeRouteUpdate",next); next(); });
return { goHome }; }, }; </script>
|
2.4 vuex的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import { createStore } from 'vuex'
export default createStore({ state: { count: 0 }, mutations: { increment(state) { state.count++ }, subtract(state, obj) { console.log(obj) state.count -= obj.step; } }, getters: { optCount: function (state) { return '当前最新的count值是:' + state.count } }, actions: { }, modules: { } })
|
1 2
| createApp(App).use(store).use(router).mount('#app')
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <template> <div class="home"> <p>count值是:{{ state.count }}</p> <p>getters值是:{{ getters.optCount }}</p> <button @click="doAdd">点我+1</button> <button @click="doSub">点我-若干</button> </div> </template>
<script lang="ts"> import { computed, defineComponent, ref } from "vue"; import { useStore } from "vuex"; import HelloWorld from "@/components/HelloWorld.vue";
export default defineComponent({ name: "Home", components: { HelloWorld, }, setup() { const { state, getters, dispatch, commit } = useStore();
const doAdd = () => { console.log("doAdd",commit) commit("increment"); }; const doSub = () => { commit("subtract", { step: 3 }); };
return { state, getters,doAdd,doSub }; }, }); </script>
|
2.5 vant组件库的使用
2.5.1 全局引入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
npm i vant@next -S
import Vant from 'vant'; import 'vant/lib/index.css';
createApp(App).use(Vant).use(store).use(router).mount('#app')
<van-button type="default" @click="showPopup">默认按钮</van-button> <van-button type="primary">主要按钮</van-button> <van-button type="info">信息按钮</van-button> <van-button type="warning">警告按钮</van-button> <van-button type="danger">危险按钮</van-button>
<van-popup v-model:show="flag">内容</van-popup>
<script lang="ts"> import { computed, defineComponent, ref } from "vue"; export default defineComponent({ name: "Home", components: { }, setup() { const flag = ref(false); const showPopup = ()=>{ flag.value = true; } return { flag,showPopup }; }, }); </script>
|
2.5.2 按需加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| npm i babel-plugin-import -D
module.exports = { presets: [ '@vue/cli-plugin-babel/preset' ], plugins: [ ['import', { libraryName: 'vant', libraryDirectory: 'es', style: true }, 'vant'] ] }
import { Button,Popup } from 'vant'; createApp(App).use(Button).use(Popup).use(store).use(router).mount('#app')
<template> <div class="home"> <van-button type="default" @click="showPopup">默认按钮</van-button> <van-button type="primary">主要按钮</van-button> <van-button type="info">信息按钮</van-button> <van-button type="warning">警告按钮</van-button> <van-button type="danger">危险按钮</van-button>
<van-popup v-model:show="flag">内容</van-popup> </div> </template>
|
2.6 vue3.0和vue2.0比较
vue3.0 的发布与 vue2.0 相比,优势主要体现在:更快、更小、更易维护、更易于原生、让开发者更轻松;
更快
1、virtual DOM 完全重写,mounting & patching 提速 100%;
2、更多编译时 (compile-time)提醒以减少 runtime 开销;
3、基于 Proxy 观察者机制以满足全语言覆盖以及更好的性能;
4、放弃 Object.defineProperty ,使用更快的原生 Proxy;
5、组件实例初始化速度提高 100%;
6、提速一倍/内存使用降低一半;
更小
1、Tree-shaking 更友好;(tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code))
2、新的 core runtime:~ 10kb gzipped;