Solid — это реактивная библиотека JavaScript для создания пользовательских интерфейсов без виртуальной DOM. Он компилирует шаблоны до реальных узлов DOM один раз и оборачивает обновления в детализированные реакции, так что при обновлении состояния выполняется только связанный код.
Таким образом, компилятор может оптимизировать начальную визуализацию, а среда выполнения оптимизирует обновления. Такой упор на производительность делает его одним из самых популярных фреймворков JavaScript.
Мне это стало любопытно, и я захотел попробовать, поэтому я потратил некоторое время на создание небольшого приложения, чтобы изучить, как этот фреймворк обрабатывает компоненты рендеринга, обновляет состояние, настраивает магазины и многое другое.
Вот последняя демонстрация, если вам просто не терпится увидеть окончательный код и результат:
Содержание статьи
Начало работы
Как и большинство фреймворков, мы можем начать с установки пакета npm. Чтобы использовать фреймворк с JSX, запустите:
npm install solid-js babel-preset-solid
Затем нам нужно добавить babel-preset-solid
в наш конфигурационный файл Babel, webpack или Rollup с помощью:
"предварительные настройки": ["solid"]
Или, если вы хотите создать небольшое приложение, вы также можете использовать один из их шаблонов:
# Создание небольшого приложения из шаблона Solid
npx degit solidjs / шаблоны / js мое приложение
# Сменить каталог на созданный проект
cd my-app
# Установить зависимости
npm i # или пряжа или pnpm
# Запускаем сервер разработки
npm run dev
Существует поддержка TypeScript, поэтому, если вы хотите запустить проект TypeScript, измените первую команду на npx degit solidjs / templates / ts my-app
.
Создание и рендеринг компонентов
Синтаксис визуализации компонентов аналогичен React.js, поэтому он может показаться знакомым:
импорт {render} из "solid-js / web";
const HelloMessage = props => Здравствуйте, {props.name} ;
оказывать(
() => ,
document.getElementById ("привет-пример")
);
Нам нужно начать с импорта функции render
затем мы создаем div с некоторым текстом и опорой и вызываем render
передавая компонент и элемент контейнера.
Затем этот код компилируется в реальные выражения DOM. Например, приведенный выше пример кода, скомпилированный Solid, выглядит примерно так:
import {render, template, insert, createComponent} из "solid-js / web";
const _tmpl $ = template (` Здравствуйте `);
const HelloMessage = props => {
const _el $ = _tmpl $ .cloneNode (истина);
вставить (_el $, () => props.name);
return _el $;
};
оказывать(
() => createComponent (HelloMessage, {name: "Taylor"}),
document.getElementById ("привет-пример")
);
Solid Playground довольно крутая и показывает, что Solid имеет разные способы рендеринга, включая клиентскую, серверную и клиентскую с гидратацией.
Отслеживание изменения значений с помощью сигналов
Solid использует ловушку createSignal
которая возвращает две функции: геттер и сеттер. Если вы привыкли использовать такой фреймворк, как React.js, это может показаться немного странным. Обычно вы ожидаете, что первым элементом будет само значение; однако в Solid нам нужно явно вызвать геттер для перехвата, где значение считывается, чтобы отслеживать его изменения.
Например, если мы пишем следующий код:
const [todos, addTodos] = createSignal ([]);
Запись todos
вернет не значение, а функцию. Если мы хотим использовать значение, нам нужно вызвать функцию, как в todos ()
.
Для небольшого списка дел это будет:
импорт {createSignal} из solid-js;
const TodoList = () => {
пусть ввод;
const [todos, addTodos] = createSignal ([]);
const addTodo = value => {
вернуть addTodos ([...todos(), value]);
};
возвращение (
Список дел:
{todos (). map (item => (
- {item}
))}
);
};
В приведенном выше примере кода отображается текстовое поле, и после нажатия кнопки «Добавить элемент» задачи обновляются новым элементом и отображаются в списке.
Это может показаться очень похожим на использование useState
так чем отличается использование геттера? Рассмотрим следующий пример кода:
console.log («Создание сигналов»);
const [firstName, setFirstName] = createSignal ("Уитни");
const [lastName, setLastName] = createSignal ("Хьюстон");
const [displayFullName, setDisplayFullName] = createSignal (истина);
const displayName = createMemo (() => {
если (! displayFullName ()) return firstName ();
return `$ {firstName ()} $ {lastName ()}`;
});
createEffect (() => console.log («Меня зовут», displayName ()));
console.log ("Установить showFullName: false");
setDisplayFullName (ложь);
console.log ("Изменить lastName");
setLastName ("Boop");
console.log ("Установить showFullName: true");
setDisplayFullName (истина);
Выполнение вышеуказанного кода приведет к:
Создание сигналов
Меня зовут уитни хьюстон
Установить showFullName: false
Меня зовут уитни
Изменить фамилию
Установить showFullName: true
Меня зовут Уитни Буп
Главное, на что следует обратить внимание, это то, как Меня зовут ...
не регистрируется после установки новой фамилии. Это потому, что на данный момент ничего не отслеживает изменения в lastName ()
. Новое значение displayName ()
устанавливается только при изменении значения displayFullName ()
поэтому мы можем видеть новую фамилию, отображаемую, когда setShowFullName
устанавливается обратно на истина
.
Это дает нам более безопасный способ отслеживать обновления значений.
Примитивы реактивности
В последнем примере кода я представил createSignal
а также пару других примитивов: createEffect
и createMemo
.
createEffect
createEffect
отслеживает зависимости и запускается после каждого рендеринга, когда зависимость изменилась.
// Не забудьте сначала импортировать его с помощью 'import {createEffect} from "solid-js";'
const [count, setCount] = createSignal (0);
createEffect (() => {
консоль
Граф на ...
регистрируется каждый раз, когда значение count ()
изменяется.
createMemo
createMemo
создает сигнал только для чтения, который пересчитывает свое значение при каждом обновлении зависимостей исполняемого кода. Вы можете использовать его, когда хотите кэшировать некоторые значения и получать к ним доступ без их повторной оценки до тех пор, пока не изменится зависимость.
Например, если бы мы хотели отображать счетчик 100 раз и обновлять значение при нажатии кнопки, использование createMemo
позволило бы выполнить пересчет только один раз за щелчок:
функция Counter () {
const [count, setCount] = createSignal (0);
// Вызов `counter` без помещения его в` createMemo` приведет к его вызову 100 раз.
// const counter = () => {
// возвращаем count ();
//}
// Вызов counter, заключенного в createMemo, вызывает его один раз за обновление.
// Не забудьте сначала импортировать его с помощью 'import {createMemo} from "solid-js";'
const counter = createMemo (() => {
счетчик возврата ()
})
возвращение (
<>
1. {counter ()}
2. {counter ()}
3. {counter ()}
4. {counter ()}
>
);
}
Методы жизненного цикла
Solid предоставляет несколько методов жизненного цикла, например onMount
onCleanup
и onError
. Если мы хотим, чтобы какой-то код запускался после первоначального рендеринга, нам нужно использовать onMount
:
// Не забудьте сначала импортировать его с помощью 'import {onMount} from "solid-js";'
onMount (() => {
console.log («Я примонтировал!»);
});
onCleanup
похож на componentDidUnmount
в React — он запускается при пересчете реактивной области.
onError
выполняется, когда есть ошибка в ближайшей дочерней области. Например, мы можем использовать его при сбое выборки данных.
Магазины
Для создания хранилищ данных Solid предоставляет createStore
возвращаемое значение которого является прокси-объектом только для чтения и функцией установки.
Например, если бы мы изменили наш пример задачи, чтобы использовать хранилище вместо состояния, это выглядело бы примерно так:
const [todos, addTodos] = createStore ({список: []});
createEffect (() => {
console.log (todos.list);
});
onMount (() => {
addTodos ("список", [
...todos.list,
{ item: "a new todo item", completed: false }
]);
});
Приведенный выше пример кода начинается с регистрации прокси-объекта с пустым массивом, за которым следует прокси-объект с массивом, содержащим объект {item: "новый элемент задачи", завершено: false}
.
Следует отметить, что объект состояния верхнего уровня не может быть отслежен без доступа к его свойству — поэтому мы регистрируем todos.list
а не todos
.
Если бы мы только регистрировали todo
`в createEffect
мы бы увидели исходное значение списка, но не то, что было после обновления, сделанного в onMount
.
Чтобы изменить значения в магазинах, мы можем обновить их с помощью функции настройки, которую мы определяем при использовании createStore
. Например, если мы хотим обновить элемент списка задач до «завершенного», мы могли бы обновить хранилище следующим образом:
const [todos, setTodos] = createStore ({
список: [{ item: "new item", completed: false }]
});
const markAsComplete = text => {
setTodos (
"список",
i => i.item === текст,
"завершенный",
c =>! c
);
};
возвращение (
);
Поток управления
Чтобы избежать расточительного воссоздания всех узлов DOM при каждом обновлении при использовании таких методов, как .map ()
Solid позволяет нам использовать помощники шаблонов.
Некоторые из них доступны, например Для
перебирать элементы, Показать
для условного отображения и скрытия элементов, Переключить
и Сопоставить
чтобы показать элементы, соответствующие определенному условию, и многое другое!
Вот несколько примеров, показывающих, как их использовать:
<Для каждого = {todos.list} fallback = { Загрузка ... }>
{(item) => {item} }
<Показать когда = {todos.list [0] .completed} fallback = { Загрузка ... }>
1-й объект завершен
<Switch fallback = { Нет элементов }>
Демонстрационный проект
Это было краткое введение в основы Solid. Если вы хотите поиграть с ним, я создал стартовый проект, который вы можете автоматически развернуть в Netlify и клонировать на свой GitHub, нажав кнопку ниже!
Этот проект включает настройку по умолчанию для проекта Solid, а также образец приложения Todo с базовыми концепциями, которые я упомянул в этом посте, чтобы вы начали!
Этот фреймворк — это гораздо больше, чем то, что я здесь рассмотрел, поэтому не стесняйтесь проверять документацию для получения дополнительной информации!