首页
Search
1
解决visual studio code (vscode)安装时没有选择安装路径问题
138 阅读
2
Linux 下 Bash 脚本 bad interpreter 报错的解决方法
131 阅读
3
Arch Linux 下解决 KDE Plasma Discover 的 Unable to load applications 错误
107 阅读
4
如何在 Clash for Windows 上配置服务
77 阅读
5
Uniapp如何引入自定义样式文件
75 阅读
clash
服务器
javascript
全部
游戏资讯
登录
Search
加速器之家
累计撰写
1,061
篇文章
累计收到
0
条评论
首页
栏目
clash
服务器
javascript
全部
游戏资讯
页面
搜索到
624
篇与
的结果
2024-10-20
实现Promise的first等各种变体
本篇文章主要是想通过ES6中Promise提供的几个方法,来实现诸如first、last、none、any等各种变体方法!在标准的ES6规范中,提供了Promise.all和Promise.race两种,我们首先来了解下这两个方法是干嘛的,方便我们后面工作的展开。Promise.all中所有的Promise实例都处于完成状态,该方法才进入完成状态,否则任意一个被拒绝,则该方法进入拒绝状态,并舍弃其他所有完成的结果,拒绝原因是第一个被拒绝的实例的原因。Promise.race中任意的一个Promise实例变成完成状态或者拒绝状态,则race结束,race的结果即为第一个变成最终状态的结果!更详细的可以参考下阮一峰的文章Promise对象之Promise.all。 1. 准备工作 # 在开始编写各种变体方法之前,这里我们首先定义几个一会儿要使用的几个Promise实例:/** * 创建一个Promise对象的实例 * @param name {string} 该实例的名称 * @param flag {boolean} 返回的结果状态:完成还是拒绝 * @param diff {number} 延迟的时间 */ var createPromiseCase = (name, flag, diff) => { return new Promise( (resolve, reject) => { setTimeout( () => { flag ? resolve( name ) : reject( new Error( 'testPromise is error, name: ' + name ) ); }, diff ); } ); }; var p1_suc_100 = createPromiseCase( 'p1-suc-100', true, 100 ); var p2_suc_500 = createPromiseCase( 'p2-suc-500', true, 500 ); var p3_suc_300 = createPromiseCase( 'p3-suc-300', true, 300 ); var p4_fail_400 = createPromiseCase( 'p4-fail-400', false, 400 ); var p5_fail_200 = createPromiseCase( 'p5-fail-200', false, 200 ); 2. 各种变体方法 # 2.1 Promise.first # 场景:一个页面当前正处于loading状态,同时请求了多个接口,无论哪个接口正确返回结果,则loading效果取消!或者其他的要获取获取第一个完成状态的值。这里就要用到了Promise.first了,只要任意一个Promise实例变成完成状态,则Promise.first变成完成状态。其实这里并不适合Promise.race方法,因为第一个变成拒绝状态的实例也会激活Promise.race,if ( !Promise.first ) { // get first resolve result Promise.first = promiseList => { return new Promise( (resolve, reject) => { var num = 0; var len = promiseList.length; promiseList.forEach( pms => { Promise.resolve( pms ).then( resolve ).catch( () => { num++; if ( num === len ) { reject( 'all promises not resolve' ); } } ); } ); } ); }; } 调用方式:Promise.first([p4_fail_400, p2_suc_500, p3_suc_300]) .then(res => console.log(res)) // p3-suc-300 .catch(e => console.error(e)) 可以看到每次获取的p3_suc_300的值,因为p4是失败的状态,p2的完成状态没有p3快,因此这里获取到了p3的结果。 2.2 Promise.last # 与Promise.first对应的则是Promise.last,获取最后变成完成状态的值。这里与Promise.first不同的是,只有最后一个Promise都变成最终态(完成或拒绝),才能知道哪个是最后一个完成的,这里我采用了计数的方式,then和catch只能二选一,等计数器达到list.length时,执行外部的resolve。if ( !Promise.last ) { // get last resolve result Promise.last = promiseList => { return new Promise( (resolve, reject) => { let num = 0; let len = promiseList.length; let lastResolvedResult; const fn = () => { if (++num===len) { lastResolvedResult ? resolve(lastResolvedResult) : reject('all promises rejected'); } } promiseList.forEach( pms => { Promise.resolve( pms ) .then(res => { lastResolvedResult = res; fn() }) .catch(fn); } ) } ) } } 调用方式:Promise.last([p1_suc_100, p2_suc_500, p5_fail_200, p3_suc_300, p4_fail_400]) .then(res => console.log(res)) // p2-suc-500 .catch(e => console.error(e)) p2需要500ms才能完成,是最晚完成的。 2.3 Promise.none # Promise.none与Promise.all正好相反,所有的promise都被拒绝了,则Promise.none变成完成状态。该方法可以用Promise.first来切换,当执行Promise.first的catch时,则执行Promise.none中的resolve。不过这里我们使用Promise.all来实现。if ( !Promise.none ) { // if all the promises rejected, then succes Promise.none = promiseList => { return Promise.all( promiseList.map( pms => { return new Promise( (resolve, reject) => { // 将pms的resolve和reject反过来 return Promise.resolve( pms ).then( reject, resolve ); } ) } ) ) } } 调用方式:Promise.none([p5_fail_200, p4_fail_400]) .then(res => console.log(res)) .catch(e => console.error(e)) // then的输出结果: // [ // Error: testPromise is error, name: p5-fail-200, // Error: testPromise is error, name: p4-fail-400 // ] 两个promise都失败后,则Promise.none进入完成状态。 2.4 Promise.any # Promise.any表示只获取所有的promise中进入完成状态的结果,被拒绝的则忽略掉。if ( !Promise.any ) { // get only resolve the results Promise.any = promiseList => { let result = []; return Promise.all( promiseList.map( pms => { return Promise.resolve( pms ) .then( res => result.push( res ) ) .catch( e => { } ); } ) ).then( (res) => { return new Promise( (resolve, reject) => { result.length ? resolve( result ) : reject(); } ) } ) } } 调用方式:Promise.any([p1_suc_100, p2_suc_500, p5_fail_200, p3_suc_300, p4_fail_400]) .then(res => console.log(res)) // ["p1-suc-100", "p3-suc-300", "p2-suc-500"] .catch(e => console.error(e)) 2.5 Promise.every # 最后一个的实现比较简单,所有的promise都进入完成状态,则返回true,否则返回false。if (!Promise.every) { // get the boolean if all promises resolved Promise.every = promiseList => { return Promise.all(promiseList) .then(() => Promise.resolve(true)) .catch(() => Promise.resolve(false)); } } 调用方式:Promise.every([p1_suc_100, p2_suc_500, p3_suc_300]) .then(result => console.log('Promise.every', result)); // Promise.every true Promise.every([p1_suc_100, p4_fail_400]) .then(result => console.log('Promise.every', result)); // Promise.every false 3. 总结 # Promise还有各种方面的应用,很多类库也都实现了类似的方法,这里也仅仅是鄙人拙见,稍微实现了Promise的变体方法,加深下对Promise的理解。
2024年10月20日
3 阅读
0 评论
0 点赞
2024-10-20
页面中长列表滚动的优化
当我们有 10 万条或者更多的数据需要展示在页面中,我们应该怎样处理呢?这里主要的方式有两种: 懒加载:即监听 scroll 事件或使用IntersecionObserver监听; 可视区域的渲染:仅在可视区域展示数据,为保证滚动条的完整性,非可视区域使用占位元素的高度后者容器的位移来撑开。 1. 懒加载 # 懒加载的方式有两种:监听 scroll 事件或使用IntersecionObserver监听某个元素。 1.1 scroll 事件 # 监听滚动事件应该是我们使用的最多的事件了,当滚动条滑动到页面最底部或者将要滑动到最底部的时候,去加载下一页的数据。同时图片懒加载或者其他组件的懒加载也可以依赖于滚动事件!为了优化滚动事件,我们也会给滚动事件添加上防抖和节流。这是一个很常见的 demo,无限加载数据系列,当滚动条滑动到底部时,加载下一页的数据:监听滚动事件懒加载数据。请分别用微信和 QQ 扫下这个二维码体验一下,体验时,请点击“向左”的按钮关闭代码的部分: 其实我们发现会有一个很有意思的体验:在 android 中的微信和 QQ 中基本可以无限的滑动,在 iOS 的微信中也可以,但是在 iOS 的 QQ 中,当我们一直滑动时,需要滑动到底部,然后才能加载新的数据,才能继续滑动!这种原因是 iOS 不同的 webview 造成的。在 iOS 中,UIWebView 是在 iOS2 中出现的,性能更好的 WKWebView 跟随 iOS8 一起出现。从性能方面来说,WKWebView 会比 UIWebView 高很多,可以算是一次飞跃。它采用了跨进程的方案,用 Nitro JS 解析器,高达 60fps 的刷新率。同时,提供了很好的 H5 页面支持,类比 UIWebView 还多提供了一个加载进度的属性。同时,在 UIWebView 中,无论是页面的 body 滚动,还是 div 级别的局部滚动,都不会实时触发,而是在滚动结束后才会触发!可以分别体验下面的两个二维码,左侧的为 body 级别的滚动,右侧为 div 的局部滚动: 总结下滚动事件在不同的 webview 中的表现情况: 机型 body 滚动 局部滚动 iOS WKWebView 实时触发 实时触发 iOS UIWebView 无法实时触发 无法实时触发 Android 实时触发 实时触发 那么该如何优化滚动的性能呢?首先就是防抖和节流。在 Chrome 浏览器中使用performance.now()测试滚动每次触发的间隔,可以看到,频率大概在 16ms 左右,但很多时候我们并不需要这么快地触发我们的监听函数,而且,比如图片懒加载等场景,也要及时的更新一次,这时候就用到了防抖和节流!再有就是使用requestAnimationFrame与requestIdleCallback代替定时器。若使用定时器进行间歇性触发监听函数,会出现掉帧的情况,我们也知道setTimeout(fn, 100),不一定是正好在 100ms 后触发 fn 函数,而是等浏览器空闲之后才调用 fn 函数,当多次积累后就会产生掉帧的,那么使用requestAnimationFrame或requestIdleCallback,则浏览器会根据自己渲染的频率适时地执行回调。在追求高性能的渲染效果时,可以考虑用 requestIdleCallback()和 requestAnimationFrame()代替定时器。前者适合流畅的动画效果场景,后者适用于分离一些优先级低的操作逻辑,使用时需要考虑清楚 1.2 IntersecionObserver # 使用 IntersecionObserver 也可以实现无限滚动,比如在底部监听一个透明的元素,当该元素可见时就加载新的资源!demo: IntersecionObserver 实现无限滚动不过也应当看到的是 IntersecionObserver 的支持程度还不太好,如果要使用的话,还是需要 polyfill 方案!而且 IntersecionObserver 在图片懒加载和组件懒加载的过程中,非常有用,我们后续会进行了解! 2. 模拟滚动 # 模拟滚动最代表的例子就是iScroll,监听手势的 touchmove 事件,然后使用 CSS3 中的 transform 产生位移。不过在模拟滚动的过程中,若图片比较多时,可以感觉到滚动时有明显的卡顿感。查看这个样例:https://www.xiabingbao.com/demos/20190423/iscroll-scroll.html。这是利用 IScroll 中的scroll事件,无限地向页面中添加元素。不过当页面的数据是无限加载时,应当使用iscroll-infinite这个类库更好,这部分留到后面讲解! 3. 可视区域数据的渲染 # 上面的几种方法里,我们都是无限地直接往页面中添加元素,滑动的页码越大,页面中的 DOM 元素也就越多。但实际上,这是没有必要的,滚动到可视区域外的元素,用户是看不到的,也没不要保留在页面中,可以用一个占位元素或者 transform 撑开上面所有不可见元素的高度,让滚动条能够正常的上下滑动即可!撑开上半部分不可见区域的高度,有两种方法,一种是在容器内的最顶部设置一个占位元素,这个占位元素的高度就是消失的所有 DOM 的高度;再一个就是给容器或者占位元素一个 transform 的位移!这里我们使用给占位元素一个高度撑起顶部的滚动区域。 3.1 固定高度的 item # 若每个 item 的 height/margin/padding 等数据都是固定写死的,这种情况比较容易实现!获取一次数据后,不用每次都重新计算。每次滚动时,都要计算应当渲染列表的哪部分!start表示列表开始的位置,fixedScrollTop表示顶部占位元素的距离// 滚动处理函数 function handleScroller() { let lastStart = 0; // 上次开始的位置 const item = document.querySelector('.container .item'); const itemStyle = getComputedStyle(item); const itemHeight = item.offsetHeight + parseInt(itemStyle['marginTop']) + parseInt(itemStyle['marginBottom']); return function () { const currentScrollTop = Math.max(document.documentElement.scrollTop, document.body.scrollTop); const fixedScrollTop = currentScrollTop - (currentScrollTop % itemHeight); let start = Math.floor(currentScrollTop / itemHeight); // 可视区域开始渲染的位置 if (lastStart !== start) { lastStart = start; createDom(start, count, fixedScrollTop); } }; } 设置顶部滚动的高度:function createDom(start, count, height) { const container = document.querySelector('.container'); // container.style.transform = `translateY(${height}px)`; // 给容易一个位移 let div = document.createDocumentFragment(); // 创建占位元素 if (height) { let p = document.createElement('p'); p.style.height = height + 'px'; div.appendChild(p); } for (let i = start, len = start + count; i < len; i++) { let item = document.createElement('div'); item.className = 'item'; item.innerHTML = i; div.appendChild(item); } // 为了方便处理,我们这里采用了更新container中的全部元素 // 你也可以尝试只增加/删除首位的元素,中间元素不变 container.innerHTML = ''; container.appendChild(div); } 可以点击链接查看样例:可视区域渲染之 DOM 元素增减,审查元素可以看得更清晰,无论我们怎么滚动,.container中永远只有那么几个元素! 3.2 每个 item 的高度都不一定 # 待定,有个问题一直搞不定 3.3 DOM 的复用 # 重点来了,在上面的章节里,我们进行了进一步的优化,只渲染可视区域内的数据。但是还是存在一个重要的问题:频繁的改动 DOM,这样会频繁的引起页面的重绘。这里我们的思想是对页面中 DOM 元素进行复用,如下图中所示,从上面滑出的元素,可以直接定位下面再重新装填元素,反之亦然!可以点击链接查看 demo,在审查元素中我们可以看到,在滚动的过程中,DOM 并没有被删掉,而是改变了 transform,放到了最下面:可视区域渲染之 DOM 元素复用。// 更新页面中DOM元素的位置 function updateDom(start, count, itemHeight, height) { document.querySelector('.container .content').style.transform = 'translateY(' + height + 'px)'; for (var i = start, len = start + count; i < len; i++) { var index = i % count; var cssIndex = (i - start) % len; document.querySelector('.item' + index).innerHTML = i; document.querySelector('.item' + index).style.transform = 'translateY(' + itemHeight * cssIndex + 'px)'; } } 这里我们还是监听了元素的 scroll 事件,我们在上面一笔带过的iScroll-infinite类库,是使用了模拟滚动+DOM 复用。点击链接查看 demo:iScroll-infinite 实现的无限加载。 4. 总结 # 最有效的方案应当是模拟滚动+DOM 复用,既解决了滚动不能实时生效的问题,又能解决页面中 DOM 元素过多,造成滚动中卡顿的问题。总是想着要讲的有很多,不过最后好像哪方面都欠缺了一点,后面有机会再针对其中的某一个点再深入进行探讨!例如使用IntersecionObserver实现 Vue 组件的懒加载,如何自己实现一个简单的模拟滚动等。参考: 自定义事件;
2024年10月20日
8 阅读
0 评论
0 点赞
2024-10-20
基于观察者模式实现一个EventEmitter类
我们在之前的文章中讲解中发布订阅者模式,不太了解这种模式的,可以先点击查看之前的文章浅谈javascript设计模式之发布订阅者模式。从发布订阅者模式的思想出发,来实现一个EventEmitter模块。在实现一些基础类库时,我们是经常需要用到EventEmitter模块的,比如在微信中下载某个APP时,前端需要了解当前下载处在哪个状态:刚开始下载、在在下载中、下载的进度、下载已完成、下载失败等等,都是需要我们向发布事件的。那么对应的就是实现emit和on。 1. 简单的实现 # 一个简单的EventEmitter模块,是只至少要实现emit和on两个方法:class EventEmitter { handles = {}; on(evName, listener) { if (!this.handles[evName]) { this.handles[evName] = []; } this.handles[evName].push(listener); } emit(evName, ...args) { const handler = this.handles[evName]; if (handler) { for(let i=0, len=handler.length; i console.log(step)); // 监听下载的进度 downapp.emit('steps', 0); // 输出: 0 downapp.emit('steps', 30); // 输出: 30 downapp.emit('steps', 100); // 输出; 100 也可以被其他类继承后使用:class People extends EventEmitter { constructor() { super(); } start() { let num = 10; let timer = setInterval(() => { this.emit('countdown', num--); if (num console.log('Tom', num)); Tom.start(); const Jerry = new People(); Jerry.on('countdown', num => console.log('Jerry', num)); Jerry.start(); 2. 对EventEmitter扩展方法 # 基于emit和on我们可以扩展出多个方法,方便使用: 方法名 说明 on(eventName, listener) 监听所有触发的事件 emit(eventName, ...args) 主动触发事件 once(eventName, listener) 与on事件类似,但只监听一次事件 off(eventName, listener) 移除某个事件 offAll([eventName]) 移除所有的事件,若传入具体的eventName,则移除该名称下所有的事件 newListener 有添加新的监听器时触发 removeListener 有监听器被移除时触发 2.1 off # 在如何实现once的方法时,必须得先知道事件是如何移除的:class EventEmitter { off(evName, listener) { if (this.handles[evName]) { // 从数组中移除lisnter this.handles[evName] = this.handles[evName].filter(fn => fn!==listener); } } } // 接上面的People类 // const Tom = new People(); const listener = (num) => { console.log('Tom', num) } const listener2 = (num) => { console.log('Tom', num*num); } Tom.on('countdown', listener); Tom.on('countdown', listener2); Tom.on('countdown', listener); Tom.off('countdown', listener); // 移除所有的listener Tom.start(); // 只有listener有输出 通过Array.prototype.filter方法,过滤掉所有要移除的监听器。注意,跟为DOM添加和移除事件一样,不能传入匿名函数,例如下面的例子是不生效的,因为两个匿名函数虽然从函数体上看起来一样,但两者是不相等的:Tom.on('countdown', (num) => {console.log(num)}); Tom.off('countdown', (num) => {console.log(num)}); // 移除 Tom.start(); // 依然有输出 2.2 once # once,只要监听到第1个事件后,则立刻移除监听器,后续不再监听此事件:Tom.once('countdown', (num) => {console.log(num)}); // 10 Tom.start(); countdown事件会从10每秒输出一个数字,至到0为止。但我们使用once后,只输出了一个10后,则不再有后续的输出,说明监听器已经被移除。 2.3 newListener和removeListener # 这两者都是表示监听器有变化时,程序主动触发的:class EventEmitter { on(evName, listener) { if (!this.handles[evName]) { this.handles[evName] = []; } this.handles[evName].push(listener); // 有新增事件时,触发newListener this.emit('newListener', evName, listener); return this; } off(evName, listener) { if (this.handles[evName]) { // 从数组中移除lisnter this.handles[evName] = this.handles[evName].filter(fn => { if (fn!==listener) { // 监听器有移除时,触发removeListener this.emit('removeListener', evName, listener); return true; } return false; }) } } } 那么这两个如何使用的呢,其实once这一个监听事件,就能触发上面的这两个监听器。先添加一个countdown监听器,执行一次监听后,马上移除countdown中的监听器:const listener = (num) => { console.log('Tom', num) } Tom.on('newListener', (evName, listener) => console.log('newListener', evName, listener) ); Tom.on('removeListener', (evName, listener) => console.log('removeListener', evName, listener) ); Tom.once('countdown', listener); Tom.start(); 3. 完整的EventEmitter模块 # 我们从上面的章节中,已经了解了EventEmitter的原理,并实现了几个常用的模块。下面我们把完成的代码展示出来:class EventEmitter { handles = {}; on(evName, listener) { if (!this.handles[evName]) { this.handles[evName] = []; } this.handles[evName].push(listener); this.emit('newListener', evName, listener); return this; } once(evName, listener) { let fired = false; let magic = (...args) => { this.off(evName, magic); if (!fired) { fired = true; listener.apply(this, args); } } this.on(evName, magic); return this; } off(evName, listener) { if (this.handles[evName]) { // 从数组中移除lisnter this.handles[evName] = this.handles[evName].filter(fn => { if (fn!==listener) { this.emit('removeListener', evName, listener) return true; } return false; }) } } emit(evName, ...args) { const handler = this.handles[evName]; if (handler) { for(let i=0, len=handler.length; i
2024年10月20日
6 阅读
0 评论
0 点赞
2024-10-20
React:实现一个带有loading效果的按钮组件
在业务经常需要点击按钮去请求接口,在接口还未返回结果前,需要给用户一个等待的感觉,同时锁住按钮,防止产生二次点击!我们实现带有loading效果的按钮组件,主要是实现以下的几个功能: 有菊花或者圈圈的loading图标,且loading颜色与字体颜色相同; loading的过程中,点击无效; 1. 按钮的结构 # 针对第1个功能,我最开始是把接口返回结果之前的loading集成到了按钮中,这个loading是用多个div拼接而成的,而loading的颜色则是渲染div中的after伪元素的背景色:/** * .lds-spinner div:after { content: ''; display: block; position: absolute; top: 3px; left: 29px; width: 5px; height: 14px; border-radius: 20%; background: #f3434a; } */ render() { return ( { Array(12).fill(0).map((item, index) => ) } ); } 这种方式如果要loading效果跟按钮字体保持一个颜色,则需要通过props传入一个色值,然后在Button组件中才能修改。实在是不太方便,我既要在CSS中设置按钮的颜色,还要通过props传给Button组件,当按钮的字体颜色需要更新时,则需要修改两个地方。再一个,loading图案的大小也要跟着按钮的大小进行变化,而div整体不太好调整,能想到的方法是使用transform: scale来对loading图案放大或者缩小。这时,loading效果用SVG实现最好了,配上currentColor,能天然继承其父级元素的color值。再使用em单位适应按钮的大小:render() { return ( 立即领取 ); } 2. loading效果 # 按钮最少要接收2个参数,一个是表示是否正在loading中,再一个是接收click事件:/** .i-button { .i-icon-loading { margin-right: 8px; } } .i-button-loading { position: relative; &:before{ content: ''; position: absolute; background-color: #ffffff; opacity: 0.4; top: -1px; right: -1px; bottom: -1px; left: -1px; z-index: 1; transition: opacity .2s; border-radius: inherit; } } */ interface ButtonProps { loading: boolean; onClick: React.MouseEventHandler; } export default class Button extends React.Component { handleClick = (e: any) => { const { loading, onClick } = this.props; if (!!loading) { // 正在loading中时,直接返回 return; } if (onClick) { (onClick as React.MouseEventHandler)(e); } } render() { const { loading } = this.props; // 将loading效果提取出来 const iconNode = loading ? : null; // loading时添加loading的class const loddingClass = loading ? ' i-button-loading' : ''; return ( {iconNode} {this.props.children} ); } } loading的icon组件如下:/** .i-icon-loading { font-size: 36px; transition: transform .3s ease-in-out; transition: transform .3s ease-in-out; will-change: transform; display: inline-block; color: inherit; font-style: normal; line-height: 0; text-align: center; text-transform: none; vertical-align: -0.125em; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; .i-icon-loading-spin { display: inline-block; animation: loadingCircle 1s infinite linear; } @-webkit-keyframes loadingCircle { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg) } } @keyframes loadingCircle { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg) } } } * */ export default class IconLoading extends Component { render() { return ( ); } } 3. 总结 # 第一次开始使用React写项目,之前都是用Vue在写项目。用Vue写了几个项目了,想着换一种技术栈来写项目,React跟Vue有一些相似之处,不过还是有些不同的,感觉React中很多地方需要自己完善,比如react的路由守卫beforeEach,CSS没有scoped属性,老担心不同的组件之前会产生样式冲突,虽然可以使用*.module.css来规避,不过这样在jsx中写className就不太方便了!另一方面来讲,写React特别能锻炼自己的js能力,比如高阶组件、ES6等新特性的使用,同时react和typescript结合的特别好。以后,随着对React更深入的了解,当然,这些也就不是问题了!参考:https://www.zhangxinxu.com/wordpress/2014/07/svg-sprites-fill-color-currentcolor/
2024年10月20日
11 阅读
0 评论
0 点赞
2024-10-20
实现react组件的递归调用
项目中有侧边栏的层级菜单或者需要根据接口生成DOM树等操作时,这里就需要用到了组件的递归调用。在本篇文章里,我们使用React来简单实现下组件的递归。在React可以通过props和children来向子组件传递信息,不过在这里大致的思路都是一样的。我们先定义一下要渲染的json结构:list = [ { name: '1', children: [ { name: '1-1' }, { name: '1-2', children: [ { name: '1-2-1' } ] }, { name: '1-3' } ] }, { name: '2', children: [ { name: '2-1' } ] } ] 我们的Item组件可以这样实现:render() { const list: any = this.props.children || []; return ( { list.map((item: ItemData, index: number) => { return {item.name} { // 当该节点还有children时,则递归调用本身 item.children && item.children.length ? {item.children} : null } }) } ) } 最后调用这个Item组件:{this.list}
2024年10月20日
6 阅读
0 评论
0 点赞
1
...
32
33
34
...
125