SolidJS: createEffect запускает бесконечный цикл при обновлении значений хранилища

Я пытаюсь сделать приложение со списком дел с ежедневной полосой, поэтому каждый раз, когда вы проверяете все свои задачи за день, ваша полоса увеличивается. Я использую Solid с Typescript. Я создаю компонент Streak, который получает от приложения с помощью реквизитов сигнал хранилища state и setState, определенные следующим образом (Todo — это тип, содержащий только строковый текст и завершенное логическое значение:

const [state, setState] = createStore<{days: number; todos: Todo[]}> ({
  days: 0,
  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 со следующим сообщением в консоли: Uncaught InternalError: too much recursion. Итак, что я делаю неправильно? Должен ли я использовать другие функции вместо createEffect? Я пробовал createMemo, но происходит то, что дни даже не обновляются. Спросите меня о любых подробностях, которые могли бы помочь вам, и я прошу прощения за длинный вопрос. Я очень новичок в веб-разработке.

Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
0
0
81
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Потому что вы устанавливаете состояние, которое вызывает бесконечный цикл. Эффекты предназначены для обработки побочных эффектов, их не следует использовать для обновления состояний. Если вы все равно собираетесь это сделать, убедитесь, что вы запускаете частичное обновление состояния, которое не запускает бесконечный цикл, или используйте метод on для прослушивания определенных свойств при обновлении других.

Из вашего кода я вижу, что вы храните сигналы в своем магазине:

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

Хранилище само по себе является реактивным и может хранить несколько элементов без дополнительных сигналов. В этом весь смысл магазина.

Чтобы ответить на ваш вопрос, есть несколько способов сделать это. Если вы собираетесь использовать эффект, проверьте, все ли задачи отмечены как выполненные, и обновите только значение 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

Альтернативным и лучшим способом будет обновление days, когда вы отметите последний todo элемент done:

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);
}));

Большое спасибо! Я был сбит с толку сигналами и магазинами, но теперь я могу понять это намного лучше.

dudu-av 13.01.2023 02:35

Другие вопросы по теме