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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理