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

面试题手册

如何实现 RPC 的异步调用?异步调用有哪些模式和优势?

异步 RPC 调用是提高系统性能和并发能力的重要技术,允许客户端在等待响应的同时处理其他任务:异步调用模式:1. Future/Promise 模式原理:调用后立即返回 Future 对象,通过 Future 获取结果优点:简单易用,不阻塞调用线程缺点:需要主动获取结果,代码可能不够优雅实现示例: // Dubbo 异步调用 <dubbo:reference interface="com.example.UserService" async="true"/> // 使用 userService.getUser(1L); Future<User> future = RpcContext.getContext().getFuture(); User user = future.get(1000, TimeUnit.MILLISECONDS);2. 回调模式(Callback)原理:调用时传入回调函数,结果返回时执行回调优点:事件驱动,适合异步处理缺点:回调地狱,代码可读性差实现示例: public interface AsyncCallback<T> { void onSuccess(T result); void onFailure(Throwable t); } // 使用 userService.getUserAsync(1L, new AsyncCallback<User>() { @Override public void onSuccess(User user) { // 处理成功结果 } @Override public void onFailure(Throwable t) { // 处理失败 } });3. 响应式编程(Reactive)原理:使用响应式流(Reactive Streams)处理异步数据优点:代码优雅,支持背压,适合流式处理缺点:学习曲线较陡实现示例: // Reactor Mono<User> userMono = userService.getUserReactive(1L); userMono.subscribe( user -> System.out.println(user), error -> System.err.println(error) ); // RxJava Observable<User> userObs = userService.getUserRx(1L); userObs.subscribe( user -> System.out.println(user), error -> System.err.println(error) );4. gRPC 异步调用原理:使用 StreamObserver 处理异步响应优点:支持流式通信,与 gRPC 深度集成实现示例: // 一元异步调用 stub.sayHello(request, new StreamObserver<HelloResponse>() { @Override public void onNext(HelloResponse response) { // 处理响应 } @Override public void onError(Throwable t) { // 处理错误 } @Override public void onCompleted() { // 调用完成 } }); // 双向流 StreamObserver<Request> requestObserver = stub.bidirectionalStream( new StreamObserver<Response>() { @Override public void onNext(Response response) { // 处理响应 } @Override public void onError(Throwable t) { // 处理错误 } @Override public void onCompleted() { // 调用完成 } }); // 发送请求 requestObserver.onNext(request1); requestObserver.onNext(request2); requestObserver.onCompleted();5. CompletableFuture原理:Java 8 引入的异步编程工具优点:功能强大,支持链式调用实现示例: CompletableFuture<User> future = CompletableFuture.supplyAsync( () -> userService.getUser(1L) ); // 链式调用 future.thenAccept(user -> System.out.println(user)) .exceptionally(t -> { System.err.println(t); return null; }); // 组合多个 Future CompletableFuture<User> userFuture = userService.getUserAsync(1L); CompletableFuture<Order> orderFuture = orderService.getOrderAsync(1L); CompletableFuture<Result> resultFuture = userFuture.thenCombineAsync( orderFuture, (user, order) -> new Result(user, order) );异步调用的优势:1. 提高并发能力不阻塞调用线程可以同时处理多个请求充分利用系统资源2. 降低延迟客户端可以并行发起多个调用减少等待时间提高响应速度3. 提高吞吐量单位时间内处理更多请求适合高并发场景4. 更好的用户体验避免界面卡顿实现实时更新异步调用的挑战:1. 代码复杂度异步代码难以理解和调试错误处理复杂需要处理线程安全问题2. 上下文传递异步调用时上下文可能丢失需要显式传递上下文信息解决方案:使用 ThreadLocal、TransmittableThreadLocal3. 超时控制需要合理设置超时时间避免无限等待实现示例: CompletableFuture<User> future = userService.getUserAsync(1L); try { User user = future.get(1000, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { future.cancel(true); }4. 资源管理需要合理管理线程池避免资源耗尽实现示例: ExecutorService executor = Executors.newFixedThreadPool(10); CompletableFuture<User> future = CompletableFuture.supplyAsync( () -> userService.getUser(1L), executor );最佳实践:1. 合理选择异步模式简单场景:Future/Promise事件驱动:回调模式流式处理:响应式编程高性能要求:CompletableFuture2. 完善的错误处理捕获所有异常提供有意义的错误信息实现重试机制3. 超时控制设置合理的超时时间超时后取消请求避免资源泄漏4. 资源管理使用线程池管理线程及时释放资源避免内存泄漏5. 监控和日志记录异步调用日志监控异步调用性能及时发现问题适用场景:高并发场景需要并行调用多个服务流式数据处理实时性要求高的场景长时间运行的任务
阅读 0·2月22日 14:06

什么是服务治理?RPC 框架中的服务治理功能有哪些?如何实现?

服务治理是微服务架构中的核心功能,确保服务的稳定运行和高效管理:核心服务治理功能:1. 服务注册与发现功能:服务实例自动注册和发现实现:Zookeeper、Nacos、Consul、Eureka关键点:健康检查:定期检测服务实例健康状态服务剔除:自动移除不健康的实例动态更新:服务列表实时更新配置示例: // Dubbo 服务注册 <dubbo:registry address="zookeeper://127.0.0.1:2181"/> // Spring Cloud 服务发现 @EnableDiscoveryClient2. 负载均衡功能:将请求分发到多个服务实例算法:随机(Random)轮询(Round Robin)最少连接(Least Connections)一致性哈希(Consistent Hash)配置示例: // Dubbo 负载均衡 <dubbo:reference loadbalance="random"/> // Spring Cloud 负载均衡 @LoadBalanced RestTemplate restTemplate;3. 服务容错功能:处理服务调用失败的情况策略:Failover:失败自动切换,重试其他实例Failfast:快速失败,只发起一次调用Failsafe:失败安全,出现异常时忽略Failback:失败自动恢复,后台记录失败请求Forking:并行调用,只要一个成功即返回Broadcast:广播调用,所有调用都成功才算成功配置示例: // Dubbo 容错策略 <dubbo:reference cluster="failover" retries="2"/> // Hystrix 熔断 @HystrixCommand(fallbackMethod = "fallback") public User getUser(Long id) { return userService.getUser(id); }4. 服务降级功能:服务不可用时提供备用方案策略:返回默认值返回缓存数据调用备用服务返回友好错误提示实现示例: @HystrixCommand(fallbackMethod = "getUserFallback") public User getUser(Long id) { return userService.getUser(id); } public User getUserFallback(Long id) { return new User(id, "默认用户"); }5. 服务限流功能:保护服务不被过载算法:令牌桶(Token Bucket)漏桶(Leaky Bucket)固定窗口(Fixed Window)滑动窗口(Sliding Window)实现示例: // Sentinel 限流 @SentinelResource(value = "getUser", blockHandler = "handleBlock") public User getUser(Long id) { return userService.getUser(id); } public User handleBlock(Long id, BlockException ex) { return new User(id, "限流"); } // Guava RateLimiter RateLimiter rateLimiter = RateLimiter.create(100); if (rateLimiter.tryAcquire()) { // 处理请求 }6. 服务熔断功能:当故障率达到阈值时,快速失败,避免雪崩状态:关闭(Closed):正常状态开启(Open):熔断状态,快速失败半开启(Half-Open):尝试恢复状态实现示例: // Hystrix 熔断配置 @HystrixCommand( commandProperties = { @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"), @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"), @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000") } ) public User getUser(Long id) { return userService.getUser(id); }7. 服务路由功能:根据规则将请求路由到特定服务实例策略:条件路由:根据参数条件路由标签路由:根据服务标签路由脚本路由:使用脚本定义路由规则配置示例: // Dubbo 条件路由 <dubbo:router> <dubbo:condition-router rule="host = 192.168.1.1 => provider = 1.0.0"/> </dubbo:router> // Spring Cloud 路由 @RequestMapping("/api/user/**") public String userService() { return "forward:/user-service/api/user/**"; }8. 服务监控功能:监控服务运行状态和性能指标指标:QPS(每秒查询数)TPS(每秒事务数)响应时间(RT)成功率错误率工具:Prometheus + GrafanaSkyWalkingZipkinELK Stack实现示例: // Micrometer 指标收集 @Autowired private MeterRegistry meterRegistry; public User getUser(Long id) { Timer.Sample sample = Timer.start(meterRegistry); try { User user = userService.getUser(id); sample.stop(meterRegistry.timer("user.get", "status", "success")); return user; } catch (Exception e) { sample.stop(meterRegistry.timer("user.get", "status", "error")); throw e; } }9. 服务配置管理功能:集中管理服务配置特性:动态配置更新配置版本管理配置推送配置回滚工具:Nacos ConfigSpring Cloud ConfigApollo配置示例: // Nacos 配置 @Value("${user.service.timeout}") private int timeout; @NacosValue(value = "${user.service.timeout}", autoRefreshed = true) private int dynamicTimeout;10. 服务灰度发布功能:逐步发布新版本服务策略:按比例流量分配按用户标签路由按地域路由实现示例: // 灰度发布配置 @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } // 使用标签路由 @FeignClient(name = "user-service", qualifiers = "v2") public interface UserServiceV2 { // ... }服务治理最佳实践:1. 分层治理基础层:服务注册、发现、负载均衡控制层:限流、熔断、降级监控层:监控、告警、日志配置层:配置管理、灰度发布2. 渐进式实施先实现基础功能逐步添加高级功能持续优化和调整3. 监控和告警完善的监控指标及时的告警机制定期的性能分析4. 容灾演练定期进行故障演练验证容错机制优化应急响应流程
阅读 0·2月22日 14:06

什么是服务注册与发现?主流的注册中心有哪些?它们各有什么特点?

服务注册与发现是微服务架构中的核心组件,解决了服务实例动态管理的问题:核心概念:1. 服务注册(Service Registration)服务启动时向注册中心注册自己的信息注册信息包括:服务名、IP、端口、元数据等定期发送心跳保持注册状态2. 服务发现(Service Discovery)客户端从注册中心获取服务实例列表支持动态更新服务列表实现负载均衡和故障转移3. 健康检查(Health Check)定期检测服务实例健康状态自动剔除不健康的实例支持主动和被动检查主流注册中心对比:1. Zookeeper特点:基于 ZAB 协议的分布式协调服务优势:成熟稳定,社区活跃强一致性(CP)支持临时节点和持久节点劣势:性能相对较低运维复杂不支持 HTTP 接口适用场景:对一致性要求高的场景2. Eureka特点:Netflix 开发的服务发现组件优势:简单易用,与 Spring Cloud 集成好支持自我保护机制高可用性(AP)劣势:已停止维护(2.x 版本)一致性较弱不支持跨数据中心适用场景:Spring Cloud 微服务架构3. Consul特点:HashiCorp 开发的服务网格工具优势:功能全面(服务发现、健康检查、KV 存储)支持 HTTP 和 DNS 接口支持多数据中心支持服务网格劣势:学习曲线较陡资源占用相对较高适用场景:需要多功能集成的场景4. Nacos特点:阿里巴巴开源的服务发现和配置管理平台优势:功能全面(服务发现、配置管理、DNS)支持 AP 和 CP 模式切换与 Spring Cloud、Dubbo 集成好支持动态配置推送中文文档完善劣势:相对较新,生态不如 Consul 成熟适用场景:国内微服务架构,特别是使用 Spring Cloud Alibaba5. Etcd特点:CoreOS 开发的分布式键值存储优势:基于 Raft 协议,强一致性性能优秀支持 gRPC 接口Kubernetes 的核心组件劣势:功能相对单一运维复杂度较高适用场景:Kubernetes 环境,对一致性要求高的场景服务发现模式:1. 客户端发现模式客户端从注册中心获取服务列表客户端自行选择服务实例优点:减少注册中心压力,响应快缺点:客户端逻辑复杂代表:Eureka、Consul2. 服务端发现模式客户端通过负载均衡器调用服务负载均衡器从注册中心获取服务列表优点:客户端逻辑简单缺点:增加一层代理,可能影响性能代表:Kubernetes Service、Nginx实现示例(Nacos):服务提供者:@SpringBootApplication@EnableDiscoveryClientpublic class ProviderApplication { public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); }}// 配置spring: application: name: user-service cloud: nacos: discovery: server-addr: localhost:8848服务消费者:@RestControllerpublic class ConsumerController { @Autowired private LoadBalancerClient loadBalancerClient; @GetMapping("/call") public String callService() { ServiceInstance instance = loadBalancerClient.choose("user-service"); String url = String.format("http://%s:%s/api/user", instance.getHost(), instance.getPort()); return restTemplate.getForObject(url, String.class); }}选择建议:Spring Cloud 生态:优先选择 Nacos 或 EurekaKubernetes 环境:使用 Etcd 或 CoreDNS需要多功能集成:选择 Consul对一致性要求高:选择 Zookeeper 或 Etcd国内项目:优先选择 Nacos
阅读 0·2月22日 14:06

Promise 的微任务机制是什么?如何理解事件循环?

Promise 的微任务机制是 JavaScript 事件循环中的重要概念,理解它对于掌握异步编程和解决复杂的异步问题至关重要。事件循环基础JavaScript 是单线程的,通过事件循环来处理异步操作。事件循环负责协调执行栈、宏任务队列和微任务队列。事件循环的执行顺序执行同步代码(执行栈)执行所有微任务执行一个宏任务重复步骤 2-3微任务 vs 宏任务微任务(Microtask)Promise.then/catch/finallyqueueMicrotask()MutationObserver宏任务(Macrotask)setTimeoutsetIntervalsetImmediate(Node.js)I/O 操作UI 渲染执行顺序示例基本示例console.log('1. 同步代码');setTimeout(() => { console.log('2. setTimeout(宏任务)');}, 0);Promise.resolve().then(() => { console.log('3. Promise.then(微任务)');});console.log('4. 同步代码');// 输出顺序:// 1. 同步代码// 4. 同步代码// 3. Promise.then(微任务)// 2. setTimeout(宏任务)复杂示例console.log('1. 开始');setTimeout(() => { console.log('2. setTimeout 1'); Promise.resolve().then(() => { console.log('3. setTimeout 1 中的微任务'); });}, 0);Promise.resolve().then(() => { console.log('4. Promise.then 1'); setTimeout(() => { console.log('5. Promise.then 1 中的 setTimeout'); }, 0);});Promise.resolve().then(() => { console.log('6. Promise.then 2');});setTimeout(() => { console.log('7. setTimeout 2');}, 0);console.log('8. 结束');// 输出顺序:// 1. 开始// 8. 结束// 4. Promise.then 1// 6. Promise.then 2// 2. setTimeout 1// 7. setTimeout 2// 3. setTimeout 1 中的微任务// 5. Promise.then 1 中的 setTimeoutPromise 的微任务机制Promise 状态改变触发微任务const promise = new Promise((resolve) => { console.log('1. Promise 构造函数(同步)'); resolve('成功');});promise.then(() => { console.log('2. Promise.then(微任务)');});console.log('3. 同步代码');// 输出顺序:// 1. Promise 构造函数(同步)// 3. 同步代码// 2. Promise.then(微任务)链式调用的微任务Promise.resolve() .then(() => { console.log('1. 第一个 then'); return '结果'; }) .then(() => { console.log('2. 第二个 then'); }) .then(() => { console.log('3. 第三个 then'); });console.log('4. 同步代码');// 输出顺序:// 4. 同步代码// 1. 第一个 then// 2. 第二个 then// 3. 第三个 then嵌套 Promise 的微任务Promise.resolve() .then(() => { console.log('1. 外层 then'); Promise.resolve().then(() => { console.log('2. 内层 then'); }); }) .then(() => { console.log('3. 外层第二个 then'); });console.log('4. 同步代码');// 输出顺序:// 4. 同步代码// 1. 外层 then// 2. 内层 then// 3. 外层第二个 then实际应用场景1. 确保代码在下一个事件循环执行function nextTick(callback) { Promise.resolve().then(callback);}console.log('1. 开始');nextTick(() => { console.log('2. 下一个事件循环');});console.log('3. 结束');// 输出顺序:// 1. 开始// 3. 结束// 2. 下一个事件循环2. 批量更新 DOMfunction batchUpdate(updates) { // 使用微任务确保在当前事件循环结束后执行 Promise.resolve().then(() => { updates.forEach(update => { update(); }); });}// 使用示例batchUpdate([ () => document.body.style.backgroundColor = 'red', () => document.body.style.color = 'white', () => document.body.style.fontSize = '16px']);3. 避免阻塞渲染function processLargeData(data) { let index = 0; function processChunk() { const chunkSize = 1000; const end = Math.min(index + chunkSize, data.length); for (; index < end; index++) { processDataItem(data[index]); } if (index < data.length) { // 使用微任务让出控制权,避免阻塞渲染 Promise.resolve().then(processChunk); } } processChunk();}常见面试题1. Promise 和 setTimeout 的执行顺序setTimeout(() => { console.log('1. setTimeout');}, 0);Promise.resolve().then(() => { console.log('2. Promise.then');});// 输出顺序:// 2. Promise.then// 1. setTimeout原因:微任务优先级高于宏任务,会在当前宏任务执行完后立即执行。2. 多个 Promise 的执行顺序Promise.resolve() .then(() => console.log('1')) .then(() => console.log('2')) .then(() => console.log('3'));Promise.resolve() .then(() => console.log('4')) .then(() => console.log('5')) .then(() => console.log('6'));// 输出顺序:// 1// 4// 2// 5// 3// 6原因:每个 Promise 链的第一个 then 都是微任务,按照注册顺序执行。3. Promise 构造函数中的同步代码console.log('1. 开始');const promise = new Promise((resolve) => { console.log('2. Promise 构造函数'); resolve();});promise.then(() => { console.log('3. Promise.then');});console.log('4. 结束');// 输出顺序:// 1. 开始// 2. Promise 构造函数// 4. 结束// 3. Promise.then原因:Promise 构造函数中的代码是同步执行的,then 回调是微任务。性能考虑1. 避免过长的微任务链// 不推荐:过长的微任务链可能导致性能问题function processItems(items) { return items.reduce((promise, item) => { return promise.then(() => processItem(item)); }, Promise.resolve());}// 推荐:使用 Promise.all 并行处理function processItems(items) { return Promise.all(items.map(item => processItem(item)));}2. 合理使用微任务// 不推荐:不必要的微任务function doSomething() { Promise.resolve().then(() => { console.log('执行'); });}// 推荐:直接执行同步代码function doSomething() { console.log('执行');}Node.js 中的微任务Node.js 中的微任务机制与浏览器略有不同:process.nextTick(() => { console.log('1. process.nextTick');});Promise.resolve().then(() => { console.log('2. Promise.then');});setImmediate(() => { console.log('3. setImmediate');});// 输出顺序:// 1. process.nextTick// 2. Promise.then// 3. setImmediate注意:process.nextTick 的优先级高于 Promise.then。总结微任务优先级高于宏任务:微任务会在当前宏任务执行完后立即执行Promise.then 是微任务:Promise 的回调会在微任务队列中执行事件循环的执行顺序:同步代码 → 微任务 → 宏任务 → 微任务 → 宏任务…链式调用的执行顺序:每个 then 都会创建一个微任务,按顺序执行Promise 构造函数是同步的:构造函数中的代码立即执行合理使用微任务:避免不必要的微任务,注意性能影响Node.js 的差异:process.nextTick 优先级高于 Promise.then
阅读 0·2月22日 14:06

MobX 中 action 的作用和使用方法是什么?

在 MobX 中,action 是修改 observable 状态的唯一推荐方式。使用 action 可以确保状态变化是可追踪、可预测的,并且能够优化性能。Action 的作用批量更新:action 内部的所有状态变化会被批量处理,减少不必要的重新计算可追踪性:所有状态变化都集中在 action 中,便于调试和追踪事务性:action 内部的状态变化是原子的,要么全部成功,要么全部失败性能优化:减少 reaction 的触发次数,提高性能Action 的使用方式1. 使用装饰器import { observable, action } from 'mobx';class TodoStore { @observable todos = []; @action addTodo(text) { this.todos.push({ text, completed: false }); } @action.bound removeTodo(id) { this.todos = this.todos.filter(todo => todo.id !== id); } @action async fetchTodos() { const response = await fetch('/api/todos'); const data = await response.json(); this.todos = data; }}2. 使用 makeObservableimport { makeObservable, observable, action } from 'mobx';class TodoStore { todos = []; constructor() { makeObservable(this, { todos: observable, addTodo: action, removeTodo: action.bound, fetchTodos: action }); } addTodo(text) { this.todos.push({ text, completed: false }); } removeTodo(id) { this.todos = this.todos.filter(todo => todo.id !== id); } async fetchTodos() { const response = await fetch('/api/todos'); const data = await response.json(); this.todos = data; }}3. 使用 runInActionimport { observable, runInAction } from 'mobx';class TodoStore { todos = []; async fetchTodos() { const response = await fetch('/api/todos'); const data = await response.json(); runInAction(() => { this.todos = data; }); }}Action 的类型1. action最基础的 action 装饰器,用于包装状态修改方法。@actionupdateTodo(id, updates) { const todo = this.todos.find(t => t.id === id); if (todo) { Object.assign(todo, updates); }}2. action.bound自动绑定 this 的 action,避免在回调函数中丢失 this 上下文。@action.boundhandleClick() { this.count++;}// 使用时不需要 bind<button onClick={this.handleClick}>Click</button>3. runInAction在异步操作中修改状态时使用。async loadData() { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; });}Action 的最佳实践1. 始终在 action 中修改状态// ❌ 错误:直接修改状态class Store { @observable count = 0; increment() { this.count++; // 不是 action }}// ✅ 正确:在 action 中修改状态class Store { @observable count = 0; @action increment() { this.count++; }}2. 使用 action.bound 处理事件处理器class Component { @observable count = 0; @action.bound handleClick() { this.count++; } render() { return <button onClick={this.handleClick}>Click</button>; }}3. 异步操作中使用 runInAction@actionasync fetchUser(id) { this.loading = true; try { const response = await fetch(`/api/users/${id}`); const data = await response.json(); runInAction(() => { this.user = data; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); }}4. 合理拆分 action// ❌ 过于复杂的 action@actionhandleComplexOperation(data) { this.loading = true; this.data = data; this.filtered = data.filter(item => item.active); this.count = this.filtered.length; this.timestamp = Date.now(); this.loading = false;}// ✅ 拆分为多个小 action@actionhandleComplexOperation(data) { this.setLoading(true); this.setData(data); this.processData(data); this.setLoading(false);}@actionsetLoading(loading) { this.loading = loading;}@actionsetData(data) { this.data = data;}@actionprocessData(data) { this.filtered = data.filter(item => item.active); this.count = this.filtered.length; this.timestamp = Date.now();}常见错误1. 在异步回调中直接修改状态// ❌ 错误@actionasync fetchData() { const data = await fetch('/api/data'); this.data = await data.json(); // 不在 action 中}// ✅ 正确@actionasync fetchData() { const data = await fetch('/api/data'); runInAction(() => { this.data = data.json(); });}2. 忘记使用 action.bound// ❌ 错误:this 可能丢失class Component { @action handleClick() { this.count++; } render() { return <button onClick={this.handleClick}>Click</button>; }}// ✅ 正确:使用 action.boundclass Component { @action.bound handleClick() { this.count++; } render() { return <button onClick={this.handleClick}>Click</button>; }}性能优化使用 action 批量更新:减少 reaction 触发次数避免在循环中创建 action:复用 action 函数合理使用 runInAction:只在必要时使用使用 action.bound:避免重复 bind总结action 是修改 observable 状态的唯一推荐方式使用 action 可以批量更新、提高性能、便于调试优先使用 action.bound 处理事件处理器异步操作中使用 runInAction 修改状态合理拆分复杂的 action,提高可维护性
阅读 0·2月22日 14:06

RPC 调用中的分布式事务问题如何解决?常见的分布式事务解决方案有哪些?

分布式事务是 RPC 调用中的难点问题,涉及多个服务之间的数据一致性保证:问题背景:在微服务架构中,一个业务操作可能涉及多个服务的调用,如何保证这些服务之间的数据一致性是一个挑战。分布式事务理论:1. CAP 定理一致性(Consistency):所有节点同时看到相同的数据可用性(Availability):每个请求都能得到响应分区容错性(Partition Tolerance):系统在网络分区时仍能继续运行结论:在分布式系统中,只能同时满足两个特性2. BASE 理论基本可用(Basically Available):系统保证基本可用软状态(Soft State):允许数据存在中间状态最终一致性(Eventually Consistent):经过一段时间后数据最终一致常见解决方案:1. 两阶段提交(2PC)准备阶段:协调者询问所有参与者是否可以提交提交阶段:根据参与者反馈决定提交或回滚优点:强一致性缺点:性能差,存在单点故障,阻塞适用场景:对一致性要求极高的场景2. 三阶段提交(3PC)在 2PC 基础上增加预提交阶段减少阻塞时间仍然存在性能问题3. TCC(Try-Confirm-Cancel)Try 阶段:预留资源,检查业务规则Confirm 阶段:确认执行业务操作Cancel 阶段:取消操作,释放资源优点:性能较好,最终一致性缺点:代码侵入性强,需要实现三个接口实现示例: public interface OrderService { // Try:创建订单,预扣库存 @Compensable boolean createOrder(Order order); // Confirm:确认订单 void confirmOrder(Long orderId); // Cancel:取消订单,回滚库存 void cancelOrder(Long orderId); }4. 本地消息表在本地数据库中记录消息定时任务扫描消息表并发送消息消费成功后删除记录优点:实现简单,可靠性高缺点:需要额外的存储和定时任务适用场景:异步场景,最终一致性5. 事务消息(如 RocketMQ)发送半消息执行本地事务提交或回滚消息消费者消费消息优点:性能好,支持高并发缺点:依赖消息中间件适用场景:高并发场景,最终一致性6. Saga 模式将长事务拆分为多个本地事务每个本地事务都有对应的补偿操作按顺序执行,失败时执行补偿优点:性能好,适合长事务缺点:需要设计补偿逻辑,可能产生脏读适用场景:长事务,业务流程复杂7. Seata(阿里开源)支持 AT、TCC、SAGA、XA 模式AT 模式:一阶段:解析 SQL,记录前镜像和后镜像二阶段:提交或回滚,通过镜像数据恢复优点:对业务代码侵入小缺点:需要数据库支持适用场景:Java 生态,Spring Cloud 微服务实现示例(Seata AT 模式):@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)public void createOrder(Order order) { // 扣减库存 inventoryService.deductStock(order.getProductId(), order.getQuantity()); // 创建订单 orderMapper.insert(order); // 扣减余额 accountService.deductBalance(order.getUserId(), order.getAmount());}选择建议:强一致性要求:2PC、3PC、Seata XA高并发场景:TCC、事务消息、Seata AT长事务:Saga 模式简单场景:本地消息表Java 生态:Seata 框架最终一致性可接受:TCC、Saga、事务消息注意事项:根据业务场景选择合适的方案考虑性能和一致性的平衡做好幂等性设计完善的监控和告警机制定期进行故障演练
阅读 0·2月22日 14:06

MobX 中 computed 的作用和使用场景有哪些?

在 MobX 中,computed 是基于 observable 状态自动更新的派生值,类似于 Vue 的 computed 属性。computed 值会缓存计算结果,只有当依赖的 observable 状态发生变化时才会重新计算。Computed 的特性自动缓存:计算结果会被缓存,避免重复计算懒计算:只有在被访问时才会计算自动追踪依赖:自动追踪依赖的 observable 状态自动更新:依赖的状态变化时自动重新计算纯函数:computed 应该是纯函数,不应该有副作用Computed 的使用方式1. 使用装饰器import { observable, computed } from 'mobx';class TodoStore { @observable todos = []; @observable filter = 'all'; @computed get completedTodos() { return this.todos.filter(todo => todo.completed); } @computed get activeTodos() { return this.todos.filter(todo => !todo.completed); } @computed get filteredTodos() { switch (this.filter) { case 'completed': return this.completedTodos; case 'active': return this.activeTodos; default: return this.todos; } }}2. 使用 makeObservableimport { makeObservable, observable, computed } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeObservable(this, { todos: observable, filter: observable, completedTodos: computed, activeTodos: computed, filteredTodos: computed }); } get completedTodos() { return this.todos.filter(todo => todo.completed); } get activeTodos() { return this.todos.filter(todo => !todo.completed); } get filteredTodos() { switch (this.filter) { case 'completed': return this.completedTodos; case 'active': return this.activeTodos; default: return this.todos; } }}3. 使用 makeAutoObservable(推荐)import { makeAutoObservable } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeAutoObservable(this); } get completedTodos() { return this.todos.filter(todo => todo.completed); } get activeTodos() { return this.todos.filter(todo => !todo.completed); } get filteredTodos() { switch (this.filter) { case 'completed': return this.completedTodos; case 'active': return this.activeTodos; default: return this.todos; } }}4. 使用 computed 函数import { observable, computed } from 'mobx';const store = observable({ todos: [], filter: 'all'});const completedTodos = computed(() => store.todos.filter(todo => todo.completed));const activeTodos = computed(() => store.todos.filter(todo => !todo.completed));console.log(completedTodos.get()); // 获取计算值Computed 的配置选项1. name为 computed 设置名称,便于调试。@computed({ name: 'completedTodos' })get completedTodos() { return this.todos.filter(todo => todo.completed);}2. equals自定义比较函数,决定是否需要重新计算。@computed({ equals: (a, b) => a.length === b.length && a.every(item => b.includes(item))})get filteredTodos() { return this.todos.filter(todo => todo.completed);}3. keepAlive保持计算值活跃,即使没有被访问。@computed({ keepAlive: true })get expensiveComputation() { return this.largeArray.reduce((sum, item) => sum + item.value, 0);}Computed 的最佳实践1. 使用 computed 缓存复杂计算class Store { @observable items = []; @computed get totalValue() { return this.items.reduce((sum, item) => sum + item.value, 0); } @computed get averageValue() { return this.items.length > 0 ? this.totalValue / this.items.length : 0; }}2. 避免在 computed 中产生副作用// ❌ 错误:computed 中有副作用@computed get filteredItems() { console.log('Filtering items'); // 副作用 return this.items.filter(item => item.active);}// ✅ 正确:使用 reaction 处理副作用@computed get filteredItems() { return this.items.filter(item => item.active);}constructor() { reaction( () => this.filteredItems, (items) => console.log('Filtered items:', items) );}3. 合理使用 computed 链class Store { @observable products = []; @observable category = 'all'; @observable minPrice = 0; @observable maxPrice = Infinity; @computed get filteredByCategory() { return this.category === 'all' ? this.products : this.products.filter(p => p.category === this.category); } @computed get filteredByPrice() { return this.filteredByCategory.filter( p => p.price >= this.minPrice && p.price <= this.maxPrice ); } @computed get sortedProducts() { return [...this.filteredByPrice].sort((a, b) => a.price - b.price); }}4. 使用 computed 处理表单验证class FormStore { @observable username = ''; @observable email = ''; @observable password = ''; @computed get usernameError() { if (this.username.length < 3) { return 'Username must be at least 3 characters'; } return ''; } @computed get emailError() { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(this.email)) { return 'Invalid email format'; } return ''; } @computed get passwordError() { if (this.password.length < 8) { return 'Password must be at least 8 characters'; } return ''; } @computed get isValid() { return !this.usernameError && !this.emailError && !this.passwordError; }}Computed vs Reaction| 特性 | Computed | Reaction ||------|----------|----------|| 用途 | 计算派生值 | 执行副作用 || 返回值 | 返回计算结果 | 不返回值 || 缓存 | 自动缓存 | 不缓存 || 触发时机 | 被访问时 | 依赖变化时立即执行 || 适用场景 | 数据转换、过滤、聚合 | 日志、API 调用、DOM 操作 |性能优化合理使用 computed:避免过度使用,只在需要缓存时使用避免复杂计算:如果计算非常耗时,考虑使用 memoization使用 computed 链:将复杂计算拆分为多个小的 computed避免在 computed 中创建新对象:可能导致不必要的重新计算常见错误1. 在 computed 中修改状态// ❌ 错误:computed 中修改状态@computed get filteredItems() { this.lastFilterTime = Date.now(); // 修改状态 return this.items.filter(item => item.active);}// ✅ 正确:使用 reaction 处理副作用@computed get filteredItems() { return this.items.filter(item => item.active);}constructor() { reaction( () => this.filteredItems, () => { this.lastFilterTime = Date.now(); } );}2. 在 computed 中使用异步操作// ❌ 错误:computed 中使用异步操作@computed async get userData() { const response = await fetch('/api/user'); return response.json();}// ✅ 正确:使用 reaction 处理异步操作constructor() { reaction( () => this.userId, async (id) => { const response = await fetch(`/api/user/${id}`); runInAction(() => { this.userData = await response.json(); }); } );}总结computed 是基于 observable 状态自动更新的派生值computed 会自动缓存计算结果,提高性能computed 应该是纯函数,不应该有副作用合理使用 computed 链处理复杂计算避免在 computed 中修改状态或使用异步操作使用 reaction 处理副作用和异步操作
阅读 0·2月22日 14:05

MobX 中 observable 的使用方法和注意事项有哪些?

在 MobX 中,observable 是核心概念之一,它将普通的 JavaScript 对象、数组、Map、Set 等数据结构转换为可观察对象,使 MobX 能够追踪其变化。Observable 的使用方式1. 使用装饰器(Decorator)import { observable, action, computed } from 'mobx';class TodoStore { @observable todos = []; @observable filter = 'all'; @computed get completedTodos() { return this.todos.filter(todo => todo.completed); } @action addTodo(text) { this.todos.push({ text, completed: false }); }}2. 使用 makeObservableimport { makeObservable, observable, action, computed } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeObservable(this, { todos: observable, filter: observable, completedTodos: computed, addTodo: action }); } get completedTodos() { return this.todos.filter(todo => todo.completed); } addTodo(text) { this.todos.push({ text, completed: false }); }}3. 使用 makeAutoObservable(推荐)import { makeAutoObservable } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeAutoObservable(this); } get completedTodos() { return this.todos.filter(todo => todo.completed); } addTodo(text) { this.todos.push({ text, completed: false }); }}4. 使用 observable 函数import { observable } from 'mobx';const state = observable({ todos: [], filter: 'all'});const completedTodos = observable(() => state.todos.filter(todo => todo.completed));Observable 的特性1. 自动追踪依赖当 observable 对象被读取时,MobX 会自动建立依赖关系。import { observable, autorun } from 'mobx';const store = observable({ count: 0});autorun(() => { console.log('Count is:', store.count);});store.count = 1; // 自动触发 autorun2. 深度可观察observable 默认是深度可观察的,会递归地将对象的所有属性都转换为可观察。const store = observable({ user: { name: 'John', address: { city: 'New York' } }});store.user.address.city = 'Boston'; // 会被追踪3. 数组和 Map 的特殊处理observable 数组和 Map 提供了额外的 API,但保持与原生 API 兼容。const list = observable([1, 2, 3]);list.push(4); // MobX 会追踪这个变化list.splice(0, 1); // 也会被追踪Observable 配置选项1. shallow(浅层可观察)只追踪对象本身的变化,不追踪嵌套对象。import { observable } from 'mobx';const store = observable({ user: { name: 'John' }}, {}, { deep: false });store.user = { name: 'Jane' }; // 会被追踪store.user.name = 'Bob'; // 不会被追踪2. asMap、asArray显式指定数据结构类型。const map = observable.map({ key: 'value' });const array = observable.array([1, 2, 3]);注意事项不要在 action 外部修改 observable 状态避免在 constructor 中使用装饰器合理使用 computed 缓存计算结果对于大型对象,考虑使用 shallow 优化性能observable 对象不能被冻结(Object.freeze)最佳实践优先使用 makeAutoObservable 简化配置对于简单状态,使用 observable 函数对于复杂状态管理,使用类 + 装饰器或 makeObservable合理使用 computed 避免重复计算始终在 action 中修改 observable 状态
阅读 0·2月22日 14:05

MobX 中 reaction 的类型和使用场景是什么?

在 MobX 中,reaction 是用于处理副作用的机制,当 observable 状态发生变化时自动执行指定的函数。reaction 类似于 React 的 useEffect,但更加灵活和高效。Reaction 的类型1. autorun自动追踪依赖并在依赖变化时立即执行,适合需要立即执行的场景。import { observable, autorun } from 'mobx';class TodoStore { @observable todos = []; constructor() { autorun(() => { console.log('Total todos:', this.todos.length); // 保存到 localStorage localStorage.setItem('todos', JSON.stringify(this.todos)); }); }}2. reaction提供更细粒度的控制,可以指定追踪的数据和执行函数,适合需要控制执行时机的场景。import { observable, reaction } from 'mobx';class TodoStore { @observable todos = []; @observable filter = 'all'; constructor() { reaction( () => this.todos.length, // 追踪的数据 (length) => { console.log('Todo count changed:', length); }, { fireImmediately: false } // 配置选项 ); }}3. when当条件满足时执行一次,然后自动清理,适合一次性操作。import { observable, when } from 'mobx';class TodoStore { @observable todos = []; @observable loading = false; constructor() { when( () => !this.loading && this.todos.length === 0, () => { console.log('Ready to load todos'); this.loadTodos(); } ); } @action loadTodos() { this.loading = true; // 加载数据... }}Reaction 的配置选项1. fireImmediately是否立即执行一次。reaction( () => this.filter, (filter) => { console.log('Current filter:', filter); }, { fireImmediately: true } // 立即执行一次);2. delay延迟执行,防抖效果。reaction( () => this.searchQuery, (query) => { this.performSearch(query); }, { delay: 300 } // 延迟 300ms 执行);3. equals自定义比较函数,决定是否触发 reaction。reaction( () => this.items, (items) => { console.log('Items changed'); }, { equals: (a, b) => { return a.length === b.length && a.every(item => b.includes(item)); } });4. name为 reaction 设置名称,便于调试。reaction( () => this.todos, (todos) => { console.log('Todos updated'); }, { name: 'saveTodosToLocalStorage' });Reaction 的使用场景1. 数据持久化class TodoStore { @observable todos = []; constructor() { // 从 localStorage 加载 this.todos = JSON.parse(localStorage.getItem('todos') || '[]'); // 保存到 localStorage autorun(() => { localStorage.setItem('todos', JSON.stringify(this.todos)); }); }}2. 日志记录class Store { @observable user = null; @observable actions = []; constructor() { reaction( () => this.user, (user) => { console.log('User changed:', user); this.actions.push({ type: 'USER_CHANGE', user, timestamp: Date.now() }); } ); }}3. API 调用class ProductStore { @observable categoryId = null; @observable products = []; @observable loading = false; constructor() { reaction( () => this.categoryId, async (categoryId) => { if (categoryId) { this.loading = true; try { const response = await fetch(`/api/products?category=${categoryId}`); const data = await response.json(); runInAction(() => { this.products = data; this.loading = false; }); } catch (error) { runInAction(() => { this.loading = false; }); } } } ); }}4. 搜索防抖class SearchStore { @observable query = ''; @observable results = []; @observable loading = false; constructor() { reaction( () => this.query, async (query) => { if (query.length > 2) { this.loading = true; try { const response = await fetch(`/api/search?q=${query}`); const data = await response.json(); runInAction(() => { this.results = data; this.loading = false; }); } catch (error) { runInAction(() => { this.loading = false; }); } } }, { delay: 300 } // 防抖 300ms ); }}5. 条件初始化class AppStore { @observable initialized = false; @observable user = null; constructor() { when( () => this.initialized, () => { this.loadUserData(); } ); } @action loadUserData() { // 加载用户数据 }}Reaction 的清理1. 使用 dispose 清理class Component { disposer = null; componentDidMount() { this.disposer = reaction( () => this.store.todos, (todos) => { console.log('Todos changed:', todos); } ); } componentWillUnmount() { if (this.disposer) { this.disposer(); // 清理 reaction } }}2. 使用 useEffect 清理import { useEffect } from 'react';import { reaction } from 'mobx';function TodoList({ store }) { useEffect(() => { const disposer = reaction( () => store.todos, (todos) => { console.log('Todos changed:', todos); } ); return () => disposer(); // 清理 reaction }, [store]); return <div>{/* ... */}</div>;}Reaction vs Computed| 特性 | Reaction | Computed ||------|----------|----------|| 用途 | 执行副作用 | 计算派生值 || 返回值 | 不返回值 | 返回计算结果 || 缓存 | 不缓存 | 自动缓存 || 触发时机 | 依赖变化时立即执行 | 被访问时才计算 || 适用场景 | 日志、API 调用、DOM 操作 | 数据转换、过滤、聚合 |最佳实践1. 合理选择 reaction 类型// ✅ 使用 autorun:需要立即执行autorun(() => { console.log('Current state:', this.state);});// ✅ 使用 reaction:需要控制执行时机reaction( () => this.userId, (id) => this.loadUser(id));// ✅ 使用 when:一次性操作when( () => this.ready, () => this.start());2. 避免在 reaction 中修改依赖的状态// ❌ 错误:在 reaction 中修改依赖的状态reaction( () => this.count, (count) => { this.count = count + 1; // 会导致无限循环 });// ✅ 正确:使用 action 修改其他状态reaction( () => this.count, (count) => { this.setCount(count + 1); });3. 使用 delay 防抖// ✅ 使用 delay 防抖,避免频繁触发reaction( () => this.searchQuery, (query) => this.performSearch(query), { delay: 300 });4. 记得清理 reaction// ✅ 在组件卸载时清理 reactionuseEffect(() => { const disposer = reaction( () => store.data, (data) => console.log(data) ); return () => disposer();}, [store]);常见错误1. 忘记清理 reaction// ❌ 错误:忘记清理 reactionclass Component { componentDidMount() { reaction(() => this.store.data, (data) => { console.log(data); }); }}// ✅ 正确:清理 reactionclass Component { disposer = null; componentDidMount() { this.disposer = reaction(() => this.store.data, (data) => { console.log(data); }); } componentWillUnmount() { if (this.disposer) { this.disposer(); } }}2. 在 reaction 中直接修改状态// ❌ 错误:在 reaction 中直接修改状态reaction( () => this.count, (count) => { this.count = count + 1; // 不在 action 中 });// ✅ 正确:在 action 中修改状态reaction( () => this.count, (count) => { runInAction(() => { this.count = count + 1; }); });总结reaction 是 MobX 中处理副作用的机制autorun 适合需要立即执行的场景reaction 提供更细粒度的控制when 适合一次性操作使用 delay 可以实现防抖效果记得在组件卸载时清理 reaction避免在 reaction 中修改依赖的状态使用 action 或 runInAction 修改状态
阅读 0·2月22日 14:05

MobX 中如何处理异步操作?

在 MobX 中,异步操作需要特别注意,因为状态变化必须在 action 中进行。MobX 提供了多种方式来处理异步操作。处理异步操作的方式1. 使用 runInActionimport { observable, action, runInAction } from 'mobx';class UserStore { @observable users = []; @observable loading = false; @observable error = null; @action async fetchUsers() { this.loading = true; this.error = null; try { const response = await fetch('/api/users'); const data = await response.json(); runInAction(() => { this.users = data; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); } }}2. 使用 async actionimport { observable, action } from 'mobx';class UserStore { @observable users = []; @observable loading = false; @observable error = null; @action.bound async fetchUsers() { this.loading = true; this.error = null; try { const response = await fetch('/api/users'); const data = await response.json(); this.users = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; } }}3. 使用 flowimport { observable, flow } from 'mobx';class UserStore { @observable users = []; @observable loading = false; @observable error = null; fetchUsers = flow(function* () { this.loading = true; this.error = null; try { const response = yield fetch('/api/users'); const data = yield response.json(); this.users = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; } });}详细对比runInAction优点:灵活性高,可以在任何地方使用适合处理复杂的异步逻辑可以精确控制状态更新的时机缺点:需要手动包裹状态更新代码代码结构可能不够清晰适用场景:需要在异步操作的不同阶段更新状态复杂的异步逻辑需要精确控制状态更新时机@actionasync complexOperation() { this.loading = true; try { const data1 = await this.fetchData1(); runInAction(() => { this.data1 = data1; }); const data2 = await this.fetchData2(data1.id); runInAction(() => { this.data2 = data2; }); const result = await this.processData(data1, data2); runInAction(() => { this.result = result; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); }}async action优点:代码结构清晰自动处理状态更新不需要手动包裹代码缺点:灵活性较低所有状态更新都在同一个 action 中适用场景:简单的异步操作不需要精确控制状态更新时机代码结构优先的场景@action.boundasync simpleFetch() { this.loading = true; try { const response = await fetch('/api/data'); const data = await response.json(); this.data = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; }}flow优点:代码结构最清晰自动处理状态更新支持取消操作更好的错误处理缺点:需要学习 generator 语法兼容性问题(需要 polyfill)适用场景:复杂的异步流程需要取消操作的场景需要更好的错误处理fetchUsers = flow(function* () { this.loading = true; this.error = null; try { const response = yield fetch('/api/users'); const data = yield response.json(); this.users = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; }});// 可以取消 flowconst fetchTask = store.fetchUsers();fetchTask.cancel();最佳实践1. 统一使用 async actionclass ApiStore { @observable data = null; @observable loading = false; @observable error = null; @action.bound async fetchData(url) { this.loading = true; this.error = null; try { const response = await fetch(url); const data = await response.json(); this.data = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; } }}2. 使用 flow 处理复杂流程class OrderStore { @observable orders = []; @observable loading = false; @observable error = null; createOrder = flow(function* (orderData) { this.loading = true; this.error = null; try { // 验证订单 const validated = yield this.validateOrder(orderData); // 创建订单 const order = yield this.createOrderApi(validated); // 支付订单 const paid = yield this.payOrder(order.id); // 更新状态 this.orders.push(paid); this.loading = false; return paid; } catch (error) { this.error = error.message; this.loading = false; throw error; } });}3. 使用 runInAction 处理分步更新class UploadStore { @observable progress = 0; @observable files = []; @observable uploading = false; @action async uploadFiles(files) { this.uploading = true; this.progress = 0; try { for (let i = 0; i < files.length; i++) { const file = files[i]; await this.uploadFile(file); runInAction(() => { this.files.push(file); this.progress = ((i + 1) / files.length) * 100; }); } runInAction(() => { this.uploading = false; }); } catch (error) { runInAction(() => { this.uploading = false; }); throw error; } }}4. 使用 reaction 处理自动加载class DataStore { @observable userId = null; @observable userData = null; @observable loading = false; constructor() { reaction( () => this.userId, (userId) => { if (userId) { this.loadUserData(userId); } } ); } @action.bound async loadUserData(userId) { this.loading = true; try { const response = await fetch(`/api/users/${userId}`); const data = await response.json(); this.userData = data; this.loading = false; } catch (error) { this.loading = false; } }}5. 错误处理和重试class Store { @observable data = null; @observable loading = false; @observable error = null; @observable retryCount = 0; @action.bound async fetchDataWithRetry(url, maxRetries = 3) { this.loading = true; this.error = null; for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; this.retryCount = 0; }); return data; } catch (error) { runInAction(() => { this.retryCount = i + 1; }); if (i === maxRetries - 1) { runInAction(() => { this.error = error.message; this.loading = false; }); throw error; } // 等待后重试 await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } } }}常见错误1. 在 async 函数中直接修改状态// ❌ 错误:在 async 函数中直接修改状态@actionasync fetchData() { this.loading = true; const response = await fetch('/api/data'); const data = await response.json(); this.data = data; // 不在 action 中 this.loading = false;}// ✅ 正确:使用 runInAction 或 async action@actionasync fetchData() { this.loading = true; const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; });}// 或者@action.boundasync fetchData() { this.loading = true; const response = await fetch('/api/data'); const data = await response.json(); this.data = data; this.loading = false;}2. 忘记处理错误// ❌ 错误:忘记处理错误@actionasync fetchData() { this.loading = true; const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; });}// ✅ 正确:处理错误@actionasync fetchData() { this.loading = true; this.error = null; try { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); }}3. 忘记重置 loading 状态// ❌ 错误:忘记重置 loading 状态@actionasync fetchData() { this.loading = true; try { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; }); } catch (error) { runInAction(() => { this.error = error.message; }); }}// ✅ 正确:在所有分支中重置 loading 状态@actionasync fetchData() { this.loading = true; try { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); }}性能优化1. 使用 debounce 防抖import { debounce } from 'lodash';class SearchStore { @observable query = ''; @observable results = []; @observable loading = false; constructor() { this.debouncedSearch = debounce(this.performSearch.bind(this), 300); } @action setQuery(query) { this.query = query; this.debouncedSearch(); } @action.bound async performSearch() { if (this.query.length < 2) { this.results = []; return; } this.loading = true; try { const response = await fetch(`/api/search?q=${this.query}`); const data = await response.json(); this.results = data; this.loading = false; } catch (error) { this.loading = false; } }}2. 使用 requestAnimationFrame 优化 UI 更新@actionasync loadData() { this.loading = true; const data = await this.fetchData(); // 使用 requestAnimationFrame 优化 UI 更新 requestAnimationFrame(() => { runInAction(() => { this.data = data; this.loading = false; }); });}总结使用 async action 处理简单的异步操作使用 runInAction 处理需要精确控制状态更新时机的场景使用 flow 处理复杂的异步流程始终处理错误和重置 loading 状态使用 reaction 处理自动加载使用 debounce 防抖优化性能使用 requestAnimationFrame 优化 UI 更新
阅读 0·2月22日 14:05