MobX(TS)结合React函数组件的Todo过滤更新异常问题
问题描述
我实现了一个基于MobX的Todo状态管理Store:
import { makeAutoObservable } from "mobx"; import { Todo } from "./../types"; class Todos { todoList: Todo[] = [ { id: 0, description: "Погулять с собакой", completed: false }, { id: 1, description: "Полить цветы", completed: false }, { id: 2, description: "Покормить кота", completed: false }, { id: 3, description: "Помыть посуду", completed: true }, ]; // Input: Add Task taskInput: string = ""; // Filter: query query: string = ""; // Filter: showOnlyCompletedTasks showOnlyCompleted: boolean = false; constructor() { makeAutoObservable(this); } setShowOnlyCompletedState(value: boolean) { this.showOnlyCompleted = value; } changeCompletionState(id: number) { const task = this.todoList.find((todo) => todo.id === id); if (task) task.completed = !task.completed; } addTask(text: string) { if (text !== "") { const newTodo: Todo = { id: Number(new Date()), description: text, completed: false, }; this.todoList.push(newTodo); } } taskChangeInput(value: string) { this.taskInput = value; } queryChangeInput(value: string) { this.query = value; } } export default new Todos();
同时实现了过滤逻辑的自定义Hook:
import { Todo } from "../types"; import { useMemo } from "react"; function useFilterByQuery (list: Todo[], query: string):Todo[] { const filteredList = useMemo(()=>{ if (!query) return list return list.filter(todo => todo.description.toLowerCase().includes(query.toLowerCase())) }, [list, query]) return filteredList } export function useFilterByAllFilters (list:Todo[], query: string, showOnlyCompleted: boolean):Todo[] { const filteredByQuery = useFilterByQuery(list, query) const filteredList = useMemo(()=>{ if(!showOnlyCompleted) return filteredByQuery return filteredByQuery.filter(todo => todo.completed) }, [filteredByQuery, showOnlyCompleted]) return filteredList }
问题现象:开启「仅显示已完成任务」(showOnlyCompleted设为true)后,修改任务的完成状态时,列表不会实时更新——已取消完成的任务不会立即被过滤掉,只有切换showOnlyCompleted为false再重新设为true,列表才会更新。
问题原因与解决方案
核心原因
- MobX可观察性传递缺失:
makeAutoObservable仅让todoList数组本身成为可观察对象,但数组内部的todo对象属性变更不会触发数组引用变化。而useMemo依赖的是数组引用,引用不变时不会重新计算过滤结果。 - 过滤Hook依赖不足:
useMemo的依赖数组仅包含数组引用、query和showOnlyCompleted,没有追踪到数组元素内部的completed属性变化,导致过滤逻辑无法响应任务状态变更。
解决方案
方案一:让Todo对象成为可观察对象
修改Store中初始数据和addTask方法,将每个Todo对象转为MobX可观察对象,确保内部属性变更能被追踪:
import { makeAutoObservable } from "mobx"; import { Todo } from "./../types"; class Todos { todoList: Todo[] = [ makeAutoObservable({ id: 0, description: "Погулять с собакой", completed: false }), makeAutoObservable({ id: 1, description: "Полить цветы", completed: false }), makeAutoObservable({ id: 2, description: "Покормить кота", completed: false }), makeAutoObservable({ id: 3, description: "Помыть посуду", completed: true }), ]; // ... 其他属性不变 addTask(text: string) { if (text !== "") { const newTodo: Todo = makeAutoObservable({ id: Number(new Date()), description: text, completed: false, }); this.todoList.push(newTodo); } } // ... 其他方法不变 } export default new Todos();
同时用MobX的observer高阶组件或useObserver包裹React组件,确保组件能响应Todo对象内部属性的变化。
方案二:将过滤逻辑转为Store派生状态(推荐)
利用MobX的自动派生能力,在Store中添加计算属性直接返回过滤后的列表,无需手动维护useMemo依赖:
class Todos { // ... 原有属性和方法不变 get filteredTodoList() { let filtered = this.todoList; // 关键词过滤 if (this.query) { const lowerQuery = this.query.toLowerCase(); filtered = filtered.filter(todo => todo.description.toLowerCase().includes(lowerQuery)); } // 完成状态过滤 if (this.showOnlyCompleted) { filtered = filtered.filter(todo => todo.completed); } return filtered; } }
在组件中直接使用store.filteredTodoList,结合observer包裹组件,MobX会自动追踪所有相关依赖(todoList内部属性、query、showOnlyCompleted),任何变化都会触发派生值更新和组件重渲染。
方案三:修改useMemo依赖以感知元素变化
如果要保留现有过滤Hook,可通过添加元素状态标识作为依赖,强制useMemo重新计算:
function useFilterByQuery (list: Todo[], query: string):Todo[] { const filteredList = useMemo(()=>{ if (!query) return list return list.filter(todo => todo.description.toLowerCase().includes(query.toLowerCase())) }, [query, list.map(todo => `${todo.id}-${todo.description}`)]) return filteredList } export function useFilterByAllFilters (list:Todo[], query: string, showOnlyCompleted: boolean):Todo[] { const filteredByQuery = useFilterByQuery(list, query) const filteredList = useMemo(()=>{ if(!showOnlyCompleted) return filteredByQuery return filteredByQuery.filter(todo => todo.completed) }, [filteredByQuery, showOnlyCompleted, list.map(todo => `${todo.id}-${todo.completed}`)]) return filteredList }
此方法适合小型列表,大型列表会因频繁生成依赖数组影响性能。
内容的提问来源于stack exchange,提问作者luckyme-
相关产品推荐
相关产品推荐

