Vue2.0简讲 @Draven
- 入门
- 1.1、响应式渲染
- 1.1.0、Vue创建
- 1.1.1、指令(7)
- 1.1.2、class与style
- 绑定HTML Class
- 对象语法
- 数组语法
- 绑定内联样式
- 对象语法
- 数组语法
- 1.1.3、条件渲染
- v-if else-if
- template v-if
- 1.1.4、列表渲染
- v-for
- key
- 数组更新检测
- 列表渲染Vue2
- 列表渲染Vue3
- 过滤应用(模糊查询)
- 1.1.5、事件处理器
- 事件修饰符
- 按键修饰符
- 系统修饰键
- 鼠标按钮修饰符
- 1.1.6、计算属性
- computed-同步
- watch-异步
- 1.2、fetch&axios
- 1.2.0、fetch-get
- 1.2.1、fetch-post
- 1.2.2、后端要求传入json格式
- 1.2.3、fetch-猫眼数据案例
- 1.2.4、axios-get
- 1.2.5、axios-post
- 1.2.6、猫眼数据--案例
- 过滤器
- 1.3、组件
- 1.3.1、组件
- 1.1、全局与局部
- 局部写法
- 1.2、组件父传子
- 属性验证&默认属性
- 1.3、组件子传父
- 1.4、中间人模式
- 1.5、中央事件总线
- 1.6、组件refs
- 1.7、动态组件
- 1.3.2、slot
- 2.1、旧版slot
- 2.2、新版slot
- 1.4、过度
- 1.4.1、过度效果
- 1.4.2、多个元素过度
- 1.4.3、多个组件过度
- 1.4.4、多个列表过度
- 1.4.5、可复用过度
- 1.5、生命周期
- 1.5.1、组件生命周期
- 1.1、创建阶段
- 1.2、更新阶段
- 1.5.2、销毁
- 1.6、Swiper
- 1.6.1、Swiper静态
- 1.6.2、Swiper动态
- 1.6.3、vue-swiper
- 1.6.4、vue-swiper-组件 ※
- 1.7、Vue3
- 1.7.1、组件定义
- 1.7.2、生命周期
- 1.8、指令
- 1.8.1、自定义指令
- 1.8.2、指令轮播
- 1.8.3、指令函数简写
- 1.8.4、Vue3-指令轮播
- 1.8.5、轮播-nextTick
- 1.9、Vue-Cli
- 1.9.1、安装
- 1.9.2、启动流程&入口文件
- 1.9.3、eslint修复
- 1.9.4、单文件组件
- 1.9.5、WebStorm的Vue模板
- 1.9.6、反向代理
- spa&路由引入
- SPA概念
- vue-router
- 开始
- 重定向
- 声明式导航
- 嵌套路由
- 编程式导航
- 动态路由
- 命名路由
- 路由模式
- 全局路由拦截
- 守卫
- 回到我们的目的页面
- 局部路由拦截
- 路由懒加载
- rem
- Swiper组件
- 猫眼案例
- axios封装
- 1 - 对于数据请求的封装
- 2- 对局数据请求的封装
- 3 - 拦截器
- 更好的滚动-betterScroll
- ElementUi组件 - pc端框架
- vant - 移动端框架
- axios拦截,绑定加载框
- 索引城市列表 组件
- Vuex - 状态管理模式
- state
- mutations
- actiones
- Vuex新写法
- 引入
- mapState
- mapActions
- mapMutations
- 注入
- Vuex持久化
- 常用方法
- 变量
- 数组
- 事件
- New系列
- localStorage-本地储存
- HTML属性
- ajax方法
- Vue
- 基础方法
- Vue外部
- Vue内部
- 方法区中
- 组件方法
- 标签
- 使用
- 过度方法
- 生命周期
入门
1.1、响应式渲染
1.1.0、Vue创建
https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js
https://cdn.jsdelivr.net/npm/vue@2
下载Vue2.0.js
<script>
new Vue(){
el: "#选择器",".选择器"....,
data:{
数据{变量,数组,对象....}
},有
methods:{
方法区
}
}
</script>
1.1.1、指令(7)
指令 | 说明 |
---|---|
v-bind | 动态绑定属性 |
v-if | 动态创建/删除 |
v-show | 动态显示/隐藏 |
v-on:click | 绑定事件 |
v-for | 遍历 |
| |
v-model | 双向绑定表单 |
v-html | 可赋html代码 |
v-bind:src | ==> :src |
v-on:click | ==> @click |
<button @click=“list_addData()”> |
1.1.2、class与style
绑定HTML Class
对象语法
<div :class="classObj">动态切换class-1-对象</div>
classObj:{
aa:true,
bb:true,
cc:false,
},
// vue2 解决方案, Vue.set(对象,属性,值)
==F12== Vue.set(vm.classObj,"dd",true) //新增 dd calss属性
数组语法
<div :class="classArr">动态切换class-2-数组</div>
classArr:["aa","bb"],
==F12== vm.classArr.push('dd') //新增 dd class属性
==F12== vm.classArr.shift() //删除第一个 calss 属性
==F12== vm.classArr.splice(1,1)//删除下标为1的calss属性,删除一个
绑定内联样式
对象语法
<div :style="styleObj">动态切换style-1-对象</div>
styleObj:{
backgroundColor:'red',
},
==F12== Vue.set(vm.styleObj,'fontSize','30px')
//新增 fontSize样式
数组语法
<div :style="styleArr">动态切换style-2-数组</div>
styleArr:[{backgroundColor: 'yellow'}],
==F12== vm.styleArr.push({fontSize:'30px'})
//新增fontSize样式
1.1.3、条件渲染
v-if else-if
<div id="box">
<!-- <div v-if="isCreated">111111111</div>-->
<!-- <div v-else>222222222</div>-->
<h2>所有订单</h2>
<ul>
<li v-for="item in dataList">
{{item.title}} <!-- -{{item.state}}-->
<span v-if="item.state===0">-未支付</span>
<span v-else-if="item.state===1">-代发货</span>
<span v-else-if="item.state===2">-已发货</span>
<span v-else>-已签收</span>
</li>
</ul>
</div>
<script>
var vm = new Vue({
el: "#box",
data:{
isCreated:false,
dataList:[
{
title:'动感小苦茶',
state:0 ,//'未支付'
},
{
title:'动感大苦茶',
state:1 ,//'代发货'
},
{
title:'情趣小苦茶',
state:2 ,//'已发货'
},
{
title:'情趣大苦茶',
state:3 ,//'已完成'
},
],
},
});
</script>
template v-if
<div id="box">
<template v-if="isCreated">
<div>111111</div>
<div>222222</div>
<div>333333</div>
</template>
</div>
<script>
new Vue({
el: "#box",
data:{
isCreated : true
}
/*
* template是一个包裹元素,不会真正创建在页面上.
* */
});
</script>
1.1.4、列表渲染
v-for
v-for | |
---|---|
v-for=“temp in dataList” | 没有 |
v-for=“temp of dataList” | 区别 |
key
- 跟踪每个节点的身份,从而重用和重新排序现有元素
- 理想的 key 值是每项都有的且唯一的 id。data.id
Vue为什么要设置key值(底层)
数组更新检测
- 使用以下方法操作数组,可以检测变动
- push() pop() shift() unshift() splice() sort() reverse()
- filter(), concat() 和 slice() ,map(),新数组替换旧数组
- 不能检测以下变动的数组
- vm.items[indexOfItem] = newValue
- 解决
- Vue.set(example1.items, indexOfItem, newValue)
- splice
检测数组的变动(底层)
列表渲染Vue2
<div id="box">
<!--数组遍历-->
<ul>
<li v-for="(temp,index) of dataList" :key="temp.id">
{{temp}}-{{index}}
</li>
</ul>
<!--对象遍历-->
<ul>
<li v-for="(temp,key) in obj ">
{{key}}-{{temp}}
</li>
</ul>
<!--数字遍历-->
<ul>
<li v-for="temp in 10">
{{temp}}
</li>
</ul>
</div>
<script>
new Vue({
el: "#box",
data:{
dataList:["111","222","333"],
obj:{
name: "Draven",
age: 18,
location: "love"
},
},
});
</script>
列表渲染Vue3
<div id="box">
<!--数组遍历-->
<ul>
<li v-for="(temp,index) of dataList" :key="temp.id">
{{temp}}-{{index}}
</li>
</ul>
<!--对象遍历-->
<ul>
<li v-for="(temp,key) in obj ">
{{key}}-{{temp}}
</li>
</ul>
<!--数字遍历-->
<ul>
<li v-for="temp in 10">
{{temp}}
</li>
</ul>
</div>
<script>
Vue.createApp({
data(){
return {
dataList:["111","222","333"],
obj:{
name: "Draven",
age: 18,
location: "love"
},
}
}
}).mount("#box")
</script>
过滤应用(模糊查询)
<div id="box">
<input type="text" @input="handleInput()" v-model="myText">
<ul>
<li v-for="temp in test()" :key="temp">
{{temp}}
</li>
</ul>
<!-- {{ test() }}-->
</div>
<script>
new Vue({
el: "#box",
data:{
myText:"",
dataList:["aaa","bbb","ccc","ddd","eee","ace","add","aee","dee","cbe","cde","dfa"]
},
methods:{
test(){
return this.dataList.filter(temp=> temp.includes(this.myText));
},
}
});
</script>
1.1.5、事件处理器
-
监听事件
-
直接触发代码
-
<button @click="count++">add-3-表达式</button>
-
-
方法事件处理器
-
写函数名 handleClick
-
<button @click="handleAdd2">add-2-函数名</button> handleAdd2(evp){ this.count++ console.log(evp.target) },
-
-
内联处理器方法
-
执行函数表达式 handleClick( e v e n t ) = = ( event) ==( event)==(event 事件对象)==
-
<button @click="handleAdd1($event,1,2,3)">add-1-函数表达式</button> handleAdd1(evt,a,b,c){ this.count++ console.log(evt.target,a,b,c) },
-
事件修饰符
- https://cn.vuejs.org/v2/guide/events.html
- `.stop`
- `.prevent`
- `.capture`
- `.self`
- `.once`
- `.passive`
-
<!-- 阻止单击事件继续传播(事件冒泡泡) --> <a v-on:click.stop="doThis"></a> <!-- 提交事件不再重载页面 --> <form v-on:submit.prevent="onSubmit"></form> <!-- 修饰符可以串联 --> <a v-on:click.stop.prevent="doThat"></a> <!-- 只有修饰符,阻止默认提交行为 --> <form v-on:submit.prevent></form> <!-- 添加事件监听器时使用事件捕获模式 --> <!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 --> <div v-on:click.capture="doThis">...</div> <!-- 只当在 event.target 是当前元素自身时触发处理函数 --> <!-- 即事件不是从内部元素触发的 --> <div v-on:click.self="doThat">...</div>
-
<ul @click.self="handleUlClick"> <!--禁止事件冒泡--> <li @click.stop="handleLiClick">1111</li> <li @click="handleLiClick">2222</li> <!--只可触发一次--> <li @click.once="handleLiClick">3333</li> <!--阻止a链接的默认行为--> <a href="http://www.baidu.com" @click.prevent>跳转</a> </ul>
按键修饰符
- https://cn.vuejs.org/v2/guide/events.html#%E6%8C%89%E9%94%AE%E4%BF%AE%E9%A5%B0%E7%AC%A6
.enter
.tab
.delete
(捕获“删除”和“退格”键).esc
.space
.up
.down
.left
.right
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">
系统修饰键
.ctrl
.alt
.shift
.meta
.exact
注意:在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。在 Sun 操作系统键盘上,meta 对应实心宝石键 (◆)。在其他特定键盘上,尤其在 MIT 和 Lisp 机器的键盘、以及其后继产品,比如 Knight 键盘、space-cadet 键盘,meta 被标记为“META”。在 Symbolics 键盘上,meta 被标记为“META”或者“Meta”。
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button v-on:click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button v-on:click.exact="onClick">A</button>
鼠标按钮修饰符
.left
.right
.middle
1.1.6、计算属性
computed-同步
- 计算属性(防止模板过重,难以维护),负责逻辑放在计算属性中来写
- 方法
- 事件绑定,逻辑计算。可以不用return,没有缓存
- 计算属性(重视结果)﹒
- 解决模板过重问题,必须有return· ,只求结果缓存,同步。
//计算的属性
computed:{
myComputedName(){
console.log("计算属性")
return this.myName.substring(0,1).toUpperCase() + this.myName.substring(1);
}
}
watch-异步
- watch(重视过程),监听一个值的改变。不用返回值﹐异步同步
watch:{
// es5 filter
myText(newVal){
console.log("改变了",newVal)
//模拟ajax异步延时状态
setTimeout(()=>{
this.dataList = this.original.filter(temp=> temp.includes(newVal))
},2000)
}
},
1.2、fetch&axios
1.2.0、fetch-get
handleFetch(){
//json文件路径,或数据来源
fetch("./json/test.json")
//返回json数据
.then(res=>res.json())
//输出json数据对象
.then(res=>{
console.log(res)
})
//如果是psot请求,返回err报错信息
.catch(err=>{
console.log(err)
})
}
1.2.1、fetch-post
handleFetch({
//路径
fetch("**")
//请求方式: post
method:'post',
//设置请求头
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
//form编码格式数据 key=value
body: "name=kerwin&age=100"
})
.then(res=>res.json())
.then(res=>{
log(res);
})
1.2.2、后端要求传入json格式
handleFetch({
//路径
fetch(url地址)
//请求方式: post
method:'post',
//设置请求头
headers: {
"Content-Type": "application/json"
},
//固定格式 json字符串编码
body: JSON.stringify({
name:"kerin",
age:100
})
})
.then(res=>res.json())
.then(res=>{
log(res);
})
1.2.3、fetch-猫眼数据案例
https://m.maizuo.com/v5/#/films/nowPlaying
- F12->Network->筛选Fetch/XHR接口->Name选项卡中的数据接口->Response中的代码复制在json文件中
<button @click="handleFetch">ajax-fetch</button>
<ul>
<li v-for="(data,index) in dataList" :key="data.filmId">
<img :src="data.poster" alt="">
{{data.name}}
主演:
<span v-for="item in data.actors ">{{item.name}} </span>
</li>
</ul>
new Vue({
el:"#box",
data:{
dataList:[],
},
methods:{
handleFetch(){
fetch("./json/maoyan.json")
.then(res=>res.json())
.then(res=>{
// console.log(res.data.films)
this.dataList=res.data.films
})
.catch(err=>{
console.log(err)
})
},
},
})
1.2.4、axios-get
-
axios数据第三方
-
要使用必须引入js文件
-
<div id="box">
<button @click="handleFetch">ajax-fetch</button>
<ul>
<li v-for="(data,index) in dataList" :key="data.filmId">
<img :src="data.poster" alt="">
{{data.name}}
主演:
<span v-for="item in data.actors ">{{item.name}} </span>
</li>
</ul>
</div>
new Vue({
el:"#box",
data:{
dataList:[],
},
methods:{
handleFetch(){
axios.get("./json/maoyan.json").then(res=>{
// console.log(res.data.data.films)
this.dataList=res.data.data.films
})
},
},
})
1.2.5、axios-post
- axios自动检测传入的是json对象还是字符串
- 从而选择传输方式
axios.post("****","name=Draven"&age="18")
axios.post("****",{name:"Draven",age:18})
1.2.6、猫眼数据–案例
json数据在1-沙漠风暴->03-fetch&axios->json->maizuo.json
- 因为访问的img路径是个接口
- 所以要进行处理使用
- 这里可以借鉴学习,后端拦截img访问请求
- 在前端进行宽高数条件处理后方可访问
<div id="box">
<button @click="handleAjax">click-ajax</button>
<ul>
<li v-for="item in dataList" :key="item.id">
<img :src="handleImg(item.img)" alt="">
{{item.nm}}
</li>
</ul>
</div>
new Vue({
el:"#box",
data:{
dataList:[]
},
methods:{
handleAjax(){
axios.get("./json/maizuo.json").then(res=>{
console.log(res.data.movieList)
this.dataList=res.data.movieList
})
},
handleImg(src){
// return src.replace("w.h/","")+"@1l_1e_1c_128w_128h"
return src.replace("w.h","128.128")
},
},
})
过滤器
- Vue3.0 不可以使用
<img :src="item.img | imgFilter" alt="">
//👆--------------------------------------------👇
//Vue 过滤器
Vue.filter("imgFilter",(url)=>{
return url.replace("w.h","")+"@1l_1e_1c_128w_128h"
})
1.3、组件
- 为什么组件化
- 扩展HTML元素,封装可重用的代码
- 组件注册方式
- 全局组件
- 局部组件
1.3.1、组件
1.1、全局与局部
- 起名字: js驼峰 , html 链接符-
- dom片段·没有代码提示· 没有高亮显示·-· vue单文件组件解决
- css只能写成行内。- vue单文件组件解决
- template包含一个根节点
- 组件是孤岛,无法【直接】访问外面的组件的状态或者方法。-间接的组件交流。
- 自定义的组件data必须是一个函数,
- 所有的组件都在一起,太乱了 — vue单文件组件解决
方法 | 说明 |
---|---|
Vue.component(“navber”,{}) | 定义一个全局标签 |
template:‘
aaaa
’ | 固定的 |
methods:{} | 方法 |
computed:{} | 计算属性 |
watch:{} | 请求 |
data(){return{}} | 数据 |
<div id="box">
<navber></navber>
</div>
//👆-------------------------------------------👇
//定义一个全局组件
Vue.component("navber",{
//dom ,js css
template:'<div>' +
'<button @click="handleLeft">left</button>' +'{{myName}}'+ '<button @click="handleRight">right</button>' +
'</div>',
methods: {
handleLeft(){
console.log("left")
},
handleRight(){
console.log("right")
}
},
computed:{},
watch:{},
//data在组件中必须是函数写法
data(){
return{
myName:"爱能克服遥远距离",
}
}
})
//根组件
new Vue({
el:"#box",
})
局部写法
方法 | 说明 |
---|---|
components:{“draven-Child”:{template:‘
’}}
| 子组件 |
<div id="box">
<navber></navber>
<child></child>
</div>
//👆-------------------------------------------👇
//定义一个全局组件
Vue.component("navber",{
//dom ,js css
template:'<section>' +
'<button>left</button>' +'{{myName}}'+ '<button>right</button>' +
'<child></child>'+
'<draven-Child></draven-Child>'
+ '</section>',
//局部,内部组件
components:{
"draven-Child":{
template:'<div>{{myName}}</div>',
data(){
return{
myName:"戒了烟我怎么办"
}
}
},
},
})
//全局
Vue.component("child",{
template:'<div style="background-color: red">"回忆化不开"</div>'
})
new Vue({
el:"#box",
})
1.2、组件父传子
- 函数调用两次
- 两次行为不一样
方法 | 说明 |
---|---|
props:{} | 接受属性 |
<div id="box">
<div style="background-color: yellow">根组件标题</div>
<navber my-Name="电影" :my-Right="false"></navber>
<navber my-Name="影院"></navber>
</div>
//👆-------------------------------------------👇
Vue.component("navber",{
// props:["my-Name","my-Right"], //接受myName属性, this.myName
/*props:{
"my-Name":String,
"my-Right":Boolean,
},//接受myName属性, 属性验证*/
props: {
"my-Name":{
type:String,
default:""
},
"my-Right": {
type:Boolean,
default:true
},
},//接受myName属性, 属性验证,默认属性
template:'<div>' +
'<button>left</button>' +
'<span>{{myName}}</span>' +
'<button v-show="myRight">right</button>' +
'</div>'
})
new Vue({
el:"#box",
})//创建根组件
属性验证&默认属性
方法 | 说明 |
---|---|
props:{} | 接受属性 |
props: {
"my-Name":{
type:String,
default:""
},
"my-Right": {
type:Boolean,
default:true
},
},//接受myName属性, 属性验证,默认属性
1.3、组件子传父
方法 | 说明 |
---|---|
this.$emit(“myevent”) | 激活子组件事件 |
-
步骤:
- 第一步, 在需要点击操作的按钮标签中 添加handleClick事件
- 第二步, 在子组件中实现handleClick方法 并使用this.$emit(“myevent”)激活
- 第三步, 在父组件中的子组件标签中 加入@myevent=“handleEvent”
- 第四步, 在父组件中实现handleEvent方法 方法区域的内容是点击后效果的具体实现
-
父传子 - 属性
-
子传父 - 事件
<div id="box">
<navbar @myevent="handleEvent"></navbar>
<sidebar v-show="isShow"></sidebar>
</div>
//👆-------------------------------------------👇
Vue.component("sidebar",{
template:' <div style="background-color: #EF4A82" >\n' +
' <ul>\n' +
' <li>11111</li>\n' +
' <li>11111</li>\n' +
' <li>11111</li>\n' +
' <li>11111</li>\n' +
' <li>11111</li>\n' +
' <li>11111</li>\n' +
' </ul>\n' +
' </div>'
})
Vue.component("navbar",{
template:'' +
' <div style="background-color: red">\n' +
' <button @click="handleClick()">点击</button>-导航栏\n' +
' </div>'
,methods:{
handleClick(){
// console.log("子传父,告诉父组件,取反isShow的值")
// this.$emit("myevent",11)
this.$emit("myevent")
}
}
})
new Vue({
el:"#box",
data:{
isShow: true
},
methods:{
handleEvent(){
console.log("父组件定义的事情")
this.isShow= !this.isShow
}
}
})
1.4、中间人模式
- 中间人是 父
- 子传父
- 父传子
<div id="box">
<button @click="handleAjax">ajax</button>
<film-item v-for="item in dataList" :key="item.filmId" :mydata="item" @myevent="handleEvent"></film-item>
<!--{{filmData}}-->
<film-datail :film-data="filmData"></film-datail>
</div>
//👆-------------------------------------------👇
//父传子
Vue.component("filmDatail",{
props:["filmData"],
template:'' +
'<div class="filminfo">' +
'{{filmData}}' +
'</div>'
})
//子传父
Vue.component("filmItem",{
props:["mydata"],
template:'' +
'<div class="item">' +
'<img :src="mydata.poster" alt="">' +
'{{mydata.name}}' +
'<div>' +
'<button @click="handleClick()">详情</button>' +
'</div>' +
'</div>',
methods:{
handleClick(){
// console.log(this.mydata.synopsis)
this.$emit("myevent",this.mydata.synopsis)
}
}
})
new Vue({
el:"#box",
data:{
dataList:[],
filmData:"",
},
methods:{
handleAjax(){
axios.get("./json/maoyan.json").then(res=>{
console.log(res.data.data.films)
this.dataList = res.data.data.films
})
},
//自定义的事件处理器
handleEvent(data){
console.log("父组件定义",data)
this.filmData = data
}
}
})
1.5、中央事件总线
- bus 中央时间总线 订阅发布模式
- vuex 状态管理
方法 | 说明 |
---|---|
bus.$on | 监听 |
bus.$emit | 发布 |
先发布 | 后监听 |
<div id="box">
<button @click="handleAjax">ajax</button>
<film-item v-for="item in dataList" :key="item.filmId" :mydata="item"></film-item>
<!--{{filmData}}-->
<film-datail></film-datail>
</div>
//👆-------------------------------------------👇
var bus = new Vue()
//bus.$on 监听
//bus.$emit 发布
//子传父
Vue.component("filmItem", {
props: ["mydata"],
template: '' +
'<div class="item">' +
'<img :src="mydata.poster" alt="">' +
'{{mydata.name}}' +
'<div>' +
'<button @click="handleClick">详情</button>' +
'</div>' +
'</div>',
methods: {
handleClick() {
console.log(this.mydata.synopsis)
bus.$emit("yqy", this.mydata.synopsis)
}
}
})
//父传子
Vue.component("filmDatail", {
//组件刚刚创建好 就开始订阅
data(){
return{
info:""
}
},
//生命周期
mounted(){
// console.log("当前组件上树后触发")
bus.$on("yqy", (data) => {
console.log(11, data)
this.info = data
})
},
template: '' +
'<div class="filminfo">' +
'{{info}}' +
'</div>'
})
new Vue({
el: "#box",
data: {
dataList: [],
},
methods: {
handleAjax() {
axios.get("./json/maoyan.json").then(res => {
console.log(res.data.data.films)
this.dataList = res.data.data.films
})
},
}
})
1.6、组件refs
-
refs容易出问题
-
因为可以随意修改子组件的状态值
-
this.$refs. this.(属性)
<div id="box">
<input type="text" ref="mytext">
<input type="password" ref="mypassword">
<button @click="handleAdd">add</button>
<child ref="myChild"></child>
</div>
//👆-------------------------------------------👇
Vue.component("child",{
data(){
return{
myname:'child-11'
}
},
template:'' +
'<div>' +
'child---{{myname}}' +
'</div>' +
'',
})
new Vue({
el:"#box",
methods:{
handleAdd(){
// console.log(this.$refs.mytext,this.$refs.mypassword)
// console.log(this.$refs.myChild.myname)
this.$refs.myChild.myname="child-22"
}
}
/*
* ref -绑定dom节点,拿到的就是 dom对象
* ref -绑定组件,拿到的就是 组件对象
* */
})
1.7、动态组件
标签 | 说明 |
---|---|
活着,用来保留数据 | |
动态组件 | |
Vue提供的组件标签 |
<div id="box">
<!--
<home v-show="which==='home'">1</home>
<list v-show="which==='list'">2</list>
<shopcar v-show="which==='shopcar'">3</shopcar>
-->
<keep-alive>
<component :is="which"></component>
</keep-alive>
<footer>
<ul>
<li @click="which='home'">首页</li>
<li @click="which='list'">列表</li>
<li @click="which='shopcar'">购物车</li>
</ul>
</footer>
</div>
//👆-------------------------------------------👇
Vue.component("home",{
template:
'<div>' +
'home' +
'<input type="search">' +
'</div>'
})
Vue.component("list",{
template:
'<div>' +
'list' +
'</div>'
})
Vue.component("shopcar",{
template:
'<div>' +
'shopcar' +
'</div>'
})
new Vue({
el:'#box',
data:{
which:"home"
}
})
1.3.2、slot
2.1、旧版slot
标签 | 说明 |
---|---|
插槽,在组件dom中加入 | |
属性 | |
slot=“right” | 使用插槽 |
-
插槽会根据模板dom中的slot属性来找组件dom中的插槽位置
-
模板dom
-
组件dom
-
人和房间的值是一样的,形成一人一间房,一个盒子对应一个插槽操作
-
插槽的意义:
- 扩展组件能力
- 提高组件的复用性
-
单个插槽
-
<slot></slot>
-
-
具有名字的插槽
-
<slot name="a"></slot>
-
<div id="box">
<!--当前组件或者节点 在那个模板中,就能访问那个模板状态-->
<child>
<div slot="a">111</div>
<div slot="b">222</div>
<div slot="c">333</div>
<div slot="a">444</div>
</child>
<navbar>
<button slot="left">aaa</button>
<i class="iconfont icon-all" slot="right">字体图标</i>
</navbar>
</div>
//👆-------------------------------------------👇
//插槽的意义: 扩展组件能力, 提高组件的复用性
Vue.component("navbar",{
template:
'<div>' +
'<slot name="left"></slot>' +
// '<button>left</button>' +
'<span>navbar</span>' +
// '<button>right</button>' +
'<slot name="right"></slot>' +
'</div>'
})
//单个插槽, <slot></slot>
//具有名字的插槽 <slot name="a"></slot>
Vue.component("child",{
template:
'<div>' +
'child' +
'<slot name="a"></slot>' +
'<slot name="b"></slot>' +
'<slot name="c"></slot>' +
'<slot></slot>' +
'</div>'
})
new Vue({
el:"#box",
})
2.2、新版slot
指令 | 说明 |
---|---|
代替 slot 属性 | |
<template #b> | 简写 |
<div id="box">
<!--当前组件或者节点 在那个模板中,就能访问那个模板状态-->
<child>
<template v-slot:a>
<div>111</div>
</template>
<template #b>
<div>222</div>
</template>
<div slot="c">333</div>
<div slot="a">444</div>
</child>
<navbar>
<template #left>
<button>aaa</button>
</template>
<template #right>
<i class="iconfont icon-all">字体图标</i>
</template>
</navbar>
</div>
//👆-------------------------------------------👇
//插槽的意义: 扩展组件能力, 提高组件的复用性
Vue.component("navbar",{
template:
'<div>' +
'<slot name="left"></slot>' +
// '<button>left</button>' +
'<span>navbar</span>' +
// '<button>right</button>' +
'<slot name="right"></slot>' +
'</div>'
})
//单个插槽, <slot></slot>
//具有名字的插槽 <slot name="a"></slot>
Vue.component("child",{
template:
'<div>' +
'child' +
'<slot name="a"></slot>' +
'<slot name="b"></slot>' +
'<slot name="c"></slot>' +
'<slot></slot>' +
'</div>'
})
new Vue({
el:"#box",
})
1.4、过度
1.4.1、过度效果
标签 | 说明 |
---|---|
固定过度标签 | |
属性 | 说明 |
enter-active-class=“” | 动画开始时切换的class |
leave-active-class=“” | 动画结束时切换的class |
name=“yqy” | 根据calss名动态查找 |
yqy-enter-active | 查找后根据enter和leave自动选择calss |
yqy-leave-active | |
appear | 出现,当进入页面的时候自动执行出现动画 |
<style>
/* 进场动画 */
.yqy-enter-active {
animation: aaa 1.5s;
}
/* 出场动画 */
.yqy-leave-active {
animation: aaa 1.5s reverse;
}
@keyframes aaa {
0% {
opacity: 0;
transform: translateX(100px);
}
100% {
opacity: 1;
transform: translateX(0px);
}
}
</style>
//👆-------------------------------------------👇
<div id="box">
<button @click="isShow = !isShow">change</button>
<transition enter-active-class="yqy-enter-active" leave-active-class="yqy-leave-active">
<div v-show="isShow">1111</div>
</transition>
<transition name="yqy">
<div v-show="isShow">2222</div>
</transition>
</div>
//👆-------------------------------------------👇new Vue({
el:"#box",
data:{
isShow:false
}
})
new Vue({
el:"#box",
data:{
isShow:false
}
})
1.4.2、多个元素过度
-
Key值一样的会去对比
-
如果标签一样
- 为了性能,选择复用
- 只会修改里面的内容
-
如果标签不一样,
- 即便设置了key值,也会自动干掉一个
- 会根据v-if v-else 删除创建dom.
-
标签一样,key值不一样
- 就是为了不让相同的标签复用
- 实现 标签不一样 时的效果
<style>
/* 进场动画 */
.yqy-enter-active {
animation: aaa 1.5s;
}
/* 出场动画 */
.yqy-leave-active {
animation: aaa 1.5s reverse;
}
@keyframes aaa {
0% {
opacity: 0;
transform: translateX(100px);
}
100% {
opacity: 1;
transform: translateX(0px);
}
}
</style>
//👆-------------------------------------------👇
<!-- <div class="kerwin-enter-active">333333333333333</div> -->
<div id="box">
<button @click="isShow = !isShow">change</button>
<transition name="yqy">
<div v-if="isShow" key="1">11</div>
<div v-else key="2">22</div>
</transition>
</div>
//👆-------------------------------------------👇
<script type="text/javascript">
var vm = new Vue({
el: "#box",
data: {
isShow: true
}
})
/*
diff算法,
1. 同层级对比
2. 同标签, 组件 对比
3. 同key对比
*/
</script>
1.4.3、多个组件过度
<div id="box">
<!--<keep-alive>
<component :is="which"></component>
</keep-alive>-->
<keep-alive>
<transition name="yqy" mode="out-in">
<component :is="which"></component>
</transition>
</keep-alive>
<footer>
<ul>
<li @click="which='home'">首页</li>
<li @click="which='list'">列表</li>
<li @click="which='shopcar'">购物车</li>
</ul>
</footer>
</div>
//👆-------------------------------------------👇
Vue.component("home", {
template:
'<div>' +
'home' +
'<input type="search">' +
'</div>'
})
Vue.component("list", {
template:
'<div>' +
'list' +
'</div>'
})
Vue.component("shopcar", {
template:
'<div>' +
'shopcar' +
'</div>'
})
new Vue({
el: '#box',
data: {
which: "home"
}
})
1.4.4、多个列表过度
标签 | 说明 |
---|---|
组件中包含多个要执行动画的标签 | |
:key=“item.id” | 使用key值的id做比较 |
提供唯一的key值属性 |
<transition-group name="yqy">
<li v-for="(item,index) in dataList" :key="item">
{{item}}
<button @click="list_delData(index)">del</button>
</li>
</transition-group>
//👆-------------------------------------------👇
new Vue({
el: "#box",
data: {
dataList: ["11", "22", "33"],
myText: "",
},
methods: {
list_addData() {
if (this.myText !== "") {
// console.log("获取value",this.myText)
this.dataList.push(this.myText);
//清空
this.myText = "";
} else {
alert("请勿为空");
}
},
list_delData(index) {
// console.log("del",index)
this.dataList.splice(index, 1);
},
}
})
1.4.5、可复用过度
<style>
.left{
}
.right{
position: fixed;
right: 0px;
top: 0px;
}
/* 进场动画 */
.left-enter-active {
animation: aaa 1.5s;
}
/* 出场动画 */
.left-leave-active {
animation: aaa 1.5s reverse;
}
@keyframes aaa {
0% {
opacity: 0;
transform: translateX(-100%);
}
100% {
opacity: 1;
transform: translateX(0px);
}
}
/* 进场动画 */
.right-enter-active {
animation: aaa 1.5s;
}
/* 出场动画 */
.right-leave-active {
animation: aaa 1.5s reverse;
}
@keyframes aaa {
0% {
opacity: 0;
transform: translateX(100px);
}
100% {
opacity: 1;
transform: translateX(0px);
}
}
</style>
//👆-------------------------------------------👇
<div id="box">
<navbar @myevent="handleEvent"></navbar>
<!-- <transition name="yqy">-->
<sidebar v-show="isShow" mode="left"></sidebar>
<!-- </transition>-->
</div>
//👆-------------------------------------------👇
Vue.component("navbar",{
template:`
<div>
nabbar-
<button @click="handleClick">click</button>
</div>
`,
methods:{
handleClick(){
// 通知父组件 取反 isShow - 子传父 依靠 事件
this.$emit("myevent")
}
}
})
Vue.component("sidebar",{
props:["mode"],
template:`
<transition :name="mode">
<ul style="background-color: yellow;width: 200px;height: 500px;" :class="mode">
<li>首页</li>
<li>钱包</li>
<li>设置</li>
</ul>
</transition>
`
})
new Vue({
el:"#box",
data:{
isShow:false
},
methods:{
handleEvent(){
console.log("父组件","1111111")
this.isShow = !this.isShow
}
}
})
1.5、生命周期
1.5.1、组件生命周期
1.1、创建阶段
方法 | 说明 |
---|---|
beforeCreate() | 创建前 |
created() | 初始化 重要 |
beforeMount() | 载入前 |
模板解析前,最后一次修改模板的节点 | |
mounted () | 载入后 |
依赖于dom创建之后,才进行初始化工作的插件 (轮播插件) | 创建完dom后的初始化 重要 |
订阅 bus.$on | 初始化ajax数据 |
<div id="box">
{{name}}
{{globalName}}
</div>
//👆-------------------------------------------👇
//根组件
new Vue({
// el:"#box",
data:{
name:"baby"
},
beforeCreate(){
console.log("beforeCreate",this.name)
},
/*初始化 *-重要*/
created(){
console.log("created","初始化状态或者挂载到当前实例的一些属性工作")
this.name+="1111" //被拦截的状态
//this下面的值
this.user=localStorage.getItem("user")
this.globalName = "this可以直接访问的属性值,"
},
beforeMount(){
console.log(document.getElementById("box").innerHTML)
//模板解析之前最后一次修改模板的节点
console.log(this.$el)
},
/*创建完dom后的初始化 *重要*/
mounted(){
console.log("拿到真实的dom节点",document.getElementById("box").innerHTML)
//依赖于dom创建之后,才进行初始化工作的插件 (轮播插件)
//订阅 bus.$on
//ajax
}
}).$mount("#box")
1.2、更新阶段
方法 | 说明 |
---|---|
beforeUpdate() | 更新前 |
updated() | 更新后 |
//更新阶段---------------
beforeUpdate(){
console.log("beforeUpdate","更新之前,记录老的mon状态,比如滚动条位置的记录")
},
updated(){
console.log("updated","更新完成,获取更新后的dom节点,才进行swiper工作的插件")
console.log(document.getElementsByTagName("li"))
}
1.5.2、销毁
方法 | 说明 |
---|---|
beforeDestroy() | 销毁前 |
destroyed() | 销毁后 |
Vue.component("child",{
data(){
return{
time:1000
}
},
mounted(){
this.id = setInterval(()=>{
console.log("姚小煜")
this.time--;
},1000)
},
template:
'<div>' +
'抢购倒计时组件' +
'<div>{{time}}</div>' +
'</div>',
created(){
this.id = null;
},
beforeDestroy(){
console.log("beforeDestroy","清除定时器,时间解绑,,,,")
clearInterval(this.id)
},
destroy(){
console.log("destroy","清除定时器,时间解绑,,,,")
}
})
var vm = new Vue({
el:"#box",
data:{
isCreated:true
}
})
1.6、Swiper
1.6.1、Swiper静态
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="lib/swiper/js/swiper.js"></script>
<link rel="stylesheet" href="lib/swiper/css/swiper.css">
<style>
.draven {
height: 500px;
}
</style>
</head>
<body>
<header>导航</header>
<div class="swiper draven">
<div class="swiper-wrapper">
<div class="swiper-slide">11111111</div>
<div class="swiper-slide">22222222</div>
<div class="swiper-slide">33333333</div>
</div>
<!-- 如果需要分页器 -->
<div class="swiper-pagination"></div>
<!-- 如果需要导航按钮 -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
<!-- <!– 如果需要滚动条 –>-->
<!-- <div class="swiper-scrollbar"></div>-->
</div>
<footer>底部内容</footer>
//👆-------------------------------------------👇
<script>
//初始化 swiper
new Swiper(".draven", {
direction: "vertical", //垂直
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
},
loop: true, // 循环
//自动
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
})
</script>
</body>
</html>
1.6.2、Swiper动态
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="lib/swiper/js/swiper.js"></script>
<link rel="stylesheet" href="lib/swiper/css/swiper.css">
<style>
.draven {
height: 500px;
}
</style>
</head>
<body>
<header>导航</header>
<div class="swiper draven">
<div class="swiper-wrapper">
</div>
<!-- 如果需要分页器 -->
<div class="swiper-pagination"></div>
<!-- 如果需要导航按钮 -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
<!-- <!– 如果需要滚动条 –>-->
<!-- <div class="swiper-scrollbar"></div>-->
</div>
<footer>底部内容</footer>
//👆-------------------------------------------👇
<script>
//swiper 初始化过早
setTimeout(()=>{
var list = ["cccc","dddd","eeee"]
var newlist = list.map(item=>`<div class="swiper-slide">${item}</div>`)
console.log(newlist)
var oweapper = document.querySelector(".swiper-wrapper")
oweapper.innerHTML = newlist.join("")
//dom插入完了之后,再new swiper
init()
},2000)
//初始化 swiper
function init(){
new Swiper(".draven", {
direction: "vertical", //垂直
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
},
loop: true, // 循环
//自动
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
})
}
</script>
</body>
</html>
1.6.3、vue-swiper
- 无法复用
- 如果当前页面 状态不止datalist一个,其他状态更新, update重新运行,new Swiper 执行多次, 出bug
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="lib/swiper/js/swiper.js"></script>
<link rel="stylesheet" href="lib/swiper/css/swiper.css">
<script src="lib/vue.js"></script>
<style>
</style>
<style>
*{
margin: 0px;
padding: 0px;
}
#box{
width: 1519px;
height: 630px;
overflow: hidden;
}
</style>
</head>
<body>
<div id="box">
<header>导航-{{myname}}</header>
<div class="swiper draven">
<div class="swiper-wrapper">
<div class="swiper-slide" v-for="(item,index) in datalist" :key="index">
<img :src="item" alt="">
</div>
</div>
<!-- 如果需要分页器 -->
<div class="swiper-pagination"></div>
</div>
<footer>底部内容</footer>
</div>
//👆-------------------------------------------👇
<script>
//初始化 swiper --初始化过早
var vm = new Vue({
el: "#box",
data: {
datalist: [],
myname: "driven"
},
mounted() {
//ajax
setTimeout(() => {
this.datalist = [
"https://ossweb-img.qq.com/upload/adw/image/977/20221028/07b4fa4c953cc2e6efae2541389a1299.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221031/aae09131279f92db243a308c1c8bf9b8.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221101/adfde1ffdc3b53024ef7917e8f530839.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221023/8a9fc13d6622b84f936dd526f7002a93.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221028/3a799afc366751ccd940bc8ff47d8593.jpeg"
]
//过早
}, 2000)
},
updated(){
new Swiper(".draven", {
// direction: "vertical", //垂直
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
},
loop: true, // 循环
//自动
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
})
}
})
/* //初始化过早
new Swiper(".draven", {
// direction: "vertical", //垂直
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
},
loop: true, // 循环
//自动
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
})*/
/*
1. 无法复用
2. 如果当前页面 状态不止datalist一个,其他状态更新, update重新运行,new Swiper 执行多次, 出bug
*/
</script>
</body>
</html>
1.6.4、vue-swiper-组件 ※
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="lib/swiper/js/swiper.js"></script>
<link rel="stylesheet" href="lib/swiper/css/swiper.css">
<script src="lib/vue.js"></script>
<script src="./module/vueswiper.js"></script>
</head>
<style>
#box{
width: 820px;
height: 340px;
overflow: hidden;
margin: 0 auto;
background-color: #EF4A82;
}
</style>
<body>
<div id="box">
<swiper v-if="datalist.length" :loop="false">
<swiper-item v-for="(item,index) in datalist" :key="index">
<img :src="item" alt="">
</swiper-item>
</swiper>
<!-- <swiper :key="datalist.length"><swiper>-->
</div>
//👆-------------------------------------------👇
<script>
Vue.component("swiperItem",{
template:
`<div class="swiper-slide">
<slot></slot>
</div>`,
})
Vue.component("swiper", {
props:{
loop:{
type:Boolean,
default:true,
}
}
template: `
<div class="swiper draven">
<div class="swiper-wrapper">
<slot></slot>
</div>
<!-- 如果需要分页器 -->
<div class="swiper-pagination"></div>
<!-- 如果需要导航按钮 -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>`,
mounted(){
new Swiper(".draven", {
// direction: "vertical", //垂直
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
},
loop: true, // 循环
//自动
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
})
}
})
new Vue({
el: "#box",
data:{
datalist:[]
},
mounted() {
//ajax
setTimeout(() => {
this.datalist = [
"https://ossweb-img.qq.com/upload/adw/image/977/20221028/07b4fa4c953cc2e6efae2541389a1299.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221031/aae09131279f92db243a308c1c8bf9b8.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221101/adfde1ffdc3b53024ef7917e8f530839.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221023/8a9fc13d6622b84f936dd526f7002a93.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221028/3a799afc366751ccd940bc8ff47d8593.jpeg"
]
//过早
}, 2000)
}
})
</script>
</body>
</html>
1.7、Vue3
1.7.1、组件定义
- 先把主对象app new出来
- 使用app去调用需要使用的函数方法
- 最后通过
.mount("#box")
进行上树
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.next.js"></script>
</head>
<body>
<div id="box">
{{myname}}
<navbar myname="aaa">
<div>11</div>
</navbar>
</div>
//👆-------------------------------------------👇
<script>
var obj = {
data() {
return {
myname: "kzc"
}
},
methods() {
},
computed() {
}
}
let app = Vue.createApp(obj)
app.component("navbar", {
props: ["myname"],
template:
'<div>' +
'navbar-{{myname}}' +
'<slot></slot>' +
'</div>'
})
app.mount("#box")
</script>
</body>
</html>
1.7.2、生命周期
- vue3的生命周期中
beforeDestroy
替换成beforeUnmount
destroy
替换成unmounted
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.next.js"></script>
</head>
<body>
<div id="box">
{{myname}}
<navbar myname="aaa">
<div>11</div>
</navbar>
</div>
//👆-------------------------------------------👇
<script>
var obj = {
data() {
return {
myname: "kzc"
}
},
methods() {
},
beforeCreate() {
console.log("beforeCreate")
},
created(){
console.log("created")
},
beforeMount(){
console.log("beforeMount")
},
mounted(){
console.log("mounted")
},
beforeUnmount(){
console.log("beforeUnmount")
},
unmounted(){
console.log("unmounted")
}
/*生命周期替换
beforeDestroy(){
console.log("beforeDestroy")
},
destroy(){
console.log("destroy")
},*/
}
let app = Vue.createApp(obj)
app.component("navbar", {
props: ["myname"],
template:
'<div>' +
'navbar-{{myname}}' +
'<slot></slot>' +
'</div>'
})
app.mount("#box")
</script>
</body>
</html>
1.8、指令
1.8.1、自定义指令
- 指令:
- 为了操作底层dom 作者流的方案
- 实际应用:
- 可以通过指令知道什么时候dom创建完成,从而进行 依赖dom的库的初始化工作
- 指令的生命周期
方法 | 说明 |
---|---|
inserted(el,binding) | 第一次插入到父节点中触发 |
update(el,binding) | 每次更新的时候触发 |
Vue.directive(“hello”,{}) | 自定义组件.组件名"hello" |
<div id="box">
<div v-hello="'red'">11111111</div>
<div v-hello="'yellow'">22222222</div>
<div v-hello="whichColor">33333333</div>
</div>
//👆-------------------------------------------👇
<script type="text/javascript">
//指令: 为了操作底层dom 作者流的方案
//
Vue.directive("hello",{
//指令的生命周期函数
//第一次插入到父节点中触发
inserted(el,binding){
console.log("inserted",binding)
el.style.backgroundColor=binding.value
},
//每次更新的时候触发
update(el,binding){
console.log("update")
el.style.backgroundColor=binding.value
}
})
var vm = new Vue({
el:"#box",
data:{
whichColor:"blue"
}
})
</script>
1.8.2、指令轮播
- 指令轮播是一只解决方案
- 而并不是最优的使用方案
<div id="box">
<header>导航-{{myname}}</header>
<div class="swiper draven">
<div class="swiper-wrapper">
<div class="swiper-slide" v-for="(item,index) in datalist" :key="index" v-swiper="{
index:index,
length:datalist.length
}">
<img :src="item" alt="">
</div>
</div>
<!-- 如果需要分页器 -->
<div class="swiper-pagination"></div>
</div>
<footer>底部内容</footer>
</div>
//👆-------------------------------------------👇
<script>
Vue.directive("swiper",{
inserted(el,binding){
console.log(el,binding.value.index,binding.value.length)
console.log(el,binding.value)
//最后一个节点插入都到父节点了,再去new
let{length,index} = binding.value
if (index===length-1){
console.log("swiper")
new Swiper(".draven", {
// direction: "vertical", //垂直
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
},
//自动
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
})
}
},
})
//初始化 swiper --初始化过早
var vm = new Vue({
el: "#box",
data: {
datalist: [],
myname: "driven"
},
mounted() {
//ajax
setTimeout(() => {
this.datalist = [
"https://ossweb-img.qq.com/upload/adw/image/977/20221028/07b4fa4c953cc2e6efae2541389a1299.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221031/aae09131279f92db243a308c1c8bf9b8.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221101/adfde1ffdc3b53024ef7917e8f530839.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221023/8a9fc13d6622b84f936dd526f7002a93.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221028/3a799afc366751ccd940bc8ff47d8593.jpeg"
]
//过早
}, 2000)
},
})
</script>
1.8.3、指令函数简写
<div id="box">
<div v-hello="'red'">11111111</div>
<div v-hello="'yellow'">22222222</div>
<div v-hello="whichColor">33333333</div>
</div>
//👆-------------------------------------------👇
<script type="text/javascript">
//指令: 为了操作底层dom 作者流的方案
//
Vue.directive("hello",(el,binding)=>{
console.log("更新和创建都会执行")
el.style.backgroundColor=binding.value
})
var vm = new Vue({
el:"#box",
data:{
whichColor:"blue"
}
})
</script>
1.8.4、Vue3-指令轮播
<div id="box">
<header>导航-{{myname}}</header>
<div class="swiper draven">
<div class="swiper-wrapper">
<div class="swiper-slide" v-for="(item,index) in datalist" :key="index" v-swiper="{
index:index,
length:datalist.length
}">
<img :src="item" alt="">
</div>
</div>
<!-- 如果需要分页器 -->
<div class="swiper-pagination"></div>
</div>
<footer>底部内容</footer>
</div>
//👆-------------------------------------------👇
<script>
//初始化 swiper --初始化过早
let obj = {
data(){
return{
datalist:[]
}
},
mounted() {
//ajax
setTimeout(() => {
this.datalist = [
"https://ossweb-img.qq.com/upload/adw/image/977/20221028/07b4fa4c953cc2e6efae2541389a1299.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221031/aae09131279f92db243a308c1c8bf9b8.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221101/adfde1ffdc3b53024ef7917e8f530839.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221023/8a9fc13d6622b84f936dd526f7002a93.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221028/3a799afc366751ccd940bc8ff47d8593.jpeg"
]
//过早
}, 2000)
},
}
let app = Vue.createApp(obj);
app.directive("swiper",{
mounted(el,binding){
console.log(el,binding.value.index,binding.value.length)
console.log(el,binding.value)
//最后一个节点插入都到父节点了,再去new
let{length,index} = binding.value
if (index===length-1){
console.log("swiper")
new Swiper(".draven", {
// direction: "vertical", //垂直
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
},
//自动
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
})
}
},
})
app.mount("#box")
/*
1. 无法复用
2. 如果当前页面 状态不止datalist一个,其他状态更新, update重新运行,new Swiper 执行多次, 出bug
*/
/*
//vue3 指令生命周器 约== 组件生命周期
*/
</script>
1.8.5、轮播-nextTick
- 无法复用
<div id="box">
<header>导航-{{myname}}</header>
<div class="swiper draven">
<div class="swiper-wrapper">
<div class="swiper-slide" v-for="(item,index) in datalist" :key="index">
<img :src="item" alt="">
</div>
</div>
<!-- 如果需要分页器 -->
<div class="swiper-pagination"></div>
</div>
<footer>底部内容</footer>
</div>
//👆-------------------------------------------👇
<script>
//初始化 swiper --初始化过早
var vm = new Vue({
el: "#box",
data: {
datalist: [],
myname: "driven"
},
mounted() {
//ajax
setTimeout(() => {
this.datalist = [
"https://ossweb-img.qq.com/upload/adw/image/977/20221028/07b4fa4c953cc2e6efae2541389a1299.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221031/aae09131279f92db243a308c1c8bf9b8.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221101/adfde1ffdc3b53024ef7917e8f530839.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221023/8a9fc13d6622b84f936dd526f7002a93.jpeg",
"https://ossweb-img.qq.com/upload/adw/image/977/20221028/3a799afc366751ccd940bc8ff47d8593.jpeg"
]
//过早
this.$nextTick(()=>{
console.log("我比updated执行的都晚,而且只执行一次")
new Swiper(".draven", {
// direction: "vertical", //垂直
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
},
loop: true, // 循环
//自动
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
})
})
}, 2000)
},
/*updated(){
new Swiper(".draven", {
// direction: "vertical", //垂直
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
},
loop: true, // 循环
//自动
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
})
}*/
})
/* //初始化过早
new Swiper(".draven", {
// direction: "vertical", //垂直
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
},
loop: true, // 循环
//自动
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
})*/
/*
1. 无法复用
2. 如果当前页面 状态不止datalist一个,其他状态更新, update重新运行,new Swiper 执行多次, 出bug
*/
</script>
1.9、Vue-Cli
1.9.1、安装
选项 | 说明 |
---|---|
Bable | 可以把ES6转换为ES5代码(兼容性问题) 必选 |
Router | 路由必选 |
Vuex | Vue全家桶,状态管理必选 |
CSS Pre-processors | CSS预处理器 |
Linter / Formatter | 保证按照国际默认代码风格规范化必选 |
- 流程图
-
在需要创建项目的文件夹中右键–>打开Powershell窗口
-
输入vue --version查看vue -Cli版本
-
PS: 如果没有安装过Cli工具 输入一下命令进行安装
-
npm install -g @vue/cli
-
输入
vue create hello-world
来创建一个新的项目-
选择需要的版本
-
自带的两个版本为:
- 默认的Vue2
- 默认的Vue3
- 我们需要自定义,所以选择
Manually select features
-
使用空格和上下键来选择我们需要的组件工具
- 其中Bable、Router、Vuex、CSS Pro-processors、Linter / Formatter为必选
-
选择项目Vue版本
-
选择路由状态,此处按回车默认
-
选择dart-sass编译器(同比node编译器效率更高)
-
选择
ESLint + Standard config
版本,检查并修复代码错误的标准配置 -
每次保存自动Lint,每次提交后自动修复
-
这么多单独的东西,是对每一个都分出来一个配置文件还是将全部组建配置到
package
中的选择 -
是否为你的选择做出预设,以便下次使用时,多个为这些配置的选项
-
回车,等待安装完成
-
安装完成提示
-
文件夹中查看项目结构
-
项目结构解析
1.9.2、启动流程&入口文件
- 看项目结构中的
package.json
文件,scripts
下的第一个为启动项目-
npm run serve
自动启动本地服务器,来运行项目 – 开发阶段用的 -
npm run buld
把所有的代码压缩,合并. – 发布阶段用的 -
npm run lint
能帮我们检查出一百多个格式错误,进行修复,修复我们所写的所有错误的格式 -
当然,这三个名字都不是固定的,可以通过修改,来改变
npm run ""
的版本
- 启动,测试项目
-
在集成工具(WebStrome)中,进入集成终端
-
输入
npm run serve
启动项目-
在浏览器地址栏中输入
Local 或 Network:
下的地址可以访问服务器入口主页
-
项目执行周期
- 项目下
public
文件夹里面的index.html
文件是入口文件 index.html
文件中有个div id=app
的标签,是vue
的父组件- 父组件通过
src
目录下的main.js
文件创建 - 组件具体代码位置在
App.vue
中实现
- 项目下
-
1.9.3、eslint修复
-
npm run link
修复 -
下载
ESLint
插件 -
配置插件
-
暂时关闭代码检测
.eslintrc.js
文件中注释以下代码-
以后再想用可以取消注释,然后勾选
ESLint
设置中的Run....
或终端执行npm run lint
1.9.4、单文件组件
属性 | 说明 |
---|---|
scoped | 局部作用域 |
<style lang=“scss” scoped |
-
父组件
-
<template> <div> hello {{myname}} <img src="/y5.jpg" alt=""> <input type="text" v-model="mytext"> <button @click="handleClick">add</button> <ul> <li v-for="(item,index) in datalist" :key="index"> {{item}} </li> </ul> <!--子组件--> <navbar myname="home" :myright="false" @myevent="handleEvent"> <div>插槽 壹壹</div> </navbar> <sidebar v-show="isShow"></sidebar> <!--组件--> <div v-hello>我是组件1</div> <div v-hello>我是组件2</div> <!--过滤器--> <img :src="imgUrl | imgFilter" alt=""> </div> </template> <script> import navbar from "@/components/Navbar"; import sidebar from "@/components/Sidebar"; import Vue from "vue"; // 全局注册 // Vue.component("navbar",navbar) Vue.directive("hello",{ inserted(el,binding){ console.log(el) el.style.border="1px solid red" console.log(binding) } }) Vue.filter("imgFilter",(path)=>{ return "/y5.jpg" }) export default { data(){ return{ myname:"杨清壹", datalist:["田洪源","孔智超","杨清壹"], mytext:"", isShow: true, imgUrl: "https://ossweb-img.qq.com/upload/adw/image/977/20221028/07b4fa4c953cc2e6efae2541389a1299.jpeg" } }, // 局部注册 components:{ navbar, sidebar }, methods:{ handleClick(){ this.datalist.push(this.mytext); this.mytext=""; }, handleEvent(){ this.isShow= !this.isShow }, }, computed:{ }, watch:{ } } </script> <style lang="scss"> $width:200px; ul{ li{ background: pink; width: $width; } } img{ width: 108px; height: 170px; } </style>
-
-
子组件 navbar
-
<template> <div> <button @click="handleLeft">left</button> {{ myname }} <button v-show="myright">right</button> <slot></slot> </div> </template> <script> export default { props:{ "myname":{ type:String, default:"杨清壹" }, "myright":{ type:Boolean, default: true } }, methods:{ handleLeft(){ this.$emit("myevent") } } } </script> <style scoped> </style>
-
-
子组件 Sidebar
-
<template> <ul> <li v-for="(item) in datalist" :key="item.filmId"> {{ item.name }} </li> </ul> </template> <script> import axios from 'axios' export default { data(){ return{ datalist:[] } }, mounted() { /*fetch("/test.json").then(res=>res.json()) .then(res=>{ console.log(res.data.films) this.datalist=res.data.films })*/ axios.get('/test.json').then(res=>{ console.log(res.data.data.films) this.datalist=res.data.data.films }) }, } </script> <!--scoped 局部作用域--> <style lang="scss" scoped> $width: 200px; ul { li { background: greenyellow; width: $width; } } </style>
-
1.9.5、WebStorm的Vue模板
-
**File—Setting—Editor—File and Code Templates **
-
点击添加,依次输入 Name 和 Extension,OK 即可。
-
<!-- @author 玫瑰到了花期 @data ${DATE} ${TIME} @love 又偷偷看你了 @V: CBWR-K --> <template> <div class=""> </div> </template> <script> // 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等) // 例如:import 《组件名称》 from '《组件路径》'; // 例如:import uploadFile from '@/components/uploadFile/uploadFile' export default { name: '${COMPONENT_NAME}', // import引入的组件需要注入到对象中才能使用 components: {}, props: { // patientIn: { // type: Object, // default () { // return {} // } // } }, data () { // 这里存放数据 return { } }, // 监听属性 类似于data概念 computed: {}, // 方法集合 methods: {}, // 监控data中的数据变化 watch: {}, // 生命周期 - 创建完成(可以访问当前this实例) created () { }, // 生命周期 - 挂载完成(可以访问DOM元素) mounted () { }, beforeCreate () { }, // 生命周期 - 创建之前 beforeMount () { }, // 生命周期 - 挂载之前 beforeUpdate () { }, // 生命周期 - 更新之前 updated () { }, // 生命周期 - 更新之后 beforeDestroy () { }, // 生命周期 - 销毁之前 destroyed () { }, // 生命周期 - 销毁完成 activated () { } // 如果页面有keep-alive缓存功能,这个函数会触发 } </script> <style scoped lang="scss"> //@import url(); 引入公共css类 </style>
1.9.6、反向代理
-
反向代理前
-
mounted() { axios.get('https://api.bilibili.com/x/web-interface/nav').then(res=>{ console.log(res.data) }) }
-
-
反向代理后
-
App.vue
-
mounted() { axios.get('/draven/x/web-interface/nav').then(res=>{ console.log(res.data) }) }
-
vue.config.js
-
// 配置反向代理 devServer: { proxy: { /* '/ajax':{ target:'https://api.bilibili.com', changeOrigin:true, }*/ '/draven':{ target: 'https://api.bilibili.com/', //替换 changeOrigin:true, //路径重启 pathRewrite:{ '^/draven':'' } } } }
-
spa&路由引入
SPA概念
-
单页面应用 多页面应用 组成 一个外壳页面和多个页面片段组成 多个完整页面构成 资源共用(css,js) 共用,只需在外壳部分加载 不共用,每个页面都需要加载 刷新方式 页面局部刷新或更改 整页刷新 url模式 a.com/#/pageone
a.com/#/pagetwoa.com/pageone.html
a.com/pagetwo.html用户体验 正面片段间的切换快,用户体验好 页面切换加载缓慢,流畅度不够 数据传递 容易 依赖url传参、或者cookie,localStorage等 搜索引擎优化(SEO) 需要单独方案、实现较为困难、不利于SEO检索、可利用服务器端渲染(SSR)优化 实现方法简易 试用范围 高要求的体验度、追求界面流畅的应用 适用于追求高度支持搜索引擎的应用 开发成本 较高,需要借助专业的框架 较低,但页面重复代码多 维护成本 相对容易 相对复杂
vue-router
开始
标签 | 说明 |
---|---|
全局路由组件 路由导航 | |
to=“/films” | 跳转到指定组件 |
active-class=“draven” | 自定义class样式 |
tag=“li” | 表示此组件会渲染成Li标签 |
路由容器,放在最后(所有路由导航后面) |
-
路由所在的文件夹 –
router->index.js
-
**
components
**文件夹属于公共组件 -
**
views
**文件夹属于主视图组件 -
在
views
文件夹中有cinemas,center,films
三个组件 -
import Vue from 'vue' // 引入路由 import VueRouter from 'vue-router' // 引入组件 import Films from "@/views/Films"; import Center from "@/views/Center"; import Cinemas from "@/views/Cinemas"; Vue.use(VueRouter) // 注册路由插件 两个全局组件 router-view router-link // 配置表 const routes = [ { path:"/films", component:Films, }, { path:"/center", component:Center, }, { path:"/cinemas", component:Cinemas, }, ] const router = new VueRouter({ routes }) export default router
-
在
App.vue
父组件中使用路由容器-
<!--路由容器--> <router-view></router-view>
-
-
在浏览器地址栏中输入配置表(routes)的path进行访问
重定向
-
在输入
localhost:8080
不指定路由的情况下,让其默认进入主路由组件-
在配置表中加入如下代码
-
// 重定向 { path: "/", redirect:'/films' }
-
这样在地址栏中输入localhost:8080就会自动重定向与films组件
-
-
在输入
localhost:8080:
后指定的路由地址为错的时候,让其默认进入主路由组件-
在配置表中修改路径后如下代码
-
// 重定向 { path: "*", redirect:'/films' }
-
-
点击按钮使用路由切换页面组件
-
<template> <div> <ul> <li> <a href="/#/films">电影</a> </li> <li> <a href="/#/cinemas">影院</a> </li> <li> <a href="/#/films">我的</a> </li> </ul> hello 小杨 <!--路由容器--> <router-view></router-view> </div> </template>
-
声明式导航
<template>
<div>
<ul>
<!--vue router(vue4版本之前) -1- 声明式导航-->
<!--<router-link to="/films" active-class="draven" tag="li">电影</router-link>
<router-link to="/cinemas" active-class="draven" tag="li">影院</router-link>
<router-link to="/center" active-class="draven" tag="li">我的</router-link>-->
<!--vue router(vue4版本) -2- 声明式导航-->
<router-link to="/films" custom v-slot="{navigate,isActive}">
<li @click="navigate" :class="isActive? 'draven':''">电影 -- {{isActive}}</li>
</router-link>
<router-link to="/cinemas" custom v-slot="{navigate,isActive}">
<li @click="navigate" :class="isActive? 'draven':''">影院 -- {{isActive}}</li>
</router-link>
<router-link to="/center" custom v-slot="{navigate,isActive}">
<li @click="navigate" :class="isActive? 'draven':''">我的 -- {{isActive}}</li>
</router-link>
</ul>
hello 小杨
</div>
</template>
嵌套路由
-
在多个组件之间来回横跳
-
项目结构:
-
App.vue
-
import Vue from 'vue' // 引入路由 import VueRouter from 'vue-router' // 引入组件 import Films from "@/views/Films"; import Center from "@/views/Center"; import Cinemas from "@/views/Cinemas"; import Comingsoon from "@/views/films/Comingsoon"; import Nowplaying from "@/views/films/Nowplaying"; import Search from "@/views/Search"; Vue.use(VueRouter) // 注册路由插件 两个全局组件 router-view router-link // 配置表 const routes = [ { path:"/films", component:Films, // 嵌套路由 children:[ { path:"/films/nowplaying", component:Nowplaying, }, { path:"/films/comingsoon", component:Comingsoon, }, //重定向 在进入films过程中,自动进入nowplaying子组件 { path: "/films", redirect: "/films/nowplaying" }, ] }, { path: "/cinemas/search", component: Search }, { path:"/center", component:Center, }, { path:"/cinemas", component:Cinemas, }, // 重定向 { path: "*", redirect:'/films' }, ] const router = new VueRouter({ routes }) export default router
-
films.vue
-
<template> <div class=""> films <div style="height: 200px;background: yellow">大轮播</div> <div> 二级的声明式导航 </div> <router-view></router-view> </div> </template>
编程式导航
-
Nowplaying.vue
films下级目录,模仿列表 -
<!-- @author 玫瑰到了花期 @data 2022/11/9 13:54 @love 又偷偷看你了 @V: CBWR-K --> <template> <div class=""> nowplaying <!--列表跳详情--> <ul> <li v-for="(item,index) in datalist" :key="index" @click="handleChangePage()"> {{item}} </li> </ul> </div> </template> <script> // 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等) // 例如:import 《组件名称》 from '《组件路径》'; // 例如:import uploadFile from '@/components/uploadFile/uploadFile' export default { name: 'Nowplaying', // import引入的组件需要注入到对象中才能使用 components: {}, props: { // patientIn: { // type: Object, // default () { // return {} // } // } }, data() { // 这里存放数据 return { datalist:['1111','2222','3333'] } }, // 监听属性 类似于data概念 computed: {}, // 方法集合 methods: { handleChangePage(){ // 编程式导航 // location.href="#/detail" this.$router.push('/detail') }, }, </script> <style scoped lang="scss"> //@import url(); 引入公共css类 </style>
动态路由
-
第一步:
url
跳转-
nowplaying
-
<div class=""> nowplaying <!--列表跳详情--> <ul> <li v-for="(item,index) in datalist" :key="index" @click="handleChangePage(item.filmId)"> {{item}} </li> </ul> </div>
-
-
第二步: 发送请求
-
nowplaying
-
methods: { handleChangePage(id){ console.log(id) // 编程式导航 // location.href="#/detail" // this.$router.push('/detail/'+id) this.$router.push(`/detail/${id}`) }, },
-
router -> index.js
-
{ path: "/detail/:id", // 动态二级路由 component: Detail },
-
-
第三步: 获取
id
-
detail.vue
-
// 生命周期 - 创建完成(可以访问当前this实例) created() { // 当前匹配的路由 console.log('created',this.$route.params.id) // axios 利用id 发请求到详情接口 , 获取详情数据 , 布局页面 },
-
-
第四步: 发送请求
-
第五步: 布局页面
命名路由
//1-通过路径跳转
// this.$router.push('/detail/'+id)
// this.$router.push(`/detail/${id}`)
//2-通过命名路由跳转
this.$router.push({
name: "dravenDetail",
params: {
id
}
})
//👆-------------------------------------------👇
created() {
// 当前匹配的路由
console.log('created',this.$route.params.id)
// axios 利用id 发请求到详情接口 , 获取详情数据 , 布局页面
},
路由模式
const router = new VueRouter({
mode:"history",
routes
})
- 由于我们的应用是一个单页的客户端应用,如果没有适当的服务器配置,用户在浏览器中直接访问
https://example.com/user/id
,就会得到一个 404 错误。这就尴尬了。 - 要解决这个问题,你需要做的就是在你的服务器上添加一个简单的回退路由。如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的
index.html
相同的页面。漂亮依旧!
// 打包工具 生产环境构建
npm run build
-
hash模式,其原理是onhashchange事件,可以在window对象上监听这个事件
- #后面 hash 值的变化,并不会导致浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面。
- 每次 hash 值的变化,会触发
hashchange
这个事件,通过这个事件我们就可以知道 hash 值发生了哪些变化。 - 然后我们便可以监听
hashchange
来实现更新页面部分内容的操作:
-
history模式,可利用“history.pushState”的API来完成URL跳转
- 因为HTML5标准发布,多了两个 API,
pushState()
和replaceState()。
- 通过这两个 API
- 可以改变 url 地址且不会发送请求
- 不仅可以读取历史记录栈,还可以对浏览器历史记录栈进行修改
- 因为HTML5标准发布,多了两个 API,
-
当你选择mode类型之后,程序会根据你选择的mode 类型创建不同的history对象(hash:HashHistory 或 history:HTML5History 或 abstract:AbstractHistory)
-
区别:
- 前面的hashchange,你只能改变#后面的url片段。而pushState设置的新URL可以是与当前URL同源的任意URL。
- history模式则会将URL修改得就和正常请求后端的URL一样,如后端没有配置对应/user/id的路由处理,则会返回404错误
-
有限的选项卡:
- 声明式导航
-
长列表中,点击后往同一个组件跳转
- 编程式跳转
-
r o u t e r 与 router与 router与route的区别
- r o u t e 从 当 前 r o u t e r 跳 转 对 象 里 面 可 以 获 取 n a m e 、 p a t h 、 q u e r y 、 p a r a m s 等 ( < r o u t e r − l i n k > 传 的 参 数 由 t h i s . route从当前router跳转对象里面可以获取name、path、query、params等(<router-link>传的参数由 this. route从当前router跳转对象里面可以获取name、path、query、params等(<router−link>传的参数由this.route.query或者 this.$route.params 接收)
- r o u t e r 为 V u e R o u t e r 实 例 。 想 要 导 航 到 不 同 U R L , 则 使 用 router为VueRouter实例。想要导航到不同URL,则使用 router为VueRouter实例。想要导航到不同URL,则使用router.push方法;返回上一个history也是使用$router.go方法
全局路由拦截
守卫
-
顾名思义,路由拦截即路由使用的拦截器
-
方法 说明 beforeEach((to,form,next)=>{}) 在路由页面进入之前 to 从哪里来 form 到哪里去 next 自己人 next在守卫代码块最后一行通过调用的方式放行 next() 括号内可以为路由路径
-
-
自定义路由状态来控制是否放行条件
-
整个拦截和转载的过程是这样的
-
点击
我的 {center}
后,路由跳转到center
组件 -
在跳转的过程中,通过拦截器进行拦截
-
router.beforeEach((to,form,next)=>{ })
-
拦截后,进行判断,判断
本地存储中,是否有token
字段,如果有,放行,如果没有,转发登陆 login
页面 -
// 判断本地存储中 是否有token字段 if (localStorage.getItem('token')){ next() }else { next("/login") }
-
转发至登陆页面后,进行身份验证,验证通过后,通过
localStorage.setItem
进行本地存储 -
handleLogin(){ setTimeout(()=>{ localStorage.setItem("token","后端返回的token字段") // this.$router.back() //返回上一个页面 this.$router.push("/center") },0) }
-
本地存储过后,通过路由重新转发至来时的页面
-
然后路由会自动的全局拦截,这时判断的本地存储就有了相应的数据
-
自动放行
-
回到我们的目的页面
-
登录后,跳转到我点击后给我拦截的页面
-
index.js
-
// 判断本地存储中 是否有token字段 if (localStorage.getItem('杨清壹')){ next() }else { next({ path:"/login", query:{ redirect : to.fullPath} }) }
-
Login.vue
-
handleLogin(){ setTimeout(()=>{ localStorage.setItem("杨清壹","后端返回的token字段") // this.$router.back() //返回上一个页面 // 1. 获取 query字段 // console.log(this.$route.query.redirect) // 2. 跳转到当时想要跳的页面去 this.$router.push(this.$route.query.redirect) },0) }
-
-
在拿到授权后,可以转身回到我们想要去的页面
局部路由拦截
-
在你的子组件中,定义
beforeRouteEnter(to,form,next)
生命周期 -
在渲染该组件的对应路由被 confirm 前调用
-
不能获取组件实例 this
-
因为当守卫执行前,组件实例还没被创建
-
Center.vue
-
beforeRouteEnter(to,form,next){ if (localStorage.getItem("杨清壹")){ next() }else { next({ path:"/login", query:{redirect: to.fullPath} }) } },
-
路由懒加载
- 懒加载,可以这么理解
- 即,用的时候加载,不用的时候趟尸待命,不会占用浏览器首次启动资源
- 我们
npm run build
后,会生成dist
文件夹,文件夹中的js
文件夹是我们上线时部署在浏览器上的核心代码 js
文件夹中一般会有两个文件app.js
我们的原生组件chunk-vendors
vue源码
- 懒加载,加载的是子组件的创建
- 我们在路由的
index.js
文件中,修改组件引入方式 - 由原先:
import Search from "@/views/Center";
- 修改为:
component: () => import("@/views/Center"),
- 当然,修改的地方是在你即将==重定向页面当时,注册组件的时候,==使用此方法进行懒加载处理
- 也不是所有的子组件都需要懒加载处理
- 一般用于页面启动速度的优化
rem
方法 | 说明 |
---|---|
window.devicePixelRatio | 查看1px对应多少物理像素比 |
document.documentElement.clientWidth | 可布局的宽度 |
-
最基本的适配方案之一
-
也就是说每当切换手机型号的时候,我们的宽高大小都是固定的px
-
我们可以将
px
转换成rem
-
我们再
public
静态资源文件夹下的index.html
页面中,加入适配计算 -
<script> //fontsize 计算 document.documentElement.style.fontSize=document.documentElement.clientWidth/750 * 100 + "px"; </script> .box{ width: 10.00rem; height: 200px; background: pink; }
-
当然,
rem
,计算方式为屏幕允许设置的大小/给予我们的大小*100/给予我们的大小 -
在
WebStrome
中,要口头转换px与rem无非是一件痛苦的事情,我们可以下载插件 -
插件的名字是:
idea px2rem
-
下载好之后重启
webstrome
-
重启之后就可以使用啦,选中我们输入好的
xxxpx
,按快捷键Alt+D
,就会自动转成rem
单位啦
Swiper组件
-
cnpm i --save swiper
下载swiper资源 -
film
-
轮播图主组件 声明 布局
-
<template> <div class=""> <film-swiper :key="datalist.length"> <film-swiper-item v-for="data in datalist" :key="data.id" class="filmswiperitem"> <img :src="data.imgUrl" alt=""> </film-swiper-item> </film-swiper> <div> 二级的声明式导航 </div> <router-view></router-view> </div> </template> <script> // webpack import filmSwiper from "@/components/films/FilmSwiper"; import filmSwiperItem from "@/components/films/FilmSwiperItem"; import axios from "axios"; export default { name: 'Films', // import引入的组件需要注入到对象中才能使用 components: { // eslint-disable-next-line vue/no-unused-components filmSwiper, filmSwiperItem }, data() { // 这里存放数据 return { datalist:[] } }, // 生命周期 - 挂载完成(可以访问DOM元素) mounted() { setTimeout(()=>{ axios.get("/banner.json").then(res=>{ console.log(res.data.banner) this.datalist=res.data.banner console.log(this.datalist) }) },1000) }, } </script> <style scoped lang="scss"> .filmswiperitem{ img{ width: 100%; } } </style>
-
filmSwiper
-
film-swiper子组件 主要用来控制组件的运动方式
-
<template> <div class="swiper draven"> <div class="swiper-wrapper"> <slot></slot> </div> <!-- 如果需要分页器 --> <div class="swiper-pagination"></div> <!-- 如果需要导航按钮 --> <div class="swiper-button-prev"></div> <div class="swiper-button-next"></div> </div> </template> // webpack import {Swiper} from "swiper"; import 'swiper/swiper.min.css' export default { name: 'filmSwiper', // import引入的组件需要注入到对象中才能使用 components: {}, props: { loop:{ type:Boolean, default:true, } }, // 生命周期 - 挂载完成(可以访问DOM元素) mounted() { new Swiper('.draven', { // direction: "vertical", //垂直 // 如果需要分页器 pagination: { el: '.swiper-pagination' }, loop: this.loop, // 循环 // 自动 autoplay: { delay: 2500, disableOnInteraction: false }, // 如果需要前进后退按钮 navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev' } }) }, </script>
-
filmSwiperItem.vue
-
用来for遍历数据的组件,主要用来存储数据的组件
-
<template> <div class="swiper-slide"> <slot></slot> </div> </template>
猫眼案例
axios封装
- 在src下新建
util
包,在util
包下新建http.js
文件
1 - 对于数据请求的封装
import axios from "axios";
function httpForList(){
return axios({
url:"https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=1&k=5238343",
headers:{
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.1","e":"16687528065266390114107393","bc":"110100"}',
'X-Host': 'mall.film-ticket.film.list'
}
})
}
function httpForDetail(params){
return axios({
url:`https://m.maizuo.com/gateway?filmId=${params}&k=4819055`,
headers:{
"X-Host": "mall.film-ticket.film.info"
}
})
}
export default {
httpForList,httpForDetail
}
- 使用
Nowplaying.vue
import http from "@/util/http";
http.httpForList().then(res=>{
console.log("Nowplaying==>",res.data.data.films)
this.datalist=res.data.data.films
})
Detail.vue
import http from "@/util/http";
// 当前匹配的路由
console.log('created', this.$route.params.id)
http.httpForDetail().then(res=>{
console.log(res)
})
// axios 利用id 发请求到详情接口 , 获取详情数据 , 布局页面
2- 对局数据请求的封装
http.js
import axios from 'axios'
const http = axios.create({
baseURL: 'https://m.maizuo.com',
timeout: 10000,
headers: {
"X-Client-Info": '{"a":"3000","ch":"1002","v":"5.2.1","e":"16687528065266390114107393"}'
},
})
export default http
Nowplaying.vue
import http from '@/util/http';
http({
url:"/gateway?cityId=110100&pageNum=1&pageSize=10&type=1&k=5238343",
headers:{
"X-Host": "mall.film-ticket.film.list"
}
}).then(res=>{
console.log("res")
console.log(res)
console.log("Nowplaying==>",res.data.data.films)
this.datalist=res.data.data.films
})
Detail.vue
import http from '@/util/http'
http({
url:`/gateway?filmId=${this.$route.params.id}&k=4819055`,
headers:{
"X-Host": "mall.film-ticket.film.info"
}
}).then(res=>{
console.log(res)
})
3 - 拦截器
//在发请求之前拦截 -- showLoading
http.interceptors.request.use(function (config) {
return config;
}, function (error) {
return Promise.reject(error);
});
//在成功后拦截 -- hideLoading
http.interceptors.response.use(function (response) {
return response;
}, function (error) {
return Promise.reject(error);
});
更好的滚动-betterScroll
- 主要完成的功能需要包含Better-Scroll实现页面中拖动滚动、拉动属性等功能
-
给予父盒子宽高及
calss
属性 -
动态绑定父盒子的
style
属性(height值) -
下载
betterScoll
cnpm i --save betterScoll
-
使用
betterScoll
-
this.$nextTick()
在挂载完dom之后执行,防止初始化过早 -
this.$nextTick(()=>{ new BetterScroll('.boxed',{ scrollbar:{ fade:true } }) })
-
-
动态计算高度–解决各设备不兼容问题
-
this.height = document.documentElement.clientHeight - (document.querySelector('div').children[0]).offsetHeight+'px'
-
ElementUi组件 - pc端框架
-
在Src目录下新建
PcApp.vue
文件 -
使用cnpm安装elementUi
cnpm i --save element-ui
-
使用
-
引入坐标
-
import Vue from 'vue'; import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI);
-
-
引入组件
-
改造组件
-
vant - 移动端框架
-
通过cnpm安装
cnpm i --save vant@latest-v2
Vue2.0cnpm i --save vant
Vue3.0
-
引入组件库
-
import Vant from 'vant'; import 'vant/lib/index.css'; Vue.use(Vant);
-
-
使用
- 引入组件
- 改造组件
axios拦截,绑定加载框
- 封装到
axios
的http.js
文件中,这样就可以在每次请求数据之前使用加载框,请求到数据之后删除加载框
import { Toast } from 'vant';
//在发请求之前拦截 -- showLoading
http.interceptors.request.use(function (config) {
Toast.loading({
message: '加载中...',
forbidClick: true,
duration: 0
});
return config;
}, function (error) {
return Promise.reject(error);
});
//在成功后拦截 -- hideLoading
http.interceptors.response.use(function (response) {
// 隐藏加载框
Toast.clear()
return response;
}, function (error) {
// 隐藏加载框
Toast.clear()
return Promise.reject(error);
});
索引城市列表 组件
-
在
vant2
官网中找到自定义索引列表,并引用至.vue组件中 -
<template> <div> <van-index-bar :index-list="computedList"> <div v-for="(item) in citieList" :key="item.type"> <van-index-anchor :index="item.type" /> <van-cell v-for="(data) in item.address" :key="data.cityId" :title="data.name" /> </div> </van-index-bar> </div> </template> <script> import http from '@/util/http' import Vue from "vue"; export default { name: "caty", data(){ return{ citieList:[], } }, computed:{ computedList(){ return this.citieList.map(item=>item.type) } }, mounted() { http({ url:"/gateway?k=7323589", headers:{ "X-Host":"mall.film-ticket.city.list" } }).then(res=>{ console.log("1",this.renderCity(res.data.data.cities)) this.citieList = this.renderCity(res.data.data.cities) }) }, methods:{ renderCity(cities){ // console.log(cities) //存储36个英文字符的临时数组 var arrListABC = [] //遍历向数组添加字符 for (let i = 65; i < 91; i++) { arrListABC.push(String.fromCharCode(i)) } //存储最后总数据整理 var citiesList = [] //存储首字母对应城市的数据 var newList = [] //筛选 arrListABC.forEach(letter=>{ var newList = cities.filter( item=>item.pinyin.substring(0,1).toUpperCase() === letter ) //如果某个首字母的城市数据为空,则跳过本次执行,如果不为空,则向新数组添加一组json数据,type的值为letter(英文字母),address的值为newList(城市条数据) newList.length > 0 && citiesList.push({ type:letter, address:newList }) }) // console.log("2",citiesList) return citiesList } } } </script>
Vuex - 状态管理模式
-
作用:
-
vuex 管理保存公共状态
-
分散在各组件内的状,统一管理
-
实现组件与状态之间的互联
-
非斧子的通信
-
后端数据的缓存快照,减少重复数据请求,减轻服务器压力,提高用户体验
-
-
注意:
- vuex 默认是管理在内存,一刷新页面,公共状态也就丢失了
-
原理图
-
store.js
文件属性分析 -
属性 作用 state 公共状态 mutations 数据接收器 actions -
使用
-
方法 说明 this.$store.commit(key(方法名),value(形参)) 提交数据完成更新 this.$store.dispatch(key(方法名))
state
-
声明
-
// state公共状态 state:{ cityId:"310100", cityName:"上海" }
-
-
使用
-
获取数据
-
{{ $store.state.cityName }}
-
-
更新数据(no 不安全 无法跟踪修改数据源)
-
this.$store.state.cityName=item.name
-
-
更新数据(正确写法 key,value传递方法)
-
this.$store.commit("changeCityName",item.name)
-
-
mutations
-
只能支持同步
-
统一管理
- 被
devtools
记录状态的修改 devtools
在chrome中有可视化插件
- 被
-
commit方法内提供的key值声明成方法,里面带有两个形参
state
和value
state属性
对象value
参数
-
声明出的方法内使用
state调用状态传值
-
mutations:{ changeCityName(state,cityName){ state.cityName=cityName console.log(cityName) }, }
-
actiones
-
异步(vuex持久化)
-
dispatch
提供的方法名在actiones
中声明出来,形参为sotre
代表store
对象vaule
参数
-
使用
-
// cinemas.vue // 先分发请求 this.$store.dispatch('getCinemasData',this.$store.state.cityId) //👆-------------------------------------------👇 // store -> index.js // 再在store.js文件中请求数据 getCinemasData(store,cityId){ return http({ url: `/gateway?cityId=${cityId}&ticketFlag=1&k=7641159`, headers: { "X-Host": "mall.film-ticket.cinema.list" } }).then(res => { console.log(res) //把请求到的数据提交给state store.commit('changeCinemasData',res.data.data.cinemas) }) },
-
Vuex新写法
方法 | 说明 |
---|---|
…mapState([key,…,key]) | 映射Store状态 |
引入
import {mapState,mapMutations,mapActions} from "vuex";
mapState
-
映射store的state公共状态
-
写在
computed
监听属性中 -
语法:
...mapState(["cinemasList"])
- 多个状态可以再数组括号内用逗号隔开
...mapState(["cinemasList","cityId","cityName"])
- 然后当我们使用
cinemasList
的时候可以不用从this.$store
对象中引用,可以直接把cinemasList
当成本组件的data
数据使用
-
使用
-
// 正确的写法 computed: { ...mapState(["cinemasList"]) }, // 等同于 computed: mapState(["cinemasList"]) // 等同于 computed: { mapState: function (){ return this.$store.state.cinemasList } },
-
//老写法 if (this.$store.state.cinemasList.length===0)
-
//引入maoState后的写法 if (this.cinemasList.length===0)
-
mapActions
-
映射store的actions 异步请求
-
写在
methods
方法集合中 -
语法
...mapActions(["getCinemasData"])
- 多个状态可以再数组括号内用逗号隔开
...mapActions(["getCinemasData","..."])
- 然后当我们使用
getCinemasData
的时候可以不用从this.$store
对象中引用,可以直接把getCinemasData
当成本组件的mounted
方法使用
-
使用
-
// methods引入 ...mapActions(["getCinemasData"]),
-
//正确写法 this.getCinemasData(this.cityId)
-
//老写法 this.$store.dispatch('getCinemasData',this.cityId)
-
mapMutations
-
映射store的mutations统一管理
-
写在
methods
方法集合中 -
语法
...mapMutations(["changeCityName"]),
- 多个状态可以再数组括号内用逗号隔开
...mapMutations(["changeCityName",...]),
- 然后当我们使用
changeCityName
的时候可以不用从this.$store
对象中引用,可以直接把changeCityName
当成本组件的mounted
方法使用
-
使用
-
// methods引入 ...mapMutations(["handleDeleteData"])
-
//正确写法 this.handleDeleteData()
-
//老写法 this.$store.commit("handleDeleteData")
-
注入
方法 | 说明 |
---|---|
mixins:[obj] | 注入obj的生命周期… |
-
在
util
包中新建mixinObj.js
文件 -
编写
-
var obj = { mounted() { // console.log("创建完成") this.$store.commit("hide") }, destroyed(){ this.$store.commit("show") } } export default obj
-
-
使用
-
在每个需要用到
hide,show
方法的组件中使用 -
import obj from "@/util/mixinObj"; // 混入(注入) mixins: [obj],
-
Vuex持久化
-
下载安装
cnpm i --save vuex-persistedstate
-
引入
- 打开我们的
store
文件 - 在导包处写入以下代码
import createPersistedState from "vuex-persistedstate"
- 打开我们的
-
使用
-
在
export...
内第一行引用 -
plugins:[createPersistedState()],
-
-
效果
- 数据会存在浏览器的
Storage
中
- 数据会存在浏览器的
-
限制
-
plugins:[createPersistedState({ reducer:(state)=>{ // 规定要存入的数据 return{ cityId:state.cityId, cityName:state.cityName } } })],
-
常用方法
变量
变量 | 说明 |
---|---|
.substring(index,index) | 截取字符串 |
.toUpperCase() | 将字符串转换为大写 |
数组
方法 | 说明 |
---|---|
.splice(index1,index2,[str]) | 删除从index1开始删除,删除index2个,替换成str的值 |
.push(data) | 向数组追加一条数据 |
.shift() | 把数组的第一个元素从其中删除,返回第一个元素的值。 |
.includes(str) | 检测是否包含字符串str |
replace(“oldStr”,“newStr”) | 字符串中用一些字符替换另一些字符 |
事件
事件 | 说明 |
---|---|
arr.filter() | 监听器 |
v-model.lazy=“myText” | 懒惰的,失去焦点后同步数据 |
v-model.number=“myAge” | 将字符串类型转换为数字类型 |
v-model.trim=“myUserNmae” | 去首尾空格 |
New系列
localStorage-本地储存
方法 | 说明 |
---|---|
.setItem(str,value) | 设置Application->Local Storage->http…key中的str值 |
.getItem(str) | 获取Application->Local Storage->http…key中的str值 |
HTML属性
属性 | 说明 |
---|---|
disabled=true | 表单只读 |
ajax方法
方法 | 说明 |
---|---|
then() | 异步执行 |
就是当.then()前的方法执行完后再执行then()内部的程序,这样就避免了,数据没获取到等的问题。 | |
text() | 返回json字符串 |
Vue
基础方法
Vue外部
方法 | 说明 |
---|---|
Vue.filter(“imgFilter”,(url)=>{return…}) | 过滤器 |
:src=“item.img | imgFilter” | Vue3不支持 |
Vue.component(“navber”,{}) | 定义一个全局组件 |
Vue内部
new Vue({…}) | 说明 |
---|---|
el: | 绑定的选择器 |
el:“#box” | |
data:{} | 数据中心 |
methods:{} | 方法区 |
computed:{} | 计算属性 |
watch:{} | 监听器 |
当一个值发生改变时触发 | |
方法区中
方法 | 说明 |
---|---|
fetch(url) | 发出 HTTP 请求 |
axios(url) | Web数据交互方式 fetchPlus |
https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js | |
组件方法
标签
标签 | 说明 |
---|---|
名称 | |
侧边栏 | |
动态组件,Vue提供的标签 | |
is属性 | 固定的 "="后面跟的是使用的组件 |
活着->被它包裹的组件,输入框内留下的内容不会因切换组件而消失 | |
代替 slot 属性 | |
插槽,在组件dom中加入 | |
使用
方法 | 说明 |
---|---|
Vue.component(“navber”,{}) | 定义一个全局标签 |
template:‘
aaaa
’ | 固定的 |
methods:{} | 方法 |
computed:{} | 计算属性 |
watch:{} | 请求 |
data(){return{}} | 数据 |
props:{} | 接受属性 |
components:{“draven-Child”:{template:‘
’}}
| 子组件 |
插槽👇 | |
插槽,在组件dom中加入 | |
属性 | |
slot=“right” | 使用插槽,在dom标签中使用 |
代替 slot 属性,在模板(dom)标签中使用 | |
<template #a> | 代替 slot 属性.Plus在模板(dom)标签中使用 |
插槽,在组件dom中加入 | |
过度方法
标签 | 说明 |
---|---|
固定过度标签 | |
组件中包含多个要执行动画的标签 | |
不同于transition,它会以一个真实元素呈现 | 默认为一个span,也可以通过tag 属性更换为其他元素 |
:key=“item.id” | 使用key值的id做比较,.提供唯一的key值属性 |
属性 | 说明 |
enter-active-class=“” | 动画开始时切换的class |
leave-active-class=“” | 动画结束时切换的class |
name=“yqy” | 根据calss名动态查找 |
yqy-enter-active | 查找后根据enter和leave自动选择calss |
yqy-leave-active | |
mode | 设置动画先后顺序 |
out-in | 先走再来 |
in-out | 先来再走 |
生命周期
方法 | 说明 |
---|---|
创建阶段👇 | |
beforeCreate() | 创建前 |
created() | 初始化 重要 |
beforeMount() | 载入前 |
模板解析前,最后一次修改模板的节点 | |
mounted () | 载入后 |
依赖于dom创建之后,才进行初始化工作的插件 (轮播插件) | 创建完dom后的初始化 重要 |
订阅 bus.$on | 初始化ajax数据 |
更新阶段👇 | |
beforeUpdate() | 更新前 |
updated() | 更新后 |
销毁👇 | |
beforeDestroy() | 销毁前 |
destroyed() | 销毁后 |