React中使用AWS Amplify订阅如何获取state最新值
问题根因
你遇到的是React useEffect常见的闭包陷阱问题:空依赖的useEffect里的回调捕获的是组件首次挂载时的状态快照,所以永远拿不到更新后的todos值;直接把todos加为依赖又会导致每次todos变化时都新增一个订阅,没有清理的话就会触发多次回调。
最优解决方案:使用setState的函数式更新
React的状态更新函数支持传入函数作为参数,这个函数接收的第一个参数就是当前最新的状态值,完全不需要把todos加到依赖数组里,也不会触发重复订阅:
useEffect(() => { todoUpdatedSubs.subscribe({ next: (status: SubscriptionStatus<OnTodoUpdatedSubscription>) => { setIsTaskUpdated(true); console.log("New Updated SUBSCRIPTION ==> ", status); if (!status.value.data.onTodoUpdated.success) return console.log("error", status); const { id, todo, isCompleted } = status.value.data.onTodoUpdated; // 用函数式更新获取最新的todos setTodos(prevTodos => { const newArr = [...prevTodos]; // 如果editingIndex也会动态更新,可搭配下面的useRef方案存储最新值 newArr[editingIndex] = { id, todo, isCompleted }; return newArr; }); }, }); }, [])
这个方案不需要修改其他逻辑,适配性最高。
备选方案1:用useRef存储最新状态
如果你的回调里需要用到多个动态变化的状态值,可以用useRef来保存最新的状态快照,每次状态更新时同步更新ref的current值,订阅回调里直接读ref.current就能拿到最新值:
// 定义ref存储最新的todos和editingIndex const todosRef = useRef(todos); const editingIndexRef = useRef(editingIndex); // 状态变化时同步更新ref useEffect(() => { todosRef.current = todos; editingIndexRef.current = editingIndex; }, [todos, editingIndex]); // 订阅逻辑不变,依赖保持空数组 useEffect(() => { todoUpdatedSubs.subscribe({ next: (status: SubscriptionStatus<OnTodoUpdatedSubscription>) => { setIsTaskUpdated(true); console.log("New Updated SUBSCRIPTION ==> ", status); if (!status.value.data.onTodoUpdated.success) return console.log("error", status); const newArr = [...todosRef.current]; console.log("Prev todos", todosRef.current); const { id, todo, isCompleted } = status.value.data.onTodoUpdated; newArr[editingIndexRef.current] = { id, todo, isCompleted }; setTodos(newArr); }, }); }, [])
备选方案2:给useEffect加订阅清理函数
如果你一定要把todos加到依赖数组里,只要在useEffect的返回函数里取消之前的订阅,就不会出现重复订阅的问题:
useEffect(() => { // 拿到订阅实例 const subscription = todoUpdatedSubs.subscribe({ next: (status: SubscriptionStatus<OnTodoUpdatedSubscription>) => { setIsTaskUpdated(true); console.log("New Updated SUBSCRIPTION ==> ", status); if (!status.value.data.onTodoUpdated.success) return console.log("error", status); const newArr = [...todos]; console.log("Prev todos", todos); const { id, todo, isCompleted } = status.value.data.onTodoUpdated; newArr[editingIndex] = { id, todo, isCompleted }; setTodos(newArr); }, }); // 组件卸载/依赖变化时取消旧订阅 return () => subscription.unsubscribe(); }, [todos, editingIndex])
这个方案每次todos变化都会先取消旧的订阅再新增新的订阅,不会有重复回调的问题,但性能略低于前两种方案。
内容的提问来源于stack exchange,提问作者plutolaser
相关产品推荐
相关产品推荐

