vue2.0
1.基本步骤
导入vue.js
const vm = new Vue()
vm中{
el 控制哪个属性,
data 数据节点,
methods 页面上的处理函数
}
MVVM对应关系
M-model(data指向的数据源)
V-view(视图,el指定控制的区域)
VM-viewmodel(核心,vue的实例对象)
2.指令
插值表达式 {{}}
v-bind 简写--冒号: 插值只能用于内容节点,这个可以用于属性节点 -- 单向绑定,数据-->视图
v-on 简写--@
v-if & v-else (v-if 不利于频繁切换,常用;v-else类似display)
v-for :key (循环创建相似ui结构,key-常用id,不可重复,字符串或者数字)
v-model(双向绑定;修饰符:.umber, .trim, .lazy)
3.过滤器
3.1基本语法
定义filters节点
函数要有return值
形参即为管道符前的值
3.2私有&全局过滤器
/*
全局过滤器
独立于实例之外
参数1--函数名
参数2--函数具体
名字一致--就近原则--调用私有
*/
Vue.filter("capitalize", (val) => {
return '全局用法---' + val.charAt(0).toUpperCase() + val.slice(1) + '---全局用法';
});
注意: filter 顺序问题: 先定义过滤器, 再新建 vue 实例
3.3传参
msg|fn(c1,c2) ---- fn(msg, c1, c2)
4.侦听器
4.1使用
watch节点
watch: {
// 侦听器,本质上是一个函数,要监听哪个数据的变化,就将其数据名定义为方法名
// 参数--新值在前,旧值在后
// 用法,eg.用户名是否被占用
username(newVal, oldVal) {
// 判断是否为空
if (newVal === '') {
return
}
// 调用ajax请求,判断是否被占用
$.get('https://www.escook.cn/api/finduser/' + newVal, (res) => {
console.log(res); //{status: 0, message: '用户名可用!'}
});
}
}
4.2格式
方法格式
缺点1:无法立即触发
缺点2:侦听的是对象中属性的变化,则无法侦听到
对象格式
好处1:通过 immediate 选项立即触发
用Handler属性定义函数
watch: {
username: {
// handler 是固定写法,表示当 username 的值变化时,自动调用 handler 处理函数
handler(newVal, oldVal) {
console.log(newVal); console.log(oldVal);
},
// 表示页面初次渲染好之后,就立即触发当前的 watch 侦听器 | 默认值是false
immediate: true
}
}
好处2:通过deep选项侦听到对象中属性的变化
<input type="text" v-model="info.username">
data:{
info: {
username: "admin"
}
}
watch: {
/*
侦听info的变化
*/
// info(newVal) {
// console.log(newVal);
// }
info: {
handler(newVal) {
console.log(newVal);
},
deep: true
}
}
5.计算属性
computed节点–方法格式
computed: {
rgb() {
// this表示当前的vm实例
return `rgb(${this.R},${this.G},${this.B})`;
}
}
当做属性使用— {{ rgb }}
好处:实现代码的复用 ||只要计算属性中依赖的数据源发生变化,就会自动重新赋值
6.axios
作用:是一个专注于网络请求的库
发数据与请求数据,业务专一
// 调用axios得到的是一个promise对象
const resPromise = axios({
method: "",
url: ""
});
// books.data 才是存放实际数据的地方
resPromise.then(function (books) {
console.log(books.data);
});
6.1发起GET请求
const resPromise = axios({
// 请求方式
method: "GET",
url: "",
// URL中的查询参数
params: {
id: 1
},
// 请求体参数
data: {}
}).then(function (res) {
console.log(res);
});
6.2发起POST请求
axios({
// 请求方式
method: "POST",
url: "",
// 请求体参数
data: {
name: "zd",
age: 19
}
}).then(function (res) {
console.log(res);
});
6.3 await async
只要返回的是一个promise实例,就可以在前面加 await ,加上之后返回的是数组
同时,await只能用在被async修饰的方法中
eg:
$(this).addEventListener('click', async function () {
const result = await axios({
xxx
}.then(function (res) {
}));
});
- 注意 这里的result不是服务器返回的真实数据
6.4 仅选择data属性
——返回真正的数据
const { data } = await axios(xxxx)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AHuXViV6-1668928664011)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220902200345821.png)]
<script>
$(".get").on('click', async function () {
/*
await axios()是一个对象,里面有6个属性,{ data }只取其中的data
{ data:res } 将解构出来的数据重命名
*/
const { data: res } = await axios({
method: "GET",
url: "http://www.liulongbin.top:3006/api/getbooks"
});
console.log(data);
});
</script>
6.5axios.get() axios.post()
$(".get").on('click', async function () {
axios.get("url地址", {
// get参数
params: {}
});
});
7.vue-cli
7.1单页面应用程序
单页面应用程序(英文名:Single Page Application)简称 SPA,顾名思义,指的是一个 Web 网站中只有唯一的一个 HTML 页面,所有的功能与交互都在这唯一的一个页面内完成
7.2vue 项目
main.js
// 创建vue实例对象
new Vue({
// 把render指定的组件渲染到HTML页面中
// 拿到test.vue里面的html部分
// 拿vue将html的#app部分替换为test
render: h => h(Test),
}).$mount('#app')
7.3创建运行
vue create xxxx
- Matually
- Babel & CSS
- 选择2.x版本
- 选择less – css处理器
- 放独立文件 In dedicated config files
- yes
- tietiepreset
- 装包时鼠标不可动,否则需要轻触窗口ctrl+C
npm run serve — 开发时运行
7.4app.vue根组件
el–所控制的区域就是根
App渲染变成了根组件
render: h => h(App), — render 函数中渲染的是哪个 .vue组件,哪个就是根组件
8.VUE组件
8.1构成
template -> 组件的模板结构
script -> 组件的 JavaScript 行为
style -> 组件的样式
// 组件的结构 只能有一个根结点
<template>
<div>
</div>
</template>
// 组件行为
<script>
// 对外导出
export default {
data() {
return { username: "zs" };
},
methods: {
},
watch: {},
filters: {},
};
</script>
// 组件样式 默认是普通样式
<style lang="less">
...
</style>
8.2练 习 demo-second
- 所有的组件都在components文件夹中
8.3使用组件的3步骤
<script>
// 1.导入需要的组件
import left from "@/components/left.vue";
import right from "@/components/right.vue";
// 2.使用components节点注册属性
export default {
components: {
left,
},
};
</script>
<!-- 3.注册好的组件,以标签的形式使用 -->
<left></left>
【注意】components注册的是私有属性
【补充】配置路径的插件:Path Autocomplete
setting.json中添加:
// 导入文件时是否携带文件的扩展名
"path-autocomplete.extensionOnImport": true,
// 配置@的路径提示
"path-autocomplete.pathMappings": {
"@": "${folder}/src"
},
要在该文件夹打开vscode才生效
8.4注册全局组件
main.js — Vue.component()方法
import Vue from 'vue'
import count from "@/components/count.vue";
// 2.2 全局注册
Vue.component("MyCount", count);
auto close tag 插件 自动配置
8.5组建中的props (default type required)
提供多用户使用,增大复用性;
允许使用者在封装属性的时候可以自定义一些属性去用
count.vue — 组件的封装者
// props自定义组件 允许使用者自定义当前组件初始值
// props中的数据可以直接在模板结构中被使用
// 注意 props是只读属性 不可改变,通过this.init获取
props: ["init"],
//props的值转存到data中
data() {
return {
count: this.init,
};
},
right/left.vue — 组件的使用者
<!-- init="9"是字符串 :init="9"是数字 -->
<MyCount init="9"></MyCount>
props不写数组,改成对象,则可以赋初始默认值;数组的默认值是undefined
// 自定义属性
props: {
init: {
// 如果外界使用count时没有传递init属性的值,则默认为0
default: 0,
type: Number,
required: true,
},
},
8.6 scoped
left.vue中:
// 添加scoped 限定h3属性只能在left.vue生效,而不会全局都生效
<style lang="less" scoped>
.left-container {
padding: 0 20px 20px;
background-color: orange;
min-height: 250px;
flex: 1;
}
h1{
color:pink;
}
使得属性为 h1[v-data-001] 的样式改变
8.7 /deep/
left.vue中
// 添加scoped 限定h3属性只能在left.vue生效,而不会全局都生效
/deep/h5{
color:pink;
}
使得属性为 [v-data-001] h5 — [v-data-001]的后代选择器中的h5 的样式改变
应用场景:
父组件中直接改造子组件的样式
如果用到第三方组件库的时候,如果需要改变第三方组件默认样式,则需要用到 /deep/
9.组件的生命周期
创建一个组件的实例—就是以标签的形式去使用的过程
生命周期(Life Cycle)是指一个组件从创建 -> 运行 -> 销毁的整个阶段,强调的是一个时间段。
生命周期函数:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。
- 创建阶段:new Vue() – beforeCreate – created – beforeMount – mounted
- 运行阶段:beforeUpdate – updated
- 销毁阶段:beforeDestroy – destroyed
注意:生命周期强调的是时间段,生命周期函数强调的是时间点。
//beforeCreate 阶段,props|data|methods都尚未创建,处于不可用状态
// 创建阶段的第一个生命周期函数
beforeCreate() {
console.log(this.info); //undefined
console.log(this.username); //undefined
console.log(this.show); //undefined
},
//----------------------------------------------------------------------
// 可以使用 props data methods
// 但组件的模板结构尚未完成,template里面的内容还没有被渲染,不能操作dom结构
created() {
console.log(this.initdata); //Hello
console.log(this.username); //zs
this.show(); //调用了test组件的show方法
},
// created函数很常用,常用于调用methods中的方法请求服务器的数据,
//并将数据转存到data中供template模板使用
initgetBooks() {
const xhr = new XMLHttpRequest();
// 监听的load事件
xhr.addEventListener("load", () => {
// res对象格式,res.data是数组数据
const res = JSON.parse(xhr.responseText);
this.books = res.data;
console.log(this.books);
});
xhr.open("GET", "http://www.liulongbin.top:3006/api/getbooks");
xhr.send();
},
// 可以发起ajax请求去请求数据,通过methods请求好之后存进data中,便于渲染
created() {
this.initgetBooks();
},
// 不能操作dom结构
const domH3 = document.querySelector("#Myh3");
console.log(domH3); //null 还没有被创建
//----------------------------------------------------------------------
/**
* 将要渲染
* 也是拿不到DOM结构
*/
beforeMount() {
console.log("beforeMount");
const domH3 = document.querySelector("#Myh3");
console.log(domH3); //null 还没有被创建
},
//----------------------------------------------------------------------
/**
* 完成渲染DOM结构
* 最早只能在当前的mounted阶段执行DOM
*/
mounted() {
console.log("mounted");
console.log(this.$el); //得到当前的HTML结构
},
//----------------------------------------------------------------------
//最少运行0次(数据不变化时)
// 数据变化时,为了能获取最新的dom结构,必须写在updated的生命周期函数内
/**
* beforeUpdate
* 数据发生变化时开始运行(数据赋值、数据修改...)
* 将要渲染
*/
beforeUpdate() {
console.log("beforeUpdate");
console.log(this.msg);
const domMsg = document.querySelector(".msg");
console.log(domMsg); //null 还没有被创建
console.log("将要渲染,即这里的数据改变,但dom结构的内容还没改变");
console.log(domMsg.innerHTML);
},
/**
* updated
* 已经渲染好了
*/
updated() {
console.log("updated");
const domMsg = document.querySelector(".msg");
console.log(domMsg.innerHTML);
},
//----------------------------------------------------------------------
// v-if = "flag" -- true/false决定是否销毁
/**
* beforeDestroy
* 将要销毁,但还未销毁
*/
beforeDestroy() {
// 点击了就开始触发
console.log("beforeDestroy");
console.log(this.msg); //
const domMsg = document.querySelector(".msg");
console.log(domMsg); //dom元素已销毁
},
/**
* destroyed
*/
destroyed() {
// 点击了就开始触发
console.log("destroyed");
console.log(this.msg); //
const domMsg = document.querySelector(".msg");
console.log(domMsg); //dom元素已销毁
},
10.组件之间的数据共享
10.1 父子组件之间的数据共享 父–>子
① 子组件新建props属性
注意:props的属性是只读的,不建议改动
<template>
<div id="container">
<p>这是msg内容:{{ msg }}</p>
<p>这是info内容:{{ info }}</p>
</div>
</template>
<script>
export default {
props: ["msg", "info"],
};
</script>
② 父组件内
导入:import Son from "@/components/Son.vue";
注册:components: { Son, },
标签化使用:<Son v-bind:msg="message" v-bind:info="userInfo"></Son>
message,userInfo是父组件的data节点的属性
③ 注意点
10.2 父子组件之间的数据共享 子–>父
使用自定义事件 $emit()方法
① 子属性先data节点定义数值
data() {
return {
count: 0,
};
},
② 父组件的data节点新建一个数据项接收子组件传来的值
data() { return { saveCountFromSonbro: 0, };},
③ 子组件的click事件函数内部新增一个自定义事件
methods: {
add() {
this.count += 1;
this.$emit("numchange", this.count);
},
},
④ 父组件注册的子组件实例中添加自定义事件
<SonBro @numchange="getCountFromSonbro"></SonBro>
<h3>自定义事件从子组件获取的值是:{{ saveCountFromSonbro }}</h3>
//-----------------函数中赋值------------------
methods: {
getCountFromSonbro(val) {
this.saveCountFromSonbro = val;
},
},
10.3 兄弟组件之间的数据共享 vue2.x–EventBus
Son.vue(发送方) 将数据传给 SonBro.vue(接收方)
① 设置发送方的数据值 与 接收方的数据空值
② EventBus.js 里面导入Vue,new一个Vue实例对象,exports default实例对象 ==> 谁导入的EventBus.js文件谁就可以使用Vue实例对象
import Vue from ‘vue’
export default new Vue();
③ 发送方添加一个函数,调用 bus.$emit(‘事件名’, 发送的值) 发送数据
<button @click="sendData">发送数据</button>
data() {
return {
message: "兄弟组件发送方要准备发送的消息",
};
},
methods: {
sendData() {
bus.$emit("share", this.message);
},
},
④ 接收方,调用 bus.$on(‘事件名’, (:就是接收的参数值)val=>{})
bus.$on(‘事件名’, val=>{})
数据发生变化,写在created节点内
<h5>接收的数据是:{{ msgFromBrother }}</h5>
data() {
return {
count: 0,
// 接收方 先设置为空
msgFromBrother: "",
};
},
// 接受数据 发送数据 数据变换的时候
created() {
bus.$on("share", (val) => {
this.msgFromBrother = val;
});
},
11.ref的引用
jQuery 操作DOM
Vue中,MVVM优势,仅需维护数据即可,数据操纵视图,极少需要操作DOM的方案–ref
每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs 指向一个空对象。
this.$ref = {}
方便取到某个DOM元素
11.1 引用DOM
步骤 【注意,一个是 ref,一个是 $refs】
① 给模板的html元素添加ref <h1 ref="myH1">vue 组件</h1>
② 拿到对应的DOM结构 this.$refs.myH1.style.color = "green"
11.2 引用组件
① 在被引用组件的标签上添加ref
ref = “自定义名字”
<Son v-bind:msg="message" v-bind:info="userInfo" ref="change"></Son>
② 在组件内添加函数
this.$refs.change.info.name = "针对对象修改的info(新建一个对象)";
组件实例 this . $refs . 前面的ref自定义名字 . 开始引用被引用组件的内容
11.3 this.$nextTick( ()=>{} )
应用常见:一些需要延迟执行的代码,比如等待DOM渲染完毕后
showInput() {
this.flag = !this.flag;
//获取焦点
/**
* this.$refs.iptRef.focus();
* Cannot read properties of undefined (reading 'focus')
* 解读:this.$refs.iptRef是undefined的
* 原因:bool值是变成true,但页面是还没渲染beforeUpdate,所以文本框还没有
* 解决:Vue提供的方法:this.$nextTick(callback)---把callBack里面的方法延迟执行,数据重新渲染好再执行
*/
this.$nextTick(() => {
this.$refs.iptRef.focus();
});
},
13.购物车案例
① 获取数据
APP中导入axios const axios from "./lib/axios.js"
methods中,定义initCartList函数,通过 const {data:res} = await axios.get('url')
请求数据
在 created
生命周期函数中,调用 this.initCartList()
方法,输出数据变化后得到的数据
② 存储数据到data里面,为了渲染在页面上
先写好Goods组件模板
HTML结构
数据
使用自定义属性props:{aaa:{default:"", type:Number/Boolean/String, required:true/false}}
绑定属性 v-bind/:src="aaa"
Vue组件内利用循环渲染商品,并绑定Goods的自定义属性,给其赋值
<Goods
<!-- Goods子组件那边动态绑定是:{{title}}、{{price}}、:src="pic"-->
v-for="item in CartList"
:key="item.id"
:title="item.goods_name"
:price="item.goods_price"
:pic="item.goods_img"
:state="item.goods_state"
:id="item.id"
:for="item.id"
></Goods>
③ 用户在App.vue点击state切换时,数据并不会同步更改,因为没有设置v-model;
另外:state在Goods.vue子组件中是在props节点中的,不可更改
所以:改用自定义事件的方式从子组件传输state状态给父组件的方式
- 监听子组件的复选框状态的变化
@change = “”
事件const newState = e.target.checked;
- 将变化后的值&变化项的id传给父组件
- 使用some方法
.some(item => {})
· 在数组this.CartList
找到对应的id - 再根据找的的item项修改对应的state值
- 使用some方法
//自定义事件 子->父
// e = {id, value}
stateToogle(e) {
// 在数组里面找到对应的id的那一项 --- .some()方法
this.CartList.some((item) => {
// 找到了对应的一项 ---> 修改
if (item.id === e.id) {
item.goods_state = e.value;
return true; //终止循环
}
});
},
④ 全选状态的修改
-
点击【全选】按钮方式
-
Footer.vue 增加自定义事件,传送当前按钮的状态
-
App.vue 绑定事件,every/some方法改变全部复选框的状态
-
App.vue getNewStage(e) { // 在数组里面找到对应的id的那一项 --- .some()方法 this.CartList.some((item) => { // 找到了对应的一项 ---> 修改 if (item.id === e.id) { item.goods_state = e.value; return true; //终止循环 } }); }, <Footer @full-change="getFullStage" :isFull="fullState"></Footer> __________________________________________________________________________________ Footer.vue methods: { // 监听全选复选框的变化 作为参数传给自定义事件 到父组件 fullChange(e) { this.$emit("full-change", e.target.checked); }, },
-
-
点击【普通复选框】方式
-
判断是否所有复选框都选中 ---- 计算属性 ---- every方法
-
属性绑定
computed: { // 计算属性 判断有无全选 fullState() { return this.CartList.every((item) => item.goods_state); }, }, <Footer @full-change="fn" :isFull="fullState"></Footer>
-
⑤ 计算商品总价
-
Counter.vue:自定义属性 num
-
Goods.vue:
- 导入Counter,v-bind绑定属性num :num=“count”
-
App.vue:
-
导入Goods,v-bind绑定属性count :num=“item.goods_count”
-
计算属性 computed
-
amt() { // 过滤 -- 筛选出被勾选的,找到符合true的 return this.CartList .filter((item) => item.goods_state) .reduce( (amt, item) => (amt += item.goods_price * item.goods_count;),0 ); }, Footer标签内添加 --- :amount="amt"
-
⑥ 加购数量改变
多层嵌套下也可以用EventBus(App–Goods–Counter)
goods
methods: {
sub() {
let nums = this.num - 1 < 0 ? 0 : this.num - 1;
const obj = { id: this.id, value: nums };
bus.$emit("share", obj);
},
add() {
const obj = { id: this.id, value: this.num + 1 };
bus.$emit("share", obj);
},
},
app
created() {
console.log(this.initCartList());
bus.$on("share", (val) => {
this.CartList.some((item) => {
if (item.id === val.id) {
item.goods_count = val.value;
}
});
});
},
14.动态组件
- 概念
- 动态组件指的是动态切换组件的显示与隐藏
- 不像v-if v-else 那么繁琐
14.1使用
- vue 提供了一个内置的 组件,专门用来实现动态组件的渲染。
- component在中类似一个占位的组件,添加 is = “components内注册的组件”
<component is="Left"></component>
- 动态绑定
- data节点定义名字
isName: "Left",
- 再动态绑定 :is=“”
<component :is="isName"></component>
- data节点定义名字
- 应用
- 类似页面上 【我的】【首页】此类功能的切换
14.2 keep-alive
keep-alive 保持状态、被缓存 — 防止组件切换隐藏期间被销毁destroy
-
使用(保证使用同一个实例,不会频繁created&destroyed)
-
<keep-alive> <component :is="isName"></component> </keep-alive>
-
14.3 deactivated & activated
- 当组件被缓存时,会自动触发组件的 deactivated 生命周期函数。
- 当组件被激活时,会自动触发组件的 activated 生命周期函数。
- 第一次创建时 同时执行 created & actived
14.4 指定要缓存的组件 keep-alive 的 include 属性
-
<keep-alive include="Left, xxx, aaa"> <component :is="isName"></component> </keep-alive> 补充:exclude 指定谁不缓存 注意:exclude & include只能使用一个 首页展示固定数据的组件
-
可以缓存多个,逗号隔开
14.5 了解-修改组件名称
-
默认为注册时的名称 components:{Left}
-
修改:Left.vue中添加节点name
15.插槽 Slot
15.1基础
-
概念
- 由组件的使用者去定义这一模块
-
使用
app.vue
<Left>
<p>这是在app.vue内添加的left区域的内容s</p>
</Left>
left.vue
添加 <!--
声明一个插槽区域,名称默认为default
-->
<slot></slot>
-
插槽名称的使用
-
注意点
- v-slot 要在template标签内
- template标签只起到包裹作用
-
<slot name="xxx"></slot>
<template v-slot:xxx>
<p>
加入插槽的内容
</p>
</template>
-
插槽简写形式
v-slot: ==== #
-
后备内容
- 用户没有指定的时候声明
- 默认内容
15.2高级
- 具名插槽
<template #title>
<p>MyLife</p>
</template>
<slot name="title"></slot>
-
作用域插槽(提供属性给父组件使用)
-
封装组件时为预留的
slot
提供属性对应的值msg,称作:作用于插槽 -
取到的值是一个对象,可以进行解构赋值
{msg, user, xxx}
– 只取对象中的msg\user\xxx...
— 可以弄多个
-
<template #content="scope">
<p>Life is so damn</p>
<p>{{ scope.msg }}</p> //hello 这是作用域插槽
</template>
<slot name="content" msg="hello 这是作用域插槽"></slot>
-
<slot name="content" msg="hello 这是作用域插槽" :user="userinfo"></slot> _____________________________________________________________________________ <template #content="{ msg, user }"> <p>Life is so damn</p> <p>{{ msg }}</p> <p>{{ user.name }}</p> </template>
15.3插槽应用 — 购物车案例重构
描述:购物车案例的数量counter组件在变化的时候,需要先传给Goods,再传给APP;故使用EventBus直接Counter传给App
改进为:在Goods处放一个插槽 — 此时在Goods放的内容都会被填充到Counter组件中
counter 也可以读取 item项
<Goods>
<Counter
:num="item.goods_count"
:id="item.id"
:num-change="shareNum(item)"
></Counter>
</Goods>
counter数值的变化 (子 —> 父)
自定义事件
传数据,函数名(参数),参数是item.id,这里不用再在counter那边定义;
参数用了之后,要想得到自定义事件传来的值,剧需要再加上一个$event
@num-change="getNum(item, $event)"
getNum(item, e) {
item.goods_count = e;
},
这里不需要再接收id的值找到对应的项,本身就可以拿到item了
16.自定义指令
私有自定义指令
16.1 bind绑定(只能绑定1次)
节点 directives
直接定义绑定 bind(el) {}
<h1 v-color>App 根组件</h1> // 自定义节点
// el固定写法,表示所绑定的DOM元素
directives: {
color: {
bind(el) {
el.style.color = "green";
},
},
<h1 v-color>App 根组件</h1>
增加数据节点绑定 bind(el, bingding) {}
v-color=“color” 传入的是一个变量
data节点添加
color: "purple",
<h1 v-color="color">App 根组件</h1>
directives: {
color: {
bind(el, binding) {
el.style.color = binding.value;
},
},
},
直接传变量的值
<p v-color="'yellow'">直接传变量值绑定</p>
16.2 update绑定
可以满足DOM元素在更新的时候再次绑定
16.3 bind&update函数简写
当bind update的业务逻辑一样时可写
color(el, binding) {
el.style.color = binding.value;
},
全局自定义指令
main.js
// 全局定义自定义组件
/**
* Vue.directive("定义好指令的名称", 处理函数方式);
* Vue.directive("xxx", {
* bind(){},
* update(){}
* }});
*/
Vue.directive('color', function (el, binding) {
el.style.color = binding.value;
});
17.eslint
17.1新建
vue create — Manually… — 勾选加上css(Linter就是默认安装aslint)— 2.x — less — 选择Eslint对应的语法规范:Standard config标准配置 — Lint on save保存时解决 — 不存预设reset–n
.eslintrc.js
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
//开发阶段不执行,发布阶段执行
debugger
写哪停哪,在网页f12source页面
https://eslint.bootcss.com/docs/rules/ 官网可添加修改自定义规则
17.2 下载配置插件
ESLint
setting.json添加:
// ESLint 插件的配置
"editor.codeActionsOnSave": {
"source.fixAll": true,
"editor.defaultFormatter": "octref.vetur"
},
新建.prettierrc文件在用户目录下
18.axios基本用法
18.1 npm安装axios
18.2 选择性写
组件命名报错 “Component name “XXX“ should always be multi-word”的解决方法:.eslintrc.js
// 关闭组件命名规则
'vue/multi-word-component-names': 'off'
18.3 问题
Cannot find module 'babel-eslint'
———— npm install eslint babel-eslint -g
18.4 axios的基本使用方式
导入:import axios from 'axios'
GET请求:请求路径
const { data: res } = await axios.get('http://www.liulongbin.top:3006/api/get')
POST请求:请求路径 & 发送的数据
async postInfo() {
const { data: res } =
await axios.post('http://www.liulongbin.top:3006/api/post',
{ name: 'zs', age: 22 }
)
},
18.5 优化
理解,每一个.vue都是一个vue的实例,先axios全局化
在main.js:先axios全局化,通过Vue构造函数的构造属性【 h t t p 只 是 一 个 命 名 】 ‘ V u e . p r o t o t y p e . http只是一个命名】 `Vue.prototype. http只是一个命名】‘Vue.prototype.http= axios` 挂载
将axios挂载到vue的原型上,让axios变成一个共享成员,这样每个组件就可以访问我们的axios了,不再需要额外import导入axios。
今后要发请求,组件中不需要再导入,而是可以直接 this.$http.get(....)
方便操作:全局配置 axios 的请求根路径 axios.defaults.baseURL = 'http://www.liulongbin.top:3006'
18.6 缺点
无法实现API接口的复用
即:多个组件都要请求数据的话,都要进行 this.$http.get操作
为了方便我们api接口的复用,可以改成写一份代码,适用三个组件,而不是写三个冗余代码。
正因为这种复用api的需求,所以可以把组件中使用组合式API创建的逻辑抽取出来封装成可复用的模块
19.前端路由的概念与原理
19.1 SPA 与前端路由
-
SPA:单页面应用程序
-
SPA 指的是一个 web 网站只有唯一的一个 HTML 页面,所有组件的展示与切换都在这唯一的一个页面内完成。此时,不同组件之间的切换需要通过前端路由来实现。
-
结论:在 SPA 项目中,不同功能之间的切换,要依赖于前端路由来完成!
19.2 前端路由
-
Hash 地址与组件components之间的对应关系。
- component动态组件标签
- component动态组件标签
-
Hash 地址 — 之前的锚链接#
-
页面不会刷新,但会有浏览历史
-
#/xxx ---- 都是hash地址 — location.hash可以输出其值
-
<div class="side-bar"> <a href="#b1">b1</a> <a href="#b2">b2</a> <a href="#b3">b3</a> <a href="#b4">b4</a> </div> <div class="box" id="b1"></div> <div class="box" id="b2"></div> <div class="box" id="b3"></div> <div class="box" id="b4"></div>
-
-
前端路由的工作方式
-
过程:点击了前端页面上的路由链接 — 导致了URL地址栏中的hash地址的变化 — 前端路由监听hash地址的变化 — 渲染hash地址对应的组件
-
应用
-
onhashchange 事件(了解即可,vue-router会自动去创建)
-
created() { // 只要当前的 App 组件一被创建,就立即监听 window 对象的 onhashchange 事件 window.onhashchange = () => { console.log('监听到了 hash 地址的变化', location.hash) switch (location.hash) { case '#/home': this.comName = 'Home' break case '#/movie': this.comName = 'Movie' break case '#/about': this.comName = 'About' break } } },
-
20.vue-router 的基本使用
vue-router 的官方文档地址:https://router.vuejs.org/zh/
20.1 安装与配置
① 安装 vue-router 包 — npm i vue-router@3.5.2 -S
② 创建路由模块 — 新建 router/index.js 路由模块
-
导包 import xxx from ‘xxxxx’ — Vue & RouterVue
-
将路由装为Vue下的插件—使用 Vue.use(VueRouter)
-
创建实例对象 new VueRouter()
-
对外共享实例对象 export default router
③ 导入并挂载路由模块 (app2.vue 写链接+占位符 — 路由模块写对应关系)
-
在main.js的Vue实例中配置
-
导入index.js中的router模块
import router from '@/router'
只有一个index.js文件在里面,可以只写外面的文件夹 -
new Vue({ render: h => h(App), // 在 Vue 项目中,要想把路由用起来,必须把路由实例对象,通过下面的方式进行挂载 // router: 路由的实例对象 router }).$mount('#app')
④ 声明路由链接和占位符
-
<!-- 当安装和配置了 vue-router 后,就可以使用 router-view 这个组件了 --> <!-- 作用:占位符,展示变化完成后的组件 || vue-router提供--> <router-view></router-view>
-
在路由模块【router/index.js】中声明对应关系
-
在router实例对象中添加一个
routes:[{path:'#后面的值', component:要展示的组件|先导入}, {}, {}]
数组,用于定义【hash地址】与【组件】之间的关系 -
【注意】是 routes 不是 routers
-
const router = new VueRouter({ routes: [ { path: '/home', component: Home }, { path: '/movie', component: Movie }, { path: '/about', component: About } ] })
-
20.2 router-link
-
router-link 代替 a 链接
-
<!-- 当安装和配置了 vue-router 后,就可以使用 router-link 来替代普通的 a 链接了,好处:不用写 # --> <!-- <a href='#/home'>首页</a> --> <router-link to="/home">首页</router-link>
-
20.3 路由重定向 redirect
-
用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面。
通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向:
-
{ path: '/', redirect: '/home' },
20.4 嵌套路由
-
通过路由实现组件的嵌套展示,叫做嵌套路由。
-
在路由模块内声明 父级路由规则 & 子级路由规则children:[]
-
const router = new VueRouter({ routes:[ {path:'xxx 跟 router-link 的 to内容一样', component:导入的组件}, {path:'', component:xxx}, {path:'/about', component:xxx, children:[{path:'tab1',component:Tab1},{},{}]} ] }) // 注意:子路由规则的path不需要再加上 ‘/’了
-
设置tab1是about的默认页面
-
① {path:'tab1',component:Tab1} 改成 {path:'',component:Tab1} ② <router-link to="/about/tab1">Tab1</router-link> 改成 <router-link to="/about">Tab1</router-link>
-
20.5 动态路由匹配
-
把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性。
在 vue-router 中使用英文的冒号(:)来定义路由的参数项。
-
<router-link to="/movie/1">A电影</router-link> <router-link to="/movie/2">B电影</router-link> <router-link to="/movie/3">C电影</router-link>
-
{ path: '/movie/:id', component: Movie } //:id动态匹配 /* to="/movie/1" movie/??自定义 :? 自定义 */
-
-
对应关系设置好后,在movie组件(Movie.vue)中,希望拿到对应的id的值(动态参数值)
-
【方式1 $this实例对象中】
-
this 对应的实例 this.$route.params.id //id----自定义名称 || this选择省略 $route.params.id
-
<!-- this.$route 是路由的“参数对象” --> <!-- this.$router 是路由的“导航对象”,提供了一系列的API方法 -->
-
【方式2 props:true传参】
-
{ path: '/movie/:id', component: Movie, props: true } //前端路由
-
props: ['id'], //接收数据
-
<!-- 使用数据 --> <h3>Movie 组件 ---- 打印对应的id {{id}}</h3>
-
-
扩展:路径参数 & 查询参数
-
router-link to=“/movie/B?name=ls&age=22” ---- 路径参数是:B || 查询参数是:name=ls&age=22
-
20.6 vue-router 中的编程式导航 API
-
① this.$router.push(‘hash 地址’)
⚫ 跳转到指定 hash 地址,并增加一条历史记录
eg:使用按钮而不是链接的方式跳转页面
② this.$router.replace(‘hash 地址’)
⚫ 跳转到指定的 hash 地址,并替换掉当前的历史记录
③ this.$router.go(数值 n)
⚫ 实现导航历史前进、后退
⚫ 常用于前进/后退一层:(没有this,直接写在行内)
① $router.back()
⚫ 在历史记录中,后退到上一个页面
② $router.forward()
⚫ 在历史记录中,前进到下一个页面
20.7 了解导航守卫 (全局前置守卫)
-
避免在未登录的情况下访问到首页
-
全局前置守卫
每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行
访问权限的控制:
// 为router实例对象声明前置全局导航,参数是回调函数守卫方法 // 触发时机:只要出发了导航跳转,必然会触发fn回调 // fn()接收的形参:to || from || next [from A to C,next允许放行] router.beforeEach(function(to, from, next) { // to 将要访问的路由的信息 console.log(to) // from 将要离开的路由的信息 console.log(from) // next函数 --- 表示放行 next() //都可以放行 })
-
next 函数的 3 种调用方式最终导致的结果
当前用户拥有后台主页的访问权限,直接放行:next()
当前用户没有后台主页的访问权限,强制其跳转到登录页面:next(‘/login’)
当前用户没有后台主页的访问权限,不允许跳转到后台主页:next(false)
if (to.path === '/main') { // 判断后台主页是否有token const token = localStorage.getItem('token') if (token) { //有token 继续放行 next() } else { // 无token 跳转登录 next('/login') } } else { // 不去后台 都可以放行 next() }
21.vue-router 的常见用法
22.后台管理案例
-
router/index.js 配置 vue-router,在main.js导入路由模块
-
渲染登录页面 components/MyLogin.vue ,在路由模块加入对应的组件以及routes:[{}]对应关系
-
登录界面–双向绑定数据username&password,.trim去除左右多余的空格
-
点击登录按钮,根据输入值是否正确,判断是否进入主页面
-
login() { if (this.username === 'admin' && this.password === '000000') { localStorage.setItem('token', 'Bearer xxxxx') this.$router.push('/home') } else { localStorage.removeItem('token') this.reset() } }
-
路由模块绑定首页对应关系
-
首页的头部区域,左侧区域也是直接加组件,路由模块绑定,右侧区域
-
定义子级路由规则、
-
用户表格的渲染 – tr里面进行for循环 –
-
点击用户详情的动态跳转
-
传参跳转 @click.prevent="gotoDetail(item.id)" this.$router.push('/home/userinfo/' + id) --- MyUser.vue // 用户详情的路由 { path: 'userinfo/:id', component: UserInfo, props: true } --- index.js
-
退出登录 — 清除token,跳转页面
-
后退 — @click="$router.back()"直接返回上一个链接
拓展
多条路径需要判断权限的情况
① if(to.path === 'xxx '||to.path === ‘bbb’||to.path === ‘ccc’… )
② const arr = [‘xxx’, ‘bbb’, ‘ccc’]
判断 to.path 是否是数组的其中之一
(pathArr.indexOf(to.path) != 1) ————证明在数组里面
③ 将数组arr单独抽取为js文件
export default [‘/home’, ‘/home/users’, ‘/home/rights’]
23.头条项目
history??? —no
components & views 文件夹的区别
某个组件通过路由来进行动态切换 — views
某个组件是可复用的 — components
23.1 安装与配置 Vant 组件库 — 移动端
官网:https://vant-ui.github.io/vant/#/zh-CN
-
快速入手:npm按照、第三种按照组件的方式(开发阶段)
-
import Vue from 'vue' import Vant from 'vant' import 'vant/lib/index.css' Vue.use(Vant) //main.js
-
测试使用:接下来类似button处的引入就不用再继续了,(有了第一步)可以直接使用
-
主界面 & 用户界面 都是通过路由加载的,放在views文件夹中【规范-组件名大写】
-
结构分析:上-router-vue占位符;下-tabbar组件
-
官网tabbar标签栏–得到结构&选择路由模式(开启对应&切换);icon图标–选择需要的图标
-
注意:组件命名解决驼峰命名法:‘vue/multi-word-component-names’: ‘off’,
-
使用路由规则:index.js路由模块内–导入组件||主页&个人中心路由规则{path,component}
-
顶部nav bar,看官网,添加固定顶部属性 fixed
-
考虑以为NavBar & TabBar的遮挡,所以home-container要设置padding
-
<van-nav-bar title="标题" fixed />
-
部分修改不了的第三方默认样式,可以在样式类名前加上 /deep/ .xxx{}
-
axios请求数据:npm装包
-
在Vue项目配置axios:main.js----Vue.prototype.$http=axios----复用性差----改进:src/utils/request.js封装axios的实例,直接调用即可
// import Vue from 'vue' import axios from 'axios' // const request = Vue.prototype.$http const request = axios.create({ baseURL: 'http://www.liulongbin.top:8000' }) export default request
-
Home组件请求文章列表数据:导入request.js,使用request.get(‘url’,{ params:{对象参数} })、
created() { this.initArticleList() }, methods: { async initArticleList() { const { data: res } = await request.get('/articles', { params: { _page: this.page, _limit: this.limit } }) console.log(res) } }
Failed to resolve loader: less-loader ---- 解决:
cnpm install less less-loader --save-dev
-
代码的复用 – 数promise 类型的代码 — Promise的实例对象
// api/articlaAPI.js import request export const xxxxx = function(参数a, 参数b){ // 对外共享函数 // return [new Promise()] return request.get('url', { params:{ 参数a, 参数b } }) } //User.vue import { xxxxx } from '...articleAPI.js' methods:{ async fn(){ const{data:res} = await xxxxx(this.a, this.b) // xxxxx(this.a, this.b)返回Promise类型 } }, created(){this.fn()}
-
将得到的res数组转存到data(新建一个空数组)里面
this.articlelist = res
-
文章为一个个模块,不需要路由跳转,直接放components内,根据数组循环将组件渲染出来
<ArticleInfo v-for="item in articlelist" :key="item.id"></ArticleInfo>
-
文章组件使用传值:props:{title:{type:xxx,default:‘’}}、{{ title }}、:title = “item.title”
通过数组定义多个可能的类型 type: [String, Number],
自定义属性名如果是小驼峰形式comCount,则大写字母在绑定时要改成连字符::com-count=“item.comm_count”
属性为对象格式
cover: { type: Object, default: function () { return { type: 0 } }
默认值需要是function,返回一个默认值
在Home.vue绑定一整个cover属性
在ArticleInfo.vue中按需使用
判断图片出现几张—v-if=“cover.type===1” —注意:是平级的,不能使用v-if/v-else
<template #title> <div class="title-box"> <!-- 标题 --> <span>{{title}}</span> <!-- 单张图片 --> <img :src="cover.images[0]" alt="" class="thumb" v-if="cover.type===1"> </div> <!-- 三张图片 --> <div class="thumb-box" v-if="cover.type === 3"> <img :src="item" alt="" class="thumb" v-for="(item, i) in cover.images" :key="i" /> </div> </template>
使用图片 vover.images[0] || 循环 v-for=“(item , i)in cover.images” :key=“i” 【添加i只是为了:key,避免报错】
-
加载更多的效果:Vant展示组件-List列表:https://vant-ui.github.io/vant/#/zh-CN/list
-
使用:van-list包裹需要加载的组件
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad" > <要包裹的标签 /> </van-list> /* 数据的加载请求 loading --- 是否正在加载下一页:请求-true,此时不会反复触发load绑定的事件,数据请求完成变成false finished --- 是否加载完毕,完成后变成true,并提示文本finished-text */ // 数据初始值 <script> data(){ return{ // 是否正在加载 设置为true,防止一进入页面就加载load loading: true, // 是否加载完毕 finished: false } }, // 数据加载完毕后(initArticleList完成后),再将loading设置为false,方便下一页面的加载 </script>
-
数据onload
methods: { async initArticleList() { const { data: res } = await getArticleListAPI(this.page, this.limit) /** * 将当前页面的数据|数组,与原来的数据|数组合并 * 不可用arr1.push[arr2] --- > [1,2,3,[4,5,6]] * 仅合并为一个大数组的方法 ...事件扩展符,提取元素中的每一个并合并在一起 * const Arr = [...arr1, ...arr2] ---->[1,2,3,4,5,6] */ this.articlelist = [...this.articlelist, ...res] // 数据res已获取完毕,取消正在加载状态 this.loading = false /** * 判断是否到了最后一页,即新得到的数据是否为空(length=0),停止加载 */ if (res.length === 0) { this.finished = true } }, onLoad() { this.page++ // 页面+1 this.initArticleList() // 调用获取当前页面的方法 } }
-
下拉刷新(下拉是在头部拼接请求的数据)
<van-pull-refresh v-model="refreshing" @refresh="onRefresh"> <van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"> <ArticleInfo v-for="item in articlelist" :key="item.id" :title="item.title" :author="item.aut_name" :com-count="item.comm_count" :pubdate="item.pubdate" :cover="item.cover"></ArticleInfo> </van-list> </van-pull-refresh> <!-- 添加 isRefresh 布尔值判断是否下拉刷新,判断方式:加一个参数isRefresh 下拉刷新采用 新数据在前 的数据拼接方式 结束时采用disable属性禁止刷新 -->
onRefresh() { this.page++ // 页面+1 this.initArticleList(true) // 调用获取当前页面的方法,参数true表示当前是下拉加载 } /* 梳理: vant官网获取下拉&上拉代码 判断有无已进入页面就触发加载的,有则根据属性改为true/false 开始加载,首先页面++,然后调用加载函数 在加载函数内部,给函数传参,判断此时是哪一种情况的加载 上拉加载的,数组拼接方式是[旧的,新的], 下拉加载的,数组拼接方式是[新的,旧的], 然后得到数据后,刷新方式都要关掉 最后,根据得来的res数据的长度判断数据是否为空,进而关掉其finished */
-
定制主题
针对情况:组件1修改默认的样式的时候,组件2并不会同步修改
-
main.js中将css样式改成less样式
// import ‘vant/lib/index.css’
import ‘vant/lib/index.less’
-
新建文件vue.config.js 根目录下
// vue.config.js module.exports = { css: { loaderOptions: { less: { // 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。 lessOptions: { modifyVars: { // 直接覆盖变量 'text-color': '#111', 'border-color': '#eee', // 或者可以通过 less 文件覆盖(文件路径为绝对路径) hack: `true; @import "your-less-file-path.less";`, }, }, }, }, }, };
-
官网查询样式变量
--van-nav-bar-background-color vue.config.js内加:'nav-bar-background-color': '#0077ff', 注释掉:// hack: `true; @import "your-less-file-path.less";` 重启 另一种:使用自定义的less文件覆盖,无需重启 hack: `true; @import "自定义命名.less";`, 注意,这里使用的是绝对路径---【webpack在进行打包的时候,底层使用的是node,js,因此这里可以使用path.join(__dirname,'./xxx')】 const path = require('path') const themePath = path.join(__dirname, './src/theme.less') hack: `true; @import ${themePath};`
-
样式覆盖
// 在 theme.less 文件中,覆盖 Vant 官方的 less 变量值 @blue: #007bff; // 覆盖 Navbar 的 less 样式 @nav-bar-background-color: @blue; @nav-bar-title-text-color: #fff;
-
项目打包
-
默认的 npm run build 打包生成的dist文件夹,只能发布在服务器上,通过http协议打开,双击的file协议打不开
-
https://cli.vuejs.org/zh/config/#publicpath指定部署路径
-
publicPath,这个值也可以被设置为空字符串 (
''
) 或是相对路径 ('./'
),vue.config.js添加publicPath: './',
-
主页面点击离开之后不会销毁:
- 添加缓存keep-alive > Home;
- 接着路由模块index.js记录路由信息,标记top值;
- 在路由模块index.js添加页面滚动的行为:scrollBehavior(to, from, savedPosition){},直接将保存的位置return出去
- Home组件,页面被activated激活时添加页面滚动监听,页面被deactivated缓存时移除页面监听