乐趣区

关于vue.js:vue商城项目问题记录

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!"

当组件和混入对象含有同名选项时,这些选项将以失当的形式进行“合并”。

比方,数据对象在外部会进行递归合并,并在发生冲突时以组件数据优先。

同名钩子函数将合并为一个数组,因而都将被调用。另外,混入对象的钩子将在组件本身钩子 之前 调用。

值为对象的选项,例如 methodscomponentsdirectives,将被合并为同一个对象。两个对象键名抵触时,取组件对象的键值对。

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 更新

退出移动版