赞
踩
<script src="地址">
// 设置 id <div id="box"> // 双大括号 {{ 10+20 }} {{ myname }} </div> <script> var vm = new Vue({ // 将 box 下的内容交给 vue 管理 el: '#box', data: { // 状态 myname: 'syp' } }) </script>
vm.myname = 'tiechui'
data
选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty
把这些 property 全部转为 getter/setter。类似于封装了以下代码:
<div id="box"> </div> <script> var obj = { } var box = document.getElementById('box') Object.defineProperty(obj, 'myname', { // 访问时 get() { console.log('get') return box.innerHTML }, // 修改时 set(value) { console.log('set') box.innerHTML = value } }) </script>
Vue2 的 Object.defineProperty 缺陷:
<style> .red { background-color: red; } .yellow { background-color: yellow; } </style> <div id="box"> {{ myname }}-{{ myage }} {{ 10>20?'aaa':'bbb' }} // 冒号 动态绑定(v-bind 的简写) <div :class="whichcolor">切换背景色1</div> <div :class="isColor?'red':'yellow'">切换背景色2</div> <img :src="imgpath"> // @ 绑定事件(v-on 的简写) <button @click="handleChange()">change</button> // v-show v-if 指令 <div v-show="isShow">我是动态显示和隐藏</div> <div v-if="isCreated">我是动态创建和删除</div> // v-for 列表渲染的指令 <ul> <li v-for="(data, index) in list">{{ data }}-{{ index }}</li> </ul> </div> <script> new Vue({ el: '#box', data: { myname: 'syp', myage: 18, whichcolor: red, isColor: true, imgpath: '', isShow: false, isCreated: false list: ['aaa', 'bbb', 'ccc', 'ddd'] }, // 方法 methods: { handleChange() { this.myname = 'tiechui' this.myage = 20 this.whichcolor = 'yellow' this.isColor = !this.isColor this.imgpath = '地址' this.isShow = !this.isShow this.isCreated = !this.isCreated } } }) </script>
:class="whichcolor"
不动态绑定的话传的只是一个字符串思路:
<input type="text" v-model="mytext">
{{ }} 默认不解析:
<div v-html="myhtml"></div>
<ul>
<li v-for="(data, index) in datalist" :class=" current===index?'active':'' " @click="handleClick(index)">
{{ data }}
</li>
</ul>
<style> .aa { } …… </style> <div :class="classobj">动态切换class-对象</div> <div :class="classarr">动态切换class-数组</div> var vm = new Vue({ …… data: { clssobj: { aa: true, bb: true, cc: true }, clssarr: ["aa", "bb", "cc"] } })
对象写法:
切换:vm.classobj.cc = false
vue2 不支持动态添加属性的拦截
解决方案:Vue.set(vm.classobj, "dd", true)
数组写法:
切换:数组操作
添加:数组.push("新属性")
<div :style="styleobj">动态切换style-对象</div>
<div :style="stylearr">动态切换style-数组</div>
var vm = new Vue({
……
data: {
styleobj: {
backgroundColor: 'red'
},
stylearr: [{backgroundColor: 'red'}]
}
})
对象写法:
切换:vm.styleobj.backgroundColor = 'bule'
vue2 不支持动态添加方法的拦截
解决方案:Vue.set(对象, "属性", "值")
数组写法:
切换:数组操作
添加:数组.push({属性: "值"})
对象.属性 = bool值
对象.属性 = "值"
<div v-if=""></div>
<div v-else-if=""></div>
<div v-else></div>
<template> // 包装作用,不破坏原 DOM 结构
<div></div>
<div></div>
<div></div>
</template>
<li v-for="(item, index) in/of datalist"></li>
// 遍历对象
<li v-for="(key, value) in/of obj"></li>
// key 值的作用是跟踪每个节点的身份,从而重用和重新排序现有元素
// 理想的 key 值是每一项都有且唯一的 id (后端提供)
<li v-for="(item, index) in datalist" :key="item.id"></li>
可以检测到变动的数组操作(底层检测不到,其实是 vue2 对数组方法进行了重写):
检测不到变动,用新数组替换旧数组
检测不到变动的数组操作
vue3 直接在底层支持对数组的拦截
@change
失去焦点且 value 改变触发
@input
value 改变即触发
v-model
双向数据绑定@input
检测输入框状态改变时触发事件filter
方法筛选符合条件的数组项v-model
双向数据绑定v-for
遍历一个 test()
方法的返回值test()
方法返回一个 filter
过滤后的数组v-model
双向数据绑定后只要拦截到数据发生变化,与其相关的方法都会重新执行// 传参 ($event 为事件对象)
<button @click="handleAdd1($event, 参数1, ……)">函数表达式写法</button>
// 不传参
<button @click="handleAdd2">函数名写法</button>
<button @click="表达式">表达式写法</button>
handleAdd1(evt, a, ……) { }
// 可得到事件对象 evt
handleAdd2(evt) {
// .target 事件源
// .target.value 值
console.log(evt.target)
}
@click.stop
事件触发后阻止冒泡
@click.self
只有事件源是自身会触发事件,点击孩子不会
@click.once
只触发一次
@click.prevent
阻止默认行为
……
@keyup.enter
按下回车键后触发
<input type="checkbox" v-model="isChecked"> 记住用户名
<input type="checkbox" v-model="checkList" value="a"> a
<input type="checkbox" v-model="checkList" value="b"> b
<input type="checkbox" v-model="checkList" value="c"> c
// 用 “数组” 存储每个选项的勾选情况
// 必须加 “value” 属性才能将勾选情况存入 “checkList”
<input type="radio" v-model="select" value="a"> a
<input type="radio" v-model="select" value="b"> b
// 双向数据绑定一个字符串
v-model.lazy
失去焦点之后再生效
v-model.number
改成数字类型
v-model.trim
去首尾空格
computed: {
// 防止模板过重难以维护
// 负责逻辑放在计算属性中来写
return
}
method 和 computed 对比:
watch: {
mytext(newval) {
console.log("改变了", newval)
}
}
ajax 是一种异步请求数据、局部更新页面的技术
xhr 是原生 JS 实现 ajax 的一种方法
fetch 是一种新的实现 ajax 的方法
xhr 与 fetch 都是标准,自带的
解决fetch 低版本浏览器兼容性问题 https://github.com/camsong/fetch-ie8
handleFetch() {
fetch(请求地址)
.then(res => {
// res 中是状态码、响应头
return res.json()
})
.then(res => {
// 第二个 .then 中是 json 数据
console.log(res)
})
.catch(err => {
console.log(err)
})
}
get 传参:url路径 ?name=syp&age=100
post 传参:body请求体
(1)x-www-formurlencoded, "name=syp&age=100"
(2)json, {name:"syp", age:100}
fetch(请求地址, {
method: "post",
// 请求头 要求传 form 格式编码数据(key=value)
header: {
"Content-Type": "application/x-www-formurlencoded"
},
// 参数
body: "name=syp&age=100"
})
.then(res => res.json())
.then(res => console.log(res))
fetch(请求地址, { method: "post", // 请求头 要求传 json 格式编码数据 header: { "Content-Type": "application/json" }, // 参数 body: JSON.stringfy({ name: "syp", age: 18 }) }) .then(res => res.json()) .then(res => console.log(res))
<script src="axios.js位置">
// get 请求
axios.get(请求地址).then(res => {
// 数据在 res.data 中
console.log(res.data)
})
// post 请求
// axios 会自动根据传的数据是判断什么类型
axios.post("请求地址", "name=syp&age=100").then(res => {})
axios.post("请求地址", {name:"syp", age:100}).then(res => {})
// 将原始数据送入过滤器
<img :src="item.img | imgFilter">
Vue.filter("imgFilter", (url) => {
return url.replace('w.h/', '')+'@……'
})
<img :src="item.img | imgFilter1 | imgFilter2 ……">
<navbar></navbar> // 定义一个全局组件 Vue.component("navbar", { // dom, js, css template: ` <div style="background:red"> <button @click="handleLeft">left</button> 猫眼电影 <button @click="handleRight">right</button> </div> `, methods: { handleLeft() { }, handleRight() { } }, computed: {}, watch() {}, // data 必须是函数写法 data() { return { key: value, } } })
<div id="box"> <father-navbar></father-navbar> // 全局定义的组件可以在全局使用 <child-navbar1></child-navbar1> </div> Vue.component("fatherNavbar", { template: ` <div> …… <child-navbar1></child-navbar1> // 局部定义的组件只能在当前组件内部使用 <child-navbar2></child-navbar2> </div> `, …… // 局部定义组件 components: { "childNavbar2": { …… } } }) // 全局定义组件 Vue.component("childNavbar1", { …… })
<father-navbar>
放在 box
内,就是 box
的子组件<child-navbar1>
和 <child-navbar2>
放在 <father-navbar>
内,就是 <father-navbar>
的子组件<div id="box"> <navbar myname="电影" :myright="true" :myfather="father"></navbar> <navbar myname="影院" :myright="false" :myfather="father"></navbar> </div> Vue.component("navbar", { // props: ["myname"], // 属性验证+默认属性 props: { myname: { type: String, default: "" }, myright: { type: Boolean, default: true }, myfather: { type: String, default: "" } }, template: ` <div> <button>left</button> <span>{{myname}}--{{myfather}}</span> <button v-show="myright">right</button> </div> `, }) new Vue({ el: "box", data: { father: "11111" } })
应用场景举例:实现抽屉功能
<div> // 在父组件中写一个监听事件 "myevent" // myevent 事件被触发后执行 handleEvent 方法 <navbar @myevent="handleEvent"></navbar> <sidebar v-show="isShow"></sidebar> </div> Vue.component("navbar", { template: ` <div> <button @click="handleClick()">点击</button>-导航栏 </div> `, methods: { // 在孩子组件中写 一点按钮就触发 "myevent" 事件 // 还能顺带传值 handleClick() { this.$emit("myevent", 1000) } } }) Vue.component("sidebar", { template: ` <div> <ul> <li>111</li> <li>111</li> <li>111</li> <li>111</li> <li>111</li> <li>111</li> </ul> </div> ` }) new Vue({ el: "box", data: { isShow: true }, methods: { // 不写 小括号 时,自带一个形参 data handleEvent(data) { console.log(data) this.isShow = !this.isShow } } })
Var bus = new Vue()
// 发布者 触发事件
bus.$emit("事件名称", 数据)
// 订阅者 监听事件
// 为了让组件刚创建好就可以监听 放在组件生命周期的 mounted 中
bus.$on("事件名称", (data) => {})
<div id="box">
<input type="text" ref="mytext">
</div>
methods: {
handleAdd() {
// 拿到的是节点
console.log(this.$refs.mytext)
}
}
状态:组件内部的,可以随意修改
属性:父组件赋予的,只有父组件可以重新传,不允许随意修改
v-once
让内容只计算一次然后缓存起来,再改也不更新了
<component is="你说我是啥组件我就是啥组件"></component>
<keep-alive>
<component is="你说我是啥组件我就是啥组件"></component>
</keep-alive>
// 其实是先暂存到内存中了
// 但是自定义组件中写组件 需要插槽来实现
<child> <div>111111111</div> <div>222222222</div> <div slot="a">333333333</div> </child> Vue.component("child", { template: ` <div> …… // 单个插槽 有啥全放进去 <slot></slot> <slot></slot> // 具名插槽 <slot name="a"></slot> </div> ` }) // 1 2 1 2 3
<child> // 老写法 不是指令也不是属性 不伦不类 <div slot="a">111111111</div> // 新写法 指令写法 <template v-slot:b> <div>222222222</div> </template> // 简写 <template #c> <div>333333333</div> </template> </child> Vue.component("child", { template: ` <div> …… <slot name="a"></slot> <slot name="b"></slot> <slot name="c"></slot> </div> ` })
<navbar>
// 留了一个插槽 把 @click 写在了父组件中
<button @click="isShow=!isShow">click</button>
</navbar>
<sidebar v-show="isShow"></sidebar>
Vue.component('navbar', {
template: `
<div>
navbar
<slot></slot>
</div>
`
})
插槽的意义:
.syp-enter-active {} .syp-leave-active {} <div id="box"> // transition 可监测 isShow 改变 // 只能监测节点改变,监测不到内容改变 <transition enter-active-class="syp-enter-active" leave-active-class="syp-leave-active"> <div v-show="isShow">111111111</div> </transition> // 简写 // appear 初始就带效果 <transition name="syp" appear> <div v-show="isShow">111111111</div> </transition> <button @click="isShow = !isShow">click</button> </div>
<transition name="syp" mode="out-in"> // mode 先走再来
<div v-if="isShow" key="1">11111</div>
<div v-else key="2">22222</div>
</transition>
<transition name="syp" >
<component :is="which"></component>
</transition>
// 实现列表插入、删除的动画效果
<ul>
<transition-group> // 可以放多个节点
<li v-for="" :key=""><li>
</transition-group>
</ul>
tag="要实例化的标签"
// 传一个自定义的 mode 属性控制动画效果
<sidebar v-show="isShow" mode="left"></sidebar>
template: `
// 接收 mode,选择对应的 css 样式
<transition :name="mode" tag="ul">
<li>……</li>
<li>……</li>
<li>……</li>
</transition>
`
beforeCreate 没啥用
created 可初始化状态或者挂载到当前实例的一些属性
beforeMount 可在模板解析之前最后一次修改模板节点
mounted 可拿到真实的 dom 节点
属于创建阶段,只在初始化时执行一次
beforeUpdate 可记录老的 dom 的某些状态
updated 可获取更新后的 dom
属于更新阶段,每次数据更新时都会执行
静态: https://www.swiper.com.cn/usage/index.html
动态(操作 dom 写法)
<div class="swiper myswiper"> <div class="swiper-wrapper"></div> …… </div> setTimeout(() => { // 模拟 Ajax var list = ["aaa", "bbb", "ccc"] var newlist = list.map(item => `<div clsaa="swiper-slide">${item}</div>`) var owrapper = document.querySelector(".swiper-wrapper") owrapper.innerHTML = newlist.join('') // 取完数据,创建完 dom 后 // 再初始化 swiper init() }, 2000) function init() { // 直接写外面会导致初始化过早 // 初始化类名是 “myswiper” 的 swiper 组件 new Swiper('.myswiper', { …… }) }
<div class="swiper"> <div class="swiper-wrapper"> <div class="swiper-slide" v-for="data in datalist" :key="data"> <img :src="data" alt=""> </div> </div> …… </div> mounted() { // 异步数据 setTimeout(() => { this.datalist = ["", "", ……] // new Swiper 在这不行 // 因为 dom 异步更新 }, 2000) // new Swiper 在这不行 // 因为数据还没取到 } updated() { new Swiper() }
当前问题:
new Swiper()
会被执行多次// 方法一: // 给 swiper 加上 :key="datalist.length" 初始为0 // 当 key 值改变,原始的 swiper 被删除 // 重新执行 mounted 生命周期,创建一个新的 swiper <swiper :key="datalist.length" :loop="false"> // 方法二: <swiper v-if="datalist.length" :loop="false"> <swiper-item v-for="data in datalist" :key="data"> <img :src="data"> </swiper-item> </swiper> Vue.component("swiperItem", { template: ` <div class="swiper-slide"> <slot></slot> </div> ` }) Vue.component("swiper", { prop: { loop: { type: Boolean, default: true } }, template: ` <div class="swiper"> <div class="swiper-wrapper"> <slot></slot> </div> …… </div> `, mounted() { new Swiper(".swiper", { // 分页器 pagination: { el: '.swiper-pagination' }, loop: this.loop, autoplay: { delay: 2500, disableOnInteraction: false } }) } }) new Vue({ …… mounted() { setTimeout(() = { …… }, 2000) } })
tips:
父组件(Vue)更新,子组件(swiper)一定会重新渲染
最终效果:
var obj = {
data() {
return: {
}
},
……
}
Vue.createApp(obj)
// 定义组件
.component("navbar" {
……
})
// 最后上树
.mount("#box")
<div v-hello="参数">1111</div>
Vue.directive("hello", {
// 指令生命周期 inserted 只在第一次会触发
inserted(el, binding) {
// el 是 dom 节点;bindig 是对象,binding.value 是传的参数
dom 操作……
},
// 更新触发
update() {
}
})
<div class="swiper"> <div class="swiper-wrapper"> // 指令传参只能传一个值,但是可以传对象 <div class="swiper-slide" v-for="(data, index) in datalist" :key="data" v-swiper="{index: index, length: datalist.length}"> <img :src="data" alt=""> </div> </div> …… </div> Vue.dircetive("swiper", { inserted(el, binding) { let {index, length} = binding.value // 还是当数据全部插入,dom 创建完毕后再 new Swiper if(index === length-1) { new Swiper(……) } } }) new Vue({ …… mounted() { setTimeout(() = { …… }, 2000) } })
Vue.directive("名称", (el, binding) => {
// 这样写 在创建和更新时都会执行
})
var app = Vue.createApp(obj)
app.directive()
app.mount('#box')
new Vue({
……
mounted() {
setTimeout(() => {
this.datalist = []
// nextTick 比 updated 执行的都晚且只执行一次
// 在 datalist 更新到 dom 之后触发一次
// 是一种捷径 但是无法复用
this.$nextTick(() => {
new Swiper(……)
})
}, 2000)
}
})
单文件组件 .vue
浏览器只认识 .html .css .js 文件,需要配置环境将 .vue 文件转化为上述
需配置:webpack、babel、sass、postcss……
使用 vue-cli 脚手架 帮助配置
Vue CLI 官方文档 安装……
vue create 文件夹名称
package.json
记录了项目下安装的所有模块
npm run serve
启动 开发阶段 / npm start
npm run build
编译 提交阶段
npm run lint
修复代码格式错误
–save(-S):安装包信息将加入到 dependencies(生产阶段的依赖,也就是项目运行时的依赖,程序上线后仍需要使用)
–save-dev(-D):安装包信息将加入到 devDependencies(开发阶段的依赖,只在开发阶段使用到)
不再需要 live service,直接在服务器中运行
入口界面 index.html
入口 main.js
import Vue from 'vue' // ES6 导入方式
import App from './App.vue' // 导入根组件 APP
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router, // 把导入的 router 对象挂到 this.$router 这个属性上了(this.$router === router)
store, // this.$store === store
render: h => h(App) // 将 App.vue 中的根组件渲染实例化后
}).$mount('#app') // 挂载到 index.html 的 app 节点上
解决 eslint 代码格式报错问题
npm run lint
lintOnSave: false
// dom <template> <> // 里面只能包裹一个标签 <> </template> // js <script> // ES6 导出规范 export default { } </script> // css <style lang="scss" scoped> // scss 写法 // scoped 局部作用域(会为组件添加一个独一无的属性),不会被父组件影响 </style>
import navbar from './mycomponents/Navbar'
引入单文件组件 navbarimport Vue from 'vue'
模块化开发 在哪用在哪引Vue.component('navbar', navbar)
引入后还要注册(全局注册(放 main.js 中),将原来 {} 内的内容替换成 navbar)export default {
……
compontent: {
// 将 {} 内的内容替换成 navbar
navbar: navbar
}
}
父传子、传值验证、插槽、子传父……都没变化
cnpm i --save axios
后 importimport Vue from 'vue'
本地 A 向远程 B 发送请求 跨域
解决:反向代理,本地 A 向本地服务器发请求,本地服务器再向远程 B 发请求
module.exports = { devServer: { proxy: { // 凡是向 syp 发请求的 '/syp': { // 都转向 target target: 'https://m.maoyan.com', changeOrigin: true, pathRewrite: { // 执行时把 '/syp' 替换成 '' '^/syp': '' } } } } }
补充:
“@” 符号是别名,永远指向 src 的绝对路径,也可自己配置
单页面应用(SPA) | 多页面应用(MPA) | |
---|---|---|
组成 | 由一个外壳页面和多个页面片段组成 | 由多个完整页面组成 |
资源共用 | 共用 只需要在外壳部分加载 | 不共用 每个页面都需要加载 |
刷新方式 | 页面局部刷新或更改 | 整页刷新 |
url模式 | a.com/#/pageone a.com/#/pagetwo | a.com/pageone.html a.com/pagetwo.html |
用户体验 | 页面片段间的切换快,用户体验良好 | 页面切换加载缓慢 |
转场动画 | 容易实现 | 无法实现 |
数据传递 | 容易 | 依赖url传参或者cookie、localStorage等 |
搜索引擎优化(SEO) | 需单独方案,实现较为困难,不利于SEO搜索。 可利用服务器端渲染(SSR)优化 | 实现方法容易 |
适用范围 | 适用于体验度要求高、追求页面流畅的应用 | 适用于追求高度支持搜索引擎的应用 |
开发成本 | 较高,常需要借助专业的框架 | 较低,但是页面重复代码多 |
维护成本 | 相对容易 | 相对复杂 |
<router-view></router-view>
import Vue from 'vue' import VueRouter from 'vue-router' import Films from '@/views/Films.vue' import Films from '@/views/Cinemas.vue' import Films from '@/views/Center.vue' // 注册路由插件 // 其实就在全局定义了俩标签 router-view、router-link Vue.use(VueRouter) // 配置表 const routes = [ { path: '/films', // localhost8080/#/films component: Films }, { path: '/cinemas', component: Cinemas }, { path: 'center', component: Center }, ] const router = new VueRouter({ routes })
{
path: '*'
redirect: '/films'
}
// 运行步骤是先找其它的,找不到之后再走重定向
路由的底层原理:
window.onhashchange
能检测到哈希值的改变,每当路径发生改变就触发location.hash
能拿到哈希值<ul> <li> // 会自动判断路由模式 // 自动判断路径加不加 “#” <router-link to="/films" active-class="syp-ctive">电影</router-link> </li> <li> <router-link to="/cinemas" active-class="syp-active">影院</router-link> </li> // 在每次被选中后会自动添加一个 ‘router-link-active’ 的类名 // 也可以重命名类名 active-class="新类名" // 自定义一下这个类名的样式 就可以实现选中之后高亮且刷新之后不消失 </ul> <style lang='scs'> .syp-active { } <style>
// navigate 跳转函数
// isActive 记录是否选中
<router-link to="/films" custom v-slot="{navigate, isActive}">
// tag 变成了想绑啥直接写,可以套随意层
// class 变成了要自己写
<li @click="navigate" :class="isActive?'sypactive':''">电影</li>
</router-link>
tips:
:to=""
用 v-for
遍历一个数组{ path: '/films', component: Films, // 这样 nowplaying 不会覆盖 films // 而是会在 films 中给它预留好的容器(router-view)显示 cildren [ { path: '/films/nowplaying', component: Nowplaying }, { path: '/films/comingsoon', component: Comingsoon }, // 在 films 中再次重定向 // 会先走 films // 进入 films 后再重定向 { path: '/films', redirect: '/films/nowplaying' } ] }, // 这样 search 会替换掉 cinemas { path: '/cinemas/search', component: Search }
<router-view></router-view>
// 例子:列表跳详情
<ul>
<li v-for="data in datalist" :key="data" @click="handleChangePage()"></li>
</ul>
methods: {
handleChangePage () {
// 老的编程式导航
location.href = '#/detail'
// vue 封装后的编程式导航
// 好处:自动判断路由模式(加不加 “#”)
this.$router.push('/detail')
}
}
列表跳详情步骤:
第3、第4步的实现方法——动态路由:
{
// 动态路由格式
path: '/detail/:myid', // ":" 代表可以随意写
component: Detail
}
methods: {
handleChangePage (id) {
// 跳转页面
this.$router.push(`/detail/${id}`)
}
}
export default {
created () {
// router 中拿到的是整个路由
// route 中拿到的是当前匹配的路由,params 中是占位的参数的实际值
console.log(this.$route.params.myid)
// 然后 axios 利用 id 发送请求到后端的详情接口
}
}
第3、第4步的实现方法——动态路由之命名路由:
{
name: 'sypDetail'
path: '/detail/:myid',
component: Detail
}
methods: {
handleChangePage (id) {
// 跳转页面
this.$router.push({
name: 'sypDetail', // 向名为 “sypDetail” 的命名路由跳转
params: {
myid: id
}
})
}
}
(1)hash 路由
location.hash
切换window.onhashchange
监听路径切换(2)history 路由
history.pushState
切换
window.onpopstate
vue-router 给封装成 “router” 了
默认 hash 模式(带 “#”)
可改为 history 模式
const router = new VueRouter({
mode: 'history',
routes
})
history 模式:
优点:更优美、更安全
缺点:访问一个 url 时浏览器也无法区分出是前端路由还是后端路由(不知道是向前端发送请求还是向后端发送请求)
解决:如果 url 匹配不到任何静态资源,则应该返回同一个 index.html 页面(交给前端接管)
方法:npm run build
后生成一个 dist 文件夹,把这个文件夹给后端
tips:
{ path: '/center', component: Center, // 把需要授权的路由加一个 meta 标签(路由元信息) meta: { isSypRequired: true } } router.beforeEach((to, from, next) => { if (to.meta.isSypRequired) { // 是需要授权的路由 if (localStorage.getItem('token')) { // 授权通过(方法:判断本地存储中是否有 token 字段) next() } else { next({ path: '/login', // 记录原先是向哪里跳转的 query: { myredirect: to.fullPath } }) } } else { next() } })
<button @click="handleLogin">登录</button>
methods: {
handleLogin () {
setTimeout(() = {
localStorage.setItem('token', '后端返回的 token 字段')
// 跳回原先要去的页面
this.$router.push(this.$route.qyery.myredirect)
}, 0)
}
}
{
path: '/center',
component: Center,
beforeEnter: (to, from, next) => { …… }
}
export default {
// 称为 “路由的生命周期(钩子函数)”
beforeRouteEnter (to, from, next) {
……
}
}
tips:
build 后生成的 dist 文件夹下的 js 文件夹内
问题:首屏加载过慢
原因:单页面开发时,用户打开首页时要加载整个 app.js
解决:路由懒加载(把不同路由对应的组件分割成不同的代码块,当相关路由被访问时再加载这段代码对应的 js 逻辑)
实现方法:
{
path: '\films',
// 把直接导入改成懒加载
component: () => import('@/views/Films.vue')
}
底层原理:Vue 的异步组件和 Webpack 的代码分割功能
<script>
document.documentElement.style.fontSize =
document.documentElement.clientWidth/设计稿宽度 * 100 + 'px'
// 100 好计算
// 或者设置成 "16",使用工具 "px to rem" 辅助计算
</script>
body {
font-size: 16px;
}
tips:
在 eslintrc.js 的 rules 中添加 'no-new': 'off'
可关闭 “no-new” 的代码格式错误检查
cnpm i --save swiper
全局下载 “Swiper”import Swiper from 'swiper/bundle'
在 “FilmSwiper” 组件内导入全部的 “Swiper” 模块(不同版本引入方式不同)import 'swiper/swiper-bundle.css'
在 “FilmSwiper” 组件内导入全部的 “Swiper” 的样式(不同版本引入方式不同)tips:
import Swiper from 'swiper'
只引入了核心模块,其它功能没有引入 // core version + navigation, pagination modules:
import Swiper, { Navigation, Pagination } from 'swiper';
// import Swiper and modules styles
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
// import Swiper bundle with all modules installed
import Swiper from 'swiper/bundle';
// import styles bundle
import 'swiper/css/bundle';
tips:
public 文件夹下的内容可以通过 “/” 找到
src 文件夹下的内容只能通过模块化的方式引入(import)
iconfont 文件放 public 下引入:在 index.html 中 <link rel="stylesheet" href="/iconfont/iconfont.css">
iconfont 文件放在 src/assets 下引入:在 Tabbar.vue 中 import '@/assets/iconfont/iconfont.css'
tips:
请求后端数据的三种设置方法:
axios({
url: '……',
headers: {
'参数名': '示例值',
}
}).then(res => {
……
})
问题一:主演名存在一个 actors 数组中的每个元素的 name 属性上
解决:过滤器 + map 映射 + 拼接成字符串 + 溢出省略号
问题二:主演为空时后端没给 actors 数组
解决:if(data === undefined)
问题三:最后一条被遮挡
原因:底部选项卡脱离了文档流
解决:给页面加一个距离底部的边距
问题四:没有评分要求不显示且占位
解决:设置一个 class,没有评分时占位隐藏
tips:
vue 为封装好的组件标签添加 class,该 class 会渗透到组件内部最外层的标签上,react 做不到
tips:
加 key 的作用:防止缓存
import axios from 'axios' function httpForList () { return axios({ url: '……', headers: { …… } }) } function httpForDetail (params) { return axios({ url: '……', headers: { …… } }) } export default { httpForList, httpForDetail }
import http from '@/util/http'
http.httpForList().then(res => { …… })
import axios from 'axios'
const http = axios.create({
baseURL: '请求地址的公共部分',
timeout: 10000, // 10s 后超时
headers: {
// 公共的请求头
}
})
export default http
import http from '@/util/http'
http({
url: '除去公共部分后的地址',
headers: {
// 除去公共请求头后的其余请求头
}
}).then(res => { …… })
tips:
数据取回来是个数组的话,初始化 datalist: []
数据取回来是个对象的话,初始化 data: null
问题一:数据是异步请求直接渲染会报错
解决:v-if 判断请求到了再创建 dom
tiips:
空对象.属性 会报错
空数组.属性 会 undefined
问题二:背景图片不固定
解决::style="{ backgroundImage: 'url(' + filmInfo.poster + ')' }"
问题三:时间戳改年月日写法
解决:cnpm i --save moment
+ 过滤器
Moment.js
tips:
动态绑定的 class 和 style 与静态绑定的是共存的
封装一个轮播组件,可以一页显示 “preview” 张图片
swiper 组件库提供了方法
问题五:一个页面用两个 swiper 会有轮播冲突
原因:第一个 swiper 会被多次 new
解决:1. 为每个 swiper 传一个 “name” 属性
2. 在 Swiper 组件中接收动态的 “name” 属性作为动态 class
3. 初始化时 new Swiper( ‘.’ + this.name, { …… })
问题五:详情轮播不加 key 值也不会出现初始化过早的问题
原因:在最外层已经 v-if 判断过了
<detail-header v-scroll="80">{{ filmInfo.name }}</detail-header> Vue.directive('scroll', { inserted (el, binding) { el.style.display = 'none' window.onscroll = () => { if((document.documentElement.scrollTop || document.body.scrollTop) > binding.value) { el.style.display = 'block' } else { el.style.display = 'none' } } }, unbind () { window.onscroll = null } })
问题一:影院数据太多,滚动卡顿(超长列表的滚动行为)
解决:better-scroll
<div class="box" :style="{ height:height }"> 列表…… </div> import BetterScroll from 'better-scroll' mounted () { this.height = document.documentElement.clientHeight - document.querySelector('footer').offsetHeight + 'px' // footer 是底部选项卡最外层的标签名 http( …… ).then(res => { …… this.$nextTick(() => { // dom 上完树之后 new BetterScroll('.box', { // 加滚动条 scrollbar: { // 划动时显示,不划时隐藏 fade: true } }) }) }) } .box { height: 不能在这设置; // rem 布局高度缩放会出现问题(愿因:rem 是按照 weight 计算比例的) overflow: hidden; // 加定位后 better-scroll 的滚动条才知道盒子高度(修正滚动条的位置) position: relative; }
原理:不让浏览器撑开滚动条
问题二:rem 布局高度缩放出现问题
解决:(方法二)
<tabbar ref="mytabbar"></tabbar>
this.$refs.mytabbar.$el.offsetHeight
vuex 管理……tips:
this.height = document.documentElement.clientHeight - this.$refs.navbar.$el.offsetHeight - document.querySelector('footer').offsetHeight + 'px'
tips:
原理:判定 “滚动过的距离+视口高度 = 内容区的高度” 时触发 “onload” 事件
看文档
注意:
问题:进到详情页面后,如果撑开滚动条再返回, onload 事件会立刻触发且被禁用
愿因:onload 事件在 ajax 异步取数据之前触发,此时数组长度为 0 且 total 初始值为 0
解决:if 判断 this.total !== 0
import { Toast } from 'vant' const http = axios.create({ …… }) // 在发送请求之前拦截 -- showLoading http.interceptors.request.use( function (config) { // Do something before request is sent Toast.loading({ message: '加载中……', fprbidClick: true, duration: 0 }) return config; }, function (error) { // Do something with request error return Promise.reject(error); } ); // 在请求成功之后拦截 -- hideLoading http.interceptors.response.use( function (response) { // Any status code that lie within the range of 2xx cause this function to trigger // Do something with response data Toast,clear() return response; }, function (error) { // Any status codes that falls outside the range of 2xx cause this function to trigger // Do something with response error Toast.clear() return Promise.reject(error); } ); export default http
期望的结构:
cityList: [
{
type: 'A',
list: ['A1', 'A2', ……]
},
{
type: 'B',
list: ['B1', 'B2', ……]
},
……
]
实现方法:
renderCity (list) { var cityList = [] // 创建一个 26 英文字母数组 var letterList = [] for (var i=65; i<+91; i++) { letterList.push(String.fromCharCode(i)) } // 分类出以每个字母开头的城市 letterList.forEach(letter => { var newList = list.filter(item => item.pinyin.substring(0, 1).toUpperCase() === letter) // 剔除掉空数组 newliat.length>0 && cityList.push({ type: letter, list: newList }) }) return cityList }
:index-list="computedList"
computed: {
computedList () {
return this.cityList.map(item => item.type)
}
}
切换城市功能的实现:
(不好)
import Vue from 'vue' import vuex from 'vuex' Vue.use(vuex) export default new Vuex.Store({ // 公共状态 state: { cityId: '', cityName: '' }, // mutations 监控状态改变,管理状态更改 mutations: { changeCityName (state, cityName) { state.cityName = cityName } } })
// 双大括号内不写 “this”
{{ $store.state.cityId }}
// this.$store.state.cityName = item.name 不好
this.store.commit('changeCityName', item.name)
tips:
问题:“影院” 页面和 “搜索” 页面取同样的数据取了两次
解决:vuex
vuex 在项目中的应用
state: { // 把影院数据缓存到这里 cinemaData: [] }, mutations: { // 缓存 changeCinemaData (state, data) { state.cinemaList = data }, clearCinemaData (state) { state.cinemaList = [] } }, actions: { getCinemaData (store, cityId) { // 返回 getCinemaData 执行结果 return http({ url: `/gateway?cityId=${cityId}&ticketFlag=1&k=7617567`, headers: { 'X-Host': 'mall.film-ticket.cinema.list' } }).then(res => { // 依旧需要把请求到的数据交给 mutations 去管理 store.commit('changeCinemaData', res.data.data.cinemas) }) } }
mounted () { if(this.$store.state.cinemaList.length === 0) { // 如果缓存里没有数据,取数据 this.$store.dispatch('getCinemaData', this.$store.state.cityId) // 在 getCinemaData 执行完毕,异步数据取到之后 .then(res => { 初始化 BetterScroll }) } else { // 缓存里有数据,直接用 初始化 BetterScroll } }, methods: { handleLeft () { this.$router.push('/cinemas/city') // 每次选择城市时先清空 cinemaList 的缓存 this.$store.commit('clearCinemaData') } }
mounted () {
if(this.$store.state.cinemaList.length === 0) { // 如果缓存里没有数据,取数据
this.$store.dispatch('getCinemaData', this.$store.state.cityId)
// 在 getCinemaData 执行完毕,异步数据取到之后
.then(res => {
初始化 BetterScroll
})
}
}
import { mapState, mapAction, mapMutations } from 'vuex' {{ cinemaList }} computed: { ...mapState(['cinemaList', 'cityId']) /* // mapState 写法的返回值就是一个 key: value 的对象 // 相当于返回了大括号包裹的如下 cinemaList: function () { return this.$store.state.cinemaList } */ } methods: { ...mapAction(['getCinemaData']) ...mapMutations(['clearCinemaDate']) } this.cinemaList this.getCinemaData(this.cityId) this.clearCinemaData()
state: {
isTabbarShow: true
},
mutations: {
show (state) {
state.isTabbarShow = true
},
hide (state) {
state.isTabbarShow = false
}
}
<tabbar v-show="$store.state.isTabbarShow"></tabbar>
mounted () {
this.$store.commit.show()
},
destroyed () {
this.$store.commit.hide()
}
问题:要多次写 “show” 和 “hide”
解决:
方法一:在路由中拦截
方法二:混入(mixin)
const obj = {
created () {
this.$store.commit.show()
},
destroyed () {
this.$store.commit.hide()
}
}
export default obj
import obj from '@/util/mixinObj'
export default {
mixin: [obj],
}
tips:
问题:刷新页面切换的 “城市” 公共状态消失
解决:vuex-persistedstate
plugins: [createPersistedState({
// 只持久化需要缓存的
reducer: (state) => {
return {
cityId: state.cityId,
cityName: state.cityName
}
}
})],
vue create 文件夹名称
全局定义组件和指令方法改变(局部定义不变)
createApp(App)
.component ("组件名", {
……
})
.directive("指令名", {
……
})
import {{ createApp }} from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
createApp(App)
.use(router)
.use(store)
.mount('#app')
// createWebHistory -- history 模式 // createWebHashHistory -- hash 模式 import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router' const routes = [ …… // 重定向改动 { path: '/', redirect: '/films' }, { path:'/:any', redirect: { // 用命名路由不报警告 name: 'film' } } // 两个一起相当于 vue2 的 "/*" ] const router = createRouter({ history: createWebHistory(), routes }) export default router
import { createStore } from 'vuex'
export default createStore({
……
})
// router-link 新写法 <ul v-show="$store.state.isTabbarShow"> <router-link to="/films" custom v-slot="{navigate, isActive}"> <li @click="navigate" :class="isActive?'active':''"> <i class="iconfont icon-video"></i> <span>电影</span> </li> </router-link> <router-link to="/cinemas" custom v-slot="{navigate, isActive}"> <li @click="navigate" :class="isActive?'active':''"> <i class="iconfont icon-video"></i> <span>影院</span> </li> </router-link> <router-link to="/center" custom v-slot="{navigate, isActive}"> <li @click="navigate" :class="isActive?'active':''"> <i class="iconfont icon-video"></i> <span>我的</span> </li> </router-link> </ul>
// vue3 没了过滤器,只能改成函数式写法
{{ actorFilter(data.actors) }}
methods: {
actorFilter (data) {
……
}
}
tips:
类写法:
函数写法:
tips:
setup 生命周期相当于 Vue3 老写法和 Vue2 的 beforeCreate 和 created
{{ obj.myname }} <button @click="handleClick()">change</button> <script> import { reactive } from 'vue' export default { setup () { // 定义状态(函数式写法) const obj = reactive({ myname = 'syp' }) const handleClick = () => { obj.myname = 'xiaoming' } return { obj, handleClick } } } </script>
tips:
痛点:要 obj.
在原来的基础上增加了新功能
老功能(拿节点)-新写法:
<input type="text" ref="mytextref">
import { ref } from 'vue'
const mytextref = ref()
// 使用时: mytextref.value 拿到的是 dom 节点
// mytextref.value.value 拿到的是文本框的内容
新功能(解决 reactive 的痛点):
{{ myname }} // 双大括号内省略 ".value",所以间接达成了目的 setup () { const myname = ref("syp") // 参数可以是一个字符串这种普通数据类型 // 看似拦截一个字符串,其实是对 myname 的 value 属性进行拦截 // myname.value 中是 "syp" const handleClick = () => // 修改时还是必须 .value myname.value = "xiaoming" } return { myname } }
新痛点:必须 .value
可一次性解决两个痛点
{{ myname }} --- {{ myage }} import { reactive, toRefs } from 'vue' const obj = reactive({ myname: 'syp', myage: '18' }) const handleClick = () => { obj.myname = 'xiaoming' } return { // 在 return 时转换为 ref 对象然后展开 ...toRefs(obj), handleClick }
<navbar myname="home" myid="111" @event="change"></navbar> <sidebar v-show="obj.isShow"></sidebar> components: { navbar, sidebar }, setup() { const obj = reactive({ isShow: true }) const change = () => { obj.isShow = !obj.isShow } return { …… } }
export default {
props: ["myname", "myid"]
setup (props, {emit}) { // 结解构赋值,emit === this.$emit
// props.myname
// props.myid
emit("event")
}
}
发 Ajax:
import { onMounted } from 'vue'
setup () {
onMounted(() => {
setTimeout(() => {
obj.list = ['aaa', 'bbb', ……]
}, 2000)
})
}
import { computed } from 'vue'
const computedList = computed(() => {
……
return
})
return {
computedList
}
import { watch } from 'vue'
watch(() => obj.mytext, // 每次 "obj.mytext" 的值发生改变,watch 的回调函数就触发一次
() =>{
……
})
tips:
function getData1 () { const obj1 = reactive({ list: [] }) onMounted(() => { axios.get().then(res => { obj1.list = res.data.list }) }) } function getData2 () { const obj2 = reactive({ list: [] }) onMounted(() => { axios.get().then(res => { obj2.list = res.data.list }) }) }
import { getData1, getData2 } from './module/app'
setup () {
const obj1 = getData1()
const obj2 = getData2()
return {
obj1,
obj2
}
}
import { useRouter, useRoute } from 'vue-router'
setup () {
const router = useRouter() // router === this.$router
const route = useRoute() // route === this.$route
// 使用:router.push()
// 使用:route.
}
import { useStore } from 'vuex'
setup () {
const store = useStore() // store === this.$store
// 使用:store.
}
provide
inject
用于少量通信时代替 vuex
例:控制底部选项卡 Tabbar 显示隐藏
<tabar v-show="isShow"></tabbar>
import { provide } from 'vue'
const isShow = ref(true)
provide("sypShow", isShow)
import { inject } from 'vue'
const isShow = inject("sypShow")
// 使用:isShow.value
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。