Supermall 我的项目笔记
1.axios 封装
能够在 network 中再创立独立的 js 文件,封装不同页面的网络申请,防止页面组件过大过于简单
2. 轮播图
钩子挂载实现时,操作 DOM 树,开启定时器
mounted: function () {
// 1. 操作 DOM, 在前后增加 Slide
setTimeout(() => {this.handleDom();
// 2. 开启定时器
this.startTimer();}, 3000)
},
这里 hanleDom()函数获取了元素数量,并克隆第一张和最初一张图片并插入到最初和最前,实现无缝滚动。
轮播图问题
图片加载慢时,会导致图片还没加载实现就执行了 mounted 中的函数操作 DOM 并开启了定时器
解决办法:
①原生的 mutationObserver 监听 DOM 树变动
//methods 中办法
swiperObserver() {const targetNode = document.querySelector('.swiper');
const config = {childList: true};
const callback = (mutationsList, observer) => {this.handleDom();
this.startTimer();
observer.disconnect();};
const observer = new MutationObserver(callback);
observer.observe(targetNode, config);
}
//mounted 中调用
// 为了避免热更新时 dom 不从新加载而导致监听生效,所以加一个判断
mounted() {if(document.querySelector(".swiper").childElementCount == 0) {this.observeSwiper();
}
else {this.handleDom()
this.startTimer()}
}
②监听 <img> 的 onload 事件,加载实现后给 swiper 组件传入一个属性,通过 watch 监听该属性
<img :src="item.img" @load="imageLoad" alt="轮播图片" />
imageLoad() {if(!this.isLoad) {this.$emit('swiperImageLoad');
this.isLoad = true;
}
}
<home-swiper @swiperImageLoad></home-swiper>
swiperImageLoad() {this.handleDom()
this.startTimer()}
3. 商品数据的保留构造
申请三种数据,依据点击取出不同的数据。思考到加载工夫,不应该一次性申请全副数据
goods 对象来保留数据
goods: {'pop': {page: 1,list: {}}, //page 保留当前页数,list 记录所有数据
'new': {page: 1,list: {}},
'sell': {page: 1,list: {}}
}
4. 滚动事件
在 mounted 中监听滚动事件,来决定显示暗藏返回顶部按钮
mounted() {window.addEventListener('scroll', () => {
this.scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
this.isScroll = this.scrollTop > 200 ? true : false;
})
},
为了平滑回滚到顶部,应用了 scrolltoView
backTopClick() {
// document.documentElement.scrollTop = 0;
document.querySelector('#home').scrollIntoView({behavior: "smooth", block: "start", inline: "nearest"})
},
实现上拉加载,须要用到 clientHeight 和 scrollHeight
clientHeight 即 height+padding,是元素可视区域高度
scrollHeight 是内容的理论高度 + 高低 padding< 如果没有限度 div 的 height,即 height 是自适应的,那么 scrollHeight=clientHeight>
拉到底即 scrollTop = scrollHeight – clientHeight
防抖与节流解决
防抖 debounce:第一次触发事件时,不立刻执行函数,而是给出一个期限。如果期限值内没有再次触发事件,则执行。如果期限值内再次触发事件,则从新开始计时
debounce(fn, delay) {
let timer = null;
// 利用闭包,保护全局污浊
return function() {if(timer) {clearTimeout(timer);
}
else {setTimeout(fn, delay);
}
}
}
然而思考到 this 的指向问题,因而应用 apply 来批改 this 指向
debounce(fn, delay) {
let timer = null;
// 利用闭包,保护全局污浊
return function() {if(timer) {clearTimeout(timer);
}
timer = setTimeout(() => {fn.apply(this)
}, delay);
}
},
如果思考到参数问题(以及 event 对象),则须要传入 arguments,这也是应用 apply 的益处,不便传参
节流 throttle: 节流是指每隔一段时间只执行一次事件,也就是第一次触发后必须要距离一段时间后能力触发下一次
节流有两种计划:工夫戳和定时器
// 工夫戳相减
throttle(fn, delay) {
let previous = 0;
return function () {let now = +new Date();
if(now - previous > delay) {fn.apply(this);
previous = now;
}
}
},
5. 非父子组件通信 —— 总线
总线:bus
Vue.prototype.$bus = new Vue()
this.bus.emit(‘ 事件名称 ’, 参数)
this.bus.on(‘ 事件名称 ’, 回调函数)
6. 组件的 offsetTop 问题
- 对于组件,是无奈获取 offsetTop 等属性的,能够用组件属性 $el 来获取组件中的元素
- 因为资源加载会对高度造成影响,因而如果在 mounted 中获取 offsetTop 是不可行的,能够监听轮播图的 @load 事件
7. 来到页面时记录状态和地位
来到首页时,首页组件就会销毁,因而对 router-view 外层加一个 <keep-alive> 包装,使其被缓存
然而 keep-alive 并不会记录缓存地位。因而来到时保留一个地位信息
这里在 beforeRouteLeave 中把 this.scrollTop 保留到 this.$route.meta.y 中,而后应用 scrollBehavior
scrollBehavior(to, from, savedPosition) {return to.meta},
这里一开始是在 deactive 中记录滚动地位,但发现这样会将数据保留到切换后的路由元信息中
8. 跳转详情页并携带 id
用 params 形式携带 id
{
path: '/detail/:iid',
component: Detail,
}
itemClick() {
// 要有后退,所以用 push
this.$router.push('/detail/' + this.goodsItem.iid)
}
9. 详情页问题
数据不从新申请:keep-alive 要设置 exclude=”Detail”
组件应该面向一个对象,因而应用了一个类将数据进行整合
export class GoodsInfo {constructor(itemInfo, columns, services) {
this.title = itemInfo.title;
this.desc = itemInfo.desc;
this.newPrice = itemInfo.price;
this.oldPrice = itemInfo.oldPrice;
this.discount = itemInfo.discountDesc;
this.columns = columns;
this.services = services;
this.realPrice = itemInfo.lowNowPrice;
}
}
详情页要暗藏掉 tabbar,能够用 watch 监听路由 * 未实现
与服务器替换数据须要工夫戳,而显示时要把工夫戳转换为日期
formatDate(date, fmt) {
// 获取年份
if (/(y+)/.test(fmt)) {
//yy - 19
//RegExp.$1 是与正则表达式匹配的第一个子匹配 (以括号为标记) 字符串
//RegExp 对象会保留最近的正则表达式匹配
//substr 截取字符串
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
}
// 匹配位数不同,且月日时分秒都须要补零,所以和年的匹配离开
let o = {
// 匹配一个或多个 M
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds()};
for (let k in o) {if (new RegExp(`(${k})`).test(fmt)) {let str = o[k] + '';
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));
}
}
return fmt;
};
// 传入一位,则截取一位,返回后果为两位
function padLeftZero (str) {return ('00' + str).substr(str.length);
};
详情页举荐商品间接应用 GoodsList 组件,然而 GoodsList 中图片援用的对象不同,因而在组件中增加一个计算属性
computed: {showImage() {return this.goodsItem.img || this.goodsItem.show.img}
},
标题栏和滚动地位的互相对应:获取每个元素的 offsetTop,然而要留神在哪获取。如果在 mounted 中,显然有的组件还没有数据,如果在申请数据的 then 中获取,也会因为渲染线程不同拿不到数据。
能够在 updated()中获取,然而要留神这样会频繁获取。
因而,能够在用 then 中用 this.$nextTick(),它将回调提早到下次 DOM 更新循环之后执行。在批改数据之后立刻应用它,而后期待 DOM 更新
留神!!间接应用数组下标赋值是无奈即时更新数据的
this.$nextTick(() => {this.scrollKey = []
this.scrollKey.push(0);
this.scrollKey.push(this.$refs.param.$el.offsetTop - 44);
this.scrollKey.push(this.$refs.comment.$el.offsetTop - 44);
this.scrollKey.push(this.$refs.recommend.$el.offsetTop - 44);
})
然而这样仍然存在问题:nextTick 执行时示意渲染实现,但图片可能还未申请加载完,因而最好还是将其放在监听 img 加载的事件中,而后加上防抖或节流
10. 混入
混入 (mixin) 提供了一种非常灵活的形式,来散发 Vue 组件中的可复用性能
// 定义一个混入对象
var myMixin = {created: function () {this.hello()
},
methods: {hello: function () {console.log('hello from mixin!')
}
}
}
// 定义一个应用混入对象的组件
var Component = Vue.extend({mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"
当组件和混入对象含有同名选项时,这些选项将以失当的形式进行“合并”。
比方,数据对象在外部会进行递归合并,并在发生冲突时以组件数据优先。
同名钩子函数将合并为一个数组,因而都将被调用。另外,混入对象的钩子将在组件本身钩子 之前 调用。
值为对象的选项,例如 methods
、components
和 directives
,将被合并为同一个对象。两个对象键名抵触时,取组件对象的键值对。
11. 购物车
因为一开始是没有购物车商品组件的,所以不能用事件总线实现增加商品到购物车,因而用 Vuex 来保留
个别 mutations 中只实现繁多工作,因而将其余批改工作放在 actions 中,用 actions 散发
actions: {addCart(context, payload) {
// 先判断商品是否已增加
//find:返回满足条件的 item
let product = context.state.cartList.find((item) => {return item.iid === payload.iid})
if(product) {context.commit('addCount', payload)
}
else {
payload.count = 1
context.commit('addToCart', payload)
}
}
},
mutations: {addCount(state, payload) {payload.count++},
addToCart(state, payload) {state.cartList.push(payload)
}
}
mapGetters
mapGetters 辅助函数是将 store 中的 getter 映射到部分计算属性
computed: {
...mapGetters(['cartLength'])
}
Toast 弹窗
退出购物车时弹窗,因而对 actions 中退出购物车办法用 promise 包裹,在 then 中进行解决操作。为了不便复用,因而将其封装为插件。
//index.js
import Toast from "@/components/common/toast/Toast";
const obj = {}
obj.install = function (Vue) {
// 创立组件结构器
const toastConstructor = Vue.extend(Toast)
//new 创立组件结构器组件对象
const toast = new toastConstructor
// 组件对象手动挂载
toast.$mount(document.createElement('div'))
//toast.$el 就对应 div,而后增加到 DOM 上
document.body.appendChild(toast.$el)
Vue.prototype.$toast = toast;
}
export default obj
//main.js
Vue.use(toast)
new Vue({
router,
render: h => h(App),
store,
toast
}).$mount('#app')
//Detail.vue
this.$store.dispatch('addCart', obj).then(res => {this.$toast.showMessage(res, 1500)
})
12. 优化
1. 解决挪动端提早 -fastclick
2. 图片懒加载 lazyload
//main.js
Vue.use(VueLazyload, {loading: require('@/assets/img/common/placeholder.png')
})
//GoodsListItem.vue
<img v-lazy="showImage" alt="">
3.px2vw 转换 postcss
13. 响应式原理
<img src=”C:\Users\Asus\AppData\Roaming\Typora\typora-user-images\image-20210427165545066.png” alt=”image-20210427165545066″ style=”zoom:80%;” />
如何监听数据的扭转
Object.defineProperty 监听对象属性扭转
数据传入 Vue,通过
// 获取 key 进行遍历
Object.keys(obj).forEach(key => {let value = obj[key]
// 从新定义属性
Object.defineProperty(obj, key, {set(newValue) {
// 数据扭转时会执行 set,因而能够在这里监听
value = newValue
//dep.notify()},
get() {
// 获取数据时会执行 get
dep.addSub()
return value
}
})
})
如何确定数据扭转时要告诉哪些局部
公布 - 订阅者模式
// 发布者
class Dep{constructor() {
// 记录订阅者
this.subscribe = []}
addSub(watcher) {this.subscribe.push(watcher)
}
notify() {
// 告诉
this.subs.forEach(item => {item.update()
})
}
}
// 订阅者
class Watcher{constructor(name) {this.name = name;}
update() {// 更新时执行}
}
const dep = new Dep();
const w1 = new Watcher('')
dep.addSub(w1)
observer 阶段:给每个 data 创立不同 dep 对象
compiler 阶段:依据应用的 data 创立 watcher,例如 name 应用两次,则创立两个 watcher,退出到 sub 数组中
初始化 view: 依据 el 初始化 view
数据扭转时,调用 dep 对象的 notify,对每个 sub 数组中的对象遍历调用 update()办法,update()对视图 view 更新