温叶博客 - 2023年8月
type: channel
link: https://wenye123.com/2023/08/
description: 温叶博客
items: 如何用绝对定位实现瀑布流
items: 如何实现错误重试

如何用绝对定位实现瀑布流
type: item
date: 2023-08-07T20:24:58+08:00
description: 前言瀑布流是一种布局方式，实现瀑布流有很多种方法，例如flex，grid布局，但或多或少有这样那样的问题。所以这里介绍的是最传统也是兼容性最好的方法，绝对定位布局。实现思路首先我们来看看瀑布流长啥样首先基于瀑布流的特点，我们很容易就能定义出这几个变量data() { return { containerWidth: 1230, // 容器的长度 colCount: 4, // 每列的个数 colGap: 10, // 每列的空隙 }; }然后为了区分元素，这里给元素进行编号(从1开始)，那么行编号和列编号也可以计算得出data() { return { num: 1， // 省略... } }, // 编号 const num = this.num++; // 当前行编号: 从1开始 const rowNum = Math.ceil(num / this.colCount); // 当前列编号: 从1开始 const colNum = num % this.colCount || this.colCount;瀑布流的特点就是先排满一行，但是因为元素有高有矮，所以底部显得参差不齐。在开启下一行的渲染时候，元素应该在当前高度中最短的列中进行生成，如上图所示，编号5的元素排在编号1的下面，因为当前编号1所在的列最矮，这个应该很容易理解。那这个是怎么实现的呢，其实很简单，就是用一个数组记录当前列的高度，下次渲染元素就渲染在最矮列下面，最高列的高度则是瀑布流的整体高度。created() { // 初始化第0列的高度 this.currheights = new Array(this.colCount + 1).fill(0); }, data() { return { // 省略... currheights: [] // 缓存当前每列的高度 }; }, computed: { // 当前列的最大高度 maxCurrHeight() { return Math.max(...this.currheights); }, // 当前最矮列的编号 minShortColNum() { let num = 1; for (let i = 2; i <= this.currheights.length; i++) { if (this.currheights[num] > this.currheights[i]) { num = i; } } return num; } }对于渲染一个元素来说，我们需要知道的无非是width, height, top, left，因为这里介绍的是不定高度的做法，所以height是渲染完元素才能知道，这也就导致实际一个列表，我们只能一个个渲染，即拿到上一个高度，根据当前列高度再渲染下一个元素的位置。容器长度，每列个数和间距大小都是确定的，那么width自然也是确定的，width = (容器长度 - (每列个数 - 1) * 间距) / 每列个数，这个很好理解；难点在于top和left怎么计算呢？left其实就是当前最矮列距离左边的距离，因为我们优先填满最矮的那列嘛const left = (this.minShortColNum - 1) * (width + this.colGap);top则需要分情况讨论，如果是第一行则为0，否则就是当前列(最矮列)的高度 + 边距。const currColHeihgt = rowNum === 1 ? 0 : this.currheights[this.minShortColNum]; const gap = rowNum === 1 ? 0 : this.colGap; const top = currColHeihgt + gap;完成例子这里给出上面瀑布流截图的具体实现例子，在线访问 瀑布流-定位实现 {{currheights}} 加载更多

如何实现错误重试
type: item
date: 2023-08-03T11:45:01+08:00
description: 前言在我们实际写业务过程中，可能会遇到一种需求，比如某个接口因为网络啥的原因不稳定 会报错，这时候我们就需要一种错误重试机制，比如失败我们就重试5次，5次还不行就算真的失败了，那这个功能具体需要怎么实现呢？实现接口本身是个promise，我们需要做的就是拦截promise的报错，在报错中加入重试机制。但是为了自定义的需求 我们可以将重试的控制权转移给调用者，以下是重试的主体代码。let retries = 0; function load(ps: () => Promise<any>, onError?: (retry: Function, fail: Function, retries: number) => void) { // 拦截报错 将报错控制权交给onError回调 return ps().catch((err) => { if (!onError) throw err; return new Promise((resolve, reject) => { // 重试函数 const retry = () => { resolve(load(ps, onError)); retries++; }; // 失败函数 const fail = () => { reject(err); }; // 执行错误回调 onError(retry, fail, retries); }); }); }在load方法中，如果接口本身响应正常就直接返回了，只有报错时候才能进入catch环节，这时候就需要记录重试次数，并将是否重试或者失败的控制权通过onError回调转移给使用者。以下是一个实际的使用例子// 模拟请求接口网络不好 需要请求5次才能成功 let count = 0; const api = () => { return new Promise((resolve, reject) => { if (count < 5) { reject("网络异常"); } else { resolve("请求成功"); } count++; }); }; // 调用接口 增加重试逻辑 load(api, (retry, fail, retries) => { // 小于指定次数则重试 if (retries < 5) { console.info("重试次数", retries); retry() } else { console.error(`超过重试次数 按照失败处理`); fail(); } });这里有一个需要注意地方，模拟请求接口是请求5次才能成功，意味着除了第1次请求，一共要重试4次才能成功。因为控制权在调用者手里，所以这里你也可以改成延时1s后再重试都可以。