前言:本文内容来源于我的听课记录。开始写本文时,我接触Vue并不久,本文是对网课内容进行了一个整理,加上了一些自己的理解。因此也可以算是一个纯小白教程,有不足的地方还请各位大佬赐教!
由于我自己是先从相对简单的微信小程序开始学习的,对小程序的代码风格较为熟悉,因此在本文中会出现部分Vue与小程序对比的内容,不喜勿喷哦~
文章目录
一、Vue各版本说明
- Alpha版本。
- Beta版本:Beta版本代表的是没有太大问题的一个版本。
- RC版本:Beta的下一个版本是RC版本(候选版本),也有可能没有RC版本,直接发布正式版本。
- 正式版本。
二、Vue 3.0的变化和特性(和2.x主要区别)
Vue 3.0 现在的问题不是在于 Vue 3.0 核心库本身,而是在于整个Vue的生态。
如:Vue Router、VueX(全局状态转管理),等第一方生态的一些库。
此外还有一些Vue第三方的组件库。
1. Vue 3.0的变更
1.1 最主要的变动(在3.0版本里,变动最大的、和开发者相关的、我们要特别关注的,就是这一条)
原来的 options API 变成了现在的 composition API (由 原来的选项式的API风格,变成了 现在的组合式的API风格)。
注:小程序的API风格和Vue 2.x的API风格就是options API的风格
1.2 总体来说,3.0这个版本,让Vue变得更加函数式了
在2.x的时候还有一些面向对象的概念(如:var vm = new Vue(el template) )。在3.0版本里面,它变得更加函数式了,更多的时候没有new Vue() 了,3.0都是从Vue的核心库里面引入一个一个的函数。
例:2.x需要通过Vue.component()去注册一个Vue的组件,3.0是从Vue的函数库里直接引入一个component函数去注册一个Vue的组件。
1.3 其他比较大的变化(这些变化并不是那么直接)
- 3.0重写了一些核心代码,让运行速度变得更快了。
- 我们都知道Vue的一个重要的特性就是双向数据绑定,在官方文档里把双向数据绑定描述成是一种响应式的对象。可以说Vue的整个核心就是在这个响应式对象上面。3.0响应式特性的机制变动了。2.x实现响应式对象的监听时,使用的是比较老的Object.defineProperties()方法,3.0的监听机制使用的是ES6的Proxy,可以说更加先进一些了。
- 3.0核心的代码都使用TypeScript编写了(不会TypeScript并不影响我们对Vue的使用)。
- 3.0里面重写了虚拟Dom
1.4 思维变化
3.0和2.x整体上的变动其实是不大的,很多核心概念和提供给我们的API,都是没有太大变化的。
学习3.0的版本,必须在思维上有一个比较大的变化。2.x组织代码的方式都是用options API的方式在组织的,options API还是有面向对象的影子在里面。在3.0里面,Vue的思想要完全切换到函数式编程。现在的Vue变得更加扁平化了,也变得更加灵活些了。让Vue变得更好维护,适合去开发更大的项目(软件工程最重要的就是维护)。
2. 组合式API的文档
https://v3.cn.vuejs.org/guide/composition-api-introduction.html
有2.x基础的开发者建议先看文档。
三、学习Vue该具备什么基础
- 前端三件套(HTML+JavaScript+CSS)
- JavaScript高阶语法ES6(如:非常通用且常用的箭头函数、结构式赋值)
注:对小程序非常了解的话,学习Vue是非常简单的,很多思想是一致的(如:数据绑定、列表循环、条件渲染,等常用机制)。不过Vue还是比小程序要复杂很多,如:Vue支持非常丰富的双向数据绑定 & 灵活多样的监听机制。
四、服务端渲染/前端渲染
1. 概述
不能说Vue/React/AngularJS是用来开发单页面/多页面的。
重要标志:看HTML是在哪里渲染的。(如:服务端渲染、前端渲染)
即:HTML+CSS+数据,这3者是在哪里产生的。
误区: 不能说单页面就是前端渲染,多页面就是服务端渲染。(虽然绝大多数前端渲染确实是单页面的,但也可以用来做多页面)
所谓的渲染,就是把数据填充到HTML里面,并没有特别神秘的技术在里面。
对于传统的Web项目来说,HTML+数据 的结合,在 服务端 由服务端结合在一起,就是服务端渲染。
API只返回数据给前端,但在非常传统的开发模式里,数据是不会直接返回的。服务端返回给开发者的并不是单纯的数据,而是 HTML+数据,最终返回的还是HTML(包含数据的HTML)。
2. 服务端渲染
怎么在服务端去做 HTML+数据 的服务端渲染呢?
各大服务端Web框架(如:SpringBoot、Flask),只要是一个Web框架,都要提供模板引擎,通过模板引擎去进行HTML+数据的结合。最终返回HTML,这就是服务端渲染。
3. 前端渲染
现在对于Vue/React/AngularJS,包括小程序,前端代码肯定不是从API里面来的。
事实上,当我们用 Vue/React/AngularJS/小程序 开发时,服务器返回的仅仅只是一个模板,也就是HTML,这个HTML里面可能还包含了CSS或动态的JavaScript代码,通常来讲它是没有数据的。
那么数据是怎么来的呢?这些数据事实上是通过JavaScript去加载服务器的API,从而返回数据。然后由前端自身,把数据都渲染到HTML里面去。所以这是一个前端渲染的过程。
五、前端与后端的模糊
1. 现在的前端、后端,可以理解为2个应用程序之间的通信。
比如说小程序,小程序不是放在我们自己的服务器上的,而是放在腾讯的服务器或是其他第三方的云端上的。我们是首先下载了小程序这个应用程序,然后小程序的应用程序和服务端的应用程序去进行通信。
所以说现在的前端,严格意义上来说已经不再是属于前端了,和服务端其实是对等的,所以说是2个应用程序之间的通信。
以前用服务端渲染时,只需要部署服务端就可以了,前端是不需要独立去部署的。对于现在的前端渲染模式来讲,服务端要部署,前端也可以独立去部署,如 可以放到CDN 或 其他地方。
前端渲染和服务端渲染并不是完全孤立的,两者可以同时存在。
如:大部分数据进行服务端渲染,少部分数据进行前端渲染。大部分数据被服务端填充到HTML里面,少部分数据由于它们是动态的,更多的在HTML里面利用js去发ajax请求。
2. 总结归纳
如果我们选择用服务端渲染,往往我们用的更多的可能是这样的:Web框架 + 模板引擎 去进行服务端渲染,最终由服务器提供给我们一个已经填充了数据的HTML。这是用服务端渲染的一个技术栈。
如果我们选择用前端渲染,往往我们经常使用的就是 Vue/React/AngularJS ,这些是专门用来开发应用程序的。然后由它们负责和服务端的应用程序进行通信,再由Vue/React/AngularJS进行渲染到模板上。
虽然Vue/React/AngularJS非常火非常流行,可以说是前端必学的技术栈,但在真正大型网站里,还是以服务端渲染(Web框架 + 模板引擎 的方式)为主。因为 前端渲染有一个致命的缺点,就是对于SEO(搜索引擎优化)的支持不是很好。 也就是说前端渲染很难被搜索引擎搜录类。如果一个网站不能被你的用户从搜索引擎里面搜索到的话,事实上这个网站是没有太大的意义的。所以要做一个Web网站,最基本的要保证的就是要被很容易地搜索到。
Vue/React/AngularJS 真正的用处并不是在开发这些传统的门户网站上面。它们 更加适合的场景 :
- 开发CMS(后台管理系统)
CMS是不需要去支持SEO的。 - 开发App里内置的H5
现在的App很多都不是原生开发的了,也就是说不是全都用Java(Android里的)或Swift(iOS里的)去开发的了,更多的时候,App里的开发都是混合式开发。App本身就不具备被搜索引擎搜录的特性。
六、如何用Vue.js进行开发
有两种模式:
- CLI(脚手架)去创建一个应用程序(用得更多)
和小程序类似,创建小程序时自带很多文件,小程序开发工具其实就是内置了一个脚手架。 - 用<script>标签引入vue.js核心库(这种方式非常淳朴)
1. 如何去获取编译好的vue.js核心库
在官网git上是找不到单一文件的vue.js核心库的,因为vue肯定是有很多依赖包的。单文件的vue.js核心库实际上是一个编译后的文件。
1.1 用Webpack自己编译
如果前端造诣比较深,可以自己用Webpack进行源码级的编译。从官网下载最新的Vue代码,用Webpack自己编译就可以了。
1.2 直接获取 已经编译好的vue.js核心库
那么有没有编译好的呢?答案是:有。
步骤如下:
-
进入网站:https://github.com/vuejs/core
通过tags标签可以看到目前的最新版本。
-
进入网站:https://unpkg.com/
可以看到提示,在浏览器地址栏输入一个像这样的URL:unpkg.com/:package@:version/:file
例:https://unpkg.com/vue@3.2.31/dist/vue.global.js
例:https://unpkg.com/vue@3.0.0-beta.17/dist/vue.global.js -
打开后可以看到一个js文件的内容,如图:
可以直接全选复制粘贴,或在页面上右击选择“另存为/存储为”,至此,就得到了vue.js单文件核心库。
在<head>
中使用<script src="vue.global.js"></script>
引入vue.js。
2. 创建vue实例
简要说明:
本文中的HTML代码及JavaScript代码书写位置如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scale=no">
<script src="vue.global.js"></script>
</head>
<body>
<!-- HTML代码 -->
</body>
<script>
// JavaScript代码
</script>
<style>
/* CSS样式 */
</style>
</html>
vue2.x 使用 const vm = new Vue();
定义一个Vue的实例,这个Vue实例是被new出来的,然后在Vue实例化时,传入各种参数。
例:
const vm = new Vue({
el: "#app", // 指明Vue的根元素,通常为 id为app的div
data: {
key: value,
},
template
})
以上这种形式就是很典型的Options API。
vue3.0写法:
const {createApp} = Vue // ES6写法,结构赋值,引入createApp方法
const value = "hello"
const app = {
setup(){ // ES6写法,等同于 setup: function(){}
return {
value // ES6写法,等同于 value: value
}
}
}
createApp(app).mount('#app') // mount:挂载;调用createApp方法,把我们创建的Object对象app传进来,然后挂载到html中的dom节点#app。
在html中用双花括号包裹起来,就能拿到上面return的值了。
<div id="app">
{{value}}
</div>
七、Vue的指令
Vue的指令均以 v-
开头,如:v-text
、v-html
、v-bind
、v-on
等,指令内部是不需要加双花括号的。
例:
<div id="app">
{{value}}
<!-- 不使用指令,需要加双花括号 -->
<div v-text="value"></div>
<!-- v-text等同于innerText,会把该标签下所有内容覆盖掉 -->
<div v-html="value"></div>
<!-- v-html等同于innerHtml,会把该标签下所有内容覆盖掉(可用于展示富文本) -->
<a v-bind:href="url">点击</a>
<img v-bind:src="url"></img>
<!-- v-bind给标签内的属性赋值,可以直接简写为“:” -->
<div v-on:click="onClick(xxx)">点击此处触发onClick事件</div>
<!-- v-on的=后绑定一个事件,v-on后面要监听一个事件,如click事件,就写成v-on:click,可以直接简写为“@click”。事件后面还可以接收参数。 -->
<a v-bind:[attr]="url">点击</a>
<!-- 假设属性名也是动态获取的,则需要加上方括号。如:假设attr = 'href' -->
<a v-bind:[attr+'f']="url">点击</a>
<!-- 在方括号内 也可以写JavaScript的表达式。如:假设attr = 'hre',方括号内:[attr+'f'] -->
<div v-text="flag ? value1 : value2"></div>
<!-- 同理,在标签内也可以使用JavaScript表达式,如:三元表达式 -->
</div>
1. Vue常用指令 及 与小程序对比
1.1 v-bind
指令(:
)
html代码:
<img v-bind:src="url"></img>
<img :src="url"></img>
<!-- v-bind给标签内的属性赋值,可以直接简写为“:” -->
同小程序中的:
<image src="{{url}}"></image>
1.2 v-on
指令(@
)
来看一个简单的例子
html代码:
<div id="app">
<div v-on:click="onClick">点击此处触发onClick事件</div>
<div @click="onClick">点击此处触发onClick事件</div>
<!-- v-on的=后绑定一个事件,v-on后面要监听一个事件,如click事件,就写成v-on:click,可以直接简写为“@click” -->
</div>
JavaScript代码:
const {createApp} = Vue
const app = {
setup() {
function onClick() {
alert('hello')
}
return {
onClick,
}
}
}
createApp(app).mount('#app')
同小程序中的:
<view bind:click="onClick">点击此处触发onClick事件</view>
此例中绑定的是一个click事件。同理,也可以绑定一个自定义事件。
1.3 v-if
指令 与 v-show
指令
v-if
html代码:
<div v-if="flag" v-text="value"></div>
同小程序中的:
<view wx:if="{{flag}}">{{value}}</view>
v-show
html代码:
<div v-show="flag" v-text="value"></div>
同小程序中的:
<view hidden="{{!flag}}">{{value}}</view>
若flag
值为false
,两者都不会显示内容。
但使用v-if
时,这个<div>
不会出现在dom结构中。
使用v-show
时,这个<div>
会出现在dom结构中,属性中会自带style="display: none;"
。
总结:v-if
事实上是一种条件渲染,为true就显示,为false就不显示。而v-show
一定会显示,至于能不能看到,是用style样式去控制的。
那么在使用的时候到底该选择使用哪一个呢?
由于dom节点初始化的渲染是要消耗一定性能的,如果需要频繁的切换状态,则用v-show
更合适,此时如果用v-if
就会反复渲染dom节点,会频繁地消耗性能。
若初始化的成本较小,使用两者均可。
对于一个dom节点,若无需频繁切换状态,且在渲染时成本和开销非常大,如某个div
下有非常复杂的子dom结构,此时应优先选择v-if
,因为v-if
有可能让页面根本不去渲染这个dom节点。
1.4 多条件渲染 v-if
、v-else
、v-else-if
例1 html代码:
<div v-if="flag" v-text="value1"></div>
<div v-else v-text="value2"></div>
同小程序中的:
<view wx:if="{{flag}}">{{value1}}</view>
<view wx:else>{{value2}}</view>
例2 html代码:
<div v-if="nubmer===1">{{value1}}</div>
<div v-else-if="nubmer===2">{{value2}}</div>
<div v-else>{{value3}}</div>
同小程序中的:
<view wx:if="{{number===1}}">{{value1}}</view>
<view wx:elif="{{number===2}}">{{value2}}</view>
<view wx:else>{{value3}}</view>
1.5 列表渲染 v-for
遍历数组 html代码:
<ul>
<li v-for="(item, index) in list">{{item}}{{index}}</li>
<!-- item和index命名任意,但顺序不能颠倒。in也可以替换成of,效果一样 -->
</ul>
同小程序中的:
<view wx:for="{{list}}">
<view>{{item}}{{index}}</view>
</view>
在Vue中,要循环哪一个标签,就把v-for加在哪个标签上。
小程序是加在外层标签上。
遍历对象 html代码:
<ul>
<li v-for="(value, key, index) in object">{{index}}-{{key}}:{{value}}</li>
<!-- value、key和index命名任意,但顺序不能颠倒。in也可以替换成of,效果一样 -->
</ul>
1.6 双向数据绑定 v-model
& Vue3.0的 ref
/ reactive
包装响应式对象
首先明确一点:“双向”并不是Vue的特点,“绑定”才是。
双向数据绑定是指:让数据可以从JS里流向HTML,反过来,HTML里的数据发生变化了之后,JS里对应的相同变量的值也会发生改变。
例:
html代码:
<div id="app">
<input v-model="age" type="text"/>
<button @click="onClick">提交</button>
</div>
JavaScript代码:
const {createApp, ref} = Vue
const age = ref(18) // 此处是把数字包装成了一个响应式对象
const app = {
setup() {
function onClick() {
alert(age.value)
}
return {
age,
onClick,
}
}
}
createApp(app).mount('#app')
ref和reactive都可以把一个对象包装成一个响应式对象。
例:
html代码:
<div id="app">
<input v-model="profile.age" type="text"/>
<button @click="onClick">提交</button>
</div>
JavaScript代码:
const {createApp, reactive} = Vue
const profile = reactive({
age: 18
}) // 此处是把对象包装成了一个响应式对象
const app = {
setup() {
function onClick() {
alert(profile.age)
}
return {
profile,
onClick,
}
}
}
createApp(app).mount('#app')
ref和reactive接收的参数是不同的。通常我们往ref传参传的是JavaScript基本数据类型,如:数字。而往reactive传参传的是一个Object对象。
ref的最佳实践是传入一个基本类型,事实上它也可以传入Object对象。根据文档描述,可以知道:如果传入 ref 的是一个对象,将调用 reactive 函数进行深层响应转换。
文档如图:
实现双向数据绑定,单用 v-model
指令是不行的,实现的关键是:必须得有响应式对象。
另外,v-model
指令并不能用在所有的标签上,事实上,只有 input
、textarea
、select
标签上可以使用 v-model
。v-model
更多时候是用来辅助可输入的html。
v-model
的实质其实是:用 v-bind
去绑定 value
属性(如:v-bind:value="age"
),这也是为什么在 input
标签上,用了 v-model
就不需要再去使用 value
了。另外,它提供了一个监听事件,即:用 v-on
去监听响应式对象的变化。
因此,v-model
并不是实现双向数据绑定的核心,响应式对象才是。
同理,单向数据绑定也需要是响应式对象,单向数据绑定意味着JS里的变量发生改变,HTML里也要跟着相应地改变。如果不用响应式对象,JS变化时,HTML只是静态地展示,不会动态地发生变化。
八、Vue3.0的监听函数、计算函数、普通JS函数
1. Vue的监听函数 Watch
监听函数和一些生命周期函数一样,其实就是:给了一个机会,去做一些事情。因此这些函数也通常被称为钩子函数,可以简单理解为某事件触发时把这个时间点钩出来,给了我们一个机会在这个时间点去做一些事。
1.1 (推荐)Watch函数监听ref响应式对象
例:
html代码:
<div id="app">
<input v-model="firstName" type="text"/>
<input v-model="lastName" type="text"/>
<div>{{fullName}}</div>
</div>
JavaScript代码:
const {createApp, ref, watch} = Vue
const firstName = ref('')
const lastName = ref('')
let fullName = ref('')
const app = {
setup() {
// watch函数接收的第一个参数为:要监听的一个响应式对象;
// 第二个参数为:回调函数(在回调函数里面去写我们的业务逻辑)
watch(firstName, (newVal, oldVal) => {
fullName.value = firstName.value + lastName.value
})
watch(lastName, (newVal, oldVal) => {
fullName.value = firstName.value + lastName.value
})
return {
firstName,
lastName,
fullName,
}
}
}
createApp(app).mount('#app')
Watch函数同小程序中的observers:
observers: {
// 单引号内的dataA是this.data中要监听的变量名;括号内的dataA是本监听方法的参数,即该监听变量,命名随意,一般情况下还是与变量名保持相同。
'dataA'(dataA) {
console.log("dataA的当前值为:", dataA)
},
'dataA, dataB'(dataA, dataB) {
console.log("dataA,dataB的当前值为:", dataA, dataB)
},
},
1.2 (不推荐)Watch函数高级用法 - 监听整个reactive响应式对象
例:
html代码:
<div id="app">
<input v-model="name.firstName" type="text"/>
<input v-model="name.lastName" type="text"/>
<div>{{fullName}}</div>
</div>
JavaScript代码:
const {createApp, ref, reactive, watch} = Vue
const name = reactive({
firstName: '',
lastName: '',
})
let fullName = ref('')
const app = {
setup() {
watch(name, (newVal, oldVal) => {
fullName.value = name.firstName + name.lastName
})
return {
name,
fullName,
}
}
}
createApp(app).mount('#app')
这里解释一下,为什么本例中name是一个const定义的常量,但watch函数还是能监听到name的变化:原因其实官方文档已经解释得很清楚了(上面有图), reactive的响应式转换是“深层”的,它影响所有嵌套的属性。 也就是说,本例中name对象下的firstName属性和lastName属性也被包装成了一个响应式对象。如果对象下还有子对象,子对象下还有子子对象,有多级,那么每一级对象 与 每一级对象下的属性,都会被包装成响应式对象。
1.3 (推荐)监听reactive对象下的单个属性
watch函数的第一个参数,除了可以接收一个响应式对象外,还可以接收一个函数。
例:
JavaScript代码:
const {createApp, ref, reactive, watch} = Vue
const name = reactive({
firstName: '',
lastName: '',
})
let fullName = ref('')
const app = {
setup() {
watch(()=>name.firstName, (newVal, oldVal) => {
fullName.value = name.firstName + name.lastName
})
watch(()=>name.lastName, (newVal, oldVal) => {
fullName.value = name.firstName + name.lastName
})
return {
name,
fullName,
}
}
}
createApp(app).mount('#app')
用这种写法可以监听reactive对象下的单个属性。在Vue的文档里,这其实被称之为是一个getter。只想监听reactive对象下的某一个属性,就用这个对象的getter方法,()=>name.firstName
相当于返回了firstName的值。
2. Computed计算函数
computed函数会监听函数内所有的变量,如果这些变量发生了变更,那么当前的computed函数就会被再次执行。
例:
html代码:
<div id="app">
<input v-model="firstName" type="text"/>
<input v-model="lastName" type="text"/>
<div>{{fullName}}</div>
</div>
JavaScript代码:
const {createApp, ref, computed} = Vue
let firstName = ref('')
let lastName = ref('')
const app = {
setup() {
// 用这种方法(computed内传参传的是一个函数),computed计算出来的内容,是只读的,不能被修改的,与const还是let无关。
const fullName = computed(() => firstName.value + lastName.value)
console.log(fullName.value)
return {
firstName,
lastName,
fullName,
}
}
}
createApp(app).mount('#app')
3. 普通JS函数
例:
html代码:
<div id="app">
<input v-model="firstName" type="text"/>
<input v-model="lastName" type="text"/>
<div>{{getFullName()}}</div>
</div>
JavaScript代码:
const {createApp, ref} = Vue
let firstName = ref('')
let lastName = ref('')
const app = {
setup() {
function getFullName() {
return firstName.value + lastName.value
}
return {
firstName,
lastName,
getFullName,
}
}
}
createApp(app).mount('#app')
以上3者(Watch、Computed、JS函数)的对比
Watch重在监听,看重的是某个变量的变化。使用场景如:监听到某变量的变化后,将此变量保存到服务器。
Computed重在结果,看重的是最终计算出来的结果。使用场景如:拼接姓和名。
Computed和JS函数都可以返回一个结果,从场景的角度来讲,并没有什么区别。但Computed性能会更好一点,因为Computed具备一个计算缓存。任何时候调用普通JS函数,这个函数内的业务逻辑都会再执行一遍,多少会有性能损耗。而Computed内的变量没有变化的话,Computed函数不会再执行一遍,会去计算缓存里读取结果。
综上,优先推荐使用Watch和Computed,这两者使用场景较为明确。
4. Computed函数的高级用法 - 给Computed计算属性赋值(不常用)
例:
html代码:
<div id="app">
<input v-model="firstName" type="text"/>
<input v-model="lastName" type="text"/>
<div>{{fullName}}</div>
</div>
JavaScript代码:
const {createApp, ref, computed} = Vue
let firstName = ref('')
let lastName = ref('')
const app = {
setup() {
const fullName = computed({
get: () => firstName.value + lastName.value,
set: (val) => { // 参数名任意
firstName.value = val
}
})
fullName.value = 7 // 这里就相当于set传参传了一个7
return {
firstName,
lastName,
fullName,
}
}
}
createApp(app).mount('#app')
九、创建Vue3项目
1. 用Cli脚手架创建Vue3项目(Cli目前来说还是一个主流)
cli最重要的是可以帮助我们自动构建和打包。
首先 安装npm 并 配置好环境变量,网上教程很多,在此不作赘述。
完成后,在 cmd
小黑窗(命令提示符)里 输入语句 npm -v
,若能成功显示版本号,即为安装成功。
输入语句 npm i -g @vue/cli
安装cli 或 更新cli (如果提示没权限,请在命令的前面加上sudo),稍等片刻后安装完成,输入 vue -V
可以查看版本号,注意 -V
是大写。若能成功显示版本号,即为安装成功。若提示Node版本太低,请自行更新Node。
确保cli安装成功后,进入要创建项目的文件夹,按住 shift
右击,选择 在此处打开命令窗口
,输入语句 vue create 项目名
,如 vue create test
。回车后如图显示:
使用键盘上下键可以选择vue3、vue2,最后一项是手动安装。
1.1 选择vue3 自动安装
待出现下图内容时,即为创建成功。
1.2 手动安装
选择最后一项,手动安装。
下图界面可以手动选择各种各样的特性。如:Vue-router、Vuex(一般建议勾选上这两项)。按键盘上下键移动,敲一下空格键即可选中。选完后回车。此处以默认勾选状态为例:
下图:选择3.x。
下图:选择ESLint规则。本例选择了标准配置:ESLint + Standard config。
注:此处仅作示例!!此项新手慎选!!标准配置可能会出现很多你无法理解的报错!!新手建议选择第一项 ESLint with error prevention only,只有错误时才提醒。
下图:选择Lint on save。
下图:把Eabel,ESLint等等存在哪里。本例选择 存在单独的文件里:In dedicated config files。
下图:将来的项目是否也用该配置。本例输入 n(否)。
待出现下图内容时,即为创建成功。
1.3 简单介绍用Cli创建项目后的部分目录结构
根目录下(这些我们不用怎么管):
package.json 记录了我们所要依赖的一些npm包。
.eslintrc.js 是 语法 或者说 编码规范 的检测的一个文件。
其他文件大多都是作浏览器兼容性的一些配置。
src文件夹(源码文件夹)下:
我们大多数时候写代码都是在这个src源码目录下面编写。
App.vue 入口文件(也可被看作是一个组件)。打开App.vue可以看到,分为三大块:
- <template>标签 是Vue的组件模板(就是我们经常谈到的 骨架);
- <script>标签 里面写业务逻辑,即JavaScript的代码;
- <style>标签 里面写样式。
main.js 启动时需要去执行的入口js文件,类似于小程序中的app.js。
src/components文件夹(放组件的文件夹)下:
HelloWorld.vue 结构同App.vue,这就是一个组件。
2. 用Vite创建Vue3项目(Vite的编译重启速度非常快)
Vite有一定潜力去替代现在的Webpack。Vite在开发阶段,编译和重启的速度是非常快的。
进入要创建项目的文件夹,按住 shift
右击,选择 在此处打开命令窗口
,输入语句 npm init vite-app 项目名
,如 npm init vite-app test
。回车后如图显示:
根据提示,输入语句 cd 项目名
,如 cd test
。
进入项目目录后,输入 npm install
(可简写为 npm i
)。等待安装完成后,即为创建成功。
可输入 npm run dev
,根据提示,打开网址:http://localhost:3000/,即可看到下图界面:
3. 在开发工具中运行Vue3项目
我个人用的是Webstorm,其他开发工具类似。在开发工具中 打开Terminal(终端)窗口,( Webstorm快捷键 Alt+F12,)进入要运行的项目根目录,如 cd test
,在根目录下输入语句 npm run serve
,待本地服务器启动完成后,即可根据提示打开网址,如 http://localhost:8080/,浏览项目内容。用Cli创建的Vue3项目默认显示如下图:
更改内容后保存,Terminal(终端)内就又会重新编译重启。
4. 打包 & 部署(生成HTML+CSS+JS)
那么,浏览器可以直接识别Vue文件吗?No,事实上浏览器里的还是HTML+CSS+JS。
上文编译之后,在浏览器打开网站时,它会生成HTML、CSS、JS文件。
我们可以继续在Terminal(终端)中输入命令 npm run build
,它会在生产环境进行一个打包。打包完成后,在项目根目录会多出一个 dist 文件夹 。dist内是生成编译之后的所有文件,也就是我们所熟悉的HTML+CSS+JS。
要部署的时候,把dist下面这些文件拿去部署就行了。
十、重置浏览器默认样式(去除默认边距)
在src\assets\style目录下,新建一个重置样式的文件 reset.css
(也可以自行去网上下载,各种版本有很多),以去除各标签默认边距。此处提供一个,如下(涉及颜色的请按需更改):
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
font-weight: normal;
vertical-align: baseline;
}
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
html, body, #app {
width: 100%;
height: 100%;
}
ol, ul, li {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
input, button {
padding: 0;
}
input:focus, button:focus {
outline: -webkit-focus-ring-color auto 0;
}
a {
color: #333333;
-webkit-backface-visibility: hidden;
text-decoration: none;
cursor: pointer;
}
img {
display: block;
width: 100%;
height: auto;
}
body {
line-height: 1;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
a:focus,
a:active {
outline: none;
}
a,
a:focus,
a:hover {
cursor: pointer;
color: inherit;
text-decoration: none;
}
导入该文件:
在根目录下 main.js
文件中,加一句 import './assets/style/reset.css'
即可导入。注意:此处只能用单引号,不能用双引号。
十一、Vue的自定义组件(一切皆组件)
在Vue的应用程序中,可以理解成是没有页面的,全部都是组件。一个Vue的应用程序就是由很多很多个组件组合在一起构成的。这个思想和小程序也是一致的。
使用组件的好处是:可复用 & 分离代码。
1. 先作简单介绍,即:自定义组件写在同一文件中的情况。
例:
html代码:
<div id="app">
<test-a></test-a> <!-- test-a为自定义组件 的 自定义标签名 -->
<test-a></test-a> <!-- 自定义组件可重复使用 -->
</div>
JavaScript代码:
const {createApp} = Vue
const app = {
setup() {
return {
}
}
}
// 将createApp(app).mount('#app')这句话拆分
const vm = createApp(app)
vm.component('test-a', {
template: '<div>hello</div>',
}) // 定义一个名为test-a的组件,组件内容是<div>hello</div>
vm.mount('#app')
2. 自定义组件是单独的文件。
从 用cli创建的vue3项目 中,我们可以看到这样的结构(多余代码我删除了):
App.vue文件内容(多余代码我删除了):
<template>
<HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
<style>
#app {
}
</style>
HelloWorld.vue文件内容(多余代码我删除了):
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.hello {
}
</style>
这里的 HelloWorld.vue 就是一个自定义组件。
通过 import HelloWorld from './components/HelloWorld.vue'
这句话引入。
用此方法可以多级引入,组件内还有组件,这些的一个一个节点,一层一层关系,就构成了Vue的整个应用程序。
export default中,name是组件名(可以理解为标签名);
props里面是属性,用这样的结构可引入该组件: <组件名 某属性="属性值" />
或 <组件名 某属性="属性值"></组件名>
,如: <HelloWorld msg="xxx"/>
;
components是当前vue文件中要引入的组件。
3. 自定义组件引入类型 简单示例
3.1 引入图片示例
3.1.1 直接引入图片
App.vue文件内容(部分):
<template>
<img class="img" alt="" src="./assets/img.jpg">
</template>
这里的图片路径是本地相对路径,也可以替换成 @/assets/img.jpg
,此处的 @
代表的是 src
文件夹(源码文件夹)。
3.1.2 在自定义组件中引入图片
在components目录下,创建文件 ImgComponent.vue
。
ImgComponent.vue文件内容:
<template>
<img class="size" :src="url"/>
</template>
<script>
export default {
name: 'ImgComponent',
props: ['url']
}
</script>
<style scoped>
.size {
width: 100%;
}
</style>
1) 使用require加载图片
App.vue文件内容(部分):
<template>
<ImgComponent :url="require('./assets/img.jpg')" />
</template>
<script>
import ImgComponent from './components/ImgComponent.vue'
export default {
name: 'App',
components: {
ImgComponent
}
}
</script>
2) 使用变量加载图片
用 import
引入 图片变量m
,再由 setup
函数return出来。
App.vue文件内容(部分):
<template>
<ImgComponent :url="m"/>
</template>
<script>
import ImgComponent from './components/ImgComponent.vue'
import m from './assets/img.jpg'
export default {
name: 'App',
components: {
ImgComponent
},
setup () {
return {
m
}
}
}
</script>
3) 在自定义组件中打印传入属性的值
如本例中:ImgComponent.vue文件内容(部分):
<script>
export default {
name: 'ImgComponent',
props: ['url'],
setup (props) { // 该参数命名任意,一般为props。该参数是Vue传入的,可以帮我们引入自己定义的属性。
console.log(props.url)
}
}
</script>
注意setup函数传入的这个参数。该参数命名任意,一般为props。该参数是Vue传入的,可以帮我们引入自己定义的属性。
另外,此处的 props: ['url']
为简写形式,可以展开成:
props: {
url: {
type: String, // Vue里面可以传Object对象,小程序不可以
default: '' // default可不写
}
}
3.2 引入 数字型、布尔型、数组型、对象型 变量 示例
要用v-bind指令,否则会被识别成字符串。
数字型:
<template>
<CustomComponent :num="1"/>
</template>
布尔型(第2、3写法均表示true):
<template>
<CustomComponent :flag="false"/>
<CustomComponent :flag="true"/>
<CustomComponent flag/>
</template>
布尔型同小程序(第2、3写法均表示true):
<CustomComponent flag="{{false}}"/>
<CustomComponent flag="{{true}}"/>
<CustomComponent flag/>
数组型(对象型 同理):
<template>
<CustomComponent :arr="[1, 2, 3]"/>
</template>
4. 自定义组件的props属性
4.1 单向数据流特性
首先明确:单向数据流 和 双向数据绑定 不冲突。
父组件可以 通过属性的方式 把数据A传到子组件中,子组件也可以 通过自定义事件 或 数据绑定的方式 把数据B传到父组件中。在Vue中,数据传递的方式是非常灵活的。
对于单向数据流,我们只需要关心一点:数据的改变!
某个变量从父组件传到子组件中,子组件中这个变量的值是只读的,不可更新。而父组件中可以更改这个变量的值,且所有子组件中这个变量的值都会相应更新。这就是 单向数据流 。只需关心某个变量的值能不能被改变。
注意:父组件中的变量传入子组件中,想要改变父组件变量的值时 子组件也相应更新值,此变量需为 响应式对象 !普通JavaScript对象不会同步更改!
cli导入ref方法:
import { ref } from 'vue'
let a = ref(1)
4.2 值类型 和 引用类型
上文 单向数据流 提到的父组件内的变量,即为 值类型 。子组件内通过属性的方式引用了该变量,子组件内此变量 即为 引用类型 。
直接修改引用类型的值是不被允许的,但其内部的属性可以被修改。
以一个Ojbect对象为例:
子组件内直接修改值,如: obj = { name: 'abc' }
,不行,会报错。
子组件内修改obj内部的属性,如: obj.name = 'abc'
,可以,不会报错。
但是子组件里去改变父组件的变量从而影响父组件的行为是不太推荐的,因为会造成很多不可预知的问题。
5. 自定义组件的自定义事件与传参
最常用的方法:
子组件要把数据传到父组件里,在子组件里编写一个自定义事件,事件携带数据和参数,在父组件监听子组件的此事件,从而捕获到子组件的相关数据。
监听原生事件用 @click="onImgClick"
这种形式。
监听自定义事件,例: @sub-event="onTestClick"
下面给出一个简单示例:子组件向父组件传值
(注意:本例中用了一张图片assets/headImg.jpg,请自行添加)
子组件ImgComponent.vue代码:
<template>
<img @click="onImgClick" class="size" :src="url"/>
</template>
<script>
const testNum = 1 // 此为 要向父组件传递的数据
export default {
name: 'ImgComponent',
props: ['url'],
setup (props, context) {
console.log(props.url)
function onImgClick () {
context.emit('sub-event', testNum) // 定义一个自定义事件sub-event
}
return {
onImgClick
}
}
}
</script>
<style scoped>
.size {
width: 100%;
}
</style>
父组件App.vue代码(监听自定义事件sub-event):
<template>
<ImgComponent :url="m" @sub-event="onTestClick"/>
</template>
<script>
import ImgComponent from './components/ImgComponent.vue'
import m from './assets/headImg.jpg'
export default {
name: 'App',
components: {
ImgComponent
},
setup () {
function onTestClick (e) {
console.log(e)
}
return {
m,
onTestClick
}
}
}
</script>
<style>
</style>
十二、vue-router
1. 初识
router,顾名思义,就是路由。可以理解成小程序的navigateTo(urlTarget)、switchTab(urlTarget),或后端的API url。
在前端里,路由 就是 页面/组件 的url。前端的路由通常不会访问服务器。
如果之前创建Vue项目时未勾选router,可以重新创建一个Vue项目(推荐),或通过命令行 npm install vue-router -g
来安装路由,此方法还需要补充一些文件,教程很多,在此不作赘述。
完成后可以看到src目录下有 views目录 和 router目录。
views目录相当于小程序的pages目录。views目录下的.vue文件,实质是组件,但可以视作页面。通常views目录下的组件不会复用,是要去关联router的。
router目录下的index.js文件,就是在配置路由。
2. vue-router编程思想
在HomeView.vue中,可以看到有export default的内容:
export default {
name: 'HomeView',
components: {
HelloWorld
}
}
因此,在router/index.js中,可以用 import HomeView from '../views/HomeView.vue'
直接导入HomeView变量,在配置路由时,可以直接写 component: HomeView
:
(推荐此方法)
{
path: '/',如:'/detail/:id'
name: 'home',
component: HomeView
}
而在AboutView.vue中,没有export default的内容。于是,在router/index.js中,我们可以看到另一种配置路由的方法,使用webpack的方式进行导入,import一个路径:
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
配置完路由后,我们便可以用 <router-link>
和 <router-view>
来完成跳转了。
注意:用此方法只会进行组件跳转,非页面跳转, <router-view>
以外的内容,不会随路由变化而变更。
例:
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<router-view/>
但是, <router-link>
的应用场景是非常局限的,下面推荐一种比 <router-link>
更好的方式:
用上文提到的自定义事件的方式(十一、5),子组件编写一个自定义事件,父组件去监听此事件并变更路由,例:
import router from '@/router'
export default {
setup() {
function onChangeView() {
router.push('/about') // 这里的路径可以是动态的,用传参的方式传递过来。
}
return {
onChangeView
}
}
}
也可以写成:
router.push({
path: '/about'
})
同理,用路由的name也可以进行跳转,如:
router.push({
name: 'about',
// 可带参
params: {
id: 3
}
})
小贴士:如果push的内容是动态的,传递的event可以采用相同结构,push时即可直接写: router.push(event)
。
总结 :在Vue中,不管是切换页面还是切换组件,都更推荐用路由的方式去进行切换。而不是通过条件渲染(如:v-if)的方式。这不单单是url的改变,更多的是 路由驱动 的思想。
但是Vue不像小程序那样,小程序可以冒泡监听子组件的事件(如:父组件可以监听子子子组件的事件),Vue不可以,默认情况下不可以跨父组件通信。也就是说,Vue如果有多级组件嵌套,得从最底层的子子…子组件一级一级向上监听。
在这种情况下, VueX全局状态管理 就有它的价值了。(后文会写)
3. 嵌套路由
例:subDetail是detail的子页面。
router/index.js部分代码示例:
import HomeView from '../views/HomeView.vue'
import DetailView from '../views/DetailView'
import SubDetail from '../views/SubDetail'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/detail',
name: 'detail',
component: DetailView,
children: [
{
path: 'sub',
name: 'subDetail',
component: SubDetail
},
{
...
}
]
},
]
4. 带参路由
如果路由要携带参数,可以写成 '路径/:参数名'
这样的形式。如: '/detail/:id'
。例:
router/index.js部分代码:
import DetailView from '../views/DetailView'
const routes = [
{
path: '/detail/:id',
name: 'detail',
component: DetailView,
},
]
获取路由参数
例:
import router from '@/router'
export default {
setup() {
console.log(router.currentRoute.value.params.id)
}
}
十三、动态组件 & Keep-Alive
根据上文我们知道,有几种方法可以实现“动态”:
- router-view 路由
- v-if 条件渲染
- v-show 显示/隐藏
这里介绍第4种方法:动态组件 & Keep-Alive。
1. 动态组件
动态组件,顾名思义,即:动态地去显示组件(如 A组件 和 B组件,满足某条件时显示A组件,否则显示B组件)。
动态组件有一个固定的标签: <component />
,它并不是某一个具体的组件,它有一个非常关键的属性: is
,例: <component v-bind:is="xxx" />
,由is去决定到底显示 A组件 还是 B组件 。这里的 xxx
,需填变量名,该变量的变量值为组件名,且该变量需要在setup函数中return出去。
以上便是 动态组件 最基本的用法。
2. 如何用Keep-Alive去保持组件的状态
Keep-Alive是Vue内置的标签,只需要用它包裹动态组件,就可以记录、保存相关组件的状态了。
使用场景如:使用动态组件切换组件时,先前A组件内input框输入的内容,切换至B组件后,再切换回A组件,input输入的内容丢失了。若使用Keep-Alive,切换组件后,先前输入的内容将仍然存在。
2.1 使用Keep-Alive缓存组件状态
使用方法非常简单,只需在动态组件的基础上加上一对标签。例:
<keep-alive>
<component v-bind:is="xxx" />
</keep-alive>
原理:使用Keep-Alive后,Vue会帮我们去缓存动态组件。如当前显示的是A组件,A组件会被Vue缓存起来,下次再使用这个A组件的时候,Vue就不会重新创建,而是会从缓存中去显示这个组件。所以它的信息是会被保留下来的。
特殊场景:有些组件需要缓存,有些组件不需要缓存。
解决方法也很简单。Keep-Alive标签里提供了2个属性:include 和 exclude。如果只想缓存A组件(假设定义组件的name为Aaa)的内容,不缓存B、C、D、E、… 组件,就用 <keep-alive include="Aaa">
。
反之,只排除A组件,就用 <keep-alive exclude="Aaa">
。
include 和 exclude 内也可以写多个组件名用逗号分隔,或用数组,或用正则表达式。
注意:include 和 exclude 内需填组件的name,区分大小写!
十四、VueX 全局状态管理
1. 为什么需要VueX?
我们知道,整个Vue是由众多的组件构成的,组件有一个非常重要的特性,就是:它具有一定的封闭性。每一个组件其实都是一个独立的个体。而组件与组件间需要相互通信。可以用事件的方式在两个组件间通信,但如果是多个组件间通信,那么每一个组件都需要一个事件转发,用事件的方法就显得不那么合适了。在这种情况下,Vue提供了一种解决方案: 全局状态管理 ,即: VueX 。
相比于windows下的全局变量,VueX的好处:
- VueX里它所管理的变量,天然就是响应式的。
- Vue对于VueX的很多操作是有记录性质的。
VueX是不允许我们直接去修改变量的,而windows下的全局变量,每一个页面都可以去修改它的值。VueX必须通过方法的方式去操作它。原因在于Vue很多时候需要去追踪这样一些变化的修改。要调用VueX的commit方法,去修改状态管理的变量。
2. 定义VueX的全局变量
首先,和router一样,创建Vue项目时需勾选VueX,完成后可以看到src目录下有 store目录。
打开store/index.js,可以看到这样一些代码:
import { createStore } from 'vuex'
export default createStore({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
我们重点关注state。可以在state下定义全局变量,例:
state: {
url: 'SubDetail'
},
定义完后,就可以在.vue文件中使用它了。例:
// 导入
import store from '@/store/index'
export default {
...
setup() {
function onClick(e) {
alert(store.state.url) // 可以顺利弹窗显示内容:“SubDetail”
}
return {
onClick
}
}
}
3. VueX改变全局状态变量并传参的3种方式
3种方式均为 通过commit方法的方式来改变上文中变量url的值。
这个方法需定义在 mutations
内。
3.1 (推荐)传函数名和单个参数。
例:
mutations: {
changeView(state, url){ // state代表的就是上面的state,是自动传入的,不需要我们手动传入。
state.url = url
}
},
使用这个changeView方法:例:
// 导入
import store from '@/store/index'
export default {
...
setup() {
function onClick(e) {
store.commit('changeView', 'newUrl') // 第一个参数代表 要调用哪个方法;第二个参数代表传入的变量值,此处为url的值。
}
return {
onClick
}
}
}
3.2 传对象。用这种方法可以一次传多个参数。
例:
定义方法:
mutations: {
changeView(state, payload){
state.url = payload.url
}
},
使用:
store.commit({
type: 'changeView',
url: 'newUrl'
// 如果还有别的参数,可以继续往下写
})
3.3 (推荐)函数名和参数分开传。这种方法也可以一次传多个参数。
例:
定义方法:
mutations: {
changeView(state, payload){
state.url = payload.url
}
},
使用:
store.commit('changeView', {
url: 'newUrl'
// 如果还有别的参数,可以继续往下写
})
4. 计算属性和监听器在VueX状态改变时的应用
4.1 用Computed和Watch函数更改路由。(比事件传参的方式要简单)
例:
import router from '@/router'
const {computed, watch} = Vue
const app = {
setup() {
const changedUrl = computed(() => store.state.url) // 利用computed函数将变量转换成响应式对象
watch(changedUrl, (newVal, oldVal) => {
router.push({
name: newVal
})
})
}
}
4.2 细化,利用全局状态管理变量进行路由切换
store/index.js:
export default createStore({
state: {
routerParams: {}
},
getters: {
},
mutations: {
changeView(state, payload){
state.routerParams = payload.routerParams
}
},
actions: {
},
modules: {
}
})
某子组件更改VueX全局变量(多个组件内均可更改):
store.commit('changeView', {
routerParams: {
name: 'Detail',
params: {
id: 3
}
}
})
父组件监听其变化并更改路由:
import router from '@/router'
const {computed, watch} = Vue
const app = {
setup() {
const changedUrl = computed(() => store.state.routerParams)
watch(changedUrl, (newVal, oldVal) => {
router.push(newVal)
})
}
}
结束语
全文到此就结束啦~
边学边写,断断续续写了一个多月,终于完成了~
撒花花~
期待大家能与本寅共同进步,加油!
本期教程 完
我是小寅,一枚95后程序媛~ 感谢阅读,欢迎补充 & 质疑~