Vue
一、Vue的基本原理和知识
- 使用虚拟DOM技术(MVVM),便于对之后的修改进行操作(对比使用diff算法)
Tips:如果提示当前错误是提示找一个网站标签
1.1基本使用
1.1.1 引入Vue
①CDN引入
<script src="https://unpkg.com/vue@3"></script>
②导入本地文件
<!-- 引入vue -->
<script type="text/javascript" src="../js/vue.js"></script>
③使用npm安装
Node.js进行
④使用官方CLI构建
1.1.2 使用示例
<!-- 准备好一个容器, -->
<div id="root">
<h1>hello,{{name}}</h1>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
//创建Vue实例
new Vue({
el: '#root',//el用于指定当前vue 实例为哪个容器服务
data: {//data中用于存储数据,数据供el所指定的容器进行使用
name: 'lmz'
}
})
</script>
容器和实例 只能一一对应,并且配合组件一起使用
{{}} 中的数据发生改变,页面中数据也会自动更新
tips:第二种写法
data:函数式写法
<script type="text/javascript">
Vue.config.productionTip = false
//创建Vue实例
el: '#root',
data() {
return {
name: 'lmz'
}
}
</script>
绑定:
<script type="text/javascript">
Vue.config.productionTip = false
//创建Vue实例
const v= new Vue({
data: {//data中用于存储数据,数据供el所指定的容器进行使用
name: 'lmz'
}
})
v.$mount('root')//另一种邦迪你那个方式
</script>
1.2 模板语法
1.2.1 分类
-
插值语法
- {{ }}
-
指令语法
用于解析标签(例如标签属性,标签体内容,绑定事件…)
-
绑定:
- v-bind(单项绑定)
<a v-bind:href="url">点击跳转</a> <!-- 或者直接 :href-->
- v-model(双向绑定,页面改变数据也改变)
只能使用在表单元素上
<a v-model:href="url">点击跳转</a> <!-- 或者直接 v-model-->
-
Tips:
数据代理
Object.defineProperty(操作对象,变量名,{
get(){}
set(value){}
})
相当于java中的Getter和Setter方法
1.3事件处理
1.3.1 绑定点击
<button v-on:click="showInfo">点我</button>
<!-- 或者直接 @click-->
之后再methods方法中添加函数事件
<script >
new Vue({
methods:{
showInfo(){
alert("你好")
}
}
})
</script>
1.3.2 Vue中默认的事件修饰符
- prevent:阻止默认事件
- stop:阻止事件冒泡
- once:事件只发生一次
后面三个使用不多
- capture:使用事件的捕捉模式
- self
- passive
示例:
<body>
<div id="root">
<h2>欢迎来到{{name}}</h2>
<button v-on:click.prevent="showInfo">点我</button>
</div>
</body>
<script >
new Vue({
el: '#root',
data() {
return {
name:"WDSJ"
}
},
methods:{
showInfo(e){
alert("你好")
}
}
})
</script>
1.3.3 键盘事件
@keydown/@keyup:按下/按下后抬起
.enter :按下回车执行方法
@keyup.ctrl.y : 使用两个按键同时按下可以执行
<input type="text" placeholder="按下回车输入提示" @keydown.enter="showInfo">
Vue中常用别名:
Vue中常用的按键别名:
- 回车 => enter
- 删除 => delete
- 退出 => esc
- 空格 => space
- 换行 => tab (特殊,必须配合keydown去使用)
1.4计算属性
用已有的属性值进行计算操作,生成全新的属性值
- 定义:要用的属性不存在,要通过已有属性计算得来
- 原理:底层借助了Objcet.defineProperty方法提供的getter和setter
- get函数什么时候执行?
- (1).初次读取时会执行一次
- (2).当依赖的数据发生改变时会被再次调用
- 优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
- 备注:
- 计算属性最终会出现在vm上,直接读取使用即可
- 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变
关键字: computed:
实际应用:
<body>
<div id="root">
<!-- 双向绑定数据 -->
姓:<input type="text" v-model="firstName"><br>
名:<input type="text" v-model="lastName"><br>
全名:<span>{{fullName}}</span>
</div>
</body>
<script>
vm = new Vue({
el: '#root',
data() {
return {
firstName: "王",
lastName: "哲"
}
},
computed: {
fullName: {
get() {
//get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
//get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
console.log('get被调用')
return this.firstName + '-' + this.lastName;
},
//set什么时候调用? 当fullName被修改时。
// 可以主动在控制台修改fullName来查看情况
set(value) {
console.log('set', value)
const arr = value.split('-')//将字符串拆成数组
this.firstName = arr[0]
this.lastName = arr[1]
}
}
}
})
</script>
简写
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<!-- 双向绑定数据 -->
姓:<input type="text" v-model="firstName"><br>
名:<input type="text" v-model="lastName"><br>
全名:<span>{{fullName}}</span>
</div>
</body>
<script>
vm = new Vue({
el: '#root',
data() {
return {
firstName: "王",
lastName: "哲"
}
},
computed: {
//将fullName当做函数getter使用,调用时是属性
fullName: function () {
console.log('get被调用')
return this.firstName + '-' + this.lastName;
}
}
})
</script>
1.5监视属性
1.5.1 基本属性
监视属性watch:
- 当被监视的属性变化时, 回调函数自动调用, 进行相关操作
- 监视的属性必须存在,才能进行监视
- 监视的两种写法:
- (1).new Vue时传入watch配置
- (2).通过vm.$watch监视
第一种
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>今天天气很{{ info }}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>
<script>
const vm = new Vue({
el: '#root',
data: {
isHot: true,
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽'//三元表表达式
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot
}
},
watch: {
isHot: {
immediate: true, // 初始化时让handler调用一下
// handler什么时候调用?当isHot发生改变时。
handler(newValue, oldValue) {
console.log('isHot被修改了', newValue, oldValue)
}
}
}
})
</script>
第二种写法
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>今天天气很{{ info }}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>
<script>
const vm = new Vue({
el: '#root',
data: {
isHot: true,
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot
}
}
})
vm.$watch('isHot', {
immediate: true, //初始化时让handler调用一下
//handler什么时候调用?当isHot发生改变时。
handler(newValue, oldValue) {
console.log('isHot被修改了', newValue, oldValue)
}
})
</script>
1.5.2 深度监视
下面的a属性通过handler监测不到,因为是在numbers属性里
data: {
isHot: true,
numbers: {
a: 1,
b: 2,
}
}
- (1).Vue中的watch默认不监测对象内部值的改变(一层)
- (2).配置deep:true可以监测对象内部值改变(多层)
备注:
(1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以
(2).使用watch时根据数据的具体结构,决定是否采用深度监视
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>今天天气很{{ info }}</h2>
<button @click="changeWeather">切换天气</button>
<hr>
<span>{{numbers.a}}</span><br>
<button @click="numbers.a++">点我a+1</button>
</div>
</body>
<script>
const vm = new Vue({
el: '#root',
data: {
isHot: true,
numbers: {
a: 1,
b: 2,
}
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽'//三元表表达式
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot
}
},
watch: {
isHot: {
immediate: true, // 初始化时让handler调用一下
// handler什么时候调用?当isHot发生改变时。
handler(newValue, oldValue) {
console.log('isHot被修改了', newValue, oldValue)
}
},
//监测某个属性变化
'numbers.a':{
handler(newValue, oldValue){
console.log('a改变了',newValue, oldValue)
}
},
numbers:{
deep:true,
handler(){
console.log("number改变")
}
}
}
})
</script>
1.5.3简写
<!-- 准备好一个容器-->
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
<script>
const vm = new Vue({
el:'#root',
data:{
isHot:true,
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
}
},
watch:{
//简写
isHot(newValue, oldValue) {
console.log('isHot被修改了', newValue, oldValue)
}
}
})
</script>
1.5.4 computed和watch之间的区别
- computed能完成的功能,watch都可以完成
- watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
两个重要的小原则:
1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象
2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象
<!-- 准备好一个容器-->
<div id="root">
姓:<input type="text" v-model="firstName"> <br/><br/>
名:<input type="text" v-model="lastName"> <br/><br/>
全名:<span>{{fullName}}</span> <br/><br/>
</div>
<script>
const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三',
fullName:'张-三'
},
watch:{
// watch 监视器里可以写 异步函数
firstName(newValue){
//设置定时器,箭头函数没有自己的this,调用外部this
setTimeout(()=>{
console.log(this)
this.fullName = newValue + '-' + this.lastName
},1000);
},
lastName(newValue){
this.fullName = this.firstName + '-' + newValue
}
}
})
</script>
1.6绑定样式
1.6.1 Class样式
写法::class=“xxx” xxx可以是字符串、对象、数。
所以分为三种写法,字符串写法,数组写法,对象写法
字符串写法
字符串写法适用于:类名不确定,要动态获取。
正常样式:class=“basic” 动态样式 :class=“change”
<style>
.normal{
background-color: skyblue;
}
</style>
<!-- 准备好一个容器-->
<div id="root">
<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="change" @click="changeMood">{{name}}</div>
</div>
<script>
const vm = new Vue({
el:'#root',
data:{
change:'normal'
}
})
</script>
数组写法
数组写法适用于:要绑定多个样式,个数不确定,名字也不确定。
<style>
.atguigu1{
background-color: yellowgreen;
}
.atguigu2{
font-size: 30px;
text-shadow:2px 2px 10px red;
}
.atguigu3{
border-radius: 20px;
}
</style>
<!-- 准备好一个容器-->
<div id="root">
<!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
<div class="basic" :class="classArr">{{name}}</div>
</div>
<script>
const vm = new Vue({
el:'#root',
data:{
classArr: ['atguigu1','atguigu2','atguigu3']
}
})
</script>
对象写法
对象写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
<style>
.atguigu1{
background-color: yellowgreen;
}
.atguigu2{
font-size: 30px;
text-shadow:2px 2px 10px red;
}
</style>
<!-- 准备好一个容器-->
<div id="root">
<!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="classObj">{{name}}</div>
</div>
<script>
const vm = new Vue({
el:'#root',
data:{
classObj:{
atguigu1:false,
atguigu2:false,
}
}
})
</script>
1.7条件渲染
1.7.1 v-if
-
写法:
(1).v-if=“表达式”
(2).v-else-if=“表达式”
(3).v-else=“表达式”
-
适用于:切换频率较低的场景
-
特点:不展示的DOM元素直接被移除
-
注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”
<!-- 准备好一个容器-->
<div id="root">
<!-- 使用v-if做条件渲染 -->
<h2 v-if="false">欢迎来到{{name}}</h2>
<h2 v-if="1 === 1">欢迎来到{{name}}</h2>
<!-- v-else和v-else-if -->
<div v-if="n === 1">Angular</div>
<div v-else-if="n === 2">React</div>
<div v-else-if="n === 3">Vue</div>
<div v-else>哈哈</div>
<!-- v-if与template的配合使用 -->
<!-- 就不需要写好多个判断,写一个就行 -->
<template v-if="n === 1">
<h2>你好</h2>
<h2>尚硅谷</h2>
<h2>北京</h2>
</template>
</div>
<script>
const vm = new Vue({
el:'#root',
data:{
styleArr:[
{
fontSize: '40px',
color:'blue',
},
{
backgroundColor:'gray'
}
]
}
})
</script>
template和div标签区别:templat标签转化后不影响样式
1.7.2 v-show
- 写法:v-show=“表达式”
- 适用于:切换频率较高的场景
- 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉(display:none)
备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到
v-if 是实打实地改变dom元素,v-show 是隐藏或显示dom元素
<!-- 准备好一个容器-->
<div id="root">
<!-- 使用v-show做条件渲染 -->
<h2 v-show="false">欢迎来到{{name}}</h2>
<h2 v-show="1 === 1">欢迎来到{{name}}</h2>
</div>
1.8列表渲染
- 用于展示列表数据
- 语法:v-for=“(item, index) in xxx” :key=“yyy”
index是每个 item的编号
key值给每一个遍历序列定义名字
- 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组)</h2>
<ul>
<li v-for="(p,index) of persons" :key="index">
{{p.name}}-{{p.age}}
</li>
</ul>
<!-- 遍历对象 -->
<h2>汽车信息(遍历对象)</h2>
<ul>
<li v-for="(value,key) of car" :key="k">
{{key}}-{{value}}
</li>
</ul>
<!-- 遍历字符串 -->
<h2>测试遍历字符串(用得少)</h2>
<ul>
<li v-for="(char,index) of str" :key="index">
{{char}}-{{index}}
</li>
</ul>
<!-- 遍历指定次数 -->
<h2>测试遍历指定次数(用得少)</h2>
<ul>
<li v-for="(number,index) of 5" :key="index">
{{index}}-{{number}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el:'#root',
data: {
persons: [
{ id: '001', name: '张三', age: 18 },
{ id: '002', name: '李四', age: 19 },
{ id: '003', name: '王五', age: 20 }
],
car: {
name: '奥迪A8',
price: '70万',
color: '黑色'
},
str: 'hello'
}
})
</script>
1.9 Vue 收集表单数据
<div id="root">
<form @submit.prevent="demo">
性别:
男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
女<input type="radio" name="sex" v-model="userInfo.sex" value="female">
</form>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el:'#root',
data:{
userInfo:{
sex:'female'
}
},
methods: {
demo(){
console.log(JSON.stringify(this.userInfo))
}
}
})
</script>
tips:
在多选框和复选框这种没有默认值的输入情况,需要手动设置value值
备注:v-model的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤
1.10 自定义语法
使用directives属性添加自定义的指令内容
需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。(函数式)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>n的值是:<span>{{n}}</span></h2>
<h2>n放大10倍是:<span v-big="n"></span></h2>
<button @click="n++">点击n+1</button>
</div>
</body>
<script>
const vm = new Vue({
el: '#root',
data: {
n: 1,
},
directives:{
big(element,binding){//element 是真实的dom元素,binding是一个对象含有真实的输入值
element.innerText=binding.value * 10
}
}
})
</script>
</html>
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。(对象式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器-->
<div id="root">
<input type="text" v-fbind:value="n">
</div>
</body>
<script>
const vm = new Vue({
el: '#root',
data: {
n: 1,
},
directives:{
fbind:{
//指令与元素成功绑定时
bind(element,binding){
element.value=binding.value
},
//指令所在元素被插入页面时
inserted(element,binding){
element.focus()
},
//指令所在模板被重新解析时
update(element,binding){
element.value=binding.value
}
}
}
})
</script>
</html>
1.11 组件的使用
①非单文件组件
定义:一个文件中包含多个组件
基本使用
Vue中使用组件的三大步骤:
- 定义组件(创建组件)
- 注册组件
- 使用组件(写组件标签)
案例:
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
区别如下:
- el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
- data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器-->
<div id="root">
<!-- 使用组件-->
<xuexiao></xuexiao>
<hr>
<xuesheng></xuesheng>
</div>
</body>
<script>
//创建school组件
const school =Vue.extend({
//el: '#root', 不能写el配置项,因为最终服务于谁不能写死
template:`
<div>
<h2>学校名称:{{schoolName}}</h2>
<h2 >学校地址:{{address}}</h2>
</div>
`,
data(){
return{
schoolName: '北京邮电大学',
address: '海淀区'
}
}
})
const student =Vue.extend({
//el: '#root', 不能写el配置项,因为最终服务于谁不能写死
template:`
<div>
<h2>学生姓名:{{studentName}}</h2>
<h2 >学生年龄: {{age}}</h2>
</div>
`,
data(){
return{
studentName:'hh',
age: 10
}
}
})
const vm = new Vue({
el: '#root',
//注册school组件
components: {
xuexiao:school,
xuesheng:student
}
})
</script>
</html>
②单文件组件
定义:一个文件中只有一个组件 文件类型 (***.vue)
来做个单文件组件的案例:
School.vue(组件)
<template>
<div class="demo">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
export default {
name:'School',
data(){
return {
name:'尚硅谷',
address:'北京昌平'
}
},
methods: {
showName(){
alert(this.name)
}
},
}
</script>
<style>
.demo{
background-color: orange;
}
</style>
Student.vue(组件)
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return {
name:'张三',
age:18
}
}
}
</script>
App.vue(组件汇总)
用来汇总所有的组件(大总管)
<template>
<div>
<School></School>
<Student></Student>
</div>
</template>
<script>
//引入组件
import School from './School.vue'
import Student from './Student.vue'
export default {
name:'App',
components:{
School,
Student
}
}
</script>
main.js(创建vue实例)
在这个文件里面创建 vue 实例
import App from './App.vue'
new Vue({
el:'#root',
template:`<App></App>`,
components:{App},
})
index.html(页面)
在这写 vue 要绑定的容器
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>练习一下单文件组件的语法</title>
</head>
<body>
<!-- 准备一个容器 -->
<div id="root"></div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" src="./main.js"></script>
</body>
</html>
二、Vue脚手架,自定义时间,插槽等
2.1脚手架
使用前置:
第一步(没有安装过的执行):全局安装 @vue/cli
npm install -g @vue/cli
第二步:切换到要创建项目的目录,然后使用命令创建项目
vue create xxxxx
第三步:启动项目
npm run serve
2.1.1脚手架文件结构
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件
main.js
import Vue from 'vue'
import App from './App.vue'
// 关闭所有的生产提示
Vue.config.productionTip = false
new Vue({
// 将App组件放入容器中
render: h => h(App),
}).$mount('#app')
index.html
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<!-- 针对IE浏览器的一个特殊配置,含义是让IE浏览器以最高的渲染级别渲染页面 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 开启移动端的理想视口 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 配置页签图标 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 引入第三方样式 -->
<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">
<!-- 配置网页标题 -->
<title>硅谷系统</title>
</head>
<body>
<!-- 当浏览器不支持js时noscript中的元素就会被渲染 -->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<!-- 容器 -->
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
2.2Vue 其他知识
2.2.1 ref属性
- 被用来给元素或子组件注册引用信息(id的替代者)
- 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
- 使用方式:
- 打标识:
<h1 ref="xxx">.....</h1>
或<School ref="xxx"></School>
- 获取:
this.$refs.xxx
- 打标识:
具体案例
<template>
<div>
<h1 v-text="msg" ref="title"></h1>
<button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
<School ref="sch"/>
</div>
</template>
<script>
//引入School组件
import School from './components/School'
export default {
name:'App',
components:{School},
data() {
return {
msg:'欢迎学习Vue!'
}
},
methods: {
showDOM(){
console.log(this.$refs.title) //真实DOM元素
console.log(this.$refs.btn) //真实DOM元素
console.log(this.$refs.sch) //School组件的实例对象(vc)
}
},
}
</script>
2.2.2 props配置项
-
功能:让组件接收外部传过来的数据
-
传递数据:
<Demo name="xxx"/>
-
接收数据:
-
第一种方式(只接收):
props:['name']
-
第二种方式(限制类型):
props:{name:String}
-
第三种方式(限制类型、限制必要性、指定默认值):
props:{ name:{ type:String, //类型 required:true, //必要性 default:'老王' //默认值 } }
备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
-
示例代码:
父组件给子组件传数据
App.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<Student></Student>
<School name="haha" :age="this.age"></School>
</div>
</template>
<script>
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {
name: 'App',
data () {
return {
age: 360
}
},
components: {
School,
Student
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
School.vue
<template>
<div class="demo">
<h2>学校名称:{{ name }}</h2>
<h2>学校年龄:{{ age }}</h2>
<h2>学校地址:{{ address }}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
export default {
name: "School",
// 最简单的写法:props: ['name', 'age']
props: {
name: {
type: String,
required: true // 必须要传的
},
age: {
type: Number,
required: true
}
},
data() {
return {
address: "北京昌平",
};
},
methods: {
showName() {
alert(this.name);
},
},
};
</script>
<style>
.demo {
background-color: orange;
}
</style>
2.2.3 mixin(父类继承)
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能(父类)。
选项合并
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
var mixin = {
data: function () {
return {
message: 'hello',
foo: 'abc'
}
}
}
new Vue({
mixins: [mixin],
data: function () {
return {
message: 'goodbye',
bar: 'def'
}
},
created: function () {
console.log(this.$data)
// => { message: "goodbye", foo: "abc", bar: "def" }
}
})
同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
var mixin = {
created: function () {
console.log('混入对象的钩子被调用')
}
}
new Vue({
mixins: [mixin],
created: function () {
console.log('组件钩子被调用')
}
})
// => "混入对象的钩子被调用"
// => "组件钩子被调用"
2.2.4插件
- 自定义插件
plugins.js
export default {
install() {
console.log('install')
}
}
- 使用插件
// 引入插件
import plugin from './plugin'
// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)
new Vue({
// ...组件选项
})
2.2.5 scoped样式
- 作用:让样式在局部生效,防止冲突。
- 写法:
<style scoped>
具体案例:
<style lang="less" scoped>
.demo{
background-color: pink;
.atguigu{
font-size: 40px;
}
}
</style>
2.3 组件自定义事件
组件自定义事件是一种组件间通信的方式,适用于:子组件 ===> 父组件
使用场景
A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
绑定自定义事件:
第一种方式,在父组件中:<Demo @atguigu="test"/>
或 <Demo v-on:atguigu="test"/>
具体代码
App.vue
<template>
<div class="app">
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
<Student @atguigu="getStudentName"/>
</div>
</template>
<script>
import Student from './components/Student'
export default {
name:'App',
components:{Student},
data() {
return {
msg:'你好啊!',
studentName:''
}
},
methods: {
getStudentName(name,...params){
console.log('App收到了学生名:',name,params)
this.studentName = name
}
}
}
</script>
<style scoped>
.app{
background-color: gray;
padding: 5px;
}
</style>
Student.vue
<template>
<div class="student">
<button @click="sendStudentlName">把学生名给App</button>
</div>
</template>
<script>
export default {
name:'Student',
data() {
return {
name:'张三',
}
},
methods: {
sendStudentlName(){
//触发Student组件实例身上的atguigu事件
this.$emit('atguigu',this.name,666,888,900)
}
},
}
</script>
<style lang="less" scoped>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
第二种方式,在父组件中:
使用 this.$refs.xxx.$on()
这样写起来更灵活,比如可以加定时器啥的。
具体代码
App.vue
<template>
<div class="app">
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
<Student ref="student"/>
</div>
</template>
<script>
import Student from './components/Student'
export default {
name:'App',
components:{Student},
data() {
return {
studentName:''
}
},
methods: {
getStudentName(name,...params){
console.log('App收到了学生名:',name,params)
this.studentName = name
},
},
mounted() {
this.$refs.student.$on('atguigu',this.getStudentName) //绑定自定义事件
// this.$refs.student.$once('atguigu',this.getStudentName) //绑定自定义事件(一次性)
},
}
</script>
<style scoped>
.app{
background-color: gray;
padding: 5px;
}
</style>
Student.vue
<template>
<div class="student">
<button @click="sendStudentlName">把学生名给App</button>
</div>
</template>
<script>
export default {
name:'Student',
data() {
return {
name:'张三',
}
},
methods: {
sendStudentlName(){
//触发Student组件实例身上的atguigu事件
this.$emit('atguigu',this.name,666,888,900)
}
},
}
</script>
<style lang="less" scoped>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
若想让自定义事件只能触发一次,可以使用
once
修饰符,或$once
方法。触发自定义事件:
this.$emit('atguigu',数据)
使用 this.$emit() 就可以子组件向父组件传数据
解绑自定义事件this.$off('atguigu')
代码
this.$off('atguigu') //解绑一个自定义事件
// this.$off(['atguigu','demo']) //解绑多个自定义事件
// this.$off() //解绑所有的自定义事件
组件上也可以绑定原生DOM事件,需要使用native
修饰符。
代码
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
<Student ref="student" @click.native="show"/>
注意:通过
this.$refs.xxx.$on('atguigu',回调)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题
2.4全局事件总线
任意组件通信
公用部分:x
- 要能被其他所有组件看见
- 要能调用
- $on
- $off
- $emit
-
安装全局事件总线
Vue.prototype.$bus = this
new Vue({ ...... beforeCreate() { Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm }, ...... })
-
使用事件总线
…补
2.5 slot插槽
-
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件 。
-
分类:默认插槽、具名插槽、作用域插槽
-
使用方式:
-
默认插槽:
父组件中: <Category> <div>html结构1</div> </Category> 子组件中: <template> <div> <!-- 定义插槽 --> <slot>插槽默认内容...</slot> </div> </template>
-
具名插槽:
父组件中: <Category> <template slot="center"> <div>html结构1</div> </template> <template v-slot:footer> <div>html结构2</div> </template> </Category> 子组件中: <template> <div> <!-- 定义插槽 --> <slot name="center">插槽默认内容...</slot> <slot name="footer">插槽默认内容...</slot> </div> </template>
-
作用域插槽:
-
理解:数据在组件的自身(子组件),但根据数据生成的结构需要组件的使用者(父组件)来决定。(games数据在Category(子)组件中,但使用数据所遍历出来的结构由App(父)组件决定)
-
具体编码:
父组件中: <Category> <template scope="scopeData"> <!-- 生成的是ul列表 --> <ul> <li v-for="g in scopeData.games" :key="g">{{g}}</li> </ul> </template> </Category> <Category> <template slot-scope="scopeData"> <!-- 生成的是h4标题 --> <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4> </template> </Category> 子组件中: <template> <div> <!-- 通过数据绑定就可以把子组件的数据传到父组件 --> <slot :games="games"></slot> </div> </template> <script> export default { name:'Category', props:['title'], //数据在子组件自身 data() { return { games:['红色警戒','穿越火线','劲舞团','超级玛丽'] } }, } </script>
-
三、VUEX
简单实现多组件共享数据
绿色框中需要被store配置项进行管理
3.1 概念
在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
Vue2使用vuex的3版本
Vue3使用vuex的4版本
3.2 何时使用?
多个组件需要共享数据时
3.3搭建环境
-
安装vuex插件
npm i vuex@3
-
创建文件:
src/store/index.js
//该文件用于创建Vuex中最为核心的store //引入Vuex import Vue from 'vue' import Vuex from 'vuex' //应用Vuex插件 Vue.use(Vuex) //准备actions——用于响应组件中的动作 const actions = {} //准备actions——用于响应组件中的动作 const mutations = {} //准备state——用于存储数据 const state = {} //创建并暴露store export default new Vuex.Store({ actions, mutations, state })
-
在
main.js
中创建vm时传入store
配置项...... //引入store import store from './store' ...... //创建vm new Vue({ el:'#app', render: h => h(App), store })
-
结果
3.4基本使用
-
初始化数据、配置
actions
、配置mutations
,操作文件store.js
//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //引用Vuex Vue.use(Vuex) const actions = { //响应组件中加的动作 jia(context,value){ // console.log('actions中的jia被调用了',miniStore,value) context.commit('JIA',value) }, } const mutations = { //执行加 JIA(state,value){ // console.log('mutations中的JIA被调用了',state,value) state.sum += value } } //初始化数据 const state = { sum:0 } //创建并暴露store export default new Vuex.Store({ actions, mutations, state, })
-
组件中读取vuex中的数据:
$store.state.sum
-
组件中修改vuex中的数据:
$store.dispatch('action中的方法名',数据)
或$store.commit('mutations中的方法名',数据)
备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写
dispatch
,直接编写commit
具体实现:
index.js
//引入Vuex
import Vue from 'vue'
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
//准备actions——用于响应组件中的动作
const actions = {
increase(context, value) {
context.commit('INCREASE', value)
},
}
//准备mutations——用于操作数据(state)
const mutations = {
INCREASE(state, value) {
state.sum += value
}
}
//准备state——用于存储数据
const state = {
sum: 0,//当前的和
}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state
})
Count.vue
<template>
<div>
<h1>当前求和为:{{$store.state.sum}}</h1>
<select v-model="selectNum">
<option :value="1">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
</select>
<button @click="increase">+</button>
<button @click="decrease">-</button>
<button @click="increaseOdd">当前求和为奇数再加</button>
<button @click="increaseWait">等一等再加</button>
</div>
</template>
<script>
// 组件交互相关代码
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Count',
data() {
return {
selectNum: 1,//用户选择的数字
}
},
methods: {
//加
increase() {
this.$store.dispatch('increase', this.selectNum)
}
}
</script>
<style>
/* 组件的样式 */
</style>
3.5 getters的使用
-
概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。
-
在
index.js
中追加getters
配置...... const getters = { bigSum(state){ return state.sum * 10 } } //创建并暴露store export default new Vuex.Store({ ...... getters })
-
组件中读取数据:
$store.getters.bigSum
3.6四个map的使用
作用:简化{{$store.state.sum}}的手写重复工作
导入
import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'
-
**mapState方法:**用于帮助我们映射
state
中的数据为计算属性computed: { //借助mapState生成计算属性:sum、school、subject(对象写法) ...mapState({sum:'sum',school:'school',subject:'subject'}), //借助mapState生成计算属性:sum、school、subject(数组写法) ...mapState(['sum','school','subject']), },
-
**mapGetters方法:**用于帮助我们映射
getters
中的数据为计算属性computed: { //借助mapGetters生成计算属性:bigSum(对象写法) ...mapGetters({bigSum:'bigSum'}), //借助mapGetters生成计算属性:bigSum(数组写法) ...mapGetters(['bigSum']) },
-
**mapActions方法:**用于帮助我们生成与
actions
对话的方法,即:包含$store.dispatch(xxx)
的函数methods:{ //靠mapActions生成:incrementOdd、incrementWait(对象形式) ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) //靠mapActions生成:incrementOdd、incrementWait(数组形式) ...mapActions(['jiaOdd','jiaWait']) }
-
**mapMutations方法:**用于帮助我们生成与
mutations
对话的方法,即:包含$store.commit(xxx)
的函数methods:{ //靠mapActions生成:increment、decrement(对象形式) ...mapMutations({increment:'JIA',decrement:'JIAN'}), //靠mapMutations生成:JIA、JIAN(对象形式) ...mapMutations(['JIA','JIAN']), }
备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则传的参数是事件对象(event)。
<template>
<div>
<h1>当前求和为:{{ sum }}</h1>
<h3>当前求和放大10倍为:{{ bigSum }}</h3>
<h3>年龄:{{ age }}</h3>
<h3>姓名:{{name}}</h3>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<!-- 用了mapActions 和 mapMutations 的话要主动传参 -->
<button @click="increment(n)">+</button>
<button @click="decrement(n)">-</button>
<button @click="incrementOdd(n)">当前求和为奇数再加</button>
<button @click="incrementWait(n)">等一等再加</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
export default {
name: "Count",
data() {
return {
n: 1, //用户选择的数字
};
},
computed: {
...mapState(['sum', 'age', 'name']),
...mapGetters(['bigSum'])
},
methods: {
...mapActions({incrementOdd: 'sumOdd', incrementWait: 'sumWait'}),
...mapMutations({increment: 'sum', decrement: 'reduce'})
},
mounted() {
console.log("Count", this);
},
};
</script>
<style lang="css">
button {
margin-left: 5px;
}
</style>
3.7多组件使用命名空间
使用命名空间更好维护代码,调理更加清晰
修改index.js
const countAbout = {
namespaced:true,//开启命名空间
state:{x:1},
mutations: { ... },
actions: { ... },
getters: {
bigSum(state){
return state.sum * 10
}
}
}
const personAbout = {
namespaced:true,//开启命名空间
state:{ ... },
mutations: { ... },
actions: { ... }
}
export default new Vuex.Store({
modules: {
countAbout,
personAbout
}
})
四、路由
- 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
- 前端路由:key是路径,value是组件
4.1 基本使用
-
安装vue-router,命令:
npm i vue-router
-
应用插件:
Vue.use(VueRouter)
-
编写router配置项:
router\index.js(重要)
//引入VueRouter import VueRouter from 'vue-router' //引入Luyou 组件 import About from '../components/About' import Home from '../components/Home' //创建router实例对象,去管理一组一组的路由规则 const router = new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] }) //暴露router export default router
-
实现切换(active-class可配置高亮样式)
<router-link active-class="active" to="/about">About</router-link>
-
指定展示位置
<router-view></router-view>
4.2 几个注意点
- 路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹。 - 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息。 - 整个应用只有一个router,可以通过组件的
$router
属性获取到。
4.3 多级路由(多级路由)
-
配置路由规则,使用children配置项:
routes:[ { path:'/about', component:About, }, { path:'/home', component:Home, children:[ //通过children配置子级路由 { path:'news', //此处一定不要写:/news component:News }, { path:'message',//此处一定不要写:/message component:Message } ] } ]
-
跳转(要写完整路径):
<router-link to="/home/news">News</router-link>
-
指定展示位置
<router-view></router-view>
4.4 路由的query参数
-
传递参数
<!-- 跳转并携带query参数,to的字符串写法 --> <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link> <!-- 跳转并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id:666, title:'你好' } }" >跳转</router-link>
-
接收参数:
$route.query.id $route.query.title
4.5 命名路由
-
作用:可以简化路由的跳转。
-
如何使用
-
给路由命名:
{ path:'/demo', component:Demo, children:[ { path:'test', component:Test, children:[ { name:'hello' //给路由命名 path:'welcome', component:Hello, } ] } ] }
-
简化跳转:
<!--简化前,需要写完整的路径 --> <router-link to="/demo/test/welcome">跳转</router-link> <!--简化后,直接通过名字跳转 --> <router-link :to="{name:'hello'}">跳转</router-link> <!--简化写法配合传递参数 --> <router-link :to="{ name:'hello', query:{ id:666, title:'你好' } }" >跳转</router-link>
-
4.6 路由的params参数
-
配置路由,声明接收params参数
{ path:'/home', component:Home, children:[ { path:'news', component:News }, { component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', //使用占位符声明接收params参数 component:Detail } ] } ] }
-
传递参数
<!-- 跳转并携带params参数,to的字符串写法 --> <router-link :to="/home/message/detail/666/你好">跳转</router-link> <!-- 跳转并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', params:{ id:666, title:'你好' } }" >跳转</router-link>
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
-
接收参数:
$route.params.id $route.params.title
4.7 路由的props配置
作用:让路由组件更方便的收到参数
{
name:'xiangqing',
path:'detail/:id',
component:Detail,
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
// props:{a:900}
//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
// props:true
//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
props($route) {
return {
id: $route.query.id,
title:$route.query.title,
a: 1,
b: 'hello'
}
}
}
方便在要跳转去的组件里更简便的写法
跳转去组件的具体代码
<template>
<ul>
<h1>Detail</h1>
<li>消息编号:{{id}}</li>
<li>消息标题:{{title}}</li>
<li>a:{{a}}</li>
<li>b:{{b}}</li>
</ul>
</template>
<script>
export default {
name: 'Detail',
props: ['id', 'title', 'a', 'b'],
mounted () {
console.log(this.$route);
}
}
</script>
<style>
</style>
4.8 <router-link>
的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为
push
和replace
,push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push
- 如何开启
replace
模式:<router-link replace .......>News</router-link>
4.9 编程式路由导航
-
作用:不借助
<router-link>
实现路由跳转,让路由跳转更加灵活 -
具体编码:
//$router的两个API this.$router.push({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.replace({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.forward() //前进 this.$router.back() //后退 this.$router.go() //可前进也可后退
4.10 缓存路由组件
-
作用:让不展示的路由组件保持挂载,不被销毁。
-
具体编码:
这个 include 指的是组件名
<keep-alive include="News"> <router-view></router-view> </keep-alive>
4.11 两个新的生命周期钩子
作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。 具体名字:
activated
路由组件被激活时触发。deactivated
路由组件失活时触发。
这两个生命周期钩子需要配合前面的缓存路由组件使用(没有缓存路由组件不起效果)
4.12 路由守卫(过滤器)
-
作用:对路由进行权限控制
-
分类:全局守卫、独享守卫、组件内守卫
-
全局守卫:
//全局前置守卫:初始化时执行、每次路由切换前执行 router.beforeEach((to,from,next)=>{ console.log('beforeEach',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'zhejiang'){ //权限控制的具体规则 next() //放行 }else{ alert('暂无权限查看') // next({name:'guanyu'}) } }else{ next() //放行 } }) //全局后置守卫:初始化时执行、每次路由切换后执行 router.afterEach((to,from)=>{ console.log('afterEach',to,from) if(to.meta.title){ document.title = to.meta.title //修改网页的title }else{ document.title = 'vue_test' } })
完整代码
// 这个文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
// 引入组件
import About from '../pages/About.vue'
import Home from '../pages/Home.vue'
import Message from '../pages/Message.vue'
import News from '../pages/News.vue'
import Detail from '../pages/Detail.vue'
// 创建并暴露一个路由器
const router = new VueRouter({
routes: [
{
path: '/home',
component: Home,
meta:{title:'主页'},
children: [
{
path: 'news',
component: News,
meta:{isAuth:true,title:'新闻'}
},
{
path: 'message',
name: 'mess',
component: Message,
meta:{isAuth:true,title:'消息'},
children: [
{
path: 'detail/:id/:title',
name: 'xiangqing',
component: Detail,
meta:{isAuth:true,title:'详情'},
props($route) {
return {
id: $route.query.id,
title:$route.query.title,
a: 1,
b: 'hello'
}
}
}
]
}
]
},
{
path: '/about',
component: About,
meta:{ title: '关于' }
}
]
})
// 全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to, from, next) => {
console.log('前置路由守卫', to, from);
if(to.meta.isAuth) {
if(localStorage.getItem('school') === 'zhejiang') {
// 放行
next()
} else {
alert('学校名不对,无权查看')
}
} else {
next()
}
})
// 全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to, from) => {
console.log('后置路由守卫', to, from)
document.title = to.meta.title || '我的系统'
})
export default router
- 独享守卫:
就是在 routes 子路由内写守卫
beforeEnter(to,from,next){
console.log('beforeEnter',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){
next()
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next()
}
}
- 组件内守卫:
在具体组件内写守卫
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}
4.13 路由器的两种工作模式
- 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
- hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
- hash模式:
- 地址中永远带着#号,不美观 。
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
- 兼容性较好。
- history模式:
- 地址干净,美观 。
- 兼容性和hash模式相比略差。
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
五、UI组件库
7.1移动端
- Vant
- Cube UI
- Mint UI
7.2PC端
-
Element UI
注意按需引入
-
Iview UI