首页
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-08-24
实现一个有趣的 RxJS Operator
问题最近有这么一个情况,生产者会产生 { id, value } 结构的值,下游接收发起异步操作。如果是同个 id 那么后产生的值总会覆盖前者,前者发起的异步如果返回得比较晚则需要丢弃过时的值。所以这里就有点类似于 switchMap 但不同的是,switchMap 总会抛弃前者,而这里只有 id 相同才会抛弃。往下阅读之前不妨想想可以如何解决。排除首先这里肯定不能是基于 switchMap,因为我们需要保留不同 id 发起的异步结果。那么剩下的子流归并操作是 mergeMap 和 concatMap。concatMap 一般用于子流产生多个顺序值,所以这里也不适用。mergeMap 是最普通的归并,没有其它合适 Operator 情况下我们就根据它来实现一个自定义的 Operator。思路从另一个角度看这个问题,我们只需要根据 id 产生一条子流,之后如果出现同个 id 的项则取消这条子流。对于判断后来的同个 id 值,我们可以借用一条只有这个 id 值的流。takeUntil(input$.pipe(filter(input => input.id === id)))所以这个思路就很明显了。import { Observable, OperatorFunction } from 'rxjs' import { mergeMap, takeUntil, filter } from 'rxjs/operators' export function switchMapBy( key: keyof T, project: (val: T) => Observable ): OperatorFunction { return input$ => { return input$.pipe( mergeMap(val => project(val).pipe( takeUntil(input$.pipe(filter(input => input[key] === val[key]))) ) ) ) } }优化在复用了流的情况下,如果这个 Operator 使用时排在较后的位置,那么它前面的操作就要都执行两次,我们可以用将流转热避免这个问题。import { Observable, OperatorFunction } from 'rxjs' import { mergeMap, takeUntil, filter, share } from 'rxjs/operators' export function switchMapBy( key: keyof T, project: (val: T) => Observable ): OperatorFunction { return input$ => { const input$$ = input$.pipe(share()) return input$$.pipe( mergeMap(val => project(val).pipe( takeUntil(input$$.pipe(filter(input => input[key] === val[key]))) ) ) ) } }最后我们还可以让 project 支持返回 Promise。import { Observable, OperatorFunction, from } from 'rxjs' import { mergeMap, takeUntil, filter, share } from 'rxjs/operators' export function switchMapBy( key: keyof T, project: (val: T) => Observable | Promise ): OperatorFunction { return input$ => { const input$$ = input$.pipe(share()) return input$$.pipe( mergeMap(val => from(project(val)).pipe( takeUntil(input$$.pipe(filter(input => input[key] === val[key]))) ) ) ) } }最后实现自定义 Operator 的确是一个比较好的练手机会,对于重新审视理解流有一定帮助。这种流复用的思考方式还得多加训练才能一步到位。
2024年08月24日
3 阅读
0 评论
0 点赞
2024-08-24
修复 Deepin Wine 迅雷崩溃
从 /usr/share/applications/deepin.com.thunderspeed.desktop 中找到运行方式 "/opt/deepinwine/apps/Deepin-ThunderSpeed/run.sh" -u %u。运行发现错误为 wine 不知为什么没能找到加载迅雷目录下的 dlls 。0028:fixme:msvcp:_Locinfo__Locinfo_ctor_cat_cstr (008AED28 1 C) semi-stub 0028:fixme:msvcp:_Locinfo__Locinfo_ctor_cat_cstr (008AED78 1 C) semi-stub 0028:fixme:msvcp:_Locinfo__Locinfo_ctor_cat_cstr (008AEA2C 1 C) semi-stub 0028:fixme:msvcp:_Locinfo__Locinfo_ctor_cat_cstr (008AED28 1 C) semi-stub 0028:fixme:heap:RtlSetHeapInformation 0x8c0000 0 0x8ae5c0 4 stub 0030:fixme:winsock:WSCGetProviderPath ({e70f1aa0-ab8b-11cf-8ca3-00805f48a192} 0x102f0ac 0x102f0a8 0x102f0a4) Stub! 0009:err:module:import_dll Library XLFSIO.dll (which is needed by L"c:\\Program Files\\Thunder Network\\Thunder\\Program\\Thunder.exe") not found 0009:err:module:import_dll Library XLLuaRuntime.dll (which is needed by L"c:\\Program Files\\Thunder Network\\Thunder\\Program\\Thunder.exe") not found 0009:err:module:import_dll Library XLGraphic.dll (which is needed by L"c:\\Program Files\\Thunder Network\\Thunder\\Program\\Thunder.exe") not found 0009:err:module:import_dll Library XLUE.dll (which is needed by L"c:\\Program Files\\Thunder Network\\Thunder\\Program\\Thunder.exe") not found 0009:err:module:import_dll Library DownloadKernel.dll (which is needed by L"c:\\Program Files\\Thunder Network\\Thunder\\Program\\Thunder.exe") not found 0009:err:module:import_dll Library libexpat.dll (which is needed by L"c:\\Program Files\\Thunder Network\\Thunder\\Program\\Thunder.exe") not found 0009:err:module:import_dll Library XLUserS.DLL (which is needed by L"c:\\Program Files\\Thunder Network\\Thunder\\Program\\Thunder.exe") not found 0009:err:module:import_dll Library BaseCommunity.DLL (which is needed by L"c:\\Program Files\\Thunder Network\\Thunder\\Program\\Thunder.exe") not found 0009:err:module:import_dll Library XLGraphicPlus.dll (which is needed by L"c:\\Program Files\\Thunder Network\\Thunder\\Program\\Thunder.exe") not found 0009:err:module:import_dll Library zlib1.dll (which is needed by L"c:\\Program Files\\Thunder Network\\Thunder\\Program\\Thunder.exe") not found 0009:err:module:import_dll Library xlstat.dll (which is needed by L"c:\\Program Files\\Thunder Network\\Thunder\\Program\\Thunder.exe") not found 0009:err:module:import_dll Library mini_unzip_dll.dll (which is needed by L"c:\\Program Files\\Thunder Network\\Thunder\\Program\\Thunder.exe") not found 0009:err:module:LdrInitializeThunk Importing dlls for L"c:\\Program Files\\Thunder Network\\Thunder\\Program\\Thunder.exe" failed, status c0000135 0041:fixme:winhttp:request_set_option 0 (null) (null)尝试将目录添加到环境变量env WINEPREFIX="$HOME/.deepinwine/Deepin-ThunderSpeed" wine .deepinwine/Deepin-ThunderSpeed/drive_c/windows/regedit.exe 打开注册表,定位到 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment,编辑 PATH 加入 C:\Program Files\Thunder Network\Thunder\Program,注意以分号 ; 相隔。重新启动迅雷,问题解决。
2024年08月24日
3 阅读
0 评论
0 点赞
2024-08-24
TypeScript 函数泛型部分赋值
问题首先,我们知道 TypeScript 很早(2017)前就实现了带默认值的泛型(generic)type Foo = T // $ExpectType number type T1 = Foo // $ExpectType string type T2 = Foo如果我们定义这么一个部分泛型带默认值的类型type Pluck = TObj[TKey]那么这个默认值是可以被正确推导的type Obj = { a: number, b: string } // $ExpectType string | number type Props = Pluck // $ExpectType string type Prop = Pluck对于函数来说,TypeScript 允许泛型从参数中推导function identity(arg: T): T { return arg } const text = identity('text') // $ExpectType 'text' typeof text有时如果泛型不在参数中,那么使用时我们就要提供function fetchJSON(src: string): Promise { return fetch(src).then(r => r.json()) } // $ExpectType Promise fetchJSON('http://blog.crimx.com/json')现在问题就来了,如果我们希望只提供部分的泛型,而剩下的泛型可以推导function fetchData( src: string, key: TKey ): Promise { return fetch(src) .then(r => r.json()) .then(json => json[key]) } // Argument of type '"result"' is not assignable to parameter of type 'never'.ts(2345) // $ExpectError fetchData('http://blog.crimx.com/json', 'result') // Expected 2 type arguments, but got 1.ts(2558) // $ExpectError fetchData('http://blog.crimx.com/json', 'result')可以看到,如果没有给泛型提供值,那么 TKey 会以 TResult 定义时的值 unknown 进行推导;如果只提供 TResult 那么 TypeScript 会要求提供全部值。这个特性目前依然存在争议,要求泛型从两个地方进行推导可能会引起混淆。解决看回我们的问题,其实我们之所以要提供 TResult 是为了声明 fetch 的结果。我们是做了这两件事fetchJSON('http://blog.crimx.com/json') .then(json => json[key])可以用柯里化的方式解决function fetchData( src: string ): (key: TKey) => Promise { return key => fetch(src) .then(r => r.json()) .then(json => json[key]) } // $ExpectType Promise fetchData('http://blog.crimx.com/json')('result')其它的应用也可以归类到这个模式,这严格上说不算是“解决”而是“变通”。牺牲运行时来让 ts 编译器满意实在有点膈应,希望未来的读者有缘读到这篇时已经出现了更好的解决方案。谢谢阅读!
2024年08月24日
4 阅读
0 评论
0 点赞
2024-08-24
TypeScript 集合转换为交集
Object Assign开始之前我们先来看看 lib 中 Object.assign 的类型是如何定义的assign(target: T, source: U): T & U; assign(target: T, source1: U, source2: V): T & U & V; assign(target: T, source1: U, source2: V, source3: W): T & U & V & W; assign(target: object, ...sources: any[]): any;意不意外,惊不惊喜。硬编码重载了三种情况,那么超过四个对象之后我们只能得到 any。这是由于 TypeScript 的局限性导致的,当然现在 TypeScript 也没有正式解决这个问题,但我们其实已经可以通过 2.8 以后引入的一些特性来 hack 掉这个问题。Intersection From Uniontype MapTopParameter = U extends any ? (arg: U) => void : never type IntersectionFromUnion = MapTopParameter extends (arg: infer T) => void ? T : never type A = { a: 1 } type B = { b: 2 } type C = { c: 2 } // $ExpectType A & B & C type Result = IntersectionFromUnion要理解这个 hack 需要明白 TypeScript 2.8 引入的两个特性:条件类型(Conditional Types)以及条件类型推导(Type inference in conditional types)。条件类型条件类型可以让我们对类型进行三元运算,根据不同情况返回不同类型T extends U ? X : Y但与普通编程语言的三元运算不一样,TypeScript 中还有这么一个特性,叫分布式条件类型(Distributive Conditional Types)。当 T 是一个集合(Union)的时候,三元运算是对集合中每个元素进行运算,而不是对 T 这个整体进行运算。可以类比为数组中的 map,对集合进行映射,这相当于往类型系统中加入了遍历功能,并且结合 never 也得到了 filter 的功能。所以现在 TypeScript 类型系统中有了变量(泛型)、条件控制、循环控制,越来越像一门编程语言了……利用这个特性,我们看回type MapTopParameter = U extends any ? (arg: U) => void : never这里是将集合 U 映射为另外一个以 U 元素为参数的函数集合。// $ExpectType ((arg: number) => void) | ((arg: 'blog.crimx.com') => void) type Result = MapTopParameter这么做有什么用呢,我们接着看。条件类型推导条件类型推导其实是一种简单的模式匹配,可以类比为正则表达式。type ReturnType = T extends (...args: any[]) => infer R ? R : any;这里可以理解为,我们给出了 (...args: any[]) => infer R 这个模板,然后让 T 套这个模板,收集 R 的部分,如这里是收集函数返回的类型。再结合前面的分布式条件类型,如果 T 是一个集合,那么最后收集的则是各项返回值的集合。// $ExpectType number | 'blog.crimx.com' type Result = ReturnType number) | (() => 'blog.crimx.com')>有趣的地方来了,如果我们推导的是函数的参数呢?type Parameter = T extends (arg: infer P) => any ? P : any其实也是一样,最后我们会得到一个参数的集合。type A = { a: number } type B = { b: 'blog.crimx.com' } // $ExpectType A | B type Result = Parameter void) | ((arg: B) => void)>但是!如果我们能想办法阻止这个分布式条件类型,让 T 集合作为一个整体去判断, 这时候表达的是 T 集合中的每一个元素都可以作为 (arg: infer P) => any 的参数使用,也就是说 P 应该是 T 中每个元素的父类,故 P 最后会得到 T 所有元素的交集(Intersection)。怎么才能达到这个效果呢?无封装类型参数让一个类型成为分布式条件类型其实有一个前提,这个类型必须是无封装的类型参数(naked type parameter),即这个类型推导完成后不能是依然包在其它类型中。所以我们简单修改一下type Parameter = [T] extends [(arg: infer P) => any] ? P : any type A = { a: number } type B = { b: 'saladict.app' } // $ExpectType A & B type Result = Parameter void) | ((arg: B) => void)>成功得到交集了!当然对于前面的实现我们无需这么做,因为 MapTopParameter 已经是一层封装。type MapTopParameter = U extends any ? (arg: U) => void : never type IntersectionFromUnion = MapTopParameter extends (arg: infer T) => void ? T : never或者写在一起(略丑)export type IntersectionFromUnion = (TUnion extends any ? (arg: TUnion) => void : never) extends (arg: infer TArg) => void ? TArg : never元组转集合这是一个很多人不知道的小特性,将一个元组(tuple)转换为集合。type tuple = [boolean, 'blog.crimx.com', number] // $ExpectType number | boolean | "blog.crimx.com" type union = tuple[number]现代版 Object Assign最后结合 TypeScript 3.0 加入的 rest 参数,我们定义一个现代版 Object.assignfunction objectAssign( target: TTarget, ...sources: TSources ): IntersectionFromUnion { return Object.assign(target, ...sources) } const a = objectAssign({ a: 1 }, { b: 2 }, { c: 3 }) // $ExpectType { a: number } & { b: number } & { c: number } type A = typeof a最后通过本文例子的讲解希望能帮助大家深入了解 TypeScript 的一些高级特性,如果有什么感想或问题欢迎留言。谢谢阅读!
2024年08月24日
4 阅读
0 评论
0 点赞
2024-08-24
如何测试 React 并发模式安全
自宣布一年多过去 React 并发模式(Concurrent Mode)依然在实验阶段,但早期生态已悄然在形成。Concurrent Mode 这个词越来越频繁出现各种 React 库的介绍和讨论中。作为库开发者或者正打算开发 React 库的朋友,现在开始测试并发模式安全能避免日后可能出现的许多隐性问题,同时这也是一个很好的招牌。注意:本文内容比较前沿,请留意文章的时限,以下的内容随时均可能发生改变。使用 React 副本测试目前只有 @experimental 版本的 React 才支持开启并发模式,考虑到稳定性,我们更希望尽量用稳定版 React 测试其它功能,只用实验版 React 测试并发模式下的功能。yarn add --dev experimental_react@npm:react@experimental experimental_react-dom@npm:react-dom@experimental experimental_react-test-renderer@npm:react-test-renderer@experimental如此我们安装实验版本并加上了 experimental_ 前缀的别名。选择前缀而不是后缀是为了方便日后统一去除。设置 Jest MocksReact 通过 scheduler 这个模块来进行调度,并提供了 jest-mock-scheduler 来在测试时 mock 掉。目前 jest-mock-scheduler 仅仅是导出了 scheduler/unstable_mock.js,所以不装也可以,React 内部也是直接引用 scheduler/unstable_mock.js,但考虑到未来兼容,还是建议安装 jest-mock-scheduler。yarn add --dev jest-mock-scheduler测试文件中:let Scheduler let React let ReactTestRenderer let act let MyLib describe('Concurrent Mode', () => { beforeEach(() => { jest.resetModules() jest.mock('scheduler', () => require('jest-mock-scheduler')) jest.mock('react', () => require('experimental_react')) jest.mock('react-dom', () => require('experimental_react-dom')) jest.mock('react-test-renderer', () => require('experimental_react-test-renderer')) MyLib = require('../src') React = require('react') ReactTestRenderer = require('react-test-renderer') Scheduler = require('scheduler') act = ReactTestRenderer.act }) })如果用 TypeScript 写测试,那么let Scheduler: import('./utils').Scheduler let React: typeof import('react') let ReactTestRenderer: typeof import('react-test-renderer') let act: typeof import('react-test-renderer').act let MyLib: typeof import('../src')其中 scheduler mock 的类型目前先手动补上,见这里。自定义断言React 内部使用了许多自定义断言,为了减少使用难度,这里我们参考同样的方式扩展 Jest expect。在 jest.config.js 中添加 setupFilesAfterEnv 指定配置文件,如 setupFilesAfterEnv: [require.resolve('./scripts/jest-setup.js')],自定义断言参考这里。如果用 TypeScript 写测试,那么还需要添加 expect-extend.d.ts,参考这里。测试调度Scheduler mock 掉之后多了许多控制调度的方法。基本逻辑是默认所有调度都只会累积而不处理,通过手动 flush 或者 act 清理。通过 yeildValue 记录锚值,然后 flush 的时候可以选择只清理到特定锚值的地方,相当于打断点。在断点处我们可以做各种额外的处理以测试我们的库是否会出现异常。测试断裂并发模式下的一个常见问题是状态出现断裂(tearing)。这通常出现在依赖外部模块或者 ref 管理状态。当组件渲染暂停时,如果外部状态发生了变化,该组件恢复渲染后将使用新的值进行渲染,但其它组件却可能在之前已经用了旧的值渲染,故出现了断裂。要测试我们的库会不会产生断裂现象,我们可以在组件渲染结束前打一个点,到断点后触发外部状态变化,然后检查组件状态是否准确。如一个捏造的监听任意 input 元素值的 hook,const useInputValue = input => { const [value, setValue] = React.useState('A') React.useEffect(() => { const callback = event => { setValue(event.currentTarget.value) } input.addEventListener('change', callback) return () => input.removeEventListener('change', callback) }, [input]) return value }为了测试这个 hook 会不会产生断裂,我们设置两个组件监听同个数据源,中断一个组件的渲染,同时数据源产生新值,再恢复组件渲染并对比两个组件结果是否相同。it('should should not tear', () => { const input = document.createElement('input') const emit = value => { input.value = value input.dispatchEvent(new Event('change')) } const Test = ({ id }) => { const value = useInputValue(input) // 打点 Scheduler.unstable_yieldValue(`render:${id}:${value}`) return value } act(() => { ReactTestRenderer.create( , // 启用并发模式 { unstable_isConcurrent: true } ) // 初次渲染 expect(Scheduler).toFlushAndYield(['render:first:A', 'render:second:A']) // 检查正常修改渲染 emit('B') expect(Scheduler).toFlushAndYield(['render:first:B', 'render:second:B']) // 这次渲染到第一个组件后停止 emit('C') expect(Scheduler).toFlushAndYieldThrough(['render:first:C']) // 同时产生新值 emit('D') expect(Scheduler).toFlushAndYield([ 'render:second:C', 'render:first:D', 'render:second:D' ]) }) })最后两者均渲染 D,故使用该 hook 没有断裂问题。
2024年08月24日
6 阅读
0 评论
0 点赞
1
...
66
67
68
...
125