渐进式javascript框架
@VUE/CLI脚手架
目标: webpack自己配置环境很麻烦, 下载@vue/cli包,用vue命令创建脚手架项目
yarn global add @vue/cli
启动项目
1. 创建项目
# vue和create是命令, vuecli-demo是文件夹名
vue create vuecli-demo
2. 安装ing
3. 进入脚手架项目下, 启动内置的热更新本地服务器
cd vuecil-demo
yarn serve// 或 npm run serve
public/index.html – 浏览器运行的网页
src/main.js – webpack打包的入口文件
src/App.vue – vue项目入口页面
@vue/cli 自定义配置
src并列处新建vue.config.js(暂时关闭eslint检查)
注意:
- 改了配置文件 是需要重启服务器的
- 只是改了 代码文件 是会自动热更新的
/* 覆盖webpack的配置 */
module.exports = {
devServer: { // 自定义服务配置
open: true, // 自动打开浏览器
port: 3000, // 自定义端口号
},
lintOnSave: false, // 关闭eslint检查
}
指令
自定义指令
inserted方法 - 指令所在标签, 被插入到网页上触发(一次)
2. update方法 - 指令对应数据/标签更新时, 此方法执行
局部注册
<template>
<div>
<!-- <input type="text" v-gfocus> -->
<input type="text" v-focus>
</div>
</template>
<script>
// 目标: 创建 "自定义指令", 让输入框自动聚焦
export default {
data(){
return {
colorStr: 'red'
}
},
directives: {
focus: {
inserted(el){
el.focus()
}
}
}
}
</script>
全局注册
在main.js用 Vue.directive()
// 全局指令 - 到处"直接"使用
Vue.directive("gfocus", {
inserted(el) {
el.focus() // 触发标签的事件方法
}
})
自定义指令-传值
<template>
<div>
<p v-color="colorStr">修改文字颜色</p>
<button @click="changeColor">改为蓝色</button>
</div>
</template>
<script>
export default {
data(){
return {
colorStr: 'red'
}
},
directives: {
color:{
inserted(el, binding) {
el.style.color = binding.value
},
update(el,binding){
el.style.color=binding.value
}
}
},
methods: {
changeColor(){
this.colorStr='blue'
}
}
}
</script>
v-model | 表单
-
下拉菜单,v-model要绑定在
select上 -
在
input[checkbox]的多选框状态,v-model- 变量为非数组, 则绑定的是所有的box的
checked的属性(true/false)- 常用于: 单个绑定使用,全选操作 - 变量为数组, 则绑定的是他们的
value属性里的值- 常用于: 收集勾选了哪些值
- 变量为非数组, 则绑定的是所有的box的
-
单选框
input[radio],需要相同的name和v-model,绑定的就是value的值(非数组)
<template>
<div>
<!--
v-model:是实现vuejs变量和表单标签value属性, 双向绑定的指令
-->
<div>
<span>用户名:</span>
<input type="text" v-model="username" />
</div>
<div>
<span>密码:</span>
<input type="password" v-model="pass" />
</div>
<div>
<span>来自于: </span>
<!-- 下拉菜单要绑定在select上 -->
<select v-model="from">
<option value="北京市">北京</option>
<option value="南京市">南京</option>
<option value="天津市">天津</option>
</select>
</div>
<div>
<!-- (重要)
遇到复选框, v-model的变量值
非数组 - 关联的是复选框的checked属性
数组 - 关联的是复选框的value属性
-->
<span>爱好: </span>
<input type="checkbox" v-model="hobby" value="抽烟">抽烟
<input type="checkbox" v-model="hobby" value="喝酒">喝酒
<input type="checkbox" v-model="hobby" value="写代码">写代码
</div>
<div>
<span>性别: </span>
<input type="radio" value="男" name="sex" v-model="gender">男
<input type="radio" value="女" name="sex" v-model="gender">女
</div>
<div>
<span>自我介绍</span>
<textarea v-model="intro"></textarea>
</div>
</div>
</template>
<script>
export default {
data() {
return {
username: "",
pass: "",
from: "",
hobby: [],
sex: "",
intro: "",
};
// 总结:
// 特别注意: v-model, 在input[checkbox]的多选框状态
// 变量为非数组, 则绑定的是checked的属性(true/false) - 常用于: 单个绑定使用
// 变量为数组, 则绑定的是他们的value属性里的值 - 常用于: 收集勾选了哪些值
}
};
</script>
v-model.修饰符
v-model.修饰符="vue数据变量"
-
.number以parseFloat转成数字类型,再传值给data中的数据 -
.trim去除首尾空白字符 -
.lazy文本框失去焦点(blur)时触发而非inupt时
v-text | v-html
v-text把值当成普通字符串显示, v-html把值当做html解析
注意:会覆盖插值表达式
{{}} | v-text | v-html区别
<div>{{msg}}-okkk</div>
// <h1>看看看</h1>-okkk (不会解析html标签)
<div v-text='msg'>-okkk</div>
// <h1>看看看</h1> (不会解析html标签)
<div v-html='msg'>-okkk</div>
// 看看看 (会解析html标签)
data:{
msg:'<h1>看看看</h1>'
}
v-show和v-if
-
v-show 和v-if都是true的时候显示,false的时候隐藏
-
v-show 用的display:none隐藏 (
频繁切换使用)- 不管初始条件是什么,元素总是会被渲染 也就是说如果找到了v-show,不管是true还是false都会先去创建元素,然后在进行css操作
- 有更高的初始渲染开销
-
v-if 直接从DOM树上移除(采用
惰性加载)- 切换过程中条件块内的事件监听器和子组件会被适当地
销毁和重建,也就是会触发组件和子组件的生命周期 - 有更高的切换开销
- 切换过程中条件块内的事件监听器和子组件会被适当地
注意:一旦视图中使用到的data发生改变,当前组件的整个视图都会重绘,消耗性能
js里面使用本地图片
js里面需要导入import,而html和css中可以直接写路径
<template>
<div id="app">
<img src="./assets/logo.png" alt />
<img :src="imgUrl" alt />
</div>
</template>
<script>
import imgUrl from './assets/logo.png'
export default {
data() {
return {
imgUrl: imgUrl
}
}
}
</script>
v-for
注意:
- v-for的范围 要存在于
双标签内部 - 例:必须在
input单标签外面套一个双标签,如label,再在lable标签中循环
更新监测
- 数组变更方法, 就会导致v-for更新, 页面更新
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
- 数组非变更方法, 返回新数组, 就不会导致v-for更新, 可采用覆盖数组或
this.$set()或Vue.set()
- slice()
- filter()
- concat()
- map()
<template>
<div>
<ul>
<li v-for="(val, index) in arr" :key="index">
{{ val }}
</li>
</ul>
<button @click="revBtn">数组翻转</button>
<button @click="sliceBtn">截取前3个</button>
<button @click="updateBtn">更新第一个元素值</button>
</div>
</template>
<script>
export default {
data(){
return {
arr: [5, 3, 9, 2, 1]
}
},
methods: {
revBtn(){
// 1. 数组翻转可以让v-for更新
this.arr.reverse()
},
sliceBtn(){
// 2. 数组slice方法不会造成v-for更新
// slice不会改变原始数组
// 解决v-for更新 - 覆盖原始数组
let newArr = this.arr.slice(0, 3)
this.arr = newArr
},
updateBtn(){
// 3. 更新某个值的时候, v-for是监测不到的
// this.arr[0] = 1000;
// 解决-this.$set()
// 参数1: 更新目标结构
// 参数2: 更新位置
// 参数3: 更新值
this.$set(this.arr, 0, 1000)
}
}
}
</script>
就地更新
v-for 的默认行为会尝试原地修改元素而不是移动它们。
虚拟dom
.vue文件中的template里写的标签, 都是模板, 都要被vue处理成
虚拟DOM对象, 才会渲染显示到真实DOM页面上
vue数据更新:
- 生成新的虚拟DOM结构
- 和旧的虚拟DOM结构对比
- 利用diff算法, 找不同, 只更新变化的部分(重绘/回流)到页面 - 也叫打补丁
diff算法-key
vue用diff算法, 新虚拟dom, 和旧的虚拟dom比较
情况2: 根元素变了, 删除重建
情况2: 根元素没变, 属性改变, ==元素复用==, 更新属性
-
无key ---- 就地更新
- 性能不高, 从第二个li往后都更新了
- 性能不高, 从第二个li往后都更新了
-
有key - 值为索引 ---- 就地更新
- v-for先循环产生新的DOM结构, key是连续的, 和数据对应,然后比较新旧DOM结构, 找到区别, 打补丁到页面上
- 最后补一个li, 然后从第二个往后, 都要更新内容
- 有key - 值为id
- key的值只能是唯一不重复的, 字符串或数值
- 比较新旧dom,比较到key相同就直接复用,新增了一个key,就在中间添加一个li,第二个li后的key未发生改变,直接后移
- 只在中间插入了一个li,其他dom都未发生改变,最节省性能
动态class
:class="{类名: 布尔值}"
data中---布尔值 style样式中---类名
<template>
<div>
<!-- 语法:
:class="{类名: 布尔值}"
使用场景: vue变量控制标签是否应该有类名
-->
<p :class="{red_str: bool}">动态class</p>
</div>
</template>
<script>
export default {
data(){
return {
bool: true
}
}
}
</script>
<style scoped>
.red_str{
color: red;
}
</style>
动态style
:style="{css属性: 值}"
data中----属性值
<template>
<div>
<!-- 动态style语法
:style="{css属性名: 值}"
-->
<p :style="{backgroundColor: colorStr}">动态style</p>
</div>
</template>
<script>
export default {
data(){
return {
colorStr: 'red'
}
}
}
</script>
过滤器
过滤器只能用在,
插值表达式和v-bind表达式过滤器传参: vue变量 | 过滤器(实参)
多个过滤器: vue变量 | 过滤器1 | 过滤器2
Vue.filter("过滤器名", (值) => {return "返回处理后的值"})
filters: {过滤器名字: (值) => {return "返回处理后的值"}
<template>
<div>
<p>原来的样子: {{ msg }}</p>
<!-- 1.
给过滤器传值
语法: vue变量 | 过滤器名(值)
-->
<p>使用翻转过滤器: {{ msg | reverse('|') }}</p>
<!-- 2.
多个过滤利使用
语法: vue变量 | 过滤器1 | 过滤器2
-->
<p :title="msg | toUp | reverse('|')">鼠标长停</p>
</div>
</template>
<script>
export default {
data(){
return {
msg: 'Hello, Vue'
}
},
filters: {
toUp (val) {
return val.toUpperCase()
}
}
}
</script>
<style>
</style>
时间格式化
yarn add moment
// 目标: 处理时间
// 1. 下载moment模块
import moment from 'moment'
// 2. 定义过滤器, 编写内部代码
filters: {
formatDate (val){
return moment(val).format('YYYY-MM-DD')
}
}
<!-- 3. 使用过滤器 -->
<td>{{ obj.time | formatDate }}</td>
计算属性 computed
方法,为了获取计算结果
注意:
-
计算属性num也是vue数据变量, 所以
不要和data里重名, 用法和data相同 -
计算属性是基于它们的依赖项(this.a, this.b)的值结果进行缓存的,只要依赖的变量不变, 都直接从
缓存取结果, 比普通方法性能更高- 多次调用,只有第一次 会执行代码,后面的调用,直接 从缓存中获取
- 一旦 计算属性中 使用的 数据发生变化,则 重新执行
<template>
<div>
<p>{{ num }}</p>
</div>
</template>
<script>
export default {
data(){
return {
a: 10,
b: 20
}
},
// 场景: 一个变量的值, 需要用另外变量计算而得来
// 注意: 计算属性和data属性都是变量-不能重名
// 注意2: 函数内变量变化, 会自动重新计算结果返回
computed: {
num(){
return this.a + this.b
}
}
}
</script>
set|get(ES6)-双向绑定
对象,为了获取 计算结果和修改计算结果
计算属性通常给
v-model使用,也可以直接普通变量使用
computed: {
"属性名": {
set(值){
},
get() {
return "值"
}
}
}
-
set(),
view中计算属性发生变化,来影响modelview中的计算属性发生了变化,调用set(),可以根据计算属性的值去设置model中其他数据的值
-
get(),
model中的计算属性发生变化,去影响view -
要去修改
设置view中的值,调用get(),根据model中依赖项发生改变,就返回同步到view中
全选案例
<template>
<div>
<span>全选:</span>
<!-- 4. v-model 关联全选 - 选中状态 -->
<input type="checkbox" v-model="isAll"/>
<ul>
<li v-for="(obj, index) in arr" :key="index">
<!-- 3. 对象.c - 关联 选中状态 -->
<input type="checkbox" v-model="obj.c"/>
<span>{{ obj.name }}</span>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
arr: [
{
name: "猪八戒",
c: false,
},
{
name: "孙悟空",
c: false,
},
{
name: "唐僧",
c: false,
},
{
name: "白龙马",
c: false,
},
],
};
},
// 5. 计算属性-isAll
computed: {
isAll: {
set(val){
// 7. 全选框 - 选中状态(true/false)
this.arr.forEach(obj => obj.c = val)
},
get(){
// 6. 统计小选框状态 -> 全选状态
// every口诀: 查找数组里"不符合"条件, 直接原地返回false
return this.arr.every(obj => obj.c === true)
}
}
}
};
</script>
侦听器 watch
目标: 可以侦听data/computed属性值改变 本质是个方法
<template>
<div>
<input type="text" v-model="uname" />
<div>
<input type="text" v-model="dog.dname" />
<input type="text" v-model="dog.age" />
</div>
</div>
</template>
<script>
export default {
data(){
return {
uname:'死鬼',
dog:{
dname:'ruiky',
age:12
}
}
},
watch:{
// 1.普通侦听器:侦听的 data变量 是 值类型数据(简单数据类型) ---------
uname(newV,oldV){
console.log(newV,oldV)
},
// 2.深度侦听器:侦听的 data变量 是 引用类型数据(复杂数据类型) -------
dog:{
deep:true, // 本质:为 被侦听的 对象里 每一个 属性 都添加 侦听器!
// immediate:true, // 视图加载时 自动 触发一次 侦听器
// 当 被侦听的 data变量 数据 发生改变时 自动触发执行
handler(newDog){
console.log(newDog)
// console.log(newDog === oldDog)
// console.log(newDog === this.dog)
}
},
//3.侦听对象中的 指定的 属性
'dog.dname':{
handler(newv){
console.log(newv)
}
}
}
}
</script>
组件
注册使用
全局注册
main.js, 在new Vue之上注册
import Vue from 'vue'
// 目标: 全局注册 (一处定义到处使用)
// 1. 创建组件 - 文件名.vue
// 2. 引入组件
import Pannel from './components/Pannel'
// 3. 全局 - 注册组件
/*
语法:
Vue.component("组件名", 组件对象)
*/
Vue.component("PannelG", Pannel)
局部注册
任意vue文件中引入, 注册,
<template>
<div id="app">
<h3>案例:折叠面板</h3>
<!-- 4. 组件名当做标签使用 -->
<!-- <组件名></组件名> -->
<PannelG></PannelG>
<PannelL></PannelL>
</div>
</template>
<script>
// 目标: 局部注册 (用的多)
// 1. 创建组件 - 文件名.vue
// 2. 引入组件
import Pannel from './components/Pannel_1'
export default {
// 3. 局部 - 注册组件
components: {
PannelL: Pannel
}
}
</script>
scoped作用
目的: 解决多个组件样式名相同, 冲突问题
<style scoped>在style上加入scoped属性, 就会在此组件的标签上加上一个随机生成的data-v开头的属性
组件通信
父向子-props
1. 属性传递:
传静态属性:则不加:title="好吃的口水鸡"
传动态属性:则需要加::title="title"
2. 单向数据流:
- 从父到子 的数据流向,叫
单向数据流- props变量本身是
只读不能重新赋值
- props变量本身是
子组件---components/MyProduct.vue
<template>
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ intro }}</p>
</div>
</template>
<script>
export default {
props: ['title', 'price', 'intro']
}
</script>
父组件 App.vue
<template>
<div>
<Product title="好贵的北京烤鸭" price="290" :intro="str"></Product>
</div>
</template>
<script>
// 1. 创建组件 (.vue文件)
// 2. 引入组件
import Product from './components/MyProduct'
export default {
data(){
return {
str: "好贵啊, 快来啊, 好吃"
}
},
// 3. 注册组件
components: {
// Product: Product // key和value变量名同名 - 简写
Product
}
}
</script>
父向子-配合循环
子组件 /components/MyProduct
<template>
<div>
<MyProduct v-for="obj in list" :key="obj.id"
:title="obj.proname"
:price="obj.proprice"
:intro="obj.info"
></MyProduct>
</div>
</template>
<script>
// 目标: 循环使用组件-分别传入数据
// 1. 创建组件
// 2. 引入组件
import MyProduct from './components/MyProduct'
export default {
data() {
return {
list: [
{
id: 1,
proname: "超级好吃的棒棒糖",
proprice: 18.8,
info: "开业大酬宾, 全场8折",
},
{
id: 2,
proname: "超级好吃的大鸡腿",
proprice: 34.2,
info: "好吃不腻, 快来买啊",
}
],
};
},
// 3. 注册组件
components: {
// MyProduct: MyProduct
MyProduct
}
};
</script>
父组件 App.vue
<template>
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ intro }}</p>
</div>
</template>
<script>
export default {
props: ['title', 'price', 'intro']
}
</script>
子向父组件通信
- 父: @自定义事件名="父methods函数"
- 子: this.$emit("自定义事件名", 传值) - 执行父methods里函数代码 子组件 /components/MyProduct_sub
<template>
<div class="my-product">
<h3>标题: {{ obj.name }}</h3>
<p>价格: {{ obj.price }}元</p>
<button @click="subFn">降低价格</button>
<button @click="emit('addFn',obj.id,1)">增加价格</button>
</div>
</template>
<script>
export default {
props: ['obj'],
methods: {
subFn(){
this.$emit('subprice', this.obj.id, 1) // 子向父
}
}
}
</script>
父组件 App.js
<template>
<div>
<!-- 目标: 子传父 -->
<!-- 1. 父组件, @自定义事件名="父methods函数" -->
<MyProduct v-for="obj in list" :key="obj.id"
:obj="obj"
@subprice="fn" @addFn='add'
></MyProduct>
</div>
</template>
<script>
import MyProduct from './components/MyProduct_sub'
export default {
data() {
return {
list: [
{
id: 1,
name: "超级好吃的棒棒糖",
price: 18.8,
},
{
id: 2,
name: "超级好吃的大鸡腿",
price: 34.2,
}
],
};
},
components: {
MyProduct
},
methods: {
fn(id, price){
// 降低价格
this.list.forEach(item=>{
if(item.id==id && item.price>1){
item.price=(item.price-price).toFixed(2);
}
})
},
add(id,price){
// 增加价格
this.list.forEach(item=>{
if(item.id==id){
item.price=(item.price+price).toFixed(2);
}
})
}
}
};
</script>
跨组件通信-EventBus
空的Vue对象, 只负责
$on注册事件,$emit触发事件, 一定要确保$on先执行
根组件 src/App.vue
<template>
<cat-house></cat-house>
<dog-house></dog-house>
</template>
<script>
import CatHouse from "./components/CatHouse";// 子组件1
import DogHouse from "./components/DogHouse";// 子组件2
export default {
components: {
CatHouse,
DogHouse,
}
};
</script>
事件中心--src/EventBus/index,js
import Vue from 'vue'
// 导出空白vue对象
export default new Vue()
子组件1--src/components/CatHouse
<template>
<div>CatHouse</div>
</template>
<script>
// 1. 引入空白vue对象(EventBus)
import eventBus from "../EventBus";
// 2. 接收方 - $on监听事件
export default {
//vue组件的入口函数
// 3. 组件创建完毕, 监听say事件
created() {
eventBus.$on("say", this.sayhi);
});
methods:{
sayhi(name){
console.log(name+'你好~~我是猫猫');
}
}
},
};
</script>
子组件2--src/components/DogHouse
<template>
<div>DogHouse</div>
<button @click='sayhi'>打招呼</button>
</template>
<script>
// 1. 引入空白vue对象(EventBus)
import eventBus from "../EventBus";
// 2. 传值方
export default {
methods:{
sayhi(){
eventBus.emit('say','狗狗')
}
}
},
};
</script>
VUE生命周期
| 阶段 | 方法名 | 描述 | 场景 |
|---|---|---|---|
| 初始化(创建Vue实例) | beforeCreate | data和methods初始化"之前" | |
created(vue的入口函数) | data和methods初始化以后,添加了观察者(响应式编程) | 网络请求, 注册全局事件 | |
| 挂载(视图渲染) | beforeMount | 生成了虚拟dom,即在真实DOM挂载之前 | 预处理data, 不会触发updated钩子函数 |
| mounted | 根据虚拟dom解析了视图,即真实DOM挂载以后 | 挂载后真实DOM | |
| 更新(视图重新渲染) | beforeUpdate | 更新之前(前提: data数据改变才执行) | |
| updated | 更新之后 | 获取更新后的真实DOM | |
| 销毁 | beforeDestroy | 前提: v-if="false" 销毁Vue实例 | 移除全局事件, 移除当前组件, 计时器, 定时器, eventBus移除事件$off方法 |
| destroyed | 实例销毁后 |
<template>
<div>
<p>学习生命周期 - 看控制台打印</p>
<p id="myP">{{ msg }}</p>
<ul id="myUL">
<li v-for="(val, index) in arr" :key="index">
{{ val }}
</li>
</ul>
<button @click="arr.push(1000)">点击末尾加值</button>
</div>
</template>
<script>
export default {
data(){
return {
msg: "hello, Vue",
arr: [5, 8, 2, 1],
timer: null // 保存计时器
}
},
// 一. 初始化
// new Vue()以后, vue内部给实例对象添加了一些属性和方法, data和methods初始化"之前"
beforeCreate(){
console.log("beforeCreate -- 执行");
console.log(this.msg); // undefined
},
// data和methods初始化以后
// 场景: 网络请求, 注册全局事件
created(){
console.log("created -- 执行");
console.log(this.msg); // hello, Vue
// this.timer = setInterval(() => {
// console.log("哈哈哈");
// }, 1000)
},
// 二. 挂载
// 真实DOM挂载之前
// 场景: 预处理data, 不会触发updated钩子函数
beforeMount(){
console.log("beforeMount -- 执行");
console.log(document.getElementById("myP")); // null
this.msg = "重新值"
},
// 真实DOM挂载以后
// 场景: 挂载后真实DOM
mounted(){
console.log("mounted -- 执行");
console.log(document.getElementById("myP")); // p
},
// 三. 更新
// 前提: data数据改变才执行
// 更新之前
beforeUpdate(){
console.log("beforeUpdate -- 执行");
console.log(document.querySelectorAll("#myUL>li")[4]); // undefined
},
// 更新之后
// 场景: 获取更新后的真实DOM
updated(){
console.log("updated -- 执行");
console.log(document.querySelectorAll("#myUL>li")[4]); // li
},
// 四. 销毁
// 前提: v-if="false" 销毁Vue实例
// 场景: 移除全局事件, 移除当前组件, 计时器, 定时器, eventBus移除事件$off方法
beforeDestroy(){
console.log('beforeDestroy -- 执行');
clearInterval(this.timer)
},
destroyed(){
console.log("destroyed -- 执行");
}
}
</script>
AXIOS(基于promise封装)
特点
- 支持客户端发送Ajax请求
- 支持服务端Node.js发送请求
- 支持Promise相关用法
- 支持请求和响应的拦截器功能
- 自动转换JSON数据
- axios 底层还是原生js实现, 内部通过Promise封装的
axios基本使用
下载并引入axios
yarn add axios
import axios from 'axios'
axios({
method: '请求方式', // get post --默认就是GET方式请求, 可以省略不写
url: '请求地址',
data: { // 拼接到请求体的参数(默认是`json字符串`格式), post请求的参数
xxx: xxx,
},
params: { // 拼接到请求行的参数, get请求的参数,也可以直接拼接到url后面(?name=jean)
xxx: xxx
}
}).then(res => {
console.log(res.data) // 后台返回的结果
}).catch(err => {
console.log(err) // 后台报错返回
})
发布图书(解构)
<template>
<div>
<p>3. 新增图书信息</p>
<div>
<input type="text" placeholder="书名" v-model="bookObj.bookname">
</div>
<div>
<input type="text" placeholder="作者" v-model="bookObj.author">
</div>
<div>
<input type="text" placeholder="出版社" v-model="bookObj.publisher">
</div>
<button @click="sendFn">发布</button>
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
bName: "",
bookObj: { // 参数名提前和后台的参数名对上-发送请求就不用再次对接了
bookname: "",
author: "",
publisher: ""
}
};
},
methods: {
// ...省略了其他代码
sendFn(){
axios({
url: "/api/addbook",
method: "POST",
data: {
appkey: "7250d3eb-18e1-41bc-8bb2-11483665535a",
...this.bookObj
// 等同于下面
// bookname: this.bookObj.bookname,
// author: this.bookObj.author,
// publisher: this.bookObj.publisher
}
})
}
},
};
</script>
全局配置
避免前缀基地址, 暴露在逻辑页面里, 统一设置
axios.defaults.baseURL = "http://123.57.109.30:3006"
// 所有请求的url前置可以去掉, 请求时, axios会自动拼接baseURL的地址在前面
getAllFn() {
axios({
url: "/api/getbooks",
method: "GET", // 默认就是GET方式请求, 可以省略不写
}).then((res) => {
console.log(res);
});
// axios()-原地得到Promise对象
},
挂载到vue原型上(作为全局属性)
只要是 vue实例 和 vue子类的实例对象,都可以通过 原型链 访问到$axios
main.js
// 目标: 请求数据 - 打印
// 1. 下载axios库, main.js - 全局绑定属性 (确保任意.vue文件可以都访问到这个axios方法)
import axios from 'axios'
// 2. 基础地址
axios.defaults.baseURL = "https://www.escook.cn"
// 3. axios方法添加到Vue的原型上
Vue.prototype.$axios = axios
new Vue({
render: h => h(App),
}).$mount('#app')
$NEXTTICK | $REFS
$refs 获取DOM / 组件对象
通过id / ref, 都可以获取原生DOM标签
this.#refs.获取组件对象, 调用组件里方法(非主流方法)
父组件---More.vue
<template>
<div>
<p>1. 获取原生DOM元素</p>
<h1 id="h" ref="myH">我是一个孤独可怜又能吃的h1</h1>
<p>2. 获取组件对象 - 可调用组件内一切</p>
<Demo ref="de"></Demo>
</div>
</template>
<script>
// 目标: 获取组件对象
// 1. 创建组件/引入组件/注册组件/使用组件
// 2. 组件起别名ref
// 3. 恰当时机, 获取组件对象
import Demo from './Child/Demo'
export default {
mounted(){
console.log(document.querySelector("#h")); // h1
console.log(document.getElementById("h")); // h1
// 获取dom对象
console.log(this.$refs.myH); // h1
// 获取demo子组件对象
let demoObj = this.$refs.de;
// 调用demo子组件里的 fn方法
demoObj.fn()
},
components: {
Demo
}
}
</script>
$nextTick
Vue更新DOM是-异步的
目标: 点击count++, 马上通过"原生DOM"拿标签内容, 无法拿到新值
DOM更新完会挨个触发$nextTick里的函数体,在这里可以拿到异步更新的dom
除此之外,也可以在updated钩子里面拿到
<template>
<div
<p ref="myP">{{ count }}</p>
<button @click="btn">点击count+1, 马上提取p标签内容</button>
</div>
</template>
<script>
// 目标: 获取组件对象
// 1. 创建组件/引入组件/注册组件/使用组件
// 2. 组件起别名ref
// 3. 恰当时机, 获取组件对象
import Demo from './Child/Demo'
export default {
components: {
Demo
},
data(){
return {
count: 0
}
},
methods: {
btn(){
this.count++; // vue监测数据更新, 开启一个DOM更新队列(异步任务)
console.log(this.$refs.myP.innerHTML); // 0
// 原因: Vue更新DOM异步
// 解决: this.$nextTick()
// 过程: DOM更新完会挨个触发$nextTick里的函数体
this.$nextTick(() => {
console.log(this.$refs.myP.innerHTML); // 1
})
}
}
}
</script>
$nextTick使用场景
目标: 点击搜索按钮, 弹出聚焦的输入框, 按钮消失
<template>
<div>
<input ref="myInp" type="text" placeholder="这是一个输入框" v-if="isShow">
<button v-else @click="btn">点击我进行搜索</button>
</div>
</template>
<script>
// 目标: 点按钮(消失) - 输入框出现并聚焦
// 1. 获取到输入框
// 2. 输入框调用事件方法focus()达到聚焦行为
export default {
data(){
return {
isShow: false
}
},
methods: {
async btn(){
this.isShow = true;
// this.$refs.myInp.focus()
// 原因: data变化更新DOM是异步的
// 输入框还没有挂载到真实DOM上
// 解决:
// this.$nextTick(() => {
// this.$refs.myInp.focus()
// })
// 扩展: await取代回调函数
// $nextTick()原地返回Promise对象
await this.$nextTick()
this.$refs.myInp.focus()
}
}
}
</script>
注意
$nextTick是在 数据更新引发视图重绘后才执行,即钩子函数updated之后才执行;
举例说明:
created() {
this.loadArtcileInfo()
},
methods: {
// 加载文章数据
async loadArtcileInfo() {
try {
const {
data: { data }
} = await getArticleById(this.articleId)
this.article = data
// 拿到数据后,再去获取img DOM元素
// 因为 loadArtcileInfo是在 created后执行的,此时还没有生成真实dom,
// 这里,也就不存在数据更新,所以就不能通过 this.$nextTick()来拿到 真实的dom元素
// 所以 需要设置10ms 延迟一下,再拿到真实的dom
setTimeout(() => {
this.previewImg()
}, 10)
} catch (err) {
...
}
// 加载完成
this.isLoading = false
},
// 从文章内容中获取到所有的 img DOM 节点
previewImg() {
// 拿到 文章内容的 的标签
const contentEl = this.$refs.contentRef
...省略其他代码
},
组件name属性使用
目标: 可以用组件的name属性值, 来注册组件名字
封装的组件-可以自己定义name属性组件名-有个统一的前缀风格
子组件--components/Com.vue
<template>
<div>
<p>我是一个Com组件</p>
</div>
</template>
<script>
export default {
name: "ComNameHaHa" // 注册时可以定义自己的名字
}
</script>
父组件--App.vue
<template>
<div>
<ComNameHaHa></ComNameHaHa>
</div>
</template>
<script>
import Com from './components/Com'
export default {
components: {
[Com.name]: Com // 对象里的key是变量的话[]属性名表达式
// "ComNameHaHa": Com
}
}
</script>
组件进阶
动态组件
多个组件使用同一个挂载点,并动态切换,这就是动态组件
实现:vue内置component组件, 配合is属性, 设置要显示的组件名字
<component :is="变量"></component>
<div>
<button @click="comName = 'UserName'">账号密码填写</button>
<button @click="comName = 'UserInfo'">个人信息填写</button>
<p>下面显示注册组件-动态切换:</p>
<div style="border: 1px solid red;">
<component :is="comName"></component>
</div>
</div>
</template>
<script>
// 目标: 动态组件 - 切换组件显示
// 场景: 同一个挂载点要切换 不同组件 显示
// 1. 创建要被切换的组件 - 标签+样式
// 2. 引入到要展示的vue文件内, 注册
// 3. 变量-承载要显示的组件名
// 4. 设置挂载点<component :is="变量"></component>
// 5. 点击按钮-切换comName的值为要显示的组件名
import UserName from '../components/01/UserName'
import UserInfo from '../components/01/UserInfo'
export default {
data(){
return {
// 组件的名字
comName: "UserName"
}
},
components: {
UserName,
UserInfo
}
}
</script>
组件缓存
问题:组件切换会导致组件被频繁销毁和重新创建, 性能不高
解决:使用Vue内置的
keep-alive组件, 可以让包裹的组件保存在内存中不再创建和销毁,而是触发**激活(activated)和非激活(deactivated)**的生命周期钩子函数
<div style="border: 1px solid red;">
<!-- Vue内置keep-alive组件, 把包起来的组件缓存起来 -->
<keep-alive>
<component :is="comName"></component>
</keep-alive>
</div>
组件插槽
用于实现组件的内容分发, 通过 slot 标签, 可以接收到写在组件标签内的内容
使用插槽 实现 父组件给子组件 传
视图标签
- 组件内用
<slot>默认内容</slot>占位- 如果父组件传递了 内容,就不会现在slot内的默认内容
- 如果没有传递,就会展示 默认内容
- 使用组件时
<Pannel></Pannel>夹着的地方, 传入标签替换slot
具名插槽
当一个组件内有2处以上需要外部传入标签的地方
解决:
- slot 用name属性 区分名字
<slot name="title"></slot> - template 配合 v-slot:名字(v-slot:---#) 来分发对应标签
<template #title>...</template>
# 子组件---components/Pannel.vue
<slot name="title"></slot>
<slot name="content"></slot>
# 父组件--UseSlot.vue
<template v-slot:title>
<h4>芙蓉楼送辛渐</h4>
</template>
// v-slot可以简化成#使用
<template #title>
<span style="color: red;">我是标题</span>
</template>
作用域插槽
让子组件里的值,能给插槽赋值时在父组件环境下使用
- 子组件, 在slot上绑定属性和子组件内的值
- 使用组件, 传入自定义标签, 用template和v-slot="自定义变量名"
- scope变量名自动绑定slot上所有属性和值
# 父组件
<Pannel>
<!-- 需求: 插槽时, 使用组件内变量 -->
<!-- scope变量: {row: defaultObj} -->
<template #header="scope">
<p>{{ scope.msg }}</p>
<p>{{ scope.row.defaultTwo }}</p>
</template>
</Pannel>
# 子组件 --components/Pannel.vue
<slot :row="defaultObj" :msg="msg" name='header'>{{ defaultObj.defaultOne }}</slot>
data() {
return {
msg: '看看传过去没',
defaultObj: {
defaultOne: "无名氏",
defaultTwo: "小传同学"
}
};
表格案例
子组件--MyTable.vue
<template>
<table class="table table-bordered table-stripped">
<!-- 表格标题区域 -->
<thead>
<tr>
<slot name="header"></slot>
<!-- <th>#</th>
<th>商品名称</th>
<th>价格</th>
<th>标签</th>
<th>操作</th> -->
</tr>
</thead>
<!-- 表格主体区域 -->
<tbody>
<tr v-for="obj in arr"
:key="obj.id"
>
<slot name="body" :row="obj"></slot>
<!-- <td>{{ obj.id }}</td>
<td>{{ obj.goods_name }}</td>
<td>{{ obj.goods_price }}</td>
<td>{{ obj.tags }}</td>
<td>
<button class="btn btn-danger btn-sm">删除</button>
</td> -->
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: 'MyTable',
props: {
arr: Array
}
}
</script>
父组件 GoodList.vue
<template>
<div>
<MyTable :arr="list">
<template #header>
<th>#</th>
<th>商品名称</th>
<th>价格</th>
<th>标签</th>
<th>操作</th>
</template>
<!-- scope的值: {row: obj} -->
<template #body="scope">
<td>{{ scope.row.id }}</td>
<td>{{ scope.row.goods_name }}</td>
<td>{{ scope.row.goods_price }}</td>
<td>
<button class="btn btn-danger btn-sm" @click="removeBtn(scope.row.id)">删除</button>
</td>
</template>
</MyTable>
</div>
</template>
<script>
import MyTable from '../components/MyTable'
export default {
components: {
MyTable
},
data() {
return {
list: [
{
id: 1,
goods_name: 'vivo',
goods_price: '2999',
tags: '手机'
},
{
id: 2,
goods_name: 'iphone',
goods_price: '7999',
tags: '手机'
}
] // 所有数据
}
},
methods: {
removeBtn(id) {
let index = this.list.findIndex(obj => obj.id === id)
this.list.splice(index, 1)
}
}
}
</script>
前端路由
作用: 在一个页面里, 通过(#锚点)切换业务场景,
不经过服务器整体不刷新页面,用户体验更好说明:地址栏中,
#后面的锚点 就是hash值,通过修改hash值window.location.hash就可以定位到不同的views页面,同时hash值发生改变,会触发hashchange事件总结:一切都围绕着hash值变化为准
.vue文件分2类, 一个是页面组件, 一个是复用组件
-
页面组件(src/
views(或pages)) - 页面展示 - 配合路由用 -
复用组件(src/
components) - 展示数据/常用于复用
知识点小结
router|route|routes
$router是指整个路由实例,你可以操控整个路由,通过'$router.push'往其中添加任意的路由对象.$route:是指当前路由实例('$router')跳转到的路由对象;- 路由实例可以包含多个路由对象.它们是父子包含关系.
routes:指router路由实例的routes API.用来配置多个route路由对象.
vue-router初步使用
安装 shell
yarn add vue-router
main.js
import Vue from 'vue'
import App from './App.vue'
// 引入 页面子组件
import Find from '@/views/Find' // @是src的绝对地址
import My from '@/views/My'
import Part from '@/views/Part'
// 1.导入路由模块
import VueRouter from 'vue-router'
// 2.在vue中,使用使用vue的插件,都需要调用Vue.use()
Vue.use(VueRouter)
// 3.创建路由规则数组
const routes = [
{
path: "/find",
component: Find
},
{
path: "/my",
component: My
},
{
path: "/part",
component: Part
}
]
// 4.创建路由对象 - 传入规则
const router = new VueRouter({
routes
})
// 5.路由对象注入到vue实例中, this可以访问$route和$router
new Vue({
router,
render: h => h(App),
}).$mount('#app')
App.vue
<template>
<div>
<div class="footer_wrap">
<a href="#/find">发现音乐</a>
<a href="#/my">我的音乐</a>
<a href="#/part">朋友</a>
</div>
<div class="top">
<!-- 6. 设置挂载点-当url的hash值路径切换, 显示规则里对应的组件到这 -->
<router-view></router-view>
</div>
</div>
</template>
声明式导航
用全局组件router-link来替代a标签
- router-link实质上最终会渲染成a链接 to属性等价于提供 href属性(to无需#)
- router-link提供了声明式导航高亮的功能(自带类名)
<template>
<div>
<div class="footer_wrap">
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/part">朋友</router-link>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {};
</script>
<style scoped>
/* .router-link-active---提供的自带类名 */
.footer_wrap .router-link-active{
color: white;
background: black;
}
</style>
跳转传参
在跳转路由时, 可以给路由对应的组件内传值
?key=value ----- 用$route.query.key 取值
/值 提前在路由规则/path/:key ----- 用$route.params.key 取值
在router-link上的to属性传值, 语法格式如下
- /path?参数名=值
- /path/值 – 需要路由对象提前配置 path: “/path/:参数名”
对应页面组件接收传递过来的值
- $route.query.参数名
- $route.params.参数名
-
components/Part.vue - 准备接收路由上传递的参数和值
<template> <div> <p>关注明星</p> <p>发现精彩</p> <p>寻找伙伴</p> <p>加入我们</p> <p>人名: {{ $route.query.name }} -- {{ $route.params.username }}</p> </div> </template> -
路由定义
{ path: "/part", component: Part }, { path: "/part/:username", // 有:的路径代表要接收具体的值(动态路由) component: Part }, -
导航跳转, 传值给MyGoods.vue组件
<router-link to="/part?name=小传">朋友-小传</router-link> <router-link to="/part/小智">朋友-小智</router-link>
重定向和模式
重定向
匹配path后, 强制切换到目标path上
// 网页默认打开, 匹配路由"/", 强制切换到"/find"上
const routes = [
{
path: "/", // 默认hash值路径
redirect: "/find" // 重定向到/find
// 浏览器url中#后的路径被改变成/find-重新匹配数组规则
}
]
404页面
问题:如果路由hash值, 没有和数组里规则匹配
解决:在路由最后,path匹配*(任意路径) –匹配到默认给的404页面
import NotFound from '@/views/NotFound'
const routes = [
// ...省略了其他配置
// 404在最后(规则是从前往后逐个比较path)
{
path: "*",
component: NotFound
}
]
模式设置
修改路由在地址栏的模式
hash路由例如: http://localhost:8080/#/home
history路由例如: http://localhost:8080/home (以后上线需要服务器端支持, 否则找的是文件夹)
router/index.js
const router = new VueRouter({
routes,
mode: "history" // 打包上线后需要后台支持, 模式是hash
})
编程式导航
用JS代码来进行跳转, 声明式导航用a标签
不同的使用场景:
- name
- 方便修改, 在页面上看不见随便定义
- 虽然用name跳转, 但是url的hash值还是切换path路径值
- path
- 可以在url的hash值看到(尽量符合组内规范)
this.$router.push({
path: "路由路径", // 都去 router/index.js定义
name: "路由名"
})
跳转传参
注意: 使用path会自动忽略params
推荐: name+query方式传参
this.$router.push({
path: "路由路径"
name: "路由名",
query: {
"参数名": 值
}
params: {
"参数名": 值
}
})
// 对应路由接收 $route.params.参数名 取值
// 对应路由接收 $route.query.参数名 取值
路由嵌套
- App.vue, 外层的router-view负责发现音乐和我的音乐页面切换
- Find.vue 内层的router-view负责发现音乐下的子tab对应的组件切换
- 配置二级导航和样式- 在Find.vue中
<template>
<div>
<!-- <p>推荐</p>
<p>排行榜</p>
<p>歌单</p> -->
<div class="nav_main">
<router-link to="/find/recommend">推荐</router-link>
<router-link to="/find/ranking">排行榜</router-link>
<router-link to="/find/songlist">歌单</router-link>
</div>
<div style="1px solid red;">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {};
</script>
- 配置路由规则-二级路由展示
const routes = [
// ...省略其他
{
path: "/find",
name: "Find",
component: Find,
redirect:'/find/recommend',// 定向到/find/recommend路径 http://localhost:8080/#/find/recommend
children: [
{
//此时path等同于'/find/recommend',子路由会继承父路由的路径.但是不能写成path:'/recommend'.因为以 / 开头的嵌套路径会被当作根路径,也就是说此时recommend成了根路径.而不是find.
path: "recommend",
component: Recommend
},
{
path: "ranking",
component: Ranking
},
{
path: "songlist",
component: SongList
}
]
}
// ...省略其他
]
模式一:子组件的path不加 '/'
模式二:子组件的path加 '/'
声明导航 - 类名区别
- router-link-exact-active (精确匹配) url中hash值路径, 与href属性值完全相同, 设置此类名
- router-link-active (模糊匹配) url中hash值, 包含href属性值这个路径
守卫
全局前置守卫
路由跳转之前, 先执行一次前置守卫函数, 判断是否可以正常跳转
场景: 当你要对路由权限判断时,例判断用户是否登陆
// 语法: router.beforeEach((to, from, next)=>{//路由跳转"之前"先执行这里, 决定是否跳转})
// 参数1: 要跳转到的路由 (路由对象信息) 目标
// 参数2: 从哪里跳转的路由 (路由对象信息) 来源
// 参数3: 函数体 - next()才会让路由正常的跳转切换, next(false)在原地停留, next("强制修改到另一个路由路径上")
// 注意: 如果不调用next, 页面留在原地
// next()放行,
// next(false)留在原地不跳转路由
// next(path路径)强制换成对应path路径跳转
// 例子: 判断用户是否登录, 是否决定去"我的音乐"/my
const isLogin = true; // 登录状态(未登录)
router.beforeEach((to, from, next) => {
if (to.path === "/my" && isLogin === false) {
alert("请登录")
next(false) // 阻止路由跳转
} else {
next() // 正常放行
}
})