首页swiper上下滑动

收藏

  swiper   https://uniapp.dcloud.net.cn/component/swiper.html 


首页有100条数据

如果渲染更多的swiper,那么数据列表的内容越来越多,导致创建了很多的节点,这时的vue中的different就需要对比很多次,造成内存消耗和内存占用


长列表渲染大量的节点导致的问题:

      占用大量的GPU资源,导致卡顿

     节点越多,就越耗费性能

长列表写法

<template>
    <view class='main'>
        <!--长列表-->
        <view :style="{ height:itemHeight * listAll.length + 'px' }" class='swiper'>
            <!--课时区域渲染的数据-->
            <view class='swiper-item' :style='{top:top+"px"}'>
                <view v-for='item in showList' :key='item.id' :style="{width:'100%',height:height+'px'}">
                    <image :src="item.skus[0].resources[0].resPath" class='goods-img'></image>
                </view>
            </view>
        </view>
    </view>
</template>
<script>
/*
    1. 如果加载10条,超过了怎么办?
    2. 加入swiper
*/
import { goodsPage } from '@/utils/api/goods.js'
export default{
    data () {
        return {
            listAll:[],    //所有数据 : 后端给的10W条数据
            showList:[],   //可视区域显示的数据
            itemHeight:100,  //每一条数据的高度
            showNum:3,     //每次加载到可视区域的数据
            top:0,         //偏移量
            scrollTop:0,   //滚动距离
            startIndex:0,  //截取数据的开始位置
            endIndex:0,    //截取数据的结束位置
            pages:{        //拿到后端的数据量
                current:1,
                size:100
            },
            height:0,       //可视区域高度
        }
    },
    onLoad() {
        //获取初始化数据
        this.getAllList();
        //可视区域高度
        this.itemHeight = this.height = uni.getSystemInfoSync().windowHeight;
    },
    //监听页面滚动
    onPageScroll( e ){
        //滚动距离赋值
        this.scrollTop = e.scrollTop;
        //截取数据范围
        this.getShowList();
    },
    methods:{
        //初始化拿到的后端数据
        async getAllList(){
            //拿到后端给的所有数据
            let { current , size  } = this.pages;
            let res = await goodsPage( { current,size });
            this.listAll = Object.freeze(this.convert( res.data.records ));
            //拿到截取后的数据
            this.getShowList();
        },
        //转换后端给的数据
        convert( record ){
            record.forEach( item=>{
                (item.skus).forEach( skusItem=>{
                    skusItem.resources = JSON.parse(skusItem.resources);
                })
            })
            return record;
        },
        //计算可视区域高度
        getShowList(){
            //滚动的距离
            this.scrollTop = this.scrollTop || 0;
            //滚动的距离/某一个元素的宽度 
            this.startIndex = Math.floor(this.scrollTop/this.itemHeight);
            //开始位置+每次加载几个数据
            this.endIndex = this.startIndex + this.showNum;
            //截取展示到可视区域的数据
            this.showList = this.listAll.slice( this.startIndex, this.endIndex );
            //item作为整除 , 随机滑时第一条数据的偏移量
            this.top = this.scrollTop - ( this.scrollTop % this.itemHeight );
        }
    }
}
</script>
<style>
.main{
    width: 100vw;
    height: 100vh;
}
.swiper{
    position: relative;
}
.swiper-item{
    width:100%;
    position: absolute;
}
.goods-img{
    width:100%;
    height: 100%;
}
</style>

虚拟列表的实现非两种

   列表高度固定

   列表高度动态

<template>
	<view class='main'>
		<swiper :style="{ height:height+ 'px' }" class='swiper' vertical @change='swiperChange'
			:current='swiperActiveIndex' :circular='true'>
			<swiper-item :style="{ height:height+ 'px' }" class='swiper-item' v-for='(item,index) in swiperList'
				:key='index'>
				<view class='goods-item' @click='show=true'>
					{{ item.title }}
				</view>
				<image @click='detailGoods(item.id)' :src="item.skus[0].resources[0].resPath"
					:style="{ height:height+ 'px' }"></image>
			</swiper-item>
		</swiper>

		<!--价格弹出层-->
		<u-popup :show="show" mode="bottom" @close="show=false">
			<view class="transition">
				<view class='transition-content'>
					<view>{{ changePrice( goods.price) }}</view>
					<view>{{ goods.title }}</view>
					<view style='display: flex;justify-content: space-around;'>
						<button open-type='share'>
							<view>分享</view>
						</button>
						<view @click='$u.debounce(fav, 500)' :class='goods.isFav==1?"fav-active":""'>收藏</view>
						<view @click="goPayment">立即购买</view>
					</view>
				</view>
			</view>
		</u-popup>
		<!--提示信息-->
		<u-toast ref="uToast"></u-toast>

		<!--选择规格-->
		<u-popup :show="isShow" mode="bottom" @close="isShow=false">
			<view>
				<view class='go-title'>
					<image :src='goodsCurrent.img'></image>
					<text>{{ changePrice( goodsCurrent.currentPrice) }}</text>
				</view>
				<view class='goods-list'>
					<view class='list-title' v-for='item in spec' :key='item.id'>
						<view class='list-type'>{{item.name}}({{item.children.length}})</view>
						<view class='list-info'>
							<view class='list-name' v-for='(k,index) in item.children' :key='index'
								:class='k.active?"active":""' @click="selectCommodity(item,k.name)">{{ k.name }}</view>
						</view>
					</view>
				</view>
				<view v-if="goodsCurrent.stock<=0">当前商品没了~</view>
				<view class='go-number'>
					<view>购买数量</view>
					<u-number-box :min="1" v-model="number"></u-number-box>
				</view>
				<view class='go-payment'>
					<button :disabled="goodsCurrent.stock<=0" @click="confirmOrder">立即购买</button>
				</view>
			</view>
		</u-popup>

	</view>
</template>

<script>
	import {
		goodsPage,
		addCollect,
		deleteCollect,
		getGoods
	} from '@/untils/api/goods.js';
	import {
		priceMixin
	} from "@/mixins/price";
	import {
		mapState
	} from 'vuex'
	export default {
		mixins: [priceMixin],
		data() {
			return {
				dataList: [], //所有数据
				swiperList: [], //dom展示的数据
				swiperLength: 3, //轮播图总数量
				pages: { //请求接口的参数
					current: 1,
					size: 10
				},
				height: 0, //可视区域高度
				currentIndex: 0, //当前展示数据在列表中的索引
				swiperActiveIndex: 0, //当前轮播图的索引
				show: false, //点击商品
				isShow: false, //点击立即购买
				goods: {
					img: '',
					title: '',
					price: '',
					isFav: '',
					id: '',
				},
				spec: [], //商品的规格
				goodsCurrent: {}, //当前选择商品的价格,图片,规格...
				number: 1,
			}
		},
		onLoad() {
			//获取初始化数据
			this.getAllList();
			//可视区域高度
			this.height = uni.getSystemInfoSync().windowHeight;
		},
		computed: {
			...mapState({
				info: state => state.user.info
			})
		},
		methods: {
			xxx(id) {

			},
			//获取到所有数据
			async getAllList() {
				let {
					current,
					size
				} = this.pages;
				let res = await goodsPage({
					current,
					size
				});
				this.dataList = Object.freeze(this.convert(res.data.records));
				//获得截取后的数据
				this.getShowList();
			},
			//转换后端给的数据
			convert(record) {
				record.forEach(item => {
					(item.skus).forEach(skusItem => {
						skusItem.resources = JSON.parse(skusItem.resources);
					})
				})
				return record;
			},
			//设置截取后的数据
			getShowList() {
				//起始位置
				let currentValue = this.dataList[this.currentIndex];
				//中间位置
				let nextValue = this.dataList[this.getDataIndex(this.currentIndex + 1)];
				//最后位置
				let prevValue = this.dataList[this.getDataIndex(this.currentIndex - 1)];

				//给要渲染的数据填充个数
				this.swiperList = new Array(3);

				//展示数据赋值操作
				this.swiperList[this.swiperActiveIndex] = currentValue;
				this.swiperList[this.getSwiperIndex(this.swiperActiveIndex + 1)] = nextValue;
				this.swiperList[this.getSwiperIndex(this.swiperActiveIndex - 1)] = prevValue;

				//当前展示商品
				let goods = this.swiperList[this.swiperActiveIndex];
				this.goods = {
					img: goods?.skus[0]?.resources[0]?.resPath,
					title: goods?.title,
					price: goods?.skus[0]?.currentPrice,
					isFav: goods?.isCollected,
					id: goods?.id
				}
			},
			//滑动轮播图触发的事件
			swiperChange(event) {

				let current = event.detail.current;

				if ([1, 1 - this.swiperLength].includes(current - this.swiperActiveIndex)) {
					//向下滑动
					this.currentIndex = this.getDataIndex(this.currentIndex + 1);
				} else {
					//向上滑动
					this.currentIndex = this.getDataIndex(this.currentIndex - 1);
				}

				this.swiperActiveIndex = current;

				this.getShowList();
			},
			getDataIndex(index) {
				if (index < 0) {
					return this.dataList.length - 1;
				} else if (index >= this.dataList.length) {
					return 0;
				} else {
					return index;
				}
			},
			getSwiperIndex(index) {
				if (index < 0) {
					return this.swiperLength - 1;
				} else if (index >= this.swiperLength) {
					return 0;
				} else {
					return index;
				}
			},
			//进入商品详情
			detailGoods(id) {
				uni.navigateTo({
					url: `/pages/goods?id=${id}`
				})
			},
			//收藏功能
			fav() {
				let {
					id
				} = this.goods;

				//要收藏
				if (this.goods.isFav == 0) {
					//接口
					addCollect(id).then(res => {
						console.log(res);
					})
					this.$refs.uToast.show({
						type: 'success',
						message: "成功",
					})
					//更新视图数据
					this.goods.isFav = 1;
					return;
				}
				//取消收藏
				if (this.goods.isFav == 1) {
					//接口
					deleteCollect(id).then(res => {
						console.log(res);
					})
					this.$refs.uToast.show({
						type: 'success',
						message: "取消收藏",
					})
					//更新视图数据
					this.goods.isFav = 0;
					return;
				}
			},
			// 立即购买选择规格
			goPayment() {
				this.isShow = true
				this.show = false
				//当前商品详情
				getGoods(this.goods.id).then(res => {
					let spec = []; //最后的数据结构
					let data = res.data;
					console.log(data)
					let specialSpecArray = data.specialSpecArray;
					let specialSpec = JSON.parse(data.specialSpec);

					let skus = data.skus; //选择出所有规格
					//重构skus
					for (let i = 0; i < skus.length; i++) {
						skus[i].resources = JSON.parse(skus[i].resources);
						skus[i].specialSku = JSON.parse(skus[i].specialSku);

						//重构skus数据结构
						for (let key in skus[i].specialSku) {
							skus[i][key] = skus[i].specialSku[key];
							skus[i]['img'] = skus[i].resources[0]['resPath'];
						}
					}
					this.skus = skus
					//选择默认当前规格
					let goodsCurrent = skus[0];
					this.goodsCurrent = goodsCurrent;

					//整理最后的数据结构
					specialSpecArray.forEach(v => {
						let children = []; //spec数据结构内部的chidlren
						//整理children的数据结构
						for (let i = 0; i < specialSpec[v.id].length; i++) {
							children.push({
								name: specialSpec[v.id][i],
								active: specialSpec[v.id][i] == goodsCurrent[v.id] ? true : false
							})
						}
						spec.push({
							id: v.id,
							name: v.name,
							children
						})
					})
					this.spec = spec;
				})
			},
			// 选择规格
			selectCommodity(item, name) {
				item.children.forEach(v => {
					v.active = false;
					if (v.name == name) {
						v.active = true
					}
				})
				// 切换数据,对应展示不同的数据
				// console.log(this.name)
				let currentObj = {};
				for (let i = 0; i < this.spec.length; i++) {
					this.spec[i].children.forEach(v => {
						// console.log(v)
						if (v.active) {
							currentObj[this.spec[i].id] = v.name
						}
					})
				}
				console.log(this.skus, currentObj)
				// 切换数据,对应展示数据
				for (let i = 0; i < this.skus.length; i++) {
					let item = this.skus[i];
					// console.log(this.skus[1], currentObj)
					let number = 0; //同价规格有几种情况
					for (let k in currentObj) {
						if (item[k] == currentObj[k]) {
							number++
						}
					}
					// 如果相等的次数一样,则表示命中,那么就会把这个数组中的对象提炼出来
					if (number == Object.keys(currentObj).length) {
						this.goodsCurrent = item
					}
				}

			},
			confirmOrder() {
				// 判断是否是登录状态
				if (!this.info.status) {
					return uni.navigateTo({
						url: '/login/login'
					})
				}
				//2. 组织传递的参数
				let {
					id,
					spuId,
					img,
					title,
					currentPrice,
				} = this.goodsCurrent;
				let params = {
					skuId: id,
					spuId,
					counter: this.number,
					img,
					price: currentPrice,
					number: this.number,
					title
				}
				console.log(params)
				//3. 进入确认订单页面
				uni.navigateTo({
					url: `/order/order/confirmOrder?goods=${  JSON.stringify(  params  )   }`
				})
			}
		}
	}
</script>

<style lang='scss'>
	.main {
		width: 100%;

		.swiper-item {
			display: flex;
			align-items: center;
			justify-content: center;
			width: 100%;
			height: 100%;
		}
	}

	.goods-item {
		position: absolute;
		z-index: 999;
		bottom: 64rpx;
		right: 34rpx;
		width: 90%;
		padding: 10rpx;
		background: #0E0E0E;
		border-radius: 36px 36px 36px 36px;
		color: #fff;
		overflow: hidden;
		white-space: nowrap;
		text-overflow: ellipsis;
		opacity: 0.5;
	}

	.transition-img {
		width: 100%;
		height: 770rpx;
	}

	.transition-img image {
		width: 100%;
		height: 100%;
	}

	.transition-content {
		background-color: #FFFFFF;
	}

	.fav-active {
		color: red;
	}

	/*立即购买*/
	.go-title {
		display: flex;
		align-items: center;

		image {
			width: 115rpx;
			height: 116rpx;
			background: #FFFFFF;
			border-radius: 7rpx 7rpx 7rpx 7rpx;
			border: 1px solid #707070;
		}

		text {
			font-size: 43rpx;
			font-weight: bold;
			color: #2C00FF;
		}

	}

	.goods-list {
		padding: 80rpx;

		.list-title {
			padding: 27rpx 0;
		}

		.list-info {
			display: flex;

			.list-name {
				background: #F7F7F7;
				border-radius: 7rpx 7rpx 7rpx 7rpx;
			}

			.active {
				border: 4rpx solid #2C00FF;
			}
		}
	}

	.list-name+.list-name {
		margin-left: 20rpx;
	}

	.go-number {
		display: flex;
		align-items: center;
		justify-content: space-between;
	}

	.go-payment {
		display: flex;
		align-items: center;
		align-items: center;
	}

	.go-payment button {
		width: 490rpx;
		height: 85rpx;
		color: #fff;
		background: linear-gradient(128deg, #525252 0%, #000000 100%);
		border-radius: 43rpx 43rpx 43rpx 43rpx;
	}
</style>

十、性能问题

    10.1 你做过哪些性能优化

        a> 冻结对象 : Object.freeze

            因为如果vue直接复制给data属性值,会走observer,使用Object.freeze冻结可以提升效率

            ****这个对象以后不能修改了

    10.2 假如后端一次性给你10W条数据,你如何把数据放在页面

        虚拟列表 、 长列表

前端拿到后端给的10w条数据,前端在页面上所呈现的数据是有限的(3个:从总的数据中截取出来)

十一、面试题

    1. 关于this.$nextTick

        干什么的:获取更新后的dom

        原理:是一个异步的行为

       class Vue{
   constructor( options ){
        options.created.bind(this)();

        this.$el = document.querySelector(options.el);
        
        options.mounted.bind(this)();
   }
   $nextTick( callback ){
        return Promise.resolve().then(res=>{
            callback()
        })
   }
}

new Vue({
    el:"#app",
    created(){
        console.log( '这是created' );
        this.$nextTick(()=>{
            console.log( 'created', this.$el );
        })
    },
    mounted(){
        console.log( 'mounted', this.$el );
    }
})



 2. 如果要在created中获取dom怎么办?

        异步就行了 , 比如使用$nextTick

十二、分包处理

    原因:所有分包大小不超过20M 单个分包/主包大小不能超过2M

    解决方案:小程序分包加载

    面试中:

        1. 小程序项目上线你会做什么?

            分包

        2. 小程序项目如果超过体积大小了怎么办?

            分包

    流程:

        1> 打开manifest.json源码视图

            "mp-weixin" : {
                "optimization":{"subPackages":true}
            },

        2> 打开pages.json进行分包

            pages:[],
            "subPackages": [
                {
                    "root": "login",
                    "pages": [
                        {
                            "path": "login"
                        },
                        {
                            "path": "bindPhone"
                        }
                    ]
                }
            ],

面试题:如果想让vue中的created放在mounted后执行

上代码

export default{
    async  created(){
        let res=await axios({
            url:'',
        })
        console.log(res)
        console.log('created')
    },
    mounted(){
        console.log('mounted')
    }
}

父子组件生命周期的执行顺序

image.png

js原生是一个单线程语言

event loop事件循环机制

先执行同步代码

再执行异步代码:可以操作执行时间的,不确定执行时间的事件

异步分为2类(微任务、宏任务)

微任务:promise.all promise.resolve.then……

宏任务:定时器

先执行微任务在执行宏任务

image.png

在上图的情况下,先执行微任务在执行宏任务,后比较两个setTimeout那个时间段先执行哪一个

image.png谁快先执行谁300秒   295秒   200秒  

十三、混入

混入就是在.vue文件【组件】,可复用功能【写js】,也能用生命周期

混入的缺点 :混入中的属性和方法,在组件中去使用,很难找到来源(难以维护)https://www.xuexiluxian.cn/blog/detail/d6411636820744289a735d55a465fb0a 




十一、2月24作业

    1. 项目登录做了,多去看看逻辑上哪里有问题?

    2. 如何把表格的数据放在页面上(导入)

    3. 如何实现架构图

    4. 数据重构 ex : 扁平化数据转换成多叉树 ...

        import XEUtils from 'xe-utils';




获取可是区域的高度

//获取可视区域高度

this.height = uni.getSystemInfoSync().windowHeight;//不包含tabbar
this.$el.clientHeight//包含tabbar


评论(

您还未登录,请先去登录
表情
查看更多

相关作者

  • 获取点赞0
  • 文章阅读量264

相关文章

联系小鹿线

咨询老师

咨询老师

扫码下载APP