Я пытаюсь сделать приложение со списком дел с ежедневной полосой, поэтому каждый раз, когда вы проверяете все свои задачи за день, ваша полоса увеличивается. Я использую 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, но происходит то, что дни даже не обновляются. Спросите меня о любых подробностях, которые могли бы помочь вам, и я прошу прощения за длинный вопрос. Я очень новичок в веб-разработке.
Потому что вы устанавливаете состояние, которое вызывает бесконечный цикл. Эффекты предназначены для обработки побочных эффектов, их не следует использовать для обновления состояний. Если вы все равно собираетесь это сделать, убедитесь, что вы запускаете частичное обновление состояния, которое не запускает бесконечный цикл, или используйте метод 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);
}));
Большое спасибо! Я был сбит с толку сигналами и магазинами, но теперь я могу понять это намного лучше.