Недавно я искал способ реагировать на изменения только после первой визуализации моего компонента.
В качестве примера:
function NameInput() {
const [name, setName] = createSignal("");
const [nameError, setNameError] = createSignal<string | null>(null);
createEffect(() => {
setNameError(name().length > 0 ? null : "Name is required");
})
return <div>
<input type = "text" placeholder = "Name" value = {name()} onInput = {(e) => setName(e.currentTarget.value)} />
<Show when = {nameError()}>
<div>{nameError()}</div>
</Show>
</div>
}
Однако это немедленно приведет к возникновению ошибки.
Теперь вопрос в том, как запустить createEffect только после первоначального рендеринга при изменении.
Я не мог найти это нигде. Я опубликую свое решение в качестве ответа здесь для всех, кто сталкивается с тем же сценарием.
Итак, мне помогла функция on
в сочетании с defer: true
:
function NameInput() {
const [name, setName] = createSignal("");
const [nameError, setNameError] = createSignal<string | null>(null);
createEffect(on(name, (name) => {
setNameError(name.length > 0 ? null : "Name is required");
}, { defer: true }));
return <div>
<input type = "text" placeholder = "Name" value = {name()} onInput = {(e) => setName(e.currentTarget.value)} />
<Show when = {nameError()}>
<div>{nameError()}</div>
</Show>
</div>
}
Solid предлагает множество решений, адаптирующихся к различным сценариям использования. Я рассмотрю их один за другим. Последнее является решением этой конкретной проблемы.
Если вам нужно среагировать на изменение сигнала всего один раз, лучше всего использовать реакцию:
const [count, setCount] = createSignal(0);
const track = createReaction(() => console.info('Running reaction'));
track(() => count());
Обратный вызов запустится один раз при обновлении сигнала счетчика. Если вы хотите снова отслеживать сигнал, вам нужно снова вызвать функцию отслеживания:
track(() => count());
Если вам нужно постоянно реагировать на обновления сигналов, используйте метод on
с установленным для defer
значением true:
const [count, setCount] = createSignal(0);
createEffect(on(count, () => {
// Executes in response to signal updates.
}, { defer: true }));
Если вы хотите реагировать на изменения при загрузке компонента, лучше всего использовать onMount
, поскольку он запускается один раз при загрузке компонента.
onMount(() => console.info('Component Mounted'));
Поскольку вы ищете способ проверить наличие значения свойства и отобразить ошибку, если оно отсутствует, лучше всего использовать условный рендеринг:
import { render } from "solid-js/web";
import { createSignal, Show } from "solid-js";
function User() {
const [name, setName] = createSignal("");
const handleChangeEvent = (event)=> {
setName(event.target.value);
}
return (
<Show fallback = {<div>Name: {name()}</div>} when = {!name()}>
<input value = {name()} onChange = {handleChangeEvent} />
</Show>
);
}
render(() => <User />, document.body);
Обратите внимание, что состояние будет обновлено, когда ввод потеряет фокус.
Живая демо: https://playground.solidjs.com/anonymous/89fbd7cd-07aa-4d48-889b-c08e7d7ab769
Эффект не является хорошим способом показать ошибку при проверке свойств, поскольку он будет выполнен после отрисовки пользовательского интерфейса, что приведет к ненужному повторному рендерингу.
PS: Я думал, вы пытаетесь проверить значение реквизита, но теперь я вижу, что вы пытаетесь собрать информацию от пользователей.
Лучше показывать ошибку, когда она есть, а не когда имя пусто. Таким образом, вы можете получить более надежный механизм обратной связи, отображающий различные ошибки. Например, если name не является именем собственным, вы можете попросить пользователя указать имя собственное.
Как видите, вам не нужен эффект, и это лучше, чем наличие эффекта, поскольку мы избегаем ненужного повторного рендеринга.
import { render } from "solid-js/web";
import { createSignal, Show, batch } from "solid-js";
function User() {
const [name, setName] = createSignal("");
const [error, setError] = createSignal("");
const handleChangeEvent = (event) => {
const value = event.target.value;
batch(() => {
if (!value) {
setName(event.target.value);
setError("Name can not be empty");
} else {
setName(event.target.value);
setError("");
}
});
}
return (
<div>
<input value = {name()} onChange = {handleChangeEvent} />
<Show when = {error()}><label>{error()}</label></Show>
</div>
);
}
render(() => <User />, document.body);
Мы группируем обновления состояния, потому что мы обновляем два сигнала одновременно, что приведет к обновлению пользовательского интерфейса дважды, если не будет пакетно. Для этого простого примера это не проблема, но в реальном приложении дополнительный рендеринг вызовет такие проблемы, как повторная выборка значений из удаленных ресурсов, если у вас есть ресурс в одном из последующих компонентов.
Живая демо: https://playground.solidjs.com/anonymous/a2ec38e9-d27a-44c2-8fa2-cb44df3e237d
Однако лучшей альтернативой было бы использование объекта или хранилища для одновременного отслеживания нескольких значений.
import { render } from "solid-js/web";
import { createSignal, Show } from "solid-js";
function User() {
const [state, setState] = createSignal({
name: "",
nameError: "",
});
const handleChangeEvent = (event) => {
const value = event.target.value;
if (!value) {
setState({
name: value,
nameError: "name can not be empty",
});
} else {
setState({
name: value,
nameError: ""
});
}
}
return (
<div>
<input value = {state().name} onChange = {handleChangeEvent} />
<Show when = {state().nameError}><label>*{state().nameError}</label></Show>
</div>
);
}
render(() => <User />, document.body);
Живая демо: https://playground.solidjs.com/anonymous/ae44b935-b79a-4300-88a5-dca618db55d7
Кстати, существует два способа сбора информации и предоставления обратной связи пользователю:
React использует контролируемый ввод, но перезаписывает поведение ввода формы по умолчанию. С другой стороны, Solid остается верным местному поведению.
В Solid onInput сработает сразу после нажатия клавиши. onChange сработает, когда поле формы потеряет фокус.
Подробнее об этом можно прочитать: https://github.com/solidjs/solid/discussions/416
Как я пояснил в своем ответе, эффект не является подходящим способом показать ошибку, поскольку он вызовет ненужный повторный рендеринг. Любой затронутый нижестоящий компонент будет перемонтирован.
Что ж, ваша живая демонстрация/код не делает то, что я предполагал или что делает мой приведенный пример. Мне все еще нужно отобразить поле ввода и, кроме того, div ошибки, если имя было изменено, но пусто... как это происходит в моем примере. Поэтому ваше первое и третье решения также неприменимы к описанному сценарию... а второе - это именно мое решение, которое по какой-то причине было отклонено как «бесполезное». Он работает и делает именно то, что я хотел. Тем не менее, я хочу похвалить вас за ваши усилия... Просто большая часть вашего ответа не является решением описанной проблемы.