首页
Search
1
Linux 下 Bash 脚本 bad interpreter 报错的解决方法
69 阅读
2
Arch Linux 下解决 KDE Plasma Discover 的 Unable to load applications 错误
52 阅读
3
Arch Linux 下解决 KDE Plasma Discover 的 Unable to load applications 错误
42 阅读
4
如何在 Clash for Windows 上配置服务
40 阅读
5
如何在 IOS Shadowrocket 上配置服务
40 阅读
clash
服务器
javascript
全部
游戏资讯
登录
Search
加速器之家
累计撰写
1,061
篇文章
累计收到
0
条评论
首页
栏目
clash
服务器
javascript
全部
游戏资讯
页面
搜索到
1061
篇与
的结果
2024-10-20
技术人员如何写好周报和日报
前端时间,阿里发布声明,取消了周报制度。虽然阿里取消了周报制度,但还是有很多公司或者部门,依然在写周报。还有团队为此专门开发了周报系统。周报其实是为了管理层能更多的了解员工在干什么,但在实施的过程中,却成为了员工的负担,经常需要想(编)这个周报怎么写,怎么把技术问题转为文字传达给上层。经常有人在纠结自己的周报或者日报该如何写,经常是自己写了很多东西,但又不知道怎么提炼,这里提供一个模板,供大家平时使用。 持续优化 xxx; 重构了 xxx; 梳理 xxx,总结 xxx; 排查 xxx 问题; 改进了 xxx 逻辑,性能得到提升; 通过 xxx 降低了 xxx 至 xxx; 为了 xxx 重新设计了 xxx; 为了 xxx 通过 xxx 完成了 xxx; 通过 xxx 优化了 xxx 为 xxx; 为了 xxx 将 xxx 应用到了 xxx; 通过 xxx 提高了 xxx 至 xxx; 为了 xxx 通过 xxx 将 xxx 集成; 为了 xxx 通过 xxx 成立了 xxx; 通过这个模板,相信大家,就不会沉迷到某个具体的代码中无法自拔。
2024年10月20日
3 阅读
0 评论
0 点赞
2024-10-20
node同构直出中多级缓存的使用
在之前的文章NodeJs:腾讯新闻构建高性能的 react 同构直出方案里,我们简单介绍了下缓存的使用,不过讲解的不深,这里我再着重讲解下。缓存有很多种方式: 前端缓存:例如 cookie, localStorage, 状态管理等,对单个设备有效; 浏览器缓存:设置 cache-control 或者 etag,对单个设备有效; nginx 缓存; 进程缓存:把数据缓存到进程中,无需额外的 I/O 开销,读写速度快;但缺点是数据容易失效,一旦程序出现异常时缓存直接丢失,同时内存缓存无法达到进程之间的共享。 分布式缓存:使用独立的第三方缓存,如 Redis 或 Memcached,好处时多个进程之间可以共享,同时减少项目本身对缓存淘汰算法的处理。 1. 前端缓存 # 因这种缓存通过只在单个设备(cookie,localstorage)或者访问周期内(mobx)有效,一般只是缓存一些跟用户相关的个性化数据。通常会缓存一些个性化的数据,例如用户今日是否已点击过某个按钮,引导性弹窗是否今天是否已弹出过,已请求到的用户相关的数据缓存到 redux, mobx 等中(避免重复请求接口)。 2. 浏览器缓存 # 网上已经有很多讲解浏览器缓存的文章了,这里我们只是简单的介绍下 Cache-Control 和 etag 的使用。 2.1 Cache-Control # Cache-Control 属于强缓存类型。 强缓存:不会向服务器发送请求,直接从缓存中读取资源,在 chrome 控制台的 Network 选项中可以看到该请求返回 200 的状态码,并且 Size 显示 from disk cache 或 from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。 设置Cache-Control: max-age=300后,则代表在这个请求正确返回时间(浏览器也会记录下来)的 5 分钟内再次加载资源,就会命中强缓存。 2.2 etag # etag 属于协商缓存。 协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况。 Etag 是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag 就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的 Etag 值放到 request header 里的 If-None-Match 里,服务器只需要比较客户端传来的 If-None-Match 跟自己服务器上该资源的 ETag 是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现 ETag 匹配不上,那么直接以常规 GET 200 回包形式将新的资源(当然也包括了新的 ETag)发给客户端;如果 ETag 是一致的,则直接返回 304 知会客户端直接使用本地缓存即可。 2.3 总结 # 强制缓存优先于协商缓存进行,若强制缓存(Expires 和 Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since 和 Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回 200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回 304,继续使用缓存。Cache-Control 通常用于静态资源的缓存,而且一般缓存时间都比较长,比如有 24 小时的,有 30 天的。而 Etag 的协商缓存通常用来缓存可变化的页面,例如展示数据的页面,这些页面的缓存根据内容的变化来进行缓存。我博客的页面则是使用了强缓存与协商缓存并存的策略,因为博客文章在生成之后,除非有错误修改,一般也不会再进行变动了。 3. 内存缓存 # 这里主要是 node 的进程缓存和远端的 redis 或 memcached 等缓存。 3.1 进程缓存 # 把数据缓存到进程中,无需额外的 I/O 开销,读写速度快;但缺点是数据容易失效,一旦程序出现异常时缓存直接丢失,同时内存缓存无法达到进程之间的共享。我们的 node 服务在启动时采用的是 cluster 模式,即按照当前机器中的 CPU 数量来启动进程。若我们把数据存储到本地进程后,读取数据也是非常的快。在这里我使用的是 lru-cache 模块,即采用的最近最少使用策略淘汰多余的多余的缓存。const LruCache = require('lru-cache'); const lru = new LruCache(50); // 最多缓存50条数据 lru.set('testKey', '123test', 1000 * 10); // 缓存10秒钟 const data = lru.get('testKey'); console.log(data); 3.2 redis 缓存 # 进程缓存之间的数据是无法共享的,因此可能会出现多个进程之间的数据产生不同步的现象。针对这个问题,我们可以把进程缓存的时间的设置的短一些,然后再读取 redis 缓存保持同步。若 redis 缓存也失效的时候,则通过 node 服务更新最新的内容。const LruCache = require('lru-cache'); const { getRedisCache, setRedisCache } = require('./redis'); const nodeLogger = require('./node_logger'); // 本地日志 const nodeAtta = require('./node_atta'); // atta上报 const lru = new LruCache(50); // https://www.npmjs.com/package/lru-cache const cache = { open: process.env.NEXT_APP_ENV === 'production' || process.env.NEXT_APP_ENV === 'pre', // 整个缓存的开关 openRedis: process.env.NEXT_APP_ENV === 'production' || process.env.NEXT_APP_ENV === 'pre', // redis缓存的开关 lruTimeout: 20, redisTimeout: 60, async get(key, lruTimeout) { if (this.open) { if (lru.has(key)) { return Promise.resolve({ value: lru.get(key), from: 'lru', // 缓存来自lru }); } // 穿透lru缓存时,进行上报,统计lru缓存穿透率 nodeAtta({ level: 'info', file: '[shell][cache]', fn: 'cache.get', msg: `penetration lru cache, key: ${key}`, }); try { if (this.openRedis) { const value = await getRedisCache(key); if (value) { // 不能直接调用this.set,会造成key无限不过期的 lru.set( key, value, (lruTimeout || this.lruTimeout) * 1000 ); return Promise.resolve({ value, from: 'redis', // 缓存来自redis }); } // 穿透redis缓存时,进行上报,统计redis缓存穿透率 nodeAtta({ level: 'info', file: '[shell][cache]', fn: 'cache.get', msg: `penetration redis cache, key: ${key}`, }); } } catch (error) { nodeLogger.error('getRedisCache error', error); nodeAtta({ file: '[shell][cache]', fn: 'cache.get.redis', msg: `${key}, ${error.message}`, }); } } return Promise.resolve({ value: null, from: null }); // 没有命中缓存 }, // 设置缓存 set(key, value, timeout) { if (this.open) { const lruTimeout = timeout && timeout.lruTimeout ? timeout.lruTimeout : this.lruTimeout; const redisTimeout = timeout && timeout.redisTimeout ? timeout.redisTimeout : this.redisTimeout; lru.set(key, value, lruTimeout * 1000); if (this.openRedis) { try { setRedisCache(key, value, redisTimeout); } catch (error) { // 设置Redis缓存失败 nodeLogger.error('setRedisCache error', error); nodeAtta({ file: '[shell][cache]', fn: 'cache.set.catch', msg: `${key}, ${error.message}`, }); } } } }, del(key) { lru.del(key); }, }; module.exports = cache; 3.3 接口缓存 # 我们可以把接口数据,页面整个 html 都缓存到内存缓存中,当遇到缓存后,则直接返回,否则进行后续的操作。注意: 开发过程中,想用req.path作为 key 来存储整个页面的 html,但后来发现一个问题,是页面会根据传入参数的不同,展示不同的页面,例如若 url 中有个参数noticetab,则首页中展示对应的大图。那么这时就要把参数考虑进去。同时,端外携带某个 noticetab 带入到端内后,也要展示这个大图,但参数会变成_addparams。因此,要么考虑这两个参数,要么考虑使用整个 url 来作为 key 进行缓存。同时,我们的抢金达人是在新闻客户端和新闻客户端极速版中,同时访问的。而且使用的是同一个 url,当时考虑的是 url+ua 来作为 key 进行缓存。可是用整个 url 来缓存的时候,又会因为扫描等带有随机参数的措施,产生缓存穿透,导致缓存失效,会把正常的缓存内容给挤掉。而且缓存整个页面时,粒度太粗,无法精细化控制。总结一下产生的问题是: 使用 path 缓存时,无法根据参数展示不同的参数; 带着参数缓存时,要么控制好对应的参数,要么使用整个 url; 使用控制好的参数,则当参数修改时,缓存的参数也要同步修改,不方便; 使用整个 url 时,容易因参数的变动,产生缓存穿透; 项目在不同的客户端内访问,展示的内容也不一样,要根据 ua 判断; 简单,但粒度太粗,无法精细化控制; 最终的方案是:接口缓存。按照接口进行精细化的缓存管理,可以设置每个接口的缓存时间,同时不受页面 url 的影响,而且也会根据 ua 的不同,请求不同的后端的接口。// request.ts const request = async ( url: string, options: RequestProps = { params: {} } ): Promise => { // 兼容主版与极速版 const app = /qqnewslite/.test(getUserAgent(options.req)) ? 'liteapp' : 'newsapp'; let params = options?.params?.cache ? { env } : { env, _: Math.random() }; params = { ...params, ...options.params, ...{ app } }; // cache: 设置缓存的时间,若cache为0则不缓存 // mock: 设置mock读取的接口地址,这里不表 if (/^\/v1/.test(url)) { // getApiOrigin根据当前的env环境和cache等参数,返回对应的接口地址 const serverUrl = getApiOrigin({ cache: params.cache, mock: params.mock, }); url = serverUrl + url; } }; 在服务文件 server.js 中,则读取/设置缓存数据:// server.js const apiHandler = async (req, res) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.header('Access-Control-Allow-Headers', 'X-Requested-With'); res.header('Access-Control-Allow-Headers', 'Content-Type'); res.header('x-cache-from', 'server-proxy'); const { path, query } = req; const { mock, env } = query; // 这里参数cache的名称跟缓存控制模块的名字冲突 if (!query.cache) { // 若没有设置缓存,则直接请求接口并返回 return req.pipe(requestApi(getApiOrigin(env) + req.url)).pipe(res); } const { url } = req; // 接口的url地址 const { value } = await cache.get(url); if (value) { return res.send(JSON.parse(value)); } const reqUrl = getApiOrigin(env) + url; try { const result = await requestApi(reqUrl); // 请求接口 if (result && result.code === 0) { // 当接口返回正常,且code===0时才缓存数据 cache.set(url, JSON.stringify(result), query.cache); } // 其他情况交给接入层处理 res.send(result); } catch (error) { nodeLogger.error('server.js[apihandle]', reqUrl, error); } }; 这里我们定义了几个规则,可以很方便的控制每个接口的缓存粒度: 每个接口都可以通过 cache 字段设置缓存的时间,若没有设置 cache 字段,则自动添加一个随机数字段; 每个接口是按照整个 url+参数来进行缓存的,若想穿透缓存读取新的数据,则在请求参数加入随机字段即可; 请求接口前,请求模块会根据当前的 ua 来请求主版或者极速版的接口,业务层不用关心; 缓存的模块即为进程缓存和 redis 缓存; 3.4 组件缓存 # 对一些不常更新的组件,例如head.jsx或foot.jsx等,我们可以将编译后产生的 html 进行缓存,这样在下次访问时,可以直接使用已编译好的内容。const getComponentCache = (Component) => { const { name, cache } = Component.type; // 若已经有缓存的内容,则直接返回 if (name && cache.has(name)) { return cache.get(name); } const html = renderToStaticMarkup(Component); // 若需要缓存,则缓存起来 if (cache && name) { cache.set(name, html, cache); } return html; }; 4. 总结 # 我们在上面讲解了缓存的手段和缓存的粒度。其实从最开始设想的直接缓存整个页面编译出来的 html,简直不要太粗暴,后面我们采用了接口粒度和组件粒度的缓存方式,能够更加精细化的控制。同时,多种缓存方式的结合,也能为我们提供更好的效果,当接口已经缓存后,那么在相应的一段时间内,浏览器缓存也是奏效的(Etag 一直保持不变),可以直接使用浏览器缓存,当接口数据更新后,浏览器缓存也会相应的更新。
2024年10月20日
3 阅读
0 评论
0 点赞
2024-10-20
第9页
service worker在新闻红包活动中的应用活动页面大多是单页面应用,加载js后,再通过js进行页面的渲染,因此js的加载速度直接影响了页面呈现的速度。这里是想通过service worker缓存静态资源,期望能直接从缓存中加载静态资源,加载页面的呈现速度➥Read Moreposted @2019/02/26基于webview的前端页面优化指南我们的业务主要是在移动端中使用和推广的,在移动端中,网络环境和内存都有不确定性,网络环境好的终端,不一定有好的内存;同样,有好内存的终端,也不一定都实时处在良好的网络环境下。因此,我们在针对移动端中应该有哪些优化呢?➥Read Moreposted @2019/02/26Vue单页面中进行业务数据的上报在单页面的应用中,我们应该怎样对用户行为和PV,进行更方便的埋点数据上报呢?➥Read Moreposted @2018/12/16js:如何截取含有表情的字符串若字符串中出现一些特殊字符,我们该如何截取才能不出现乱码的现象呢?➥Read Moreposted @2018/12/01Vue: 单页面应用如何保持登录状态在单页面应用中,若使用vuex管理登录状态时,经常会存在刷新页面后登录状态消失的问题,这个问题怎么解决呢?➥Read Moreposted @2018/11/20博客,就是一个折腾前几天,即使工作很忙,也从鲁迅的海绵里挤出了点水,来给自己的博客小小的改造了一番➥Read Moreposted @2018/11/19在红包活动中如何保障账户的安全我们经常在做一些福利活动来拉动用户的活跃度,提升APP的拉新和拉活。不过总会有些人在寻找我们活动中的漏洞,以此牟利。那么我们是如何预防这些别有用心的人的恶意攻击呢?这里我们就来聊聊是如何防范的➥Read Moreposted @2018/10/30单页面切换路由时的倒计时和Vue倒计时组件单页面中使用倒计时的注意事项,同时也分享一下我正在使用的倒计时组件➥Read Moreposted @2018/10/11Vue中的嵌套组件中数据无法实时更新的问题Vue中的嵌套组件中数据无法实时更新的问题➥Read Moreposted @2018/10/10实现了几个基于腾讯新闻客户端的h5前端基础组件在平时工作中做了不少基于新闻客户端的需求,在这些工作中,对其进行汇总提取,形成了这些npm基础组件➥Read Moreposted @2018/07/19单页面应用中js获取url中的参数单页面应用中以hash作为路由,那么在单页面中怎么能方便地获取到参数呢?➥Read Moreposted @2018/07/19如何在npm上发布你的package在自己平时工作或者学习中,总结出来的模块,我们可以将其提炼发布到npm上,这样即使有多个项目使用这个包时,也能方便地进行更新➥Read Moreposted @2018/06/30Vue中对数组特殊的操作Vue中对数组类型的数组是怎么操作的呢?我们看下Vue源码中的实现➥Read Moreposted @2018/06/20CSS中margin出现空白的问题当内部元素的高度加上外边距没有超过外部元素,却依然产生了滚动条,这是怎么回事呢?➥Read Moreposted @2018/06/09仿Vue中的双向数据绑定实现在使用Vue的过程中,给我印象最深的就是双向数据绑定,那么双向绑定的原理是什么呢,我们也来实现一个简单的双向绑定➥Read Moreposted @2018/06/08Vue与Git结合进行环境区分与自动化部署如何使用vue和git能够进行环境区分与自动化部署,让我们更加专注于代码的编写➥Read Moreposted @2018/06/07再见2017,你好2018类似的标题,不一样的心情。每年在写总结的时候,心里都是挺沉重的,原来才发现,这一年又白干了。活儿没少干,不过就是没挣到钱➥Read Moreposted @2018/01/04innerHTML对IScroll组件的影响使用innerHTML与appendChild会对IScroll组件产生什么影响呢➥Read Moreposted @2017/12/13移动端里的逐帧动画第一次做逐帧动画没有经验,想当然的用两张或三张图片做显示隐藏的切换,结果在手机里的动画效果简直没法看,这时,才想起了steps➥Read Moreposted @2017/11/27基于webpack搭建前端工程的思考webpack是一个前端打包构建工具, 功能强大,本篇也是基于webpack简单探讨下前端的工程化➥Read Moreposted @2017/10/13
2024年10月20日
3 阅读
0 评论
0 点赞
2024-10-20
前端业务的监控与埋点数据的上报
我曾经在 2018 年 12 月底的时候,发表过Vue 单页面中进行业务数据的上报,现在这 1 年多以来,我对此也有了更深的理解。这里,我们还是主要探讨业务数据的上报,关于页面性能和错误日志的收集上报,不在此讨论范围内。我们前端团队的业务数据上报功能,主要集中在新闻客户端内部,依赖于新闻客户端提供的特性,因此其他团队的前端上报组件是没办法适应我们的需求的,这里我本人基于之前上报的经验,开发了一套完整的前端业务埋点数据上报组件。在开发这套组件之前,我们要先明确几个问题: 哪些数据是可以自行收集的,哪些数据需要开发者埋点上报; 开发者进行数据埋点的方式有哪些? 如何保障设备的唯一性; 跳转前的上报丢失如何处理; 在新闻客户端、微信、QQ 等环境都可以进行上报; 单页面应用中,切换页面时如何上报; 用户并未退出页面,而是切换了 APP,又重新切换回来怎么办? 是否支持自定义上报的方式? 上面的这些疑问,我们一一进行了解决。 1. 可以自行收集的信息与设备唯一性 # 很多信息都是可以自行收集,而不用每个开发者都收集一遍,例如基于新闻客户端提供的能力,可以拿到设备信息,已登录用户的个人信息,ua 等,都是可以自行收集的,然后存储起来。不过在新闻客户端外,就无法拿到设备信息了。我在这里,通过前端生成一个尽量唯一 的码来标识设备,并存储到 cookie 和 localStorage 中,若存在则使用,若不存在则生成一个新的。以此来保证设备的唯一性。let uid = cookie.getCookie('wzpuser') || localStorage.getItem('wzpuser'); if (!uid) { uid = 'gh' + Date.now() + (Math.random() + '').substr(-6); cookie.setCookie('wzpuser', uid, 360); localStorage.setItem('wzpuser', uid); } this.__data.uid = uid; return Promise.resolve(this.__data); 开发者可以更加关注在页面和用户行为上。 2. 开发者进行数据埋点的方式有哪些? # 主要有 2 种方式,一个是开发者的主动上报:reporter.send({ pagename: 'mainpage', event_id: 'banner_click', }); 还有一种方式,是在 html 元素上添加data-reporter属性,然后进行事件冒泡,若遇到 data-reporter 后,则进行上报;若一直冒泡到 document 上都没有该属性,则不上报。// 渲染每一个专场 const renderItem = (item: SpecialItem) => ( handleAnswer(item.ename, item.zhname, item)} > ); 3. 跳转前的上报丢失如何处理 # 我们经常需要上报点击某个链接的 pv,但通常会存在上报还没有完成,页面已经卸载了,导致上报失败。针对这种情况,我是在跳转前,先存储到本地,等下次重新回到这个页面时,将存储的数据进行上报。为了方便传入和存储,我对字段也进行了过滤,没有数据的字段全部去掉,只上报有数据的字段。reporter.send( { pagename: 'mainpage', event_id: 'link_click', }, 'save' ); // 先进行存储 window.location.href = 'https://www.xiabingbao.com'; 4. 在新闻客户端、微信、QQ 等环境都可以进行上报 # 新闻客户端中提供了一些获取设备信息和用户信息的能力,而微信、QQ、浏览器等环境,只能使用前端的一些骚操作了,例如分析 cookie 中的字段,ua 中的一些字段等等。这里我也编写了一个工具方法https://github.com/wenzi0github/utils,将这些能力独立出来。欢迎 star。 5. 单页面应用中,切换页面时如何上报 # 这个问题没有很好地解决,虽然能监听到 history 路由和 hash 路由的变化(前端中的 hash 和 history 路由),但页面的名称需要提前定义好,而组件是不知道的,因此这里交给开发者开进行监听路由的变化并上报数据。// 每个hash路由的PV上报 router.afterEach((to) => { // to为当前已打开的页面,to.name为在router/index.ts中设定的name reporter.send({ pagename: to.name, event_id: 'pv', }); }); 6. 用户并未退出页面,而是切换了 APP,又重新切换回来怎么办? # 用户没有退出页面,而是使用了系统级的 home 键,进行了 APP 的切换,其实用户也是真实的离开了,若用户重新回来,其实 page view 应该是要+1 的,然而页面没有刷新,会产生丢失 PV 的情况。这里我们可以监听页面的可见性,当页面重新可见时,我们就把 pv+1。当然还有一种情况要考虑到:用户可能只是在多个 APP 中切换,当前页面只是其中的过客而已,而用户并没有真的进入到这个页面中。因此我们需要设置下重新上报 PV 的条件: 距离上次可见时有 30 分钟左右; 当前可见已有 5 秒以上; let lastShowTime = 0; // 上次可见时的时间 const visibility = new PageVisibility(); visibility.visibilityChange((isShow) => { let timer; if (isShow && Date.now() - lastShowTime >= 1000 * 60 * 30) { // 页面可见时 timer = setTimeout(() => { report.send({ pagename: 'mainpage', event_id: 'pv', }); }, 5000); // 5秒后上报 } else { clearTimeout(timer); } }); 7. 是否支持自定义上报的方式 # 组件中提供了 img 标签上报和 post 方式的方式,如果这两种方式也不满足上报要求,可以自定义上报方式,并上报到对应的服务器上。const report = new Report({ actid: 56, adapter: (data) => { // 要上报的数据 console.log(data); }, }); 8. 总结 # 通过这次业务埋点数据的上报,也了解了很多基础功能的实现,同时还有如何提供自定义的能力。
2024年10月20日
2 阅读
0 评论
0 点赞
2024-10-20
如何在 react 中使用 if-elseif-else 多重条件判断
使用过 Vue 的同学们都知道,在 Vue 中有v-if, v-elseif和v-else的语法糖,方便我们进行流程控制。但 react 中使用的 jsx,并没有这些语法糖,而是使用一些其他方式来实现条件控制的。 1. react 中的条件渲染 # 在 react 中有多种实现条件渲染的方式,我们每个稍微介绍一下。 1.1 与运算符&& # 当前面的表达式为真时,则执行后面的表达式。相当于if。// 当showModal变量为真时,则渲染弹窗,否则不显示 {showModal && } 这里需要注意的是,当使用长度来控制的时候,要使用判断表达式,而不是直接用.length来判断,// 要判断length>0,若只用list.length判断时,最后会渲染出来一个0 {list.length > 0 && } 1.2 三目运算 # 当需要非黑即白时,这里就用到了三目运算。相当于if-else:{list.length>0 ? `您一共有${list..length}条数据` : '您暂时还没有数据'} 1.3 立即执行或者单独的方法渲染 # 当需要更复杂的判断时,例如 3 个及以上的情况,或者多个判断条件时,即if-elseif-else的类型时,情况就稍微复杂一些。不过我们依然也有多种方式来实现。 多个嵌套的三目运算; 立即执行方法; 单独的方法; 1.3.1 多个嵌套的三目运算 # 嵌套的三目运算,也能作为多个 if 判断,但看起来实现逻辑有点乱:const Home = () => { return {A ? a right : B ? b right : else}; }; 1.3.2 立即执行 # const Home = () => { return ( { (() => { if (A) { return a right } if (B) { return b right } return else })(); } ); } 1.3.3 单独的方法 # const Home = () => { const projectStatus = 1; const renderItem = (item) => { if (projectStatus !== 1) { return 活动暂未开始或已结束; } if (item.status === 1) { return 点击参与; } return 已无库存; }; return {list.map((item) => renderItem(item))}; }; 立即执行方法和单独的方法,都把判断逻辑进行了割裂。如果我依然想在 render 中的 jsx 中,写判断怎么办呢? 2. 新的方式 # 这里我一直想着用 Vue 中的方式一样,无需将条件判断独立出来,而是直接在顺着整体的思路往下写。这里我就封装了一个if-elseif-else的组件。 2.1 调用方式 # 先看下使用方式,然后再看代码实现。import { When, Case } from './when'; const Home = () => { return ( {status === 0 ? '活动还未开始' : '活动已结束'} = 100}> 该项目已捐满 捐热力值帮助TA ); }; 这里用When进行包装,然后每个Case对应 1 个判断条件,从上往下,若某个为真,则直接渲染 children 中的内容,否则接着往下判断,直到最后。这样实现起来,流程也比较顺,然后条件判断也很清晰。 2.2 代码实现 # 我们在这里直接贴代码,看看是怎么实现的:import React from 'react'; const isArray = (arg: any): boolean => { if (Array.isArray) { return Array.isArray(arg); } return Object.prototype.toString.call(arg) === '[object Array]'; }; interface CaseProps { if?: boolean; elseif?: boolean; else?: boolean; children: JSX.Element | JSX.Element[]; } export const Case = (props: CaseProps) => { return {props.children}; }; export const When = ({ children }: { children: any }): JSX.Element | null => { if (!children) { return null; } let schildren: any = []; if (isArray(children)) { // 当存在多个case节点时,children为数组 schildren = children; } else { // 当只有一个case节点,children为object类型 schildren.push(children); } // 判断所有的子节点是不是Case类型 schildren.forEach((child: any) => { if (!child.type || child.type.name !== Case.name) { throw new Error('the children of component When muse be component Case'); } }); // 查找if的节点,若有则直接返回 const ifChildren = schildren.filter((item: any) => item.props.if); if (ifChildren.length) { return {ifChildren}; } // 再查找elseif的节点 const elseIfChildren = schildren.filter((item: any) => item.props.elseif); if (elseIfChildren.length) { // 这里只输出第1个为true的组件 return {elseIfChildren[0]}; } // 返回其他的节点 return {schildren.filter((item: any) => item.props.else)}; }; 最终实现起来也非常的简单,按照顺序查找相应的属性,然后进行返回即可。 3. 总结 # 在 react 中,条件判断的方式有很多,官方的文档就介绍了很多种,但要实现一些稍微复杂点的条件判断时,就会让代码显得很凌乱。这里我也就实现实现了一套多重判断的机制。
2024年10月20日
2 阅读
0 评论
0 点赞
1
...
39
40
41
...
213