Typescript从联合类型转化到交叉合并后的对象类型
我们有如下一个联合类型:
type union = { a: string } | { b: number } | { c: boolean }
如何得到最终的类型:
type obj = { a: string b: number c: boolean }
如果是用js来写,不要太简单,但是typescript没有对union的遍历能力,所以只能走其他办法,经过探索后最终得到如下解:
type GetUnionKeys<Unoin> = Unoin extends any ? { [key in keyof Unoin]: key; } extends { [key in keyof Unoin]: infer K; } ? K : never : never; type UnionToInterByKeys<Union, Keys extends string | number | symbol> = { [key in Keys]: Union extends any ? { [k in keyof Union]: k extends key ? Union[k] : never; } extends { [k in keyof Union]: infer P; } ? P : never : never; }; type UnionToInter<Unoin> = UnionToInterByKeys<Unoin, GetUnionKeys<Unoin>>;
在使用时如下:
type obj = UnionToInter<union>
这样就可以实现我们的目的。我写了两个原子范型,GetUnionKeys用于获取联合类型里面的全部key,UnionToInterByKeys是关键,用于基于前面获得的keys再得到最后的对象。
不过这个方法存在一定的风险,假如联合类型中存在相同的属性,那么可能存在不确定性,比如:
type obj = UnionToInter<{ a: string, b: number } | { b: boolean }>
我们最终就会得到:
type obj = { a: string b: number | boolean }
但是我们再开发中,常常会让后面一个覆盖前面的,我们期望得到的是:
type obj = { a: string b: boolean }
我之前在读一些博客的时候,有同学提到可以通过冲载机制得到联合类型中的最后一个元素,但是我没有找到这篇文章,如果你看到,请在下方留言给我。
Nodejs检查端口是否占用,如果被占用使用一个随机端口
在日常开发中,我们常常要自己启动一个server来进行本地调试,但是如果写死端口,就会导致端口冲突,怎么解决呢?
/* eslint-disable @typescript-eslint/no-require-imports */ const net = require('net'); function checkPortUsable(port) { return new Promise((resolve, reject) => { const server = net.createConnection({ port }); server.on('connect', () => { server.end(); reject(`Port ${port} is not available!`); }); server.on('error', () => { resolve(port); }); }); } function randomNumByRange(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function findUsablePort(port, minPort = 10000, maxPort = 65536) { const retry = () => { const port = randomNumByRange(minPort, maxPort); return findUsablePort(port, minPort, maxPort); }; return checkPortUsable(port) .then(() => port) .catch(retry); } module.exports = { findUsablePort, checkPortUsable, };
具体使用的时候,调用findUsablePort即可:
findUsablePort(4000).then((port) => app.listen(port))
如果4000可用,就会直接用4000,否则就会使用一个随机端口。
-
net.createConnection({ port })这种模式是建立客户端尝试连接该端口, 但是如果该端口已经被使用,但防火墙做了限制,应该也会触发连接错误; 会造成误判;
用创建服务监听的方式应该更完善一些.#1220 躁动de气球 2022-08-31 17:33
用外观模式实现跨端开发
外观模式即对外提供统一接口,底层实现随意。对于使用者而言,使用基于外观模式的库时,在任何情况下,想要调用某个功能时,都使用相同的api。在日常中,常见的外观模式就是不同浏览器,都基于HTML标准提供相同接口,但是实际上浏览器内部怎么实现这些接口的,完全不一样(当然,由于不一样的实现,在实际运行时也会导致运行效果差异)。
那么在跨端开发时,怎么做呢?我们要创建一个H5页面和一个RN界面,我们使用相同的接口调用某库,然后希望被调用的接口在web和原生上都能用。例如:
import SomeLib from 'some-lib' const pdf = SomeLib.print('xxx')
对于some-lib这个库而言,它提供 print 接口。在web和原生平台上都这样去使用,使用层面没有差异。但是,我们知道,web上和原生上调用系统的打印功能是不一样的,所以我们又要分开去实现不同平台上的具体能力。设计上怎么做呢?如下:
SomeLib.implement((text) => { console.log(text) })
这是在web上的实现,在原生上的实现可能需要借助原生提供的接口来做。对于some-lib这个库而言,它本身实际上没有实现打印这个过程,这个过程要开发者自己去实现。但是这个库却提供一系列用于打印的功能。
通过这种设计,我们就可以在应用的开发上做到比较方便的跨端开发。
-
和react有点像呢#1185 1188 2022-04-28 09:55
JS如何判断用户是否点击浏览器“退回”按钮返回上一个界面?
通过window的popstate事件可以监听history的变化,但是,“popstate会在浏览器前进后退操作、history.go/back/forward调用、hashchange的时候触发”,它是一个复合事件,你根本判断不了到底是哪种情况引起的popstate。更难理解的是,我不知道为什么要把forward也设计为popstate,pop这个词的意思不就是从stack顶把最上面的一个从栈中移除么?forward明显是push的行为,怎么也放到popstate里面。这个事件明显是有设计缺陷的。回到题目,我们怎么去判断用户是点击了“退回”按钮?
我们要用到一些取巧的办法。具体我总结为3步:
- 充实history stack,以提供更多信息让我们可以在用户刷新浏览器的情况下,仍然获得上下页信息关联
- 为history创建一个私有的记录值,用以区分history当前的state和上一个state(我们在事件回调中只能拿到当前state)
- 通过第一步和第二步铺垫的内容,在回调函数中进行判断,从而判断是否是用户点击了“退回”按钮
接下来我们进行实施。在此之前,我们需要了解一些简单的知识。history是浏览器用户记录用户浏览器历史的全局对象,既然是“浏览历史”,那么就是一组单一数据的列表(有顺序),这个所谓单一数据,就是其内部的state概念,一个state表示用户在浏览一个界面(对应一个url)时留下的痕迹,不过,这个state必须由开发者定义,如果开发者不定义,那么state就是null,而且为null的时候,就没哟意义。注意,浏览器不会主动帮你记录当前的url,虽然它自己记录在了浏览器内部,你可以通过浏览器的“浏览历史”功能查看,但是,你无法在代码层面直接读取这些历史记录,你只能读取history当前的state,即通过 window.history.state
来读取,当然,你也可以通过 window.location
来读取当前的url信息。“只能读取当前的”,也就意味着这是一种栈结构的数据管理,而且,这个栈在用户刷新当前浏览器tab时,仍然是维持的。
基于这一特性,我们可以自己在state栈中,构建一个链表结构,从而可以读取整个栈内的state链。具体怎么做呢?
我们知道history有pushState和replaceState两个接口,对于SPA应用而言,整个应用中只会使用pushState和replaceState两个接口进行url的跳转(还有一种是在a标签href中使用#触发,例如 <a href="#/base/xxx">
这种也可以做到无刷新的界面跳转,但是由于它走另外一个体系,也就是hashchange的体系,是比较老的技术,现在大部分框架都是基于state的体系来做,因此,我们本文不考虑hashchange这种方案),因此,我们可以对这两个接口进行改造,从而在跳转时,对state进行信息充实。
const originalPushState = history.pushState.bind(history); // 要求在调用pushState时state必须是一个对象 history.pushState = function(state, title, url) { const { state: currentState } = history; // 获取未跳转之前的state,也就是当前的state const nextState = state || {}; nextState.prevState = currentState; originalPushState(nextState); };
通过这一改造,我们重写了history.pushState,这样我们就让每一个被push到state栈中的state拥有了prevState属性,通过该属性就可以形成一条反向链表,用以追踪state之间的关系。
接下来,我们创建一个私有的变量,用来记录history.state发生变化前的state.
let latestState = history.state; window.addEventListener('popstate', () => { const prevState = latestState; const { state } = history; latestState = state; .... })
在每一次popstate被触发时,我们去修改latestState,这样,我们就可以记录在state发生变化前的state是哪一个。
最后,我们通过latestState和当前的state进行对比,来猜测用户是否点击了“退回”按钮。
window.addEventListener('popstate', () => { const currentState = latestState; const { state: nextState } = history; latestState = nextState; if (currentState?.prev === nextState) { // 用户点击了“退回”按钮 } })
当变化后的state正好是变化前state.prev时,我们就认为用户点击了“退回”按钮。当然,这里有一个点需要提前预设,因为在不同的SPA框架中,触发popstate的可能包含在代码中调用history.back()和history.go(-1)的情况,所以,在代码层面,需要继续去区分到底是程序里调用.back还是用户点击“退回”按钮。区分方法也很简单,你可以重写back和go方法,在里面做一个标记,从而在判断时增加对该标记的判断。
css-loader和file-loader/url-loader冲突无法显示图片
最近在调试的时候,同事反馈说图片没有展示出来,去看了以后简直莫名奇妙,因为我自认为对css-loader和file-loader的配置没有问题,难道css-loader又挖了什么坑?于是开始漫长的调试,测试过程。最终发现,问题是真的存在,而且我google了半天,恁是在国内外都没有找到靠谱的解决方法。调试过程中可以肯定的是,css-loader竟然自己把url()的图片生成到目录下了,然后file-loader又做了一遍:
这也就意味着一张图片被处理了两次,具体造成的问题是css-loader给的路径不对还是file-loader给的不对,就不想去深究,总之就是两个loader冲突了。按照我以前的理解,css-loader只负责把url()转化为require()的形式,并不负责生成真实的图片,然后file-loader可以识别require(image)的形式,并生成图片,两个loader分工明确。那么这里是为什么呢?于是,我把file-loader从rules中去掉,然后……图片竟然展示出来了!!
所以说,无论是css-loader的问题,还是结合webpack5一起的问题,现在webpack5+新版css-loader就可以自己把图片处理到目标目录下了。然而,问题在于,如果去掉file-loader,那么我们在js中是没有办法直接import一张图片作为url来使用的。这就矛盾了。而且检查了file-loader的配置,也没有接口可以让我们忽略对css文件中的图片进行排除。似乎就这样卡住了。
于是重新回去阅读文档。
果然,不独不知道,webpack5现在已经牛x到自己可以把非js的文件资源作为模块读取了。什么?不懂?意思就是,不需要file-loader了,webpack内置了工具!具体做法就是把我们配置文件中的 loader: 'file-loader'
一句改为 type: 'asset/resource'
就可以了。
webpack的rule.type在v4中就已经加入了,只是很少被用到,它的文档也说的不清不楚,我也不知道它有这个能力,而 asset/resource
也是到v5才悄悄加进去的,你可以查看v4的文档和v5的文档的差异。
新增的asset这个选项就是对非js相关的资源的处理,它文档单独通过一节对这些项目进行解释,而且里面就明确提到可以代替以前的file-loader, url-loader, raw-loader,然而,大哥你虽然可以代替,但是直接破坏原来的效果,是不是有点不厚道呀!
-
来 vite 保平安(逃
-
Cool!#1183 ayu 2022-04-16 16:09
-
Cool#1300 HduSy 2024-01-27 12:17
-
确实,如果升级后想用的话,可将`type: javascript/auto`,同时`esModule: false`#1301 HduSy 2024-01-29 19:06
项目CSS管理方案对比
在项目中,我们不仅要管理js代码,还要管理css代码,但是我们现在业界对js研究的比较多,对css比较少,不过这两年我注意到已经有不少有关css的新思想。不过,抛开这些新东西,我们的项目中,应该怎么管理我们的css呢?我做了调研,大致结果如下:
方案 | 解释 | 优点 | 缺点 |
Atomic CSS | Tailwind, https://acss.io/ | 复用性,css代码量 | html代码量,多处修改易漏 |
AMCSS | <div button="large blue">Button</div> | ||
OOCSS | 结构和设计的分离,容器和内容的分离 .mt20 { margin-top: 20px } -> 样式 .flex .flex1 { flex: 1 } -> 结构 .tc { text-align: center } -> 内容 .abs { position: absolute } -> 容器 .clearfix:after { content: '', display: block; clear: both; height: 0 } |
||
SMACSS | Base 基本规则,整体样式,比如 body、input、button、form Layout 布局规则,比如 顶部,页脚,边栏,模块的大小等 Module 可复用的模块样式规则 State 状态样式 比如 隐藏 当前高亮 Theme 主题 控制整体UI |
层次分明 | 需要整体掌控易出错 |
MCSS | multilayer CSS,层层覆盖叠加 | ||
BEM | .block__element--modifier {} | 代码量 | |
scoped css | vue | 隔离 | 依赖vue |
css modules | import * as Css from './some.css' | 隔离,tree shaking,编译为RN Stylesheet对象 | 依赖编译 |
css-in-js | styled-components | 隔离,组件化 | 依赖运行时,代码量 |
实际上,没有哪一种是最好的,只有适合不适合。对于我个人而言,样式隔离是一个必须选项,因为我开发工具库比较多一些,我提供库给别人用,别人可能还会使用其他人的库,两个库之间撞名的情况很容易发生,所以,隔离对我来说非常重要。在考虑到各种场景后,我最终使用css modules比较多,在nautil中我就坚持css和css modules一起用。不过,除了隔离之外,复用性就不是很好,因为很少能复用其他库的样式,最终会导致使用了我的库的站点,css代码量可能会篇多一些。所以我说,没有那个最好,只有最适合。
-
方案列为什么会折行呢#1174 1188 2022-03-18 14:35
-
页面宽度不够,已经调整了#1175 回复给#1174 否子戈 2022-03-18 19:48
今天遇到一个怪事,用Macbook,前几天还比较正常,今天刷B站的时候,视频卡的跟狗一样,重启了光猫,也是一样,在偏好设置里面搞了很久,也没有解决,但是平板上刷B站一点问题没有,难道它知道我是用网页或app?但是在另外一台windows上却也稳如老狗。于是一通搜索,在一个角落里面找到一句话,大概意思是“USB拖太多东西,导致网卡供电不足”,日妈还有这种事?于是真的是了一下,拔掉USB Hub上的东西,真的恢复了,草啊!!!
最近的一些感悟
已经很久没有更新博客的内容了,便随意写点什么。最近经历了工作上的很多事,有了一些感悟。但是我不会一条一条的列出来,也不会说我经历了什么。感悟这种东西,它不一定对其他人有用,或者说你并不认同。但是,有些东西,表面上的差异,本质上是一致的,感悟这类东西,就往往是表面的,深层的东西埋在感悟下面,更加世俗,更加有价值,但是却失去了一些浪漫。所以,从这个角度去想,我这个人还是有一点浪漫主义,但是我也并不稀奇,毕竟浪漫是不能当饭吃,很多人可悲的是,在生存的边缘挣扎,却在追求极致的浪漫主义。
我不能完全体会到“命运”,但是呢,最近在网上看到一些知名的,但是不像是装出来的名人,他们多次提到“信命”这个事情。我也深刻理解到,“命”不是迷信,更多的是对生命的敬畏,有所为,有所不为。信命的人总是带着这种敬畏,所以看上去总是蹑手蹑脚,但是他自己在其中并不觉得可惜,并且总能做到脚踏实地问心无愧。而不信命的人,往往追求“我命由我不由天”,所以往往比较激进,甚至不惜代价去冲击,有的跳跃了,有的败下来,但是,当这些过去之后,他们总是会有诸多遗憾,甚至内心惭愧悔恨。所以,我的感悟总结起来就一句话“尽人事以听天命”。也就是跟着命运的方向走,在这个过程中自己张开翅膀享受这个过程。这里面所遇到的困难,有的时候很痛苦,但是总是会在之后获得内心的慰藉。一时的得失也好,所谓的人生重大决定也罢,都不是特别重要,关键是,当这件事摆在你面前的时候,不逃避,而是去积极准备和面对它。
你说我信命,但我并不信,我只是遵从了一种理念去生活,是积极的,和那些信命躺平的是截然相反的。不过,我也因此知道,并不是任何努力都有你想要的结果,在这里面,95%的努力是没有用的,这些努力仅仅是你付出的信仰,只有剩下的那5%是有用的。但是,你可能会说,现在那么多人一朝就翻身了,难道是因为遵从你这套理论吗?当然不完全是,但是,他们的命和你的命不同,所以你的努力和他的努力不同。就像我一样,我时常会觉得不公平,有些人明明能力不如我,思想不如我,甚至品德不如我,为什么却比我混的好,赚的钱比我多。然而,当我回过来,并不是我不够好,而是在这个世界的规律里面,在我们这条通道里,他才是适应规律的,就像管道里顺着水流的枯叶,虽然已经腐败不堪,但是和逆流而上的水虫而言,却是更早离开管道看见阳光。我们越是挣扎,或许里得到越远。但我们是没有办法做枯叶的,每个人的性质不同,我们是金子,沉在水底,纹丝不动,永不见光明,这是我们骨子里的倔强决定的,不是可以改变的。所以,即使我们心想我们可以同流合污,最后还是发现不可能得到。这就是命。
那我们就不作为,也是不可取。信命是积极的,信命总是往前,因为信命会顺其自然,尽人事以听天命,而不信命则不然,会逆天而行,所以消耗的更快,看上去轰轰烈烈,实际上很快弹尽粮绝。不过,奇怪的是,竟然有人把“就是死了,也要轰轰烈烈”当作格言,甚至人不在少数。我想,这样的人归根结底,是内在的病态,也是一种性质,就是逆流而上的性质,非要挣扎,一身伤残。这样的人自然是可以存在,但是性质不是这样的人,不该去学,世界上大部分人都是水流地下的泥沙,不快,但总归还是会随着水流慢慢前行,而若这些人非觉得要不一样,那他们只会成为逆流而上的那种,而不可能顺流而下的那种,逆流而上本质上是降级,是更容易的事,而泥沙的性质不可能升格为枯叶般轻快,这是性质决定,但非要降级自己的性质,终究会消散在时间里。
今日就讲这么多,并不值得去深入。
-
明灯不息的熊熊烈火才正是最为纯粹的生命本质(逃#1169 rxliuli 2022-02-26 09:03
type UnionZ<A> = {
[k in (keyof A | keyof B)]: k extends keyof B ? B[k] : k extends keyof A ? A[k] : never;
};