面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

服务端阅读 05月30日 10:11

ASCII 和 UTF-8、GB2312 有什么区别?

ASCII 是 7 位字符编码,只能表示 128 个字符,适合英文、数字、控制符等基础文本。它和其他编码最大的区别不是“好不好”,而是覆盖范围不同:ASCII 管英文基础字符,GB2312/Shift-JIS 管特定语言,UTF-8 管全球字符。追问ASCII 和 ISO-8859-1 有什么区别?ASCII 只有 0-127;ISO-8859-1 扩展到 256 个字符,前 128 个字符和 ASCII 完全一致。ASCII 和 GB2312、Shift-JIS 兼容吗?它们通常保留 ASCII 区间,所以英文部分可兼容。但中文、日文字符需要额外字节,不能按纯 ASCII 解析。ASCII 和 UTF-8 是什么关系?UTF-8 完全兼容 ASCII:ASCII 字符在 UTF-8 中仍然占 1 字节,编码值不变。现在还会直接选 ASCII 吗?纯英文协议、日志、嵌入式小文本可以用 ASCII;只要涉及中文、多语言、表情或国际化,优先选 UTF-8。
服务端阅读 05月30日 10:11

Gradle Wrapper 是什么?如何生成和使用?

Gradle Wrapper 是项目自带的一组脚本和配置,用来固定 Gradle 版本。开发者不用提前安装 Gradle,只要执行 ./gradlew build,Wrapper 就会按配置下载并运行指定版本,保证本地、CI、同事机器上的构建环境一致。它通常包含 gradlew、gradlew.bat、gradle/wrapper/gradle-wrapper.jar、gradle/wrapper/gradle-wrapper.properties,真正决定版本的是 distributionUrl。追问为什么不用本机安装的 gradle?本机版本可能不一致,构建结果就可能不同。Wrapper 把版本写进仓库,CI 也能复现。Wrapper 文件要提交到 Git 吗?要提交脚本、jar 和 properties。不要提交下载下来的 Gradle 分发包。bin 和 all 版本怎么选?多数项目用 bin,体积小、下载快;需要 IDE 查看源码或调试时再用 all。写段代码gradle wrapper --gradle-version 8.0./gradlew --version./gradlew clean build
服务端阅读 05月30日 10:11

Gradle 和 Maven 有什么区别?该怎么选?

Gradle 和 Maven 都是 Java 构建工具。简单选型:项目简单、团队追求稳定统一,选 Maven;项目大、模块多、构建慢,或做 Android/Kotlin,优先选 Gradle。Maven 的核心是“约定优于配置”,结构固定,上手容易,企业老项目和 CI 生态成熟;缺点是 XML 冗长,自定义构建逻辑不够顺手。Gradle 用 Groovy/Kotlin DSL,任务模型更灵活,增量构建、构建缓存、并行构建更强;缺点是自由度高,脚本容易失控。追问配置方式有什么区别?Maven 写 XML,声明式强;Gradle 写 DSL,更像代码,能表达复杂逻辑。构建速度谁更快?大型多模块项目通常 Gradle 更快,主要靠增量构建、缓存和 daemon。小项目差距不一定明显。老项目要不要从 Maven 迁到 Gradle?如果只是构建时间慢一点,不一定值得迁。只有当多模块构建、定制任务、Android/Kotlin 支持成为痛点,迁移收益才明显。面试里怎么回答选择标准?先说团队熟悉度和项目复杂度,再说构建性能;可维护性比炫技重要。
前端阅读 05月30日 10:11

Promise.all 和 Promise.race 有什么区别?

Promise.all 等“全部成功”,Promise.race 等“第一个完成”。all 会并行启动所有 Promise,只有全部 fulfilled 才 fulfilled,结果数组顺序和传入顺序一致;任意一个 rejected,整体立刻 rejected。race 也是并行启动,但谁先 settled 就采用谁的结果,不管成功还是失败。追问all 和 race 的返回值有什么不同?all 返回结果数组;race 返回第一个完成的值或错误。all 里有一个失败,其他请求会取消吗?不会。all 只是让返回的 Promise 变成 rejected,已经发出去的请求仍会继续,除非额外用 AbortController 取消。空数组会怎样?Promise.all([]) 立即 fulfilled,值是 [];Promise.race([]) 会一直 pending。allSettled 和 any 什么时候用?想知道每个任务成败,用 allSettled;只要任意一个成功就够,用 any。写段代码const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 3000));Promise.race([fetch('/api/data'), timeout]).catch(console.error);Promise.all([fetch('/a'), fetch('/b')]);
前端阅读 05月30日 10:11

Promise 链式调用是怎么工作的?

Promise 链式调用的核心是:每次调用 .then() 都会返回一个新的 Promise,后一个 .then() 接收前一个回调的返回值。返回普通值就直接传下去;返回 Promise 就等待它 settled;抛错或返回 rejected Promise,错误会沿链向后冒泡,直到被 .catch() 捕获。追问then 里返回普通值和 Promise 有什么区别?返回普通值时,下一个 then 立即拿到这个值;返回 Promise 时,下一个 then 要等它完成后再执行。then 里不 return 会怎样?等于返回 undefined,所以下一个 then 收到的就是 undefined。很多链式调用断数据,问题都出在这里。catch 后面的 then 还会执行吗?会。catch 如果返回正常值,链会恢复为 fulfilled;如果继续 throw,后面仍然走 rejected 分支。async/await 和链式调用是什么关系?async/await 本质是 Promise 的语法糖,适合写顺序流程;链式调用适合短管道或函数组合。写段代码Promise.resolve(1) .then(v => v + 1) .then(v => Promise.resolve(v * 2)) .then(v => { throw new Error('bad') }) .catch(() => 'fallback') .then(console.log);
前端阅读 05月30日 02:24

Promise 错误处理面试怎么答?

Promise 错误处理要抓住两句话:错误会沿 Promise 链向后传播,最近的 catch 或 then 第二个参数会接住它;catch 返回普通值表示恢复,重新 throw 才会继续失败。项目里一般推荐在链尾统一 catch,async/await 用 try/catch;多个任务用 Promise.allSettled 处理部分失败,避免一个请求失败拖垮全部结果。追问catch 和 then 的第二个参数有什么区别?then 的第二个参数只能处理前一个 Promise 的失败,抓不到同一个 then 成功回调里新抛出的错误;catch 更适合放在链尾统一兜底。catch 里 return 和 throw 有什么区别?return 会把链恢复成 fulfilled,后面的 then 会继续走;throw 或 return rejected Promise 才会让后续 catch 接着处理。Promise.all 里一个失败怎么办?Promise.all 会快速失败,只要一个 reject 整体就 reject。需要拿到每个任务结果时,用 Promise.allSettled,或给每个任务单独 catch。未捕获的 Promise 错误怎么排查?浏览器看 unhandledrejection,Node 看 unhandledRejection 日志。根因通常是忘记 await、忘记 return Promise,或 catch 里吞错。写段代码async function loadAll(tasks) { const results = await Promise.allSettled(tasks.map(t => t())); return results.map(r => r.status === 'fulfilled' ? r.value : { error: r.reason.message } );}
前端阅读 05月30日 02:24

Promise 并发控制如何实现?

Promise 并发控制就是限制同一时间运行的异步任务数量。面试可以先说结论:维护一个任务池,启动任务后放入 executing,数量达到上限就 await Promise.race(executing),等任意任务结束再继续放新任务。最后用 Promise.all 收集全部结果。真实项目里常用于批量请求、上传、爬取、发邮件,目的是保护浏览器连接数、服务端限流和内存。追问并发控制和 Promise.all 有什么区别?Promise.all 会一次性启动所有任务;并发控制只同时跑固定数量。前者适合少量独立任务,后者适合几百、几千个任务。为什么用 Promise.race?它能等“最先完成的一个任务”。有任务完成后,池子空出位置,就可以继续补下一个任务。失败任务怎么处理?看业务。要快速失败就让错误抛出;要尽量完成全部任务,就给每个任务包一层 catch,返回 {status, value, reason}。并发数怎么定?没有固定答案。浏览器请求可从 4-8 开始;Node 服务要看下游 QPS、CPU、连接池和超时率,再动态调。写段代码async function pool(limit, list, worker) { const ret = []; const running = []; for (const item of list) { const p = Promise.resolve().then(() => worker(item)); ret.push(p); const e = p.finally(() => running.splice(running.indexOf(e), 1)); running.push(e); if (running.length >= limit) await Promise.race(running); } return Promise.all(ret);}
前端阅读 05月30日 02:24

Promise 性能优化面试怎么答?

Promise 性能优化的核心不是“少用 Promise”,而是少制造没必要的异步层级,让能并行的任务并行,让高频请求有缓存、去重和并发限制。面试里先答三点:避免 new Promise 包一层已有 Promise;独立任务用 Promise.all 并行;批量任务别一次性打满,用队列或 p-limit 控制数量。再补一句:性能问题通常来自请求瀑布、重复请求、长链微任务和未释放的引用。追问Promise.all 一定更快吗?不一定。只有任务互不依赖时才更快;如果后一个请求依赖前一个结果,强行并行会写错逻辑。Promise.all 还会遇到一个失败就整体失败的问题。为什么不建议包一层 new Promise?已有 Promise 直接返回即可。多包一层会增加对象创建、微任务调度和错误传播复杂度,还容易漏掉 reject。请求去重怎么做?用 Map 保存进行中的 Promise,相同 key 直接复用;结束后在 finally 里删除,避免内存泄漏。长 Promise 链会慢吗?真正慢的通常不是链本身,而是链里塞了大量同步计算或无意义 then。可读性差时改 async/await,但不要把独立任务写成串行 await。写段代码const pending = new Map();function once(key, fn) { if (pending.has(key)) return pending.get(key); const p = fn().finally(() => pending.delete(key)); pending.set(key, p); return p;}async function load() { const [user, posts] = await Promise.all([ once('user', fetchUser), once('posts', fetchPosts) ]); return { user, posts };}
服务端阅读 05月30日 02:24

Promise.allSettled() 有什么作用?和 Promise.all 有什么区别?

Promise.allSettled() 会等待一组 Promise 全部结束,不管成功还是失败,最后返回每个任务的状态和结果;Promise.all() 则要求全部成功,只要一个 reject 就立刻 reject。面试里一句话区分:all 适合“缺一个都不行”,allSettled 适合“尽量都跑完,再分别处理结果”。例如批量请求、批量上传、页面多个独立模块加载,更适合 allSettled。追问allSettled 返回值长什么样?每一项都有 status。成功是 { status: 'fulfilled', value },失败是 { status: 'rejected', reason }。allSettled 会吞掉错误吗?不会吞,只是把错误包装进结果数组。你仍然要检查 rejected 项,否则失败会被业务层忽略。什么时候不能用 allSettled?任务之间有强依赖时不适合。比如用户信息失败后,后续请求必须停止,这时用 all 或串行 await 更清楚。和给每个 Promise 单独 catch 有什么区别?单独 catch 可以兼容更老环境,也能自定义返回结构;allSettled 是标准化写法,语义更明确。写段代码const results = await Promise.allSettled([fetchUser(), fetchPosts()]);const ok = results.filter(r => r.status === 'fulfilled').map(r => r.value);const failed = results.filter(r => r.status === 'rejected').map(r => r.reason);
服务端阅读 05月30日 02:24

Promise 和回调函数有什么区别?为什么能解决回调地狱?

Promise 和回调函数都能处理异步,但抽象层次不同。回调是“异步完成后调用你给的函数”,容易出现多层嵌套、错误分散、控制流难追踪;Promise 把异步结果封装成一个有状态对象,可以链式调用、统一 catch、组合 all/race/any/allSettled。面试里可以说:Promise 不是让异步变同步,而是让异步流程更可组合、错误更集中、代码更容易维护。追问Promise 解决了回调地狱吗?解决了一部分。链式 then 可以拉平嵌套,async/await 又进一步接近同步写法。但如果业务拆分不好,Promise 链也会写得很乱。Promise 的错误处理强在哪里?回调通常要每层传 err;Promise 的错误会沿链传播,最后一个 catch 可以兜底处理。回调还有用吗?有。事件监听、流、Node 风格 API、低层库里仍常见回调。Promise 更适合“一次性成功或失败”的异步结果。Promise 有什么代价?它会引入微任务调度和对象创建,不适合滥用在纯同步逻辑里。性能敏感代码要避免无意义包装。写段代码readFile('a.txt') .then(parse) .then(save) .catch(handleError);
服务端阅读 05月30日 02:24

Promise.any() 有什么作用?和 Promise.race 有什么区别?

Promise.any() 的作用是:一组 Promise 里只要有一个成功,就立刻返回这个成功结果;只有全部失败时,才会 reject,并抛出 AggregateError。它适合“多个候选源,谁先成功用谁”的场景,比如多 CDN 拉资源、多个镜像接口兜底。面试里要强调:它忽略失败,只关心第一个成功;这和 Promise.race() 谁先 settled 就返回完全不同。追问Promise.any 和 Promise.race 最大区别是什么?race 看第一个完成,不管成功还是失败;any 看第一个成功,失败会被暂时忽略,除非全部失败。全部失败时会发生什么?会 reject 一个 AggregateError,里面的 errors 保存所有失败原因。不要只 catch 后打印 message,最好把 errors 也记录下来。它和 Promise.all 有什么区别?all 要全部成功才成功,一个失败就失败;any 只要一个成功就成功。一个适合“都要”,一个适合“有一个可用就行”。实际项目里怎么用?适合容灾兜底,不适合支付、写入、下单这类不能重复尝试的操作。并行请求多个源时也要考虑取消慢请求或控制成本。写段代码try { const data = await Promise.any([ fetch('/cdn-a/config.json'), fetch('/cdn-b/config.json') ]); console.log(await data.json());} catch (e) { console.error(e.errors);}
服务端阅读 05月30日 02:24

RPC 和 RESTful API 有什么区别?什么时候选 RPC?

RPC 和 RESTful API 的核心区别是抽象不同:RPC 像调用远程函数,关注方法、参数和返回值;REST 更像操作资源,关注 URL、HTTP 方法和状态码。内部微服务、低延迟、高吞吐、强类型契约、双向流式通信,通常选 RPC,比如 gRPC、Dubbo。对外开放接口、浏览器直接访问、需要易调试和缓存语义,REST 更合适。面试里不要说谁替代谁,关键是边界:内部效率优先选 RPC,对外通用性优先选 REST。追问RPC 为什么通常性能更好?很多 RPC 框架使用二进制序列化和长连接,协议开销更小,也更容易做连接复用、流式传输和代码生成。REST 的优势是什么?它基于 HTTP 语义,curl、Postman、浏览器都好调试;URL、状态码、缓存头也更适合开放平台和前后端协作。gRPC 能直接给浏览器用吗?原生 gRPC 对浏览器不友好,通常需要 gRPC-Web 或网关转换。面向公网用户时,很多团队会在外层提供 REST。项目里怎么组合两者?常见做法是外部 REST 网关,内部服务之间用 RPC。网关负责鉴权、限流、协议转换,内部服务专注高效调用。
服务端阅读 05月30日 02:24

RPC 常见序列化协议有哪些?各自怎么选?

RPC 常见序列化协议有 Protobuf、Thrift、JSON、Hessian、MessagePack 和 Avro。面试先给结论:内部高性能微服务优先 Protobuf 或 Thrift;需要可读、易调试、对外兼容用 JSON;Java 旧系统可能见到 Hessian;需要类 JSON 但更小的体积可考虑 MessagePack;大数据和日志链路常用 Avro。选择时看四件事:体积、速度、跨语言、schema 演进能力。追问Protobuf 为什么常用于 RPC?它是二进制格式,体积小、解析快,靠 .proto 定义字段和类型,跨语言代码生成成熟。缺点是调试不如 JSON 直观。Thrift 和 Protobuf 有什么区别?Thrift 更像一整套 RPC 方案,包含 IDL、协议和传输;Protobuf 更专注数据序列化,常和 gRPC 搭配使用。JSON 为什么还没被淘汰?因为它人类可读、生态通用、排查方便。对外 API、管理接口、低频调用里,调试成本往往比极致性能更重要。协议升级最容易出什么问题?字段删除、类型变更、枚举兼容最容易翻车。新增字段通常安全,变更字段语义要同时考虑新老客户端。写段代码message UserReq { int64 id = 1; string name = 2;}
服务端阅读 05月30日 02:24

如何优化 RPC 调用性能并降低网络延迟?

优化 RPC 性能先看调用链路:连接、序列化、网络传输、线程模型、服务端处理和观测。面试可以先答:复用长连接,开启连接池和预热;选 Protobuf、Thrift 这类二进制协议,减少字段和大对象;小包低延迟场景开启 TCP_NODELAY;用异步调用、批量请求、就近路由和客户端缓存降低等待时间。最后用 P95/P99、错误率、QPS、线程池队列和链路追踪定位瓶颈,不要只凭感觉调参数。追问TCP_NODELAY 一定要开吗?不一定。它能减少小包等待,但可能增加包数量。低延迟 RPC 常开,吞吐优先的批量传输要压测后决定。序列化为什么影响延迟?序列化影响 CPU、对象分配和网络包大小。JSON 好调试但体积大;Protobuf 体积小、速度快,更适合内部高频调用。异步调用能让单次 RPC 更快吗?不一定降低单次网络耗时,但能减少线程阻塞,提高并发吞吐。真正耗时的服务端逻辑仍要单独优化。项目里怎么排查慢 RPC?先看 P99 和超时分布,再用 Trace 拆成客户端排队、网络、服务端处理、序列化几段。定位后再调连接池、线程池、负载均衡或缓存。写段代码bootstrap.option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 300);
服务端阅读 05月30日 02:24

RPC 负载均衡算法有哪些?如何选择合适策略?

RPC 负载均衡常见算法有随机、轮询、加权随机/轮询、最少连接、最少活跃、最短响应时间、一致性哈希和 IP Hash。面试里先说选择原则:实例差不多用随机或轮询;机器配置不同用加权;请求耗时差异大用最少连接/最少活跃;需要会话保持或本地缓存命中用一致性哈希。真正落地还要配健康检查、熔断、权重动态调整,否则算法再好也会把流量打到故障节点。追问随机和轮询有什么区别?随机实现最简单,长期看分布均匀;轮询更可预测,但如果某台机器变慢,仍会按顺序分流。两者都适合实例能力接近的场景。为什么 Dubbo 默认常用加权随机?它简单、开销低,配合权重能表达机器能力差异。比普通轮询更不容易在短时间内形成固定流量节奏。一致性哈希解决什么问题?它让同一个 key 尽量落到同一台实例,适合会话、缓存、分片类场景。节点增减时只迁移少量 key,但要用虚拟节点缓解倾斜。实际项目里会踩什么坑?只看请求数不看耗时会误判负载;服务注册中心实例下线不及时,会出现短时间错误流量。通常要把负载均衡和健康检查、超时、重试一起设计。写段代码ServiceInstance select(List<ServiceInstance> list) { return list.stream() .filter(ServiceInstance::isHealthy) .min(Comparator.comparingInt(ServiceInstance::getActive)) .orElseThrow();}
服务端阅读 05月30日 01:39

什么是分布式链路追踪?OpenTelemetry、Jaeger 和 SkyWalking 怎么选?

分布式链路追踪就是给一次请求打上 Trace ID,把它经过的网关、服务、数据库、消息队列调用都串起来。面试里先答核心:Trace 表示一次完整请求,Span 表示其中一次操作,Span 之间用 parentId 形成调用树;上下文通常通过 HTTP Header、RPC Metadata 传播;数据由 SDK 或 Agent 采集,再异步上报到 Jaeger、SkyWalking、Zipkin 等后端。现在更推荐用 OpenTelemetry 做统一采集标准,后端再按团队习惯选择 Jaeger、SkyWalking 或商业 APM。追问Trace、Span、Trace ID 有什么区别?Trace 是整条调用链,Span 是链路上的一个节点,比如一次 HTTP 调用或 SQL 查询。Trace ID 贯穿全链路,Span ID 标识当前节点,Parent Span ID 用来还原父子关系。OpenTelemetry 和 Jaeger 是什么关系?OpenTelemetry 主要解决“怎么埋点、怎么采集、怎么传输”的标准化问题;Jaeger 更像存储、查询和展示链路的后端。实际项目里常见组合是 OTel SDK/Collector + Jaeger。Jaeger、SkyWalking、Zipkin 怎么选?Java 微服务、想要 APM 能力更全,可以选 SkyWalking;多语言、高并发链路追踪,Jaeger 更常见;Zipkin 简单稳定,适合轻量场景。新项目优先保证采集侧接 OpenTelemetry,避免后续迁移被某个后端绑死。项目里最容易踩什么坑?第一是异步线程、消息队列、定时任务没传上下文,链路会断。第二是采样率过高拖慢系统,过低又抓不到问题;线上通常按流量、错误率和核心接口分层采样。链路追踪和日志、监控有什么区别?监控告诉你“哪里慢了”,日志告诉你“发生了什么”,链路追踪告诉你“一次请求到底卡在哪个调用”。排障时三者结合,Trace ID 要能在日志里直接检索。写段代码Span span = tracer.spanBuilder("queryUser").startSpan();try (Scope scope = span.makeCurrent()) { return userClient.getUser(id);} catch (Exception e) { span.recordException(e); span.setStatus(StatusCode.ERROR); throw e;} finally { span.end();}
前端阅读 05月30日 01:39

MobX 中 observable 怎么用?有哪些注意事项?

MobX 的 observable 用来把普通状态变成“可被追踪的状态”。组件、computed、autorun 或 reaction 读取它时,MobX 会记录依赖;之后状态变化,相关派生值和视图就会自动更新。现在项目里更常用 makeAutoObservable 或 makeObservable,装饰器写法能见到,但要看团队 Babel/TypeScript 配置。注意:修改状态最好放在 action 里,大对象可用 shallow 降低追踪成本。追问makeAutoObservable 和 makeObservable 有什么区别?makeAutoObservable 会按成员类型自动推断:字段是 observable,getter 是 computed,方法通常是 action。makeObservable 需要手动标注,麻烦一点,但控制更精确,适合复杂 store。observable 默认是深度追踪吗?是的,普通对象会递归转成可观察结构。数据很大、嵌套很深,或者只关心引用变化时,可以用 shallow,避免不必要的代理和依赖追踪。为什么建议在 action 中修改状态?action 能把多次修改合并成一次事务,减少中间状态暴露,也方便开启 enforceActions 做约束。异步请求完成后修改 observable,常用 runInAction。observable 和 computed 怎么配合?observable 存原始状态,computed 负责派生结果。比如 todos 是 observable,completedTodos 应该是 computed,而不是每次在组件里重复 filter。项目里常见坑是什么?一是解构 observable 后丢失响应式读取场景;二是把 observable 对象直接传给不支持代理的第三方库;三是冻结对象或随意替换深层结构,导致追踪和更新不符合预期。写段代码class TodoStore { todos = [] filter = 'all' constructor() { makeAutoObservable(this) } addTodo(text) { this.todos.push({ text, done: false }) } get doneTodos() { return this.todos.filter(t => t.done) }}
前端阅读 05月30日 01:39

MobX 中 computed 有什么作用?和 reaction 怎么选?

MobX 的 computed 用来声明“由 observable 推导出来的值”,比如过滤后的列表、总价、表单是否有效。它的关键点是自动追踪依赖、懒计算、缓存结果:没人读取时不算,依赖没变时重复读取也不重算。面试回答要强调:computed 应该像纯函数,只负责返回值,不要发请求、写日志或修改状态;这些副作用应该交给 reaction。追问computed 为什么能提升性能?因为它会缓存上一次计算结果。只有依赖的 observable 变化,并且 computed 再次被读取时,MobX 才会重新计算;复杂过滤、排序、聚合都适合放进去。computed 和普通 getter 有什么区别?普通 getter 每次访问都执行。computed getter 会被 MobX 管理依赖和缓存,在 observer、autorun、reaction 等响应式上下文中效果最明显。computed 里能不能写异步请求?不建议,也不应该。computed 要同步返回派生值;异步请求会产生副作用,应该用 action 改状态,再用 computed 读取状态生成结果。computed 和 reaction 怎么选?要“算出一个值”,选 computed;要“值变了以后做一件事”,选 reaction。比如 completedTodos 是 computed,userId 变化后拉接口是 reaction。项目里有什么坑?不要在 computed 里返回每次都新建且结构相同的对象,否则可能让观察者误以为结果变了。需要时可以拆小 computed,或使用结构比较配置。写段代码class TodoStore { todos = [] filter = 'all' constructor() { makeAutoObservable(this) } get visibleTodos() { if (this.filter === 'done') return this.todos.filter(t => t.done) return this.todos }}
前端阅读 05月30日 01:39

MobX 中 autorun、reaction 和 when 有什么区别?

MobX 里的 reaction 用来处理副作用:状态变了以后,去做日志、请求、持久化、路由跳转这类“不产生派生值”的事。常见有三种:autorun 会立即执行并自动追踪用到的 observable;reaction 把“追踪什么”和“执行什么”分开,更适合精确控制触发条件;when 只在条件第一次满足时执行一次,然后自动清理。面试里要先说清:派生数据用 computed,副作用才用 reaction。追问autorun 和 reaction 有什么区别?autorun 会立即跑一次,函数里读到什么 observable 就追踪什么。reaction 先用 data 函数明确返回要追踪的数据,只有这个数据变化时才执行 effect,适合监听 userId、query 这类明确字段。when 适合什么场景?适合“一次性条件触发”,比如用户登录成功后加载资料、初始化完成后启动订阅。它触发一次后会自动 dispose,不适合长期监听。reaction 里最容易踩什么坑?一是忘记清理 disposer,组件卸载后还在监听;二是在 reaction 里修改自己依赖的状态,造成循环触发;三是异步请求回来后没用 runInAction 修改状态。reaction 和 computed 怎么选?要返回可缓存的派生值,用 computed;要调用接口、写 localStorage、打印日志、操作外部系统,用 reaction。一个记忆法是:computed 回答“值是什么”,reaction 回答“变化后做什么”。写段代码const dispose = reaction( () => store.query, query => { if (query.length > 2) store.search(query) }, { delay: 300 })// React 卸载或不再需要时// dispose()
服务端阅读 05月30日 01:39

React 中如何正确使用 MobX 和 observer?

在 React 中用 MobX,核心是让读取 observable 的组件被 observer 包住。store 可以通过 Context、props 或模块变量传入;实际项目更推荐 Context,测试和多实例更好控。observer 会追踪组件渲染时真正读到的 observable,相关字段变化才重渲染,所以不要在外层提前把 observable 解成普通值再传下去。函数组件优先用 mobx-react-lite,类组件或旧项目才考虑 mobx-react。追问observer 应该包父组件还是子组件?谁读取 observable 就包谁。把整个 App 包起来不等于所有子组件都响应,细粒度 observer 反而更容易减少无关渲染。Context 里的 store 要不要经常替换?通常不要。Provider 的 value 保持同一个 store 实例,更新 observable 字段即可;频繁替换 store 会让依赖关系和测试都变复杂。组件为什么没有更新?优先查三件事:组件是否用了 observer,读取的对象是否真是 observable,是否在 observer 组件外提前解构成普通值。第三方组件能直接吃 observable 吗?不建议。第三方组件不是 observer,传入前最好转成普通数据,或只传它需要的字段。写段代码const StoreContext = createContext(null)export const useStore = () => useContext(StoreContext)const TodoList = observer(() => { const store = useStore() return store.todos.map(todo => <span key={todo.id}>{todo.text}</span>)})