乐闻世界logo
搜索文章和话题

面试题手册

Prettier 的 ignore 文件如何配置?有哪些常用规则?

Prettier 的 ignore 文件配置详解Prettier 的 .prettierignore 文件允许指定不需要格式化的文件和目录,这对于优化性能和避免不必要的格式化非常重要。基本语法.prettierignore 文件使用类似 .gitignore 的语法:# 忽略 node_modules 目录node_modules# 忽略构建产物distbuildout# 忽略特定文件*.min.js*.min.css# 忽略特定目录coverage.next.nuxt# 忽略配置文件package-lock.jsonyarn.lockpnpm-lock.yaml忽略模式1. 目录忽略# 忽略整个目录node_modules# 忽略嵌套目录**/dist# 忽略特定路径的目录src/dist2. 文件扩展名忽略# 忽略所有 .min.js 文件*.min.js# 忽略多个扩展名*.min.js*.min.css*.min.html3. 路径模式# 忽略特定路径下的文件src/temp/*.js# 忽略所有子目录中的文件**/*.test.js# 使用通配符src/**/*.generated.js4. 否定模式# 忽略所有 .js 文件,但保留 src 目录下的*.js!src/**/*.js# 忽略 dist 目录,但保留 dist/publicdist!dist/public常见忽略规则1. 依赖和构建产物node_modulesdistbuildout.next.nuxt.cache2. 压缩文件*.min.js*.min.css*.min.html*.bundle.js3. 测试覆盖率coverage.nyc_output4. 锁定文件package-lock.jsonyarn.lockpnpm-lock.yaml5. 生成的文件*.generated.js*.generated.ts*.d.ts6. 临时文件*.tmp*.temp*.swp高级用法1. 使用自定义 ignore 文件# 指定自定义 ignore 文件prettier --write --ignore-path .prettierignore.custom "**/*.js"2. 禁用默认忽略# 忽略 node_modules 也会被格式化prettier --write --ignore-unknown "**/*.js"3. 命令行指定忽略# 在命令行中指定忽略模式prettier --write "**/*.js" --ignore "node_modules/**"最佳实践1. 性能优化# 忽略大型目录以提高性能node_modulesdistbuildcoverage2. 避免格式化第三方代码# 不格式化第三方库lib/vendorpublic/assets/vendor3. 保护特殊文件# 不格式化需要保持原样的文件*.min.js*.min.css*.bundle.js4. 项目特定规则# 根据项目需求自定义src/generated/**scripts/temp/**常见问题1. ignore 规则不生效检查 .prettierignore 文件位置(应在项目根目录)确认语法正确(类似 .gitignore)使用 --debug-check 查看详细信息2. 想要格式化被忽略的文件# 使用绝对路径绕过 ignore 规则prettier --write /absolute/path/to/file.js# 临时修改 ignore 文件3. 复杂的忽略模式# 组合使用多种模式node_modulesdist**/*.min.js!src/vendor/*.js与其他工具的 ignore 文件1. 与 .gitignore 协作# 可以在 .prettierignore 中引用 .gitignore# 但需要手动复制规则2. 与 ESLint ignore 协作# .eslintignore 和 .prettierignore 可以有不同的规则# 根据工具特性分别配置通过合理配置 .prettierignore,可以优化 Prettier 的性能,避免不必要的格式化,提高开发效率。
阅读 0·2月21日 16:56

Prettier 如何在团队协作中发挥作用?有哪些最佳实践?

Prettier 在团队协作中的应用Prettier 在团队协作中扮演着重要角色,通过强制统一的代码风格,可以有效减少代码审查中的格式争议,提高团队开发效率。团队协作的价值1. 消除风格争议统一代码格式,避免团队成员之间的风格偏好争论减少代码审查时的格式相关评论让开发者专注于代码逻辑而非格式2. 提高代码可读性统一的格式使代码更易于阅读和理解新成员可以更快适应项目代码风格降低代码维护成本3. 自动化流程通过 Git hooks 自动格式化代码在 CI/CD 中强制检查代码格式减少手动格式化的工作量团队配置策略1. 统一配置文件// .prettierrc - 团队统一配置{ "semi": true, "singleQuote": true, "tabWidth": 2, "trailingComma": "es5", "printWidth": 80, "bracketSpacing": true}2. 配置文件版本控制将 .prettierrc 和 .prettierignore 提交到版本控制确保所有团队成员使用相同的配置在 README 中说明 Prettier 配置3. 编辑器配置统一// .vscode/settings.json - VS Code 团队配置{ "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": true }}工作流程集成1. Git Hooks 集成# 使用 Husky 和 lint-stagednpm install --save-dev husky lint-staged prettier# 配置 pre-commit hooknpx husky add .husky/pre-commit "npx lint-staged"2. CI/CD 检查# GitHub Actions- name: Check code format run: npx prettier --check "**/*.{js,jsx,ts,tsx,json,css,md}"3. Pull Request 模板在 PR 模板中提醒开发者格式化代码:## 提交前检查- [ ] 代码已通过 Prettier 格式化- [ ] 运行 `npm run format:check` 确认格式正确团队最佳实践1. 新成员入职在 onboarding 文档中说明 Prettier 使用方法提供编辑器配置指南安装必要的依赖和插件2. 代码审查流程使用 Prettier 自动格式化,减少审查中的格式讨论在 CI 中强制检查格式,不合格的 PR 不能合并提供清晰的格式化错误提示3. 定期更新定期更新 Prettier 版本评估新的配置选项记录配置变更历史4. 文档维护在 README 中说明 Prettier 配置提供常见问题解答记录团队约定的格式规范解决常见问题1. 配置不一致使用统一的配置文件定期同步团队成员的配置在 CI 中强制使用项目配置2. 性能问题使用 lint-staged 只格式化暂存文件配置 .prettierignore 忽略不必要的文件使用缓存机制提高性能3. 历史代码格式化逐步格式化历史代码,避免大量变更使用 --write 选项批量格式化在单独的 PR 中进行格式化通过合理配置和使用,Prettier 可以显著提升团队协作效率,让团队专注于代码质量而非格式问题。
阅读 0·2月21日 16:56

如何在 VS Code、WebStorm 等编辑器中配置 Prettier?

Prettier 在不同编辑器中的配置Prettier 支持在主流代码编辑器中集成,实现自动格式化和实时预览,提高开发效率。VS Code 配置1. 安装扩展在 VS Code 扩展市场搜索 "Prettier - Code formatter"安装官方 Prettier 扩展2. 基本配置在 .vscode/settings.json 中配置:{ "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, "editor.formatOnPaste": true, "editor.formatOnType": false, "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }}3. 高级配置{ "prettier.requireConfig": true, "prettier.useEditorConfig": false, "prettier.documentSelectors": ["**/*.js", "**/*.ts", "**/*.json"], "prettier.enableDebugLogs": false}WebStorm 配置1. 内置支持WebStorm 内置了 Prettier 支持,无需额外安装2. 配置步骤打开 Settings/Preferences导航到 Languages & Frameworks > JavaScript > Prettier启用 "On 'Reformat Code' action"指定 Prettier 包路径3. 保存时格式化在 Settings > Tools > Actions on Save 中:勾选 "Reformat code"选择 Prettier 作为格式化工具Vim/Neovim 配置1. 使用 coc-prettier" 安装 coc.nvim 和 coc-prettier:CocInstall coc-prettier" 配置自动格式化autocmd BufWritePre *.js,*.jsx,*.ts,*.tsx,*.json :call CocAction('format')2. 使用 vim-prettier" 安装 vim-prettierPlug 'prettier/vim-prettier', { \ 'do': 'yarn install', \ 'for': ['javascript', 'typescript', 'css', 'less', 'scss', 'json', 'graphql'] }" 配置let g:prettier#autoformat = 1let g:prettier#exec_cmd_async = 13. 使用 null-ls (Neovim)local null_ls = require("null-ls")null_ls.setup({ sources = { null_ls.builtins.formatting.prettier.with({ extra_args = { "--no-semi", "--single-quote" }, }), },})-- 自动格式化vim.cmd([[autocmd BufWritePre * lua vim.lsp.buf.formatting_sync()]])Sublime Text 配置1. 安装 Package Control通过 Package Control 安装 "JsPrettier"2. 配置在 Preferences > Package Settings > JsPrettier > Settings - User:{ "prettier_cli_path": "/path/to/prettier", "node_path": "/path/to/node", "auto_format_on_save": true, "auto_format_on_save_excludes": ["*.min.js"]}Atom 配置1. 安装插件通过 Atom 设置安装 "prettier-atom"2. 配置在 Settings > Packages > prettier-atom:启用 "Format on Save"配置 Prettier 选项Emacs 配置1. 使用 prettier-emacs(use-package prettier :ensure t :hook ((js-mode . prettier-mode) (typescript-mode . prettier-mode) (json-mode . prettier-mode)) :config (setq prettier-pre-warn nil))通用最佳实践1. 项目级配置在项目根目录创建 .prettierrc确保编辑器使用项目配置将配置文件提交到版本控制2. 编辑器配置管理使用 .editorconfig 统一基本配置在 .vscode/settings.json 中配置 VS Code 特定设置在项目文档中说明编辑器配置3. 团队协作提供编辑器配置指南在 README 中说明推荐配置使用统一的配置文件4. 性能优化只在必要时启用格式化使用缓存机制配置忽略规则常见问题1. 格式化不工作确认 Prettier 已安装检查配置文件路径验证编辑器扩展是否启用2. 配置冲突优先使用项目级配置禁用编辑器默认格式化器检查多个配置文件的优先级3. 性能问题减少自动格式化的触发频率使用增量格式化配置忽略规则通过合理配置编辑器集成,可以充分发挥 Prettier 的优势,提高开发效率。
阅读 0·2月21日 16:56

RxJS 中 switchMap、mergeMap、concatMap 有什么区别?

switchMap、mergeMap、concatMap 的核心区别这三个操作符都是用于处理高阶 Observable(Observable of Observable)的,但它们的处理策略完全不同:1. switchMap特点: 取消之前的内部 Observable,只处理最新的工作原理:当新的值到达时,取消之前未完成的 Observable只保留最新的 Observable 的结果适合需要取消旧请求的场景示例代码:import { of, interval } from 'rxjs';import { switchMap, take, delay } from 'rxjs/operators';// 模拟异步请求function makeRequest(id) { return of(`Result ${id}`).pipe(delay(1000));}interval(500).pipe( switchMap(id => makeRequest(id)), take(5)).subscribe(console.log);// 输出: Result 4// 解释: 因为每500ms触发一次,但请求需要1000ms// 所以每次新请求来时,之前的请求都被取消了// 最终只有最后一个请求完成了实际应用场景:搜索框输入:每次输入都取消上一次的搜索请求自动完成:只显示最新输入的结果导航切换:取消未完成的页面加载// 搜索框示例searchInput.pipe( switchMap(query => searchAPI(query))).subscribe(results => { // 只显示最新搜索的结果 displayResults(results);});2. mergeMap特点: 并行处理所有内部 Observable工作原理:同时订阅所有内部 Observable所有 Observable 的结果都会被发出不保证顺序,结果可能交错示例代码:import { of } from 'rxjs';import { mergeMap, delay } from 'rxjs/operators';function makeRequest(id) { return of(`Result ${id}`).pipe(delay(id * 200));}of(1, 2, 3).pipe( mergeMap(id => makeRequest(id))).subscribe(console.log);// 输出: Result 1, Result 2, Result 3// 解释: 所有请求并行执行,按完成顺序输出实际应用场景:并行加载多个资源批量处理独立任务不需要顺序的并发请求// 并行加载用户数据示例merge( getUserProfile(userId), getUserPosts(userId), getUserComments(userId)).pipe( mergeMap(response => response.json())).subscribe(data => { // 所有数据并行加载完成 updateUI(data);});3. concatMap特点: 顺序处理内部 Observable,一个完成后再处理下一个工作原理:按顺序订阅内部 Observable当前 Observable 完成后才订阅下一个保证结果的顺序示例代码:import { of } from 'rxjs';import { concatMap, delay } from 'rxjs/operators';function makeRequest(id) { return of(`Result ${id}`).pipe(delay(500));}of(1, 2, 3).pipe( concatMap(id => makeRequest(id))).subscribe(console.log);// 输出: Result 1, Result 2, Result 3// 解释: 每个请求按顺序执行,前一个完成后才执行下一个实际应用场景:需要保证顺序的请求依赖前一个结果的后续请求防止服务器过载// 顺序上传文件示例files.pipe( concatMap(file => uploadFile(file))).subscribe(result => { // 文件按顺序上传 console.log('Uploaded:', result);});对比总结| 特性 | switchMap | mergeMap | concatMap ||------|-----------|----------|-----------|| 执行方式 | 取消旧的,只保留最新的 | 并行执行所有 | 顺序执行 || 结果顺序 | 只保留最新结果 | 不保证顺序 | 保证顺序 || 并发数 | 1(同时只有1个) | 无限制 | 1(同时只有1个) || 适用场景 | 搜索、自动完成 | 并行加载 | 顺序处理 || 性能 | 最快(取消旧请求) | 最快(并行) | 较慢(顺序) || 内存占用 | 低 | 高 | 低 |选择指南使用 switchMap 当:需要取消旧请求只关心最新结果搜索、自动完成等场景避免不必要的网络请求使用 mergeMap 当:需要并行处理请求之间没有依赖需要最大化性能不关心结果顺序使用 concatMap 当:需要保证顺序请求之间有依赖需要限制并发数避免服务器过载性能考虑// 性能对比示例const source = interval(100).pipe(take(10));// switchMap: 只处理最后一个source.pipe( switchMap(x => of(x).pipe(delay(1000)))).subscribe();// mergeMap: 并行处理所有(可能造成性能问题)source.pipe( mergeMap(x => of(x).pipe(delay(1000)))).subscribe();// concatMap: 顺序处理(可能较慢)source.pipe( concatMap(x => of(x).pipe(delay(1000)))).subscribe();实际项目中的选择// 1. 搜索功能 - 使用 switchMapsearchInput.pipe( debounceTime(300), distinctUntilChanged(), switchMap(query => searchAPI(query))).subscribe(results => displayResults(results));// 2. 批量加载 - 使用 mergeMapproductIds.pipe( mergeMap(id => getProductDetails(id))).subscribe(products => renderProducts(products));// 3. 顺序操作 - 使用 concatMapcommands.pipe( concatMap(command => executeCommand(command))).subscribe(result => logResult(result));注意事项内存泄漏: mergeMap 可能创建大量并发请求,需要注意内存使用取消逻辑: switchMap 会自动取消,但 concatMap 和 mergeMap 不会错误处理: 任何一个内部 Observable 出错都会导致整个流失败取消订阅: 始终记得取消订阅以避免内存泄漏最佳实践// 1. 结合错误处理searchInput.pipe( switchMap(query => searchAPI(query).pipe( catchError(error => of([])) ) )).subscribe(results => displayResults(results));// 2. 限制并发数(使用 mergeMap)import { mergeMap } from 'rxjs/operators';source.pipe( mergeMap(value => process(value), 3) // 限制并发数为3).subscribe();// 3. 添加重试机制source.pipe( switchMap(value => apiCall(value).pipe( retry(3), catchError(error => of(defaultValue)) ) )).subscribe();
阅读 0·2月21日 16:55

RxJS 中 Subject、BehaviorSubject、ReplaySubject 和 AsyncSubject 有什么区别?

Subject 的核心概念Subject 是 RxJS 中一种特殊的 Observable,它既是 Observable 又是 Observer。这意味着它可以:被多个观察者订阅主动推送新值给所有订阅者实现多播(multicast)功能Subject 类型1. Subject基础 Subject,每次有新订阅者时,不会回放之前的值const subject = new Subject();subject.next(1);subject.next(2);const subscription1 = subject.subscribe(value => console.log('订阅者1:', value));// 订阅者1: 3// 订阅者1: 4subject.next(3);subject.next(4);const subscription2 = subject.subscribe(value => console.log('订阅者2:', value));// 订阅者2: 5// 订阅者2: 6subject.next(5);subject.next(6);2. BehaviorSubjectBehaviorSubject 会记住最新的值,新订阅者会立即接收到当前值const behaviorSubject = new BehaviorSubject('初始值');behaviorSubject.subscribe(value => console.log('订阅者1:', value));// 订阅者1: 初始值behaviorSubject.next('值1');// 订阅者1: 值1behaviorSubject.subscribe(value => console.log('订阅者2:', value));// 订阅者2: 值1 - 立即收到最新值behaviorSubject.next('值2');// 订阅者1: 值2// 订阅者2: 值23. ReplaySubjectReplaySubject 可以回放指定数量的历史值const replaySubject = new ReplaySubject(2); // 回放最后2个值replaySubject.next('值1');replaySubject.next('值2');replaySubject.next('值3');replaySubject.subscribe(value => console.log('订阅者1:', value));// 订阅者1: 值2// 订阅者1: 值3replaySubject.next('值4');// 订阅者1: 值4replaySubject.subscribe(value => console.log('订阅者2:', value));// 订阅者2: 值3// 订阅者2: 值44. AsyncSubjectAsyncSubject 只在完成时发出最后一个值const asyncSubject = new AsyncSubject();asyncSubject.subscribe(value => console.log('订阅者1:', value));asyncSubject.next('值1');asyncSubject.next('值2');asyncSubject.next('值3');// 此时还没有输出asyncSubject.complete();// 订阅者1: 值3 - 只发出最后一个值asyncSubject.subscribe(value => console.log('订阅者2:', value));// 订阅者2: 值3 - 新订阅者也能收到最后一个值实际应用场景Subject 使用场景事件总线状态管理多个组件共享数据流BehaviorSubject 使用场景存储当前状态表单状态管理用户信息管理ReplaySubject 使用场景缓存历史数据重播操作日志错误重试机制AsyncSubject 使用场景HTTP 请求缓存只需要最终结果的计算异步操作的最后值与普通 Observable 的区别// 普通 Observable - 单播const observable = new Observable(subscriber => { subscriber.next(1); subscriber.next(2);});observable.subscribe(v => console.log('观察者1:', v));observable.subscribe(v => console.log('观察者2:', v));// 每个订阅者独立执行// Subject - 多播const subject = new Subject();const source = new Observable(subscriber => { subscriber.next(1); subscriber.next(2);});source.subscribe(subject);subject.subscribe(v => console.log('订阅者1:', v));subject.subscribe(v => console.log('订阅者2:', v));// 所有订阅者共享同一个数据流性能考虑Subject 在多播场景下可以提高性能,避免重复执行相同的异步操作。但需要注意内存泄漏问题,及时取消订阅。
阅读 0·2月21日 16:55

RxJS 中如何进行性能优化?

RxJS 性能优化的核心原则RxJS 性能优化主要关注以下几个方面:减少不必要的订阅和取消订阅优化操作符链的执行效率合理使用多播和缓存避免内存泄漏减少计算开销优化策略1. 使用 share 和 shareReplay避免重复执行相同的 Observable。import { of } from 'rxjs';import { share, shareReplay } from 'rxjs/operators';// ❌ 不好的做法:每次订阅都重新执行const data$ = http.get('/api/data');data$.subscribe(data => console.log('Subscriber 1:', data));data$.subscribe(data => console.log('Subscriber 2:', data));// 发起两次 HTTP 请求// ✅ 好的做法:共享 Observableconst shared$ = http.get('/api/data').pipe( share() // 或 shareReplay(1));shared$.subscribe(data => console.log('Subscriber 1:', data));shared$.subscribe(data => console.log('Subscriber 2:', data));// 只发起一次 HTTP 请求2. 使用 debounceTime 和 throttleTime减少高频事件的处理频率。import { fromEvent } from 'rxjs';import { debounceTime, throttleTime } from 'rxjs/operators';// ❌ 不好的做法:处理每个滚动事件fromEvent(window, 'scroll').subscribe(event => { handleScroll(event); // 可能每秒触发数百次});// ✅ 好的做法:节流处理fromEvent(window, 'scroll').pipe( throttleTime(200) // 每 200ms 最多处理一次).subscribe(event => { handleScroll(event);});// ✅ 好的做法:防抖处理fromEvent(searchInput, 'input').pipe( debounceTime(300) // 停止输入 300ms 后才处理).subscribe(event => { search(event.target.value);});3. 使用 distinctUntilChanged避免处理重复的值。import { fromEvent } from 'rxjs';import { debounceTime, distinctUntilChanged } from 'rxjs/operators';// ❌ 不好的做法:可能处理相同的搜索词fromEvent(searchInput, 'input').pipe( debounceTime(300)).subscribe(event => { search(event.target.value);});// ✅ 好的做法:只在值变化时处理fromEvent(searchInput, 'input').pipe( debounceTime(300), distinctUntilChanged() // 避免重复搜索).subscribe(event => { search(event.target.value);});4. 使用 take 和 takeWhile及时取消不再需要的订阅。import { interval } from 'rxjs';import { take, takeWhile } from 'rxjs/operators';// ❌ 不好的做法:无限订阅interval(1000).subscribe(value => { console.log(value); // 需要手动取消订阅});// ✅ 好的做法:自动取消订阅interval(1000).pipe( take(10) // 只取 10 个值).subscribe(value => { console.log(value);});// ✅ 好的做法:条件取消interval(1000).pipe( takeWhile(value => value < 10)).subscribe(value => { console.log(value);});5. 使用 switchMap 而不是 mergeMap在需要取消旧操作的场景中。import { fromEvent } from 'rxjs';import { switchMap, mergeMap } from 'rxjs/operators';// ❌ 不好的做法:所有请求都会完成fromEvent(searchInput, 'input').pipe( debounceTime(300), mergeMap(query => searchAPI(query)) // 所有请求都会完成).subscribe(results => { displayResults(results);});// ✅ 好的做法:取消旧请求fromEvent(searchInput, 'input').pipe( debounceTime(300), switchMap(query => searchAPI(query)) // 取消旧请求).subscribe(results => { displayResults(results);});6. 使用 buffer 和 bufferTime批量处理数据,减少处理次数。import { interval } from 'rxjs';import { bufferTime } from 'rxjs/operators';// ❌ 不好的做法:逐个处理interval(100).pipe( take(100)).subscribe(value => { processItem(value); // 处理 100 次});// ✅ 好的做法:批量处理interval(100).pipe( take(100), bufferTime(1000) // 每秒批量处理).subscribe(buffer => { processBatch(buffer); // 只处理 10 次});7. 使用 filter 提前过滤尽早过滤掉不需要的数据。import { of } from 'rxjs';import { map, filter } from 'rxjs/operators';// ❌ 不好的做法:先处理再过滤of(1, 2, 3, 4, 5).pipe( map(x => { console.log('Processing:', x); // 处理所有值 return x * 2; }), filter(x => x > 5)).subscribe(value => { console.log('Result:', value);});// ✅ 好的做法:先过滤再处理of(1, 2, 3, 4, 5).pipe( filter(x => x > 2), // 先过滤 map(x => { console.log('Processing:', x); // 只处理需要的值 return x * 2; })).subscribe(value => { console.log('Result:', value);});8. 使用 finalize 清理资源确保资源被正确释放。import { interval } from 'rxjs';import { take, finalize } from 'rxjs/operators';// ❌ 不好的做法:可能忘记清理const subscription = interval(1000).pipe( take(10)).subscribe(value => { console.log(value);});// 可能忘记取消订阅// ✅ 好的做法:自动清理interval(1000).pipe( take(10), finalize(() => { console.log('Cleaning up...'); cleanupResources(); })).subscribe(value => { console.log(value);});高级优化技巧1. 使用 combineLatest 而不是嵌套订阅// ❌ 不好的做法:嵌套订阅this.userService.getUser(userId).subscribe(user => { this.postsService.getPosts(user.id).subscribe(posts => { this.commentsService.getComments(posts[0].id).subscribe(comments => { // 处理数据 }); });});// ✅ 好的做法:使用 combineLatestcombineLatest([ this.userService.getUser(userId), this.postsService.getPosts(userId), this.commentsService.getComments(postId)]).pipe( map(([user, posts, comments]) => ({ user, posts, comments }))).subscribe(data => { // 处理数据});2. 使用 mergeMap 限制并发import { of } from 'rxjs';import { mergeMap } from 'rxjs/operators';// ❌ 不好的做法:可能同时发起大量请求const ids = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];from(ids).pipe( mergeMap(id => fetchData(id)) // 可能同时发起 10 个请求).subscribe(result => { console.log(result);});// ✅ 好的做法:限制并发数from(ids).pipe( mergeMap(id => fetchData(id), 3) // 最多同时 3 个请求).subscribe(result => { console.log(result);});3. 使用 ReplaySubject 缓存数据import { ReplaySubject } from 'rxjs';// ❌ 不好的做法:每次都重新获取class DataService { getData() { return http.get('/api/data'); }}// ✅ 好的做法:缓存数据class DataService { private cache$ = new ReplaySubject(1); getData() { if (!this.hasFetched) { http.get('/api/data').subscribe(data => { this.cache$.next(data); this.hasFetched = true; }); } return this.cache$.asObservable(); }}4. 使用 scan 而不是 reducescan 可以持续发出中间结果,而 reduce 只在完成时发出结果。import { of } from 'rxjs';import { scan, reduce } from 'rxjs/operators';// ❌ 不好的做法:只在完成时得到结果of(1, 2, 3, 4, 5).pipe( reduce((sum, value) => sum + value, 0)).subscribe(sum => { console.log('Final sum:', sum); // 只输出一次});// ✅ 好的做法:持续输出中间结果of(1, 2, 3, 4, 5).pipe( scan((sum, value) => sum + value, 0)).subscribe(sum => { console.log('Current sum:', sum); // 输出 5 次});5. 使用 tap 进行调试import { of } from 'rxjs';import { map, filter, tap } from 'rxjs/operators';// ✅ 好的做法:使用 tap 调试of(1, 2, 3, 4, 5).pipe( tap(value => console.log('Input:', value)), filter(x => x > 2), tap(value => console.log('After filter:', value)), map(x => x * 2), tap(value => console.log('After map:', value))).subscribe(value => { console.log('Output:', value);});性能监控1. 使用 performance.now() 测量性能import { of } from 'rxjs';import { map, tap } from 'rxjs/operators';const startTime = performance.now();of(1, 2, 3, 4, 5).pipe( map(x => x * 2), tap(() => { const elapsed = performance.now() - startTime; console.log(`Elapsed: ${elapsed.toFixed(2)}ms`); })).subscribe();2. 使用 count 统计数据量import { of } from 'rxjs';import { count, tap } from 'rxjs/operators';of(1, 2, 3, 4, 5).pipe( tap(value => console.log('Processing:', value)), count()).subscribe(count => { console.log('Total processed:', count);});常见性能问题1. 过多的订阅// ❌ 不好的做法:创建多个订阅const data$ = http.get('/api/data');data$.subscribe(data => updateUI1(data));data$.subscribe(data => updateUI2(data));data$.subscribe(data => updateUI3(data));// ✅ 好的做法:共享订阅const data$ = http.get('/api/data').pipe( share());data$.subscribe(data => updateUI1(data));data$.subscribe(data => updateUI2(data));data$.subscribe(data => updateUI3(data));2. 内存泄漏// ❌ 不好的做法:忘记取消订阅class MyComponent { ngOnInit() { this.dataService.getData().subscribe(data => { this.data = data; }); } // 组件销毁时订阅仍然存在}// ✅ 好的做法:正确取消订阅class MyComponent implements OnDestroy { private destroy$ = new Subject<void>(); ngOnInit() { this.dataService.getData().pipe( takeUntil(this.destroy$) ).subscribe(data => { this.data = data; }); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); }}3. 不必要的计算// ❌ 不好的做法:重复计算of(1, 2, 3).pipe( map(x => { const result = expensiveCalculation(x); return result; }), map(x => { const result = expensiveCalculation(x); // 重复计算 return result; })).subscribe();// ✅ 好的做法:避免重复计算of(1, 2, 3).pipe( map(x => { const result = expensiveCalculation(x); return { value: x, result }; })).subscribe(data => { console.log(data.value, data.result);});最佳实践1. 使用 AsyncPipe@Component({ template: ` <div *ngIf="data$ | async as data"> {{ data }} </div> `})export class MyComponent { data$ = this.service.getData(); // AsyncPipe 自动管理订阅}2. 使用 takeUntilexport class MyComponent implements OnDestroy { private destroy$ = new Subject<void>(); ngOnInit() { this.service.getData().pipe( takeUntil(this.destroy$) ).subscribe(data => { this.data = data; }); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); }}3. 使用 shareReplay@Injectable()export class DataService { private cache = new Map<string, Observable<any>>(); getData(key: string) { if (!this.cache.has(key)) { this.cache.set(key, http.get(`/api/data/${key}`).pipe( shareReplay(1) )); } return this.cache.get(key)!; }}总结RxJS 性能优化的关键点:共享订阅: 使用 share 和 shareReplay 避免重复执行减少处理频率: 使用 debounceTime 和 throttleTime过滤重复数据: 使用 distinctUntilChanged及时取消订阅: 使用 take 和 takeWhile选择合适的操作符: switchMap vs mergeMap vs concatMap批量处理: 使用 buffer 和 bufferTime提前过滤: 使用 filter 尽早过滤不需要的数据清理资源: 使用 finalize 确保资源释放避免嵌套订阅: 使用 combineLatest 和 forkJoin限制并发: 使用 mergeMap 的并发参数掌握这些优化技巧可以显著提升 RxJS 应用的性能。
阅读 0·2月21日 16:54

RxJS 在 Angular 中如何应用?

RxJS 在 Angular 中的应用RxJS 是 Angular 框架的核心依赖,广泛应用于异步操作、事件处理和数据流管理。核心应用场景1. HTTP 请求Angular 的 HttpClient 返回 Observable,便于处理异步请求。import { HttpClient } from '@angular/common/http';import { Observable } from 'rxjs';@Injectable({ providedIn: 'root'})export class DataService { constructor(private http: HttpClient) {} getUsers(): Observable<User[]> { return this.http.get<User[]>('/api/users'); } getUserById(id: number): Observable<User> { return this.http.get<User>(`/api/users/${id}`); } createUser(user: User): Observable<User> { return this.http.post<User>('/api/users', user); } updateUser(id: number, user: User): Observable<User> { return this.http.put<User>(`/api/users/${id}`, user); } deleteUser(id: number): Observable<void> { return this.http.delete<void>(`/api/users/${id}`); }}在组件中使用:import { Component, OnInit } from '@angular/core';import { DataService } from './data.service';@Component({ selector: 'app-user-list', template: ` <div *ngIf="users$ | async as users"> <div *ngFor="let user of users"> {{ user.name }} </div> </div> `})export class UserListComponent implements OnInit { users$: Observable<User[]>; constructor(private dataService: DataService) {} ngOnInit() { this.users$ = this.dataService.getUsers(); }}2. 表单处理Angular 的响应式表单与 RxJS 完美集成。import { Component, OnInit } from '@angular/core';import { FormBuilder, FormGroup, Validators } from '@angular/forms';@Component({ selector: 'app-search-form', template: ` <form [formGroup]="searchForm"> <input formControlName="search" placeholder="Search..."> </form> `})export class SearchFormComponent implements OnInit { searchForm: FormGroup; constructor(private fb: FormBuilder) { this.searchForm = this.fb.group({ search: ['', Validators.minLength(3)] }); } ngOnInit() { // 监听搜索输入 this.searchForm.get('search')?.valueChanges.pipe( debounceTime(300), distinctUntilChanged(), filter(query => query.length >= 3), switchMap(query => this.search(query)) ).subscribe(results => { this.displayResults(results); }); } search(query: string): Observable<SearchResult[]> { return this.http.get<SearchResult[]>(`/api/search?q=${query}`); } displayResults(results: SearchResult[]) { // 显示搜索结果 }}3. 路由处理使用 RxJS 处理路由参数和查询参数。import { Component, OnInit } from '@angular/core';import { ActivatedRoute, Router } from '@angular/router';@Component({ selector: 'app-user-detail', template: ` <div *ngIf="user$ | async as user"> <h1>{{ user.name }}</h1> <p>{{ user.email }}</p> </div> `})export class UserDetailComponent implements OnInit { user$: Observable<User>; constructor( private route: ActivatedRoute, private router: Router, private dataService: DataService ) {} ngOnInit() { // 监听路由参数变化 this.user$ = this.route.paramMap.pipe( switchMap(params => { const id = Number(params.get('id')); return this.dataService.getUserById(id); }) ); } navigateToUser(id: number) { this.router.navigate(['/users', id]); }}4. 状态管理使用 BehaviorSubject 或 NgRx 进行状态管理。简单状态管理:import { Injectable } from '@angular/core';import { BehaviorSubject, Observable } from 'rxjs';@Injectable({ providedIn: 'root'})export class StateService { private state$ = new BehaviorSubject<AppState>({ user: null, isLoading: false, error: null }); getState(): Observable<AppState> { return this.state$.asObservable(); } updateUser(user: User) { const currentState = this.state$.value; this.state$.next({ ...currentState, user }); } setLoading(loading: boolean) { const currentState = this.state$.value; this.state$.next({ ...currentState, isLoading: loading }); }}在组件中使用:@Component({ selector: 'app-app', template: ` <div *ngIf="state$ | async as state"> <div *ngIf="state.isLoading">Loading...</div> <div *ngIf="state.user">Welcome, {{ state.user.name }}</div> </div> `})export class AppComponent { state$: Observable<AppState>; constructor(private stateService: StateService) { this.state$ = this.stateService.getState(); }}高级应用模式1. 使用 AsyncPipeAsyncPipe 自动管理订阅和取消订阅。@Component({ selector: 'app-user-list', template: ` <div *ngIf="users$ | async as users"> <div *ngFor="let user of users"> {{ user.name }} </div> </div> `})export class UserListComponent { users$: Observable<User[]>; constructor(private dataService: DataService) { this.users$ = this.dataService.getUsers(); }}2. 使用 takeUntil 防止内存泄漏import { Component, OnInit, OnDestroy } from '@angular/core';import { Subject } from 'rxjs';import { takeUntil } from 'rxjs/operators';@Component({ selector: 'app-component', template: `...`})export class MyComponent implements OnInit, OnDestroy { private destroy$ = new Subject<void>(); ngOnInit() { this.dataService.getUsers().pipe( takeUntil(this.destroy$) ).subscribe(users => { this.users = users; }); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); }}3. 使用 shareReplay 缓存数据import { Injectable } from '@angular/core';import { Observable, shareReplay } from 'rxjs';@Injectable({ providedIn: 'root'})export class CacheService { private cache = new Map<string, Observable<any>>(); get<T>(key: string, fetchFn: () => Observable<T>): Observable<T> { if (!this.cache.has(key)) { this.cache.set(key, fetchFn().pipe( shareReplay(1) )); } return this.cache.get(key) as Observable<T>; } clear() { this.cache.clear(); }}4. 使用 combineLatest 组合多个数据源@Component({ selector: 'app-dashboard', template: ` <div *ngIf="dashboardData$ | async as data"> <h2>Users: {{ data.users.length }}</h2> <h2>Posts: {{ data.posts.length }}</h2> <h2>Comments: {{ data.comments.length }}</h2> </div> `})export class DashboardComponent { dashboardData$: Observable<DashboardData>; constructor(private dataService: DataService) { this.dashboardData$ = combineLatest([ this.dataService.getUsers(), this.dataService.getPosts(), this.dataService.getComments() ]).pipe( map(([users, posts, comments]) => ({ users, posts, comments })) ); }}常见问题和解决方案1. 处理错误this.dataService.getUsers().pipe( catchError(error => { console.error('Failed to load users:', error); return of([]); // 返回空数组作为降级 })).subscribe(users => { this.users = users;});2. 重试失败的请求this.dataService.getUsers().pipe( retry(3), // 重试 3 次 catchError(error => { console.error('Failed after retries:', error); return of([]); })).subscribe(users => { this.users = users;});3. 加载状态管理@Component({ selector: 'app-user-list', template: ` <div *ngIf="isLoading">Loading...</div> <div *ngIf="users$ | async as users"> <div *ngFor="let user of users"> {{ user.name }} </div> </div> `})export class UserListComponent { isLoading = false; users$: Observable<User[]>; constructor(private dataService: DataService) {} loadUsers() { this.isLoading = true; this.dataService.getUsers().pipe( finalize(() => { this.isLoading = false; }) ).subscribe(users => { this.users = users; }); }}4. 搜索防抖@Component({ selector: 'app-search', template: ` <input #searchInput (input)="onSearch($event)" placeholder="Search..."> <div *ngIf="results$ | async as results"> <div *ngFor="let result of results"> {{ result.name }} </div> </div> `})export class SearchComponent { results$: Observable<SearchResult[]>; constructor(private dataService: DataService) {} onSearch(event: Event) { const query = (event.target as HTMLInputElement).value; this.results$ = of(query).pipe( debounceTime(300), distinctUntilChanged(), switchMap(q => this.dataService.search(q)) ); }}最佳实践1. 使用 AsyncPipe// ✅ 推荐@Component({ template: `<div *ngIf="data$ | async as data">{{ data }}</div>`})export class MyComponent { data$ = this.service.getData();}// ❌ 不推荐@Component({ template: `<div>{{ data }}</div>`})export class MyComponent implements OnInit, OnDestroy { data: any; private subscription: Subscription; ngOnInit() { this.subscription = this.service.getData().subscribe(data => { this.data = data; }); } ngOnDestroy() { this.subscription.unsubscribe(); }}2. 防止内存泄漏// ✅ 推荐export class MyComponent implements OnDestroy { private destroy$ = new Subject<void>(); ngOnInit() { this.service.getData().pipe( takeUntil(this.destroy$) ).subscribe(data => { this.data = data; }); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); }}// ❌ 不推荐export class MyComponent { ngOnInit() { this.service.getData().subscribe(data => { this.data = data; }); // 忘记取消订阅 }}3. 错误处理// ✅ 推荐this.service.getData().pipe( catchError(error => { console.error('Error:', error); return of(defaultData); })).subscribe(data => { this.data = data;});// ❌ 不推荐this.service.getData().subscribe({ next: data => { this.data = data; }, error: error => { console.error('Error:', error); // 没有降级处理 }});4. 类型安全// ✅ 推荐interface User { id: number; name: string; email: string;}this.http.get<User[]>('/api/users').subscribe(users => { users.forEach(user => { console.log(user.name); // 类型安全 });});// ❌ 不推荐this.http.get('/api/users').subscribe((users: any) => { users.forEach((user: any) => { console.log(user.name); // 没有类型检查 });});总结RxJS 在 Angular 中的关键应用:HTTP 请求: 使用 HttpClient 处理异步请求表单处理: 监听表单值变化,实现防抖和验证路由处理: 监听路由参数和查询参数变化状态管理: 使用 BehaviorSubject 或 NgRx 管理应用状态AsyncPipe: 自动管理订阅,防止内存泄漏错误处理: 使用 catchError 和 retry 处理错误性能优化: 使用 debounceTime、shareReplay 等优化性能掌握 RxJS 在 Angular 中的应用是成为 Angular 高级开发者的关键。
阅读 0·2月21日 16:54

Kafka 中 ZooKeeper 的作用是什么?

Kafka ZooKeeper 的作用ZooKeeper 在 Kafka 集群中扮演着关键角色,负责协调和管理集群的各种元数据和状态。虽然 Kafka 2.8+ 版本开始引入了 KRaft 模式(无 ZooKeeper),但 ZooKeeper 仍然是大多数 Kafka 集群的核心组件。ZooKeeper 的核心作用1. Broker 注册与发现功能描述:每个 Broker 启动时向 ZooKeeper 注册ZooKeeper 维护 Broker 列表和状态Producer 和 Consumer 通过 ZooKeeper 发现 Broker实现机制:ZooKeeper 节点结构:/brokers/ids/[broker_id] -> broker 信息关键信息:Broker IDBroker 地址和端口Broker 状态(活跃/非活跃)Broker 的 Rack 信息2. Controller 选举功能描述:Kafka 集群中选举一个 Broker 作为 ControllerController 负责管理分区状态和副本分配ZooKeeper 协调 Controller 选举过程选举过程:所有 Broker 竞争创建 /controller 临时节点创建成功的 Broker 成为 Controller其他 Broker 监听 /controller 节点Controller 故障时,重新选举Controller 职责:管理 Partition Leader 选举管理 Partition 副本分配管理 Topic 创建和删除管理集群元数据变更3. Topic 元数据管理功能描述:存储 Topic 的分区信息存储 Topic 的副本分配信息存储 Topic 的配置信息ZooKeeper 节点结构:/brokers/topics/[topic_name] -> 分区信息/config/topics/[topic_name] -> Topic 配置/admin/delete_topics/[topic_name] -> 待删除 Topic存储内容:Topic 的 Partition 数量每个 Partition 的副本分布Topic 的覆盖配置(如 retention.ms)4. Consumer Group 管理功能描述:管理 Consumer Group 的成员信息管理 Consumer Group 的 Offset 提交协调 Consumer Group RebalanceZooKeeper 节点结构:/consumers/[group_id]/ids/[consumer_id] -> Consumer 信息/consumers/[group_id]/offsets/[topic]/[partition] -> Offset/consumers/[group_id]/owners/[topic]/[partition] -> Partition 所有者管理内容:Consumer Group 成员列表每个 Consumer 订阅的 Topic每个 Partition 的 OffsetPartition 与 Consumer 的分配关系5. ACL 权限管理功能描述:存储 Kafka 的访问控制列表管理用户和权限信息ZooKeeper 节点结构:/kafka-acl/Topic/[topic_name] -> Topic 权限/kafka-acl/Cluster/kafka-cluster -> 集群权限/kafka-acl/Group/[group_id] -> Consumer Group 权限6. 配置管理功能描述:存储集群级别的配置存储 Topic 级别的配置存储 Client 级别的配置ZooKeeper 节点结构:/config/brokers/[broker_id] -> Broker 配置/config/topics/[topic_name] -> Topic 配置/config/clients/[client_id] -> Client 配置ZooKeeper 与 Kafka 的交互Broker 启动流程连接 ZooKeeperBroker 连接到 ZooKeeper 集群创建会话注册 Broker在 /brokers/ids/ 下创建临时节点注册 Broker 信息参与 Controller 选举尝试创建 /controller 节点竞争成为 Controller加载元数据从 ZooKeeper 读取 Topic 信息从 ZooKeeper 读取配置信息Topic 创建流程创建 Topic 节点在 /brokers/topics/ 下创建 Topic 节点存储分区和副本信息创建配置节点在 /config/topics/ 下创建配置节点存储 Topic 配置通知 ControllerController 监听 Topic 变化Controller 执行分区分配Consumer Group Rebalance 流程Consumer 加入 GroupConsumer 在 /consumers/[group_id]/ids/ 下创建临时节点注册 Consumer 信息触发 RebalanceGroup Coordinator 检测到成员变化启动 Rebalance 过程分配 PartitionLeader Consumer 制定分配方案更新 /consumers/[group_id]/owners/ 节点提交 OffsetConsumer 提交 Offset 到 ZooKeeper更新 /consumers/[group_id]/offsets/ 节点ZooKeeper 配置Kafka 配置# ZooKeeper 连接地址zookeeper.connect=localhost:2181# ZooKeeper 连接超时时间zookeeper.connection.timeout.ms=6000# ZooKeeper 会话超时时间zookeeper.session.timeout.ms=6000# ZooKeeper 同步时间zookeeper.sync.time.ms=2000ZooKeeper 配置# 客户端连接数限制maxClientCnxns=60# 数据目录dataDir=/var/lib/zookeeper# Tick 时间tickTime=2000# 初始同步超时initLimit=10# 同步超时syncLimit=5# 客户端端口clientPort=2181ZooKeeper 高可用ZooKeeper 集群部署部署架构:至少 3 个 ZooKeeper 节点奇数个节点(避免脑裂)分布在不同的物理机配置示例:tickTime=2000dataDir=/var/lib/zookeeperclientPort=2181initLimit=5syncLimit=2server.1=zoo1:2888:3888server.2=zoo2:2888:3888server.3=zoo3:2888:3888故障恢复ZooKeeper 故障:多数节点存活,集群继续服务少数节点故障,自动恢复Kafka 故障:Broker 故障,ZooKeeper 检测并触发 Controller 选举Controller 故障,重新选举新 ControllerKRaft 模式(无 ZooKeeper)KRaft 模式介绍Kafka 2.8+ 引入了 KRaft 模式,移除了对 ZooKeeper 的依赖。优势:简化部署和运维减少组件依赖提高性能更好的扩展性架构变化:使用内部元数据存储替代 ZooKeeperController 集群管理元数据Broker 直接与 Controller 通信KRaft 模式配置# 启用 KRaft 模式process.roles=broker,controller# Controller 列表controller.quorum.voters=1@localhost:9093,2@localhost:9094,3@localhost:9095# 监听地址listeners=PLAINTEXT://:9092,CONTROLLER://:9093# 元数据目录metadata.log.dir=/var/lib/kafka/metadata最佳实践1. ZooKeeper 集群规划至少 3 个节点分布在不同机架使用独立磁盘2. 监控 ZooKeeper监控 ZooKeeper 延迟监控 ZooKeeper 连接数监控 ZooKeeper 节点状态3. 优化 ZooKeeper 性能调整 JVM 参数优化网络配置使用 SSD 存储4. 备份 ZooKeeper 数据定期备份 ZooKeeper 数据目录建立灾难恢复方案测试备份恢复流程通过理解 ZooKeeper 在 Kafka 中的作用,可以更好地设计、部署和运维 Kafka 集群,确保系统的稳定性和可靠性。
阅读 0·2月21日 16:54

RxJS 6 和 RxJS 7 有什么区别?

RxJS 6 与 RxJS 7 的主要变化1. 导入方式的变化RxJS 6:// 创建操作符import { of, from, interval } from 'rxjs';// 操作符(使用 pipe)import { map, filter, switchMap } from 'rxjs/operators';// 工具函数import { Subscription } from 'rxjs';RxJS 7:// 导入方式基本相同,但更加模块化import { of, from, interval } from 'rxjs';import { map, filter, switchMap } from 'rxjs/operators';// 新增了一些操作符import { debounceTime, distinctUntilChanged } from 'rxjs/operators';2. 新增的操作符RxJS 7 新增了多个有用的操作符:1. partition根据谓词函数将 Observable 分成两个。import { of, partition } from 'rxjs';const source$ = of(1, 2, 3, 4, 5, 6);const [evens$, odds$] = partition(source$, x => x % 2 === 0);evens$.subscribe(x => console.log('Even:', x)); // 2, 4, 6odds$.subscribe(x => console.log('Odd:', x)); // 1, 3, 52. tap 的改进tap 现在接受一个对象,可以分别处理不同的通知类型。import { of } from 'rxjs';import { tap } from 'rxjs/operators';of(1, 2, 3).pipe( tap({ subscribe: () => console.log('Subscribed'), next: value => console.log('Next:', value), error: error => console.log('Error:', error), complete: () => console.log('Completed'), unsubscribe: () => console.log('Unsubscribed') })).subscribe();3. connectable 操作符简化了多播 Observable 的创建。import { interval, connectable } from 'rxjs';import { take } from 'rxjs/operators';const source$ = interval(1000).pipe(take(5));const connectable$ = connectable(source$);connectable$.subscribe(value => console.log('Subscriber 1:', value));connectable$.subscribe(value => console.log('Subscriber 2:', value));connectable$.connect();4. shareReplay 的改进shareReplay 现在支持配置对象。import { interval } from 'rxjs';import { shareReplay, take } from 'rxjs/operators';const shared$ = interval(1000).pipe( take(5), shareReplay({ bufferSize: 2, refCount: true }));5. filter 的类型推断改进更好的 TypeScript 类型推断。import { of } from 'rxjs';import { filter } from 'rxjs/operators';const source$ = of(1, 2, 3, 4, 5);// RxJS 7 中类型推断更准确const even$ = source$.pipe( filter(x => x % 2 === 0) // x 被推断为 number);even$.subscribe(x => console.log(x)); // x 是 number 类型3. 废弃的操作符以下操作符在 RxJS 7 中被废弃:1. throwErrorRxJS 6:import { throwError } from 'rxjs';throwError('Error message');RxJS 7:import { throwError } from 'rxjs';// 推荐使用工厂函数throwError(() => new Error('Error message'));2. concat 和 merge 的静态方法虽然仍然可用,但推荐使用 concatWith 和 mergeWith。import { of, concat, merge } from 'rxjs';// 仍然可用concat(of(1), of(2)).subscribe();merge(of(1), of(2)).subscribe();// 推荐使用of(1).pipe(concatWith(of(2))).subscribe();of(1).pipe(mergeWith(of(2))).subscribe();4. 性能优化1. 更小的包体积RxJS 7 通过树摇优化减少了包体积。// RxJS 7 只导入需要的操作符import { of } from 'rxjs';import { map } from 'rxjs/operators';// 打包时只会包含 of 和 map2. 更快的执行速度某些操作符的执行速度得到了优化。import { of } from 'rxjs';import { map, filter } from 'rxjs/operators';// RxJS 7 中这些操作符执行更快of(1, 2, 3, 4, 5).pipe( map(x => x * 2), filter(x => x > 5)).subscribe();5. TypeScript 改进1. 更好的类型推断import { of } from 'rxjs';import { map, filter } from 'rxjs/operators';// RxJS 7 中类型推断更准确const result$ = of(1, 2, 3).pipe( map(x => x.toString()), // 推断为 Observable<string> filter(x => x.length > 0));2. 严格类型检查import { of } from 'rxjs';import { map } from 'rxjs/operators';// RxJS 7 中类型检查更严格of(1, 2, 3).pipe( map(x => x.toUpperCase()) // TypeScript 错误:number 没有 toUpperCase);6. 错误处理改进1. 更好的错误信息import { of } from 'rxjs';import { map } from 'rxjs/operators';of(1, 2, 3).pipe( map(x => { if (x === 2) throw new Error('Custom error'); return x; })).subscribe({ error: error => { console.log(error.message); // 更清晰的错误信息 }});2. onErrorResumeNext 的改进import { of, onErrorResumeNext } from 'rxjs';const source1$ = of(1, 2, 3).pipe( map(x => { if (x === 2) throw new Error('Error'); return x; }));const source2$ = of(4, 5, 6);onErrorResumeNext(source1$, source2$).subscribe(console.log);// 输出: 1, 4, 5, 67. 调度器改进1. animationFrameScheduler 的改进import { interval, animationFrameScheduler } from 'rxjs';import { take } from 'rxjs/operators';// RxJS 7 中 animationFrameScheduler 更稳定interval(0, animationFrameScheduler).pipe( take(60)).subscribe(frame => { console.log('Frame:', frame);});2. asapScheduler 的改进import { of, asapScheduler } from 'rxjs';// RxJS 7 中 asapScheduler 性能更好of(1, 2, 3, asapScheduler).subscribe(value => { console.log(value);});8. 测试工具改进1. TestScheduler 的改进import { TestScheduler } from 'rxjs/testing';// RxJS 7 中 TestScheduler 更易用const testScheduler = new TestScheduler((actual, expected) => { expect(actual).toEqual(expected);});testScheduler.run(({ cold, expectObservable }) => { const source$ = cold('-a-b-c|'); const expected = '-a-b-c|'; expectObservable(source$).toBe(expected);});2. marble testing 的改进import { TestScheduler } from 'rxjs/testing';// RxJS 7 中 marble testing 更直观testScheduler.run(({ cold, hot, expectObservable, expectSubscriptions }) => { const source$ = cold('-a-b-c|'); const expected = '-a-b-c|'; expectObservable(source$).toBe(expected);});9. 文档和示例改进1. 更好的文档RxJS 7 提供了更详细的文档和示例。// 官方文档提供了更多实用的示例import { of } from 'rxjs';import { map, filter } from 'rxjs/operators';of(1, 2, 3, 4, 5).pipe( map(x => x * 2), filter(x => x > 5)).subscribe(console.log);2. 更多的最佳实践官方文档中包含了更多的最佳实践建议。// 推荐使用 pipe 而不是链式调用import { of } from 'rxjs';import { map, filter } from 'rxjs/operators';// ✅ 推荐of(1, 2, 3).pipe( map(x => x * 2), filter(x => x > 5)).subscribe();// ❌ 不推荐of(1, 2, 3).pipe( map(x => x * 2)).pipe( filter(x => x > 5)).subscribe();10. 迁移指南1. 从 RxJS 6 迁移到 RxJS 7# 升级 RxJSnpm install rxjs@7# 检查废弃的 APInpm run lint2. 常见迁移问题问题 1: throwError 的使用// ❌ RxJS 6throwError('Error message');// ✅ RxJS 7throwError(() => new Error('Error message'));问题 2: 类型推断问题// 可能需要显式类型import { of } from 'rxjs';import { map } from 'rxjs/operators';const result$ = of(1, 2, 3).pipe( map(x => x.toString())) as Observable<string>;总结RxJS 7 相比 RxJS 6 的主要改进:新增操作符: partition、connectable 等性能优化: 更小的包体积,更快的执行速度TypeScript 改进: 更好的类型推断和严格类型检查错误处理: 更好的错误信息和处理机制调度器改进: 更稳定和高效的调度器测试工具: 更易用的 TestScheduler文档改进: 更详细的文档和示例迁移到 RxJS 7 可以带来更好的性能和开发体验,但需要注意一些 API 的变化。
阅读 0·2月21日 16:54