You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,列表才会更新。


问题原因与解决方案

核心原因

  1. MobX可观察性传递缺失:makeAutoObservable仅让todoList数组本身成为可观察对象,但数组内部的todo对象属性变更不会触发数组引用变化。而useMemo依赖的是数组引用,引用不变时不会重新计算过滤结果。
  2. 过滤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-

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 18:49:13