Vue基础
介绍
一套用于构建用户界面的渐进式框架,被设计为可以自底向上逐层应用,核心库只关心视图层
问题:什么叫渐进式框架?
progressive framework, vue对自己框架和其他框架对比后,生成的一个特定名词
问题:vue的核心是什么?
用模板的方式进行一系列的编译,有自己的核心库会编译模板,然后会渲染 DOM
Vue将数据于 DOM进行关联,并建立响应式关联,数据改变视图更新
优点:
应用范围广,生态环境友好,社区完善,易上手,代码轻量,发展迅速
注意:兼容性问题,
IE8及以下不支持vue
区别
Angular是一个综合性框架,也是一个开发平台,用于创建高效,复杂,精致的单页面应用- 关注项目应用
- 不关注视图渲染和状态管理
- 适合开发大型应用
- 高度集成方法
- 自上而下的延展
React是构建用户界面的JavaScript库- 关注视图层
- 数据渲染视图
- 管理视图和状态关系
- 没有状态中央管理(需借助
Redux) - 不提供路由(
react-router) - 自下而上的延展
Vue是视图层的核心库- 关注用户界面
- 关注视图层
- 关注数据渲染视图
- 可以选择集成
Vuex - 可以选择集成
Vue-router - 微型库概念(
Micro libs) - 自下而上的延展
- 库和库的集合形成框架
安装
方法一:
vite + cdn 方式
//1.初始化项目npm init -y//2.安装打包依赖npm i -D vite@2.3.8//3.修改 package.json 里script的dev为vite//4.新建 index.html//5.写入入口标签<div id="app"></div>//6.引入 vue cdn 地址<script src="https://unpkg.com/vue@3.1.2/dist/vue.global.js"></script>//7.引入入口文件//type="module" es6原生写法要求<script type="module" src="./src/main.js"></script>//8.启动项目npm run dev//9.启动成功//10.入口文件编写const { createApp } = Vue;const App = {data() {return {text: 'Hello Vue!'}},template: `<h1>{{text}}</h1>`}createApp(App).mount('#app');//11.页面显示 Hello Vue!
补充:cdn 加速网站
方法二:
结合webpack打包工具搭建项目
vue2.x项目搭建:
注:因为
Vue提供了编写单文件组件的配套工具,如果想要使用单文件组件,2.0 得需安装vue-template-compiler和vue-loader依赖,3.0则需安装vue/compiler-sfc依赖
//1.项目初始化npm init -y//2.webpack依赖安装npm i -D webpack@4.44.2npm i -D webpack-cli@3.3.12npm i -D webpack-dev-server@3.11.2//3.更改 package.json 脚本命令scripts为dev: webpack-dev-server//4.新建 index.html//5.写入入口标签<div id="app"></div>//6.引入vue2.x的cdn<script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>//7.安装依赖npm i -D vue-loader@15.9.7npm i -D vue-template-compiler@2.6.14npm i -D html-webpack-plugin@4.5.0//8.配置webpack.config.js, 详细代码往下看const { resolve } = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin');const VueLoaderPlugin = require('vue-loader/lib/plugin');//node环境下commonjs规范:module.exports = {mode: 'development',entry: './src/main.js',output: {path: resolve(__dirname, 'dist'),filename: 'main.js'},//配置外部文件externals: {'vue': 'Vue'},devtool: 'source-map',module: {rules: [{test: /\.vue$/,loader: 'vue-loader'}]},plugins: [new VueLoaderPlugin(),new HtmlWebpackPlugin({template: resolve(__dirname, 'public/index.html')})]}//9.启动项目npm run dev//10.编写入口文件new Vue({render: h => h(App)}).$mount('#app');//11.编写app组件<template><div>{{ title }}</div></template><script>export default {name: "App",data() {return {title: "Hello Vue",};},};</script>//12.浏览器成功显示渲染后的页面
vue3.x项目搭建:基于2.x的文件做以下修改
//1.修改入口文件为3.x写法Vue.createApp(App).mount('#app');//2.引入vue3.x的cdn<script src="https://unpkg.com/vue@3.1.2/dist/vue.global.js"></script>//3.安装3.x依赖npm i -D @vue/compiler-sfcnpm i -D vue-loader@16.3.0 //vue-loader@next//4.更改配置文件 webpack-config.js 的 vue-loader 引入const { VueLoaderPlugin } = require('vue-loader');//5.启动项目npm run dev//6.浏览器成功渲染
进一步配置:
npm i -D sass-loader@10.1.1npm i -D sass@1.35.2npm i -D autoprefixer@10.3.1npm i -D css-loader@4.3.0npm i -D postcss-loader@4.3.0npm i -D postcss@8.3.6npm i -D vue-style-loader@4.1.3npm i -D @vue/devtools@5.3.4
webpack.config.js配置代码:
const {resolve} = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin');// const VueLoaderPlugin = require('vue-loader/lib/plugin');const {VueLoaderPlugin} = require('vue-loader');const autoprefixer = require('autoprefixer');//node环境下commonjs规范:module.exports = {mode: 'development',entry: './src/main.js',output: {path: resolve(__dirname, 'dist'),filename: 'main.js'},//配置外部文件externals: {'vue': 'Vue'},devtool: 'source-map',module: {rules: [{test: /\.vue$/,loader: 'vue-loader'}, {test: /\.scss$/,use: ['vue-style-loader','css-loader',{loader: 'postcss-loader',options: {postcssOptions: {plugins: [autoprefixer({overrideBrowserslist: ["> 1%","last 2 versions"]})]}}},'sass-loader']}]},plugins: [new VueLoaderPlugin(),new HtmlWebpackPlugin({template: resolve(__dirname, 'public/index.html')})]}
package.json的配置代码:
{"name": "vue-webpack-demo","version": "1.0.0","description": "","main": "index.js","scripts": {"dev": "webpack-dev-server","build":"webpack"},"keywords": [],"author": "","license": "ISC","devDependencies": {"@vue/compiler-sfc": "^3.1.5","html-webpack-plugin": "^4.5.0","vue-loader": "^16.4.1","vue-template-compiler": "^2.6.14","webpack": "^4.44.2","webpack-cli": "^3.3.12","webpack-dev-server": "^3.11.2"}}
模板语法
template里面的一些 HTML字符串内部除开 HTML本身特性以外 Vue的特性,如文本,表达式,属性,指令等
Vue模板都是基于 HTML的,模板中直接写 HTML都是能够被 HTML解析器解析的
Vue提供一套模板编译系统
基本是开发者写的template,然后分析它将 HTML字符串变成AST树,把表达式/自定义属性/指令等转化为新的原生的 HTML,把 JS写法的 HTML模板遍历出来后形成虚拟 DOM树节点,最后根据虚拟 DOM树变成真实 DOM树渲染到页面上
问题:如何虚拟 DOM对比真实 DOM数据?
将真实 DOM数据转为对象结构存储,再进行对比,有差别的情况下形成新的补丁再一定的算法基础下替换
插值
数据绑定最常见的形式就是使用Mustache语法 (双大括号) 的文本插值:
//title为字符串<h1 id="title"></h1>//title为变量<h1 v-bind:id="title"></h1>var url = "https://www.baidu.com";//html -> 插入JS的表达式 -> v-bind:href="url";
标签内部插入表达式
{{}}
属性上插入表达式
v-bind:xx=""
关于mustache.js:
它是一个零依赖的模板系统,mustache中是不支持在 HTML属性中插值的,Vue中因为用底层的模板编译系统,支持内置的属性
npm i -S mustache
import Mustache from 'mustache';var data = {title: 'This is my TITLE for MUSTACHE'}var html = Mustache.render(`<h1>{{title}}</h1>`,data);document.getElementById('app').innerHTML = html;
关于属性:
attribute:HTML 的拓展title/src/hrefproperty:在对象内部存储数据,通常用来描述数据结构prop
关于表达式:
作用:数学运算/字符串拼接/判断/JS API/不能绑定多个表达式/绑定语句
//数学运算<h1 :title="a + b">{{a+b}}</h1>//字符串拼接<h2>{{'a + b =' + (a + b)}}</h2>//判断表达式<h3>{{a + b > 5 ? '大于5' : '小于等于5'}}</h3><h3>{{title || subTitle}}</h3>//使用JS API<h4>{{title.replace('main', '')}}</h4><h4>{{subTitle.split('').reverse().join('-')}}</h4>//绑定语句 报错{{ var a = 1; }}
指令
指令 (Directives) 是带有 v- 前缀的特殊 attribute,它一般结合视图模板使用,响应式的作用于dom
问题:为什么叫指令?
模板应该按照怎样的逻辑进行渲染或绑定行为
内置指令有:
v-if/v-else/v-else-if/v-for/v-show/v-html/v-once…
自定义指令:
开发者也可以给 Vue拓展指令,v-自定义名称
v-model:数据双向绑定 用v-model="数据来源"实现,也是一个语法糖,是@input="方法"和:input="数据"的语法糖
适用元素:input/textarea/select/checkbox/radio
注意:使用
v-model会忽略这些属性value/checked/selected
v-bind绑定属性,目的是引号内部看做变量,vue会对它进行解析
注意:插值表达式里的变量必须是实例里面定义的属性
//绑定数据变量v-bind:title="被绑定变量,这里绑定内容是插值表达式里的内容"<p v-bind:title="content">{{content}}</p>//动态的属性名参数不能出现空格和引号,HTML的合法属性名不能出现空格引号<h1 v-bind:[attr]="tag">{{title}}</h1>//null作为属性是无效的,可以利用null解除属性绑定<h1 v-bind:[null]="title">{{title}}</h1>//简写<p :title="变量">{{content}}</p><p title="字符串">{{content}}</p>
v-on事件绑定方法,类似于onclick/addEventListener绑定事件处理函数
//简写@click="likeThisArticle"<span>Like: {{like}}</span><button v-on:click="likeThisArticle"></button>
v-if为真就显示/为假不显示
<button v-if="isLogin" v-on:click="likeThisArticle"></button><button v-else disabled>Please login first!</button>
v-model视图双向绑定,vue完成了数据双向绑定的机制,好处是业务关注点全部可以放到业务逻辑层,而视图层交给了v-model帮助渲染和更新
//写一个评论<div class="form">//oninput事件会将其value交给myComment数据变量//v-model这个特点会更改视图<input type="text" placeholder="请填写评论" v-model="myComment" /></div>
v-for遍历,列表渲染
//v-for必须搭配key表示唯一性 key 属性必须是唯一的值<li v-for="item of commentList" :key="item.id"></li>//遍历数组<ul><li v-for="(item, index) of list" :key="item.id"><span>{{item.id}}</span><span>{{item.name}}</span></li><ul>//遍历计算属性computed: {computedList(){return this.list.map(item => {item.pass = item.score >= 60;return item;})}}<ul><li v-for="item of computedList" :key="item.id"><span>Order {{index}}</span></li><ul>//遍历method里的方法属性var myArr = [[1,2,3],[4,5,6],[7,8,9,0]];method: {even(numbers){return numbers.filter(number => number % 2 === 0);}}<ul v-for="numbers of myArr"><li v-for="number of even(numbers)">{{number}}</li><ul>//值范围<span v-for="star in 5" :key="star">※</span>//组件与v-for//item是不会自动传入组件的 :item="item"//1.避免v-for与组件功能与数据耦合//2.保证组件有合理的配置性//3.达到最后的复用效果<ul><list-itemv-for="item of list":key="item.id":item="item"></list-item></ul>
v-once:一次插值,永不更新,不建议
v-html:安全原因,插值不会解析 HTML,因为插值是 JS表达式,没有对 DOM的操作,rawHTML原始 HTML,不要试图用v-html做子模版,vue本身有一个底层的模板编译系统,而不是直接使用字符串来渲染的
//子模版放到子组件中,让模板的重用和组合更强大//不要把用户提供的内容作为`v-html`的插值,这种插值容易导致 XSS 攻击const App = {data(){return {title: 'This is my Title'}},template: `{{title}}`}Vue.createApp(App).mount('#app');
v-if/v-else-if/v-else:分支判断是否渲染视图
v-show:隐藏节点,是否显示
关于条件渲染v-if/v-show的区别:
v-if是对DOM的移除和添加,在移除的时候用注释节点占位,对内部的子组件与事件监听都会销毁与重建v-if只有条件是truthy的时候,才会被渲染(惰性渲染)v-show总会会被渲染,用display来控制其显示与隐藏v-if在切换的时候会提高开销,如果条件为假值,初始化渲染是不会进行的v-show在切换的时候开销较低,但是初始化渲染时无论显示与否都要被渲染
v-if/v-show的使用选择:
- 如果切换频繁就用
v-show - 如果切换不频繁,(加载时不需要的视图),可以用
v-if
特殊attribute
ref引用
ref引用,reference被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。
如果在普通的 DOM元素上使用,引用指向的就是 DOM元素;如果用在子组件上,引用就指向组件实例
vue里是不需要操作 dom的,也不需要去获取 dom,有些场景需要去对 dom节点进行获取,也可能对 dom节点相关的信息,万不得已的情况下对节点进行操作
//拿到组件的实例//也可以访问实例底下的属性和方法<my-list ref="myRef"></my-list>
组件的实例的实际应用:
兄弟组件相互调用方法或者获取属性
注意:
$refs本身并不是响应式的,所以不要在模板中使用- 不要在计算属性中访问,因为不具备响应式
- 不要尝试更改
ref, 它提供给获取节点或实例引用,并不是改变的
问题:为什么要获取组件实例?
可以通过调用组件相关的方法获取相应的属性,兄弟组件的情况下,需要获取自己兄弟上面的方法或者是属性的时候,可以用 ref把组件的实例拿出来,然后在实例上去使用属性和方法
问题:为什么尽量不使用 ref?
因为 vue是数据绑定机制,经常使用 ref可能导致 vue的解决方案的匮乏, dom的增删改查是 ViewModel实现的而不是开发者去操作的
//通过$refs(vue内部方法的引用集合)可以访问dom里面的属性<input ref="myRef"></input>console.log(this.$refs.myRef);
注:
$refs早在生命周期beforeCreate时候已经存在了,只有组件装载完毕后(mounted)才存在myRef
问题:为什么只有组件装载完毕后才存在 myRef?
如果没有挂载,节点就不会存在,那么就无法拿到指定节点的引用,说明在有些开发场景遇到 ref为 null/undefined 时要考虑组件是否加载完毕后才去获取 dom引用
案例:移动端滚动页面加载更多
技术:ref(html dom)
实现上下滚动页面渲染加载更多或者暂无数据
原理:
通关 ref标签属性拿到绑定在dom节点里面的clientHeight/scrollHeight/scrollTop数据进行计算是否触底,从而每次触底时新增 5 条数据渲染到页面
源码地址:
https://gitee.com/kevinleeeee/ref-dom-demo
应用实例
问题:应用实例是什么?
通过createApp创建 APP返回一个应用实例
//Application 应用 返回一个vue实例对象const app = Vue.createApp({});console.log(app);
应用实例里面的 component属性主要是用来注册全局组件
app.component('MyTitle', {data(){return {title: 'I Love You!'}},template: `<h1>{{title}}</h1>`});app.mount('#app');
在 <template></template>标签里使用组件
<div><!-- 写法一 --><MyTitle /><!-- 写法二 --><my-title /></div>
在实例上暴露了很多方法,如 :
component注册组件directive注册全局自定义指令filter注册过滤器mixin全局注册混入器provide注入全局跨组件层级的属性use使用插件
实例里的大多数方法都会返回 createApp创建出来的应用实例,目的是允许链式调用操作
生命周期
组件是有初始化过程的,在过程中,Vue提供了很多每个阶段运行的函数,函数会在对应的初始化阶段自动运行
实例的生命周期过程:
- 创建实例并挂载:
app = Vue.createApp(options); app.mount(el) - 执行完后初始化事件和生命周期,实例对象身上有默认的一些生命周期函数和默认事件
beforeCreate阶段- 初始化,数据注入,整理好跟视图相关的响应式开发相关特性(初始化
data/method) created阶段判断是否有模板:编译模板,把
data对象里面的数据和Vue语法写的模板编译成HTML- 有:编译模板至渲染函数
- 没:编译
el的innerHTML至模板
- 挂载之前执行
beforeMount阶段 - 创建
app.$el(真实的根节点)并添加至el mounted阶段已挂载状态监听数据是否存在变化
- 有变化,虚拟节点对应补丁重新渲染更新
beforeUpdate阶段- 更新完之后到
updated阶段 - 调用
app.unmount()方法后会销毁组件 销毁组件经历两个阶段:
beforeUnmount阶段unmounted阶段

data属性
逻辑区域的对象里的 data,它的核心有:
- data 必须是一个函数
- vue 在创建实例的过程中调用 data 函数,返回数据对象
- 通过响应式包装后存储在实例的
$data - 并且实例可以直接越过
$data可访问属性
问题:为什么要用data?
每次组件实例时先执行data()返回一个函数来保证数据的引用是唯一的
//data()方法执行返回一个对象const app = Vue.createApp({data(){return {title: 'This is my Title'}},template: `<h1>{{title}}</h1>`});cosnt vm = app.mount('#app');console.log(vm);//$, _, __ 都是vue提供的内置API//开发者尽量避免用这些前缀命名自己的属性和方法/*** console.log(vm):* Proxy{...}* title: 'This is my Title'* $: ...* $el: ...* $emit: ...* $data: Proxy* title: 'This is my Title'*/console.log(vm.$data.title);//This is my Title//越过$data访问titleconsole.log(vm.title);//This is my Titlevm.$data.title = 'This is your Title';console.log(vm.title);//This is your Titlevm.title = 'This is my Title';console.log(vm.$data.title);//This is my Title//说明vm.title 和 vm.$data.title都是同一个数据//$data是响应式数据对象vm.autor = 'kevin';console.log(vm.author); //kevinconsole.log(vm.$data.author); //找不到//这个写法会被vue警告,且需要在data里定义author变量vm.$data.author = 'kevin';console.log(vm.author); //kevinconsole.log(vm.$data.author); //kevin
问题:data 为什么必须是一个函数?
如果 data是一个对象的话,那么很难防止同一引用重复使用的问题,而每次实例 Vue执行函数都会返回一个新的对象可以解决同一对象引用的问题
//此写法会报错,vue时刻监听data是否为一个函数//Uncaught TypeError: dataOptions.call is not a functionconst App = {data: {a: 1},template: `<h1>{{a}}</h1>`}Vue.createApp(App).mount('#app');
methods属性
逻辑区域的对象里的 逻辑方法都写在 method的对象里,向组件实例添加方法
Vue创建实例时,会自动为 methods绑定当前实例 this,保证在事件监听时,回调始终指向当前组件实例,方法要避免使用箭头函数,箭头函数不能更改 this指向,箭头函数会阻止 Vue正确绑定组件实例 this
methods: {likeThisArticle(){this.like ++;}}
//注意:函数名 + () 不是执行符号,是传入实参的容器@click="changeTitle('xxx')"//拆分写法onclick = "() => { changeTitle('xxx')}"//可以在视图模板里 方法函数的执行 响应式的执行//注意:模板直接调用的方法尽量避免副作用操作 如更改数据,异步操作//<h1>{{ yourTitle() }}</h1>
注:
methods里的定义的方法是存放在vm实例对象里,而不是methods里,因为可以让实例直接调用改方法
computed属性
计算属性的核心:
- 解决模板中复杂的逻辑运算及复用的问题
- 计算属性旨在内部逻辑依赖的数据发生变化的时候才会被再次调用
- 计算属性会缓存(缓存在实例里)其依赖的上一次计算出的数据结果
- 多次复用一个相同值数据,计算属性只调用一次(只要
data数据没有发生更改就不会调用)
//这个情景反映一个问题:模板里含有逻辑判断//模板逻辑样式尽可能的绝对分离//逻辑运算结果需要被复用const App = {data(){return {studentCount: 1}},template: `//不建议写法:<h1>{{studentCount > 0 ? ('学生数:' + studentCount) : '暂无学生'}}//建议的写法<h1>{{ studentCountInfo }}`,computed: {studentCountInfo(){return this.studentCount > 0? ('学生数:' + this.studentCount): '暂无学生';}}}
//注意:computed对象里是一个getter/setter结构computed: {calData: {get(){...},set(){...}}}
watch属性
侦听器的关注点在数据更新:给数据增加侦听器,当数据更新时,侦听器函数执行
特点:
数据更新时,需要完成什么样的逻辑,如 Ajax数据提交
const app = Vue.createApp({data(){return {a: 'This is my Title'}},computed: {result(){}},watch: {//侦听computed里的数据://可以获取到新老值//result更新会触发侦听器result(newValue, oldValue){}//侦听data里的数据:a(newValue, oldValue){}}});
案例:答题系统
有 4 道题目,点击对应答案的按钮后会显示答案结果的页面
技术:
webpack + vue + express
实现功能:
监听数据发生变化时触发相应的程序
源码地址:
https://gitee.com/kevinleeeee/exam-vue-watch-demo
Class与 Style绑定
操作元素的 class列表和内联样式是数据绑定的一个常见需求。
因为它们都是 attribute,所以我们可以用 v-bind 处理它们:
只需要通过表达式计算出字符串结果即可。
不过,字符串拼接麻烦且易错。因此,在将 v-bind 用于 class 和 style 时,表达式结果的类型除了字符串之外,还可以是对象或数组。
//vue 对v-bind:class/style进行了特殊的封装//形式是比较多的,对象和数组的绑定方式const MyAlert = {data() {return {title: 'This is my first Alert',isShow: true,hasError: true,alertClassObject: {show: true,'bg-danger': true}}},computed: {alertClassObjectComputed() {return {show: this.isShow,danger: this.isShow && this.hasError}}},template: `<!--<divclass="my-alert":class="{//加某个样式类名的真假条件show: isShow,danger: hasError}">--><divclass="my-alert":class="alertClassObjectComputed"><header class="header"><h1>{{title}}</h1></header></div>`}
//vue会在运行时自动检测添加相应的前缀:style="{display: ['-webkit-box', '-ms-flexbox', 'flex']}"
命名
//6种命名方法:1.camelCae 小驼峰命名 thisIsMyVariable2.kebab-case 短横线命名法 this-is-my-variable3.脊柱命名法 spinal-case train-case4.蛇形命名法 snake_case this_is_my_variable5.匈牙利命名法 变量 属性+类型 描述 以空字符为结尾的字符串的长整形指针6.大驼峰命名法 ThisIsMyVariable
事件处理
事件处理函数的绑定,实际上是原生 JavaScript里绑定事件处理函数,用户行为触发,事件和处理函数进行绑定行为,事件的触发会执行其绑定的处理函数
//vue 绑定写法v-on="eventType"v-on:click=""@click=""//1.绑定JavaScript表达式(逻辑简单也不推荐)<button @click="count += 1" ></button>//2.绑定处理函数(逻辑较为复杂)<button @click="addCount"></button>//3.内联绑定处理函数(调用:这里不会执行methods里对应的方法,目的是为了传参)<button @click="addCount(1)"></button>//4.多事件处理函数绑定<button @click="addCount(1), setLog('add', 2)"></button>
问题:vue是如何实现事件绑定?
首先,在 JavaScript里,@click="addCount(1)"默认就会自己执行,但在 vue里不会自动执行,而是先通过模板编译,当填写@click="addCount(1)"时,会返回一个函数传入一个$event参数
function($event){ addCount($event, 2); }
修饰符
事件修饰符@click.once,目的在与把事件处理函数中非纯逻辑的程序分离出去
.once只调用一次事件处理,调用一次以后自动移除监听器.prevent阻止默认事件.capture采用捕获.stop阻止事件冒泡.passive拥有不调用Event.preventDefault()v-model.lazy在input+value输入完成失去焦点时,数据改变v-model.number如果无法被parseFloat解析,就返回原始值/在有number时,就返回数值v-model.trim过滤掉首尾的空白字符
全局API
Vue.filter
注册或获取全局过滤器。Vue.js允许你自定义过滤器,可被用于一些常见的文本格式化。对视图上的数据绑定的时候的再加工
过滤器可以用在两个地方:双花括号插值和 v-bind 表达式
// 注册Vue.filter('my-filter', function (value) {// 返回处理后的值})// getter,返回已注册的过滤器var myFilter = Vue.filter('my-filter')
