typescript SolidJS:更新存储值时createEffect触发无限循环

2w3rbyxf  于 2023-01-14  发布在  TypeScript
关注(0)|答案(1)|浏览(170)

我正在尝试开发一个带有每日条纹的to-do list应用,所以每当你在一天中检查所有的to-do,你的条纹就会增加。我正在使用带有Typescript的Solid。我正在制作一个Streak组件,它通过props从App接收商店信号statesetState,定义如下(Todo是一个只有字符串 text 和布尔 complete 的类型:

const [state, setState] = createStore<{days: number; todos: Todo[]}> ({
  days: 0,
  todos: todos(),
})

我还在App中做了一个效果,查看选中的todos是否与完整的todos列表长度相同,因此days变量可以递增:

createEffect(() => {
  let newTodos = todos();
  let getOneMoreDay: number = 0;
  if (newTodos.filter((x) => x.complete === true).length === newTodos.length)
    getOneMoreDay = 1;
  setState({
    days: state.days + getOneMoreDay,
    todos: newTodos,
  });
});

问题是,在页面中,当我检查所有待办事项时,Streak组件开始循环,而不是显示1 day,它显示8492 days,并在控制台中显示以下消息:* 未捕获的内部错误:太多的递归 *。那么,我做错了什么?我应该用其他函数代替createEffect吗?我试过createMemo,但发生的事情是,天甚至没有更新。问我任何细节,可以帮助你,我很抱歉的长问题。我是非常新的网络开发。

svmlkihl

svmlkihl1#

因为您正在设置状态,这会导致无限循环。效果是用于处理副作用的,所以不应使用它们来更新状态。如果无论如何都要更新状态,请确保运行不会触发无限循环的部分状态更新,或者使用on方法在更新其他属性时侦听某些属性。
从您的代码中,我可以看到您正在存储信号:

({
  days: 0,
  todos: todos(), // ← This is bad!
})

商店本身是React式的,可以存储多个物品而不需要额外的信号。这就是拥有商店的全部意义。
要回答你的问题,有多种方法。如果你要使用一个效果,检查是否所有待办事项都标记为完成,并只更新days值:

import { createEffect, For } from "solid-js";
import { render } from 'solid-js/web';
import { createStore } from 'solid-js/store';

interface Todo {
  title: string;
  done: boolean;
}

interface Store {
  days: number,
  todos: Array<Todo>
}

export const App = () => {
  const [store, setStore] = createStore<Store>({ days: 0, todos: [] });

  const addTodo = () => {
    const id = store.todos.length;
    setStore('todos', id, { title: 'Todo number ' + id, done: false });
  };

  const markDone = (index: number) => () => {
    setStore('todos', index, todo => ({ ...todo, done: true }));
  };

  createEffect(() => {
    setStore('days', val => store.todos.length > 0 && store.todos.every(item => item.done) ? val + 1 : val);
  });

  return (
    <div>
      <div onClick={addTodo}>Add Todo</div>
      <For each={store.todos}>
        {(item, index) => {
          return (
            <li onClick={markDone(index())}><input type="checkbox" checked={item.done} /> {item.title}</li>
          );
        }}
      </For>
      <div>{store.days}</div>
    </div>
  );
}
render(() => <App />, document.body);

您可以在以下位置找到现场演示:https://playground.solidjs.com/anonymous/bea60daa-cf72-4880-82e4-ccdac72fbd8b
另一种更好的方法是在标记最后一个tododone时更新days

interface Todo {
  title: string;
  done: boolean;
}

interface Store {
  days: number,
  todos: Array<Todo>
}

export const App = () => {
  const [store, setStore] = createStore<Store>({ days: 0, todos: [] });

  const addTodo = () => {
    const id = store.todos.length;
    setStore('todos', id, { title: 'Todo number ' + id, done: false });
  };

  const markDone = (index: number) => () => {
    setStore(prev => {
      const newTodos = prev.todos.map((item, i) => i === index ? { ...item, done: true } : item);
      const newDays = newTodos.every(item => item.done) ? prev.days + 1 : prev.days;
      return { days: newDays, todos: newTodos };
    })
  };

  return (
    <div>
      <div onClick={addTodo}>Add Todo</div>
      <For each={store.todos}>
        {(item, index) => {
          return (
            <li onClick={markDone(index())}><input type="checkbox" checked={item.done} /> {item.title}</li>
          );
        }}
      </For>
      <div>{store.days}</div>
    </div>
  );
}

下面是一个现场演示:https://playground.solidjs.com/anonymous/927300e2-d801-4e48-b461-4e29527f5788
通过侦听未由内部回调更新的属性,可以使效果不受无限循环的影响:

createEffect(on(() => store.todos, () => {
  // Effects will run only when state.todos update
  setStore('days', val => val + 1);
}));

另一种方法是记忆todos,这样其他属性就不会触发该效果:

createEffect(createMemo(() => store.todos, () => {
  // Effects will run only when state.todos update
  setStore('days', val => val + 1);
}));

相关问题