+ );
+ }
}
// --repl-after
-render(, document.getElementById("app"));
+render(, document.getElementById('app'));
```
diff --git a/content/ru/guide/v10/server-side-rendering.md b/content/ru/guide/v10/server-side-rendering.md
index a11952e23..4696e8ba9 100644
--- a/content/ru/guide/v10/server-side-rendering.md
+++ b/content/ru/guide/v10/server-side-rendering.md
@@ -21,123 +21,212 @@ Server-Side Rendering (сокращённо SSR) позволяет вывест
npm install -S preact-render-to-string
```
-После выполнения указанной выше команды мы можем сразу же приступить к её использованию.
+После выполнения указанной выше команды мы можем сразу же приступить к использованию рендерера.
-## Пример использования
+## HTML-строки
-Основную функциональность лучше всего объяснить с помощью простого фрагмента:
+Оба варианта ниже возвращают одну HTML-строку, представляющую полностью отрендеренный результат вашего приложения Preact.
+
+### renderToString
+
+Самый простой и прямолинейный метод рендеринга, `renderToString` преобразует дерево Preact в строку HTML синхронно.
```jsx
-import render from 'preact-render-to-string';
-import { h } from 'preact';
+import { renderToString } from 'preact-render-to-string';
const name = 'пользователь Preact!'
const App =
Привет, {name}
;
-console.log(render(App));
+const html = renderToString(App);
+console.log(html);
//
Привет, пользователь Preact!
```
-## Асинхронный рендеринг с `Suspense` и `lazy`
-
-Вы можете столкнуться с необходимостью визуализации динамически загружаемых компонентов, например, при использовании `Suspense` и `lazy` для облегчения разделения кода (наряду с некоторыми другими случаями использования). Асинхронный рендерер будет ожидать разрешения обещаний, позволяя вам полностью сконструировать вашу HTML-строку:
+### renderToStringAsync
-```jsx
-// page/home.js
-export default () => {
- return
Домашняя страница
;
-};
-```
+Ожидает выполнения промисов перед возвратом полной HTML-строки. Это особенно полезно при использовании Suspense для ленивой загрузки компонентов или получения данных.
```jsx
-// main.js
+// app.js
import { Suspense, lazy } from 'preact/compat';
-// Создание lazy-компонента
-const HomePage = lazy(() => import('./pages/home'));
+const HomePage = lazy(() => import('./pages/home.js'));
-const Main = () => {
+function App() {
return (
Загрузка
}>
);
-};
+}
```
-Выше приведена очень типичная настройка для приложения Preact, использующего разделение кода, без каких-либо изменений, необходимых для использования рендеринга на стороне сервера.
+```jsx
+import { renderToStringAsync } from 'preact-render-to-string';
+import { App } from './app.js';
-Для рендеринга мы немного отклонимся от базового примера использования и воспользуемся экспортом `renderToStringAsync` для рендеринга нашего приложения:
+const html = await renderToStringAsync();
+console.log(html);
+//
Домашняя страница
+```
+
+> **Примечание:** К сожалению, в реализации «возобновляемой гидратации» в Preact v10 есть несколько известных ограничений — то есть гидратации, которая может приостанавливаться и ожидать загрузки и доступности JS-чанков или данных перед продолжением. Эта проблема решена в предстоящем выпуске Preact v11.
+>
+> На данный момент следует избегать асинхронных границ, которые возвращают 0 или более 1 DOM-узла в качестве дочерних элементов, как в следующих примерах:
+>
+> ```jsx
+> function X() {
+> // Некоторая ленивая операция, например, инициализация аналитики
+> return null;
+> };
+>
+> const LazyOperation = lazy(() => /* import X */);
+> ```
+>
+> ```jsx
+> function Y() {
+> // Тег `` исчезает при рендеринге, оставляя два DOM-элемента `
`
+> return (
+>
+>
Foo
+>
Bar
+>
+> );
+> };
+>
+> const SuspendingMultipleChildren = lazy(() => /* import Y */);
+> ```
+>
+> Для более подробного описания известных проблем и того, как мы их решили, пожалуйста, ознакомьтесь с [Hydration 2.0 (preactjs/preact#4442)](https://github.com/preactjs/preact/issues/4442).
+
+## HTML-потоки
+
+Потоковая передача — это метод рендеринга, который позволяет отправлять части вашего приложения Preact на клиент по мере их готовности, не дожидаясь завершения всего рендеринга.
+
+### renderToPipeableStream
+
+`renderToPipeableStream` — это потоковый метод, использующий [потоки Node.js](https://nodejs.org/api/stream.html) для рендеринга вашего приложения. Если вы не используете Node, вместо этого рассмотрите [renderToReadableStream](#rendertoreadablestream).
```jsx
-import { renderToStringAsync } from 'preact-render-to-string';
-import { Main } from './main';
+import { renderToPipeableStream } from 'preact-render-to-string/stream-node';
+
+// Синтаксис и форма обработчика запросов будут различаться в зависимости от фреймворка
+function handler(req, res) {
+ const { pipe, abort } = renderToPipeableStream(, {
+ onShellReady() {
+ res.statusCode = 200;
+ res.setHeader('Content-Type', 'text/html');
+ pipe(res);
+ },
+ onError(error) {
+ res.statusCode = 500;
+ res.send(
+ `
Произошла ошибка:
${error.message}
`
+ );
+ }
+ });
+
+ // Переключаемся на клиентский рендеринг, если прошло достаточно времени.
+ setTimeout(abort, 2000);
+}
+```
-const main = async () => {
- // Отрисовка lazy-компонента
- const html = await renderToStringAsync();
+### renderToReadableStream
- console.log(html);
- //
Домашняя страница
-};
+`renderToReadableStream` — это ещё один потоковый метод, похожий на `renderToPipeableStream`, но предназначенный для использования в средах, поддерживающих стандартизированные [веб-потоки](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API).
-// Выполнение и обработка ошибок
-main().catch((error) => {
- console.error(error);
-});
+```jsx
+import { renderToReadableStream } from 'preact-render-to-string/stream';
+
+// Синтаксис и форма обработчика запросов будут различаться в зависимости от фреймворка
+function handler(req, res) {
+ const stream = renderToReadableStream();
+
+ return new Response(stream, {
+ headers: {
+ 'Content-Type': 'text/html'
+ }
+ });
+}
```
-## Неглубокий рендеринг
+## Настройка вывода рендера
-Для некоторых целей часто предпочтительнее отображать не всё дерево, а только один его уровень. Для этого у нас есть неглубокий рендерер, который будет выводить дочерние компоненты по имени, а не по их возвращаемому значению.
+Модуль `/jsx` предоставляет несколько опций для настройки вывода рендера для ряда популярных сценариев использования.
+
+## Режим JSX
+
+Режим рендеринга JSX особенно полезен, если вы занимаетесь каким-либо видом тестирования моментальных снимков. Он отображает вывод так, как если бы он был написан на JSX.
```jsx
-import { shallow } from 'preact-render-to-string';
-import { h } from 'preact';
+import renderToString from 'preact-render-to-string/jsx';
-const Foo = () =>
+const html = renderToString(App, {}, { jsx: true });
+console.log(html);
+//
```
## Режим Pretty
-Если вам нужно получить вывод в более удобном для человека виде, мы поможем вам! Передав опцию `pretty`, мы сохраним пробельные символы и отступы в выводе, как и ожидалось.
+Если вам нужно получить вывод в более удобном для человека виде, мы поможем вам! При передаче опции `pretty` мы сохраним пробельные символы и отступы в выводе, как и ожидается.
```jsx
-import render from 'preact-render-to-string/jsx';
-import { h } from 'preact';
+import renderToString from 'preact-render-to-string/jsx';
const Foo = () =>
```
-## Режим JSX
+### Режим Shallow
-Режим рендеринга JSX особенно полезен, если вы занимаетесь каким-либо видом тестирования моментальных снимков. Он отображает вывод так, как если бы он был написан на JSX.
+Для некоторых целей часто предпочтительнее не рендерить всё дерево, а только один уровень. Для этого у нас есть shallow-рендерер, который выводит дочерние компоненты по их именам, а не по возвращаемому значению.
```jsx
-import render from 'preact-render-to-string/jsx';
-import { h } from 'preact';
+import renderToString from 'preact-render-to-string/jsx';
-const App = ;
+const Foo = () =>
+```
+
+### Режим XML
+
+Для элементов без потомков режим XML будет рендерить их как самозакрывающиеся теги.
+
+```jsx
+import renderToString from 'preact-render-to-string/jsx';
+
+const Foo = () => ;
+const App = (
+
+ );
}
```
@@ -356,6 +356,16 @@ name.value = 'Джон';
// Лог: "Джон Доу"
```
+Опционально, вы можете вернуть функцию очистки из колбэка, переданного в [`effect()`](#effectfn), которая будет выполнена перед следующим обновлением. Это позволяет «очистить» побочный эффект и, при необходимости, сбросить состояние для следующего вызова колбэка.
+
+```js
+effect(() => {
+ Chat.connect(username.value);
+
+ return () => Chat.disconnect(username.value);
+});
+```
+
Вы можете уничтожить эффект и отказаться от подписки на все сигналы, к которым он получил доступ, вызвав возвращаемую функцию.
```js
@@ -382,13 +392,14 @@ name.value = 'Джон';
В тех редких случаях, когда вам нужно записать сигнал внутри [`effect(fn)`](#effectfn), но вы не хотите, чтобы эффект повторно запускался при изменении этого сигнала, вы можете использовать `.peek()`, чтобы получить текущее значение сигнала без подписки.
+
```js
const delta = signal(0);
const count = signal(0);
effect(() => {
- // Обновляем `count` без подписки на `count`:
- count.value = count.peek() + delta.value;
+ // Обновляем `count` без подписки на `count`:
+ count.value = count.peek() + delta.value;
});
// Установка значения `delta` повторно запускает эффект:
@@ -398,19 +409,21 @@ delta.value = 1;
count.value = 10;
```
-> :bulb: Совет: Сценарии, в которых вы не хотите подписываться на сигнал, встречаются редко. В большинстве случаев вы хотите, чтобы ваш эффект подписывался на все сигналы. Используйте `.peek()` только тогда, когда это вам действительно нужно.
+> :bulb: Совет: Сценарии, в которых вы не хотите подписываться на сигнал, встречаются редко. В большинстве случаев вы хотите, чтобы ваш эффект подписывался на все сигналы. Используйте `.peek()` только тогда, когда вам это действительно нужно.
В качестве альтернативы `.peek()` у нас есть функция `untracked`, которая принимает функцию в качестве аргумента и возвращает результат выполнения этой функции. В `untracked` вы можете ссылаться на любой сигнал с помощью `.value` без создания подписки. Это может быть полезно, когда у вас есть многоразовая функция, которая обращается к `.value`, или вам нужно получить доступ к более чем одному сигналу.
+
+
```js
const delta = signal(0);
const count = signal(0);
effect(() => {
- // Обновляем `count` без подписки на `count` или `delta`:
- count.value = untracked(() => {
- return count.value + delta.value
- });
+ // Обновляем `count` без подписки на `count` или `delta`:
+ count.value = untracked(() => {
+ return count.value + delta.value;
+ });
});
```
@@ -423,8 +436,8 @@ const todos = signal([]);
const text = signal('');
function addTodo() {
- todos.value = [...todos.value, { text: text.value }];
- text.value = '';
+ todos.value = [...todos.value, { text: text.value }];
+ text.value = '';
}
```
@@ -432,10 +445,10 @@ function addTodo() {
```js
function addTodo() {
- batch(() => {
- todos.value = [...todos.value, { text: text.value }];
- text.value = '';
- });
+ batch(() => {
+ todos.value = [...todos.value, { text: text.value }];
+ text.value = '';
+ });
}
```
@@ -452,12 +465,12 @@ const triple = computed(() => count.value * 3);
effect(() => console.log(double.value, triple.value));
batch(() => {
- // Устанавливаем `count`, делая недействительными `double` и `triple`:
- count.value = 1;
+ // Устанавливаем `count`, делая недействительными `double` и `triple`:
+ count.value = 1;
- // Несмотря на пакетную обработку, `double` отражает новое вычисленное значение.
- // Однако `triple` будет обновляться только после завершения обратного вызова.
- console.log(double.value); // Лог: 2
+ // Несмотря на пакетную обработку, `double` отражает новое вычисленное значение.
+ // Однако `triple` будет обновляться только после завершения обратного вызова.
+ console.log(double.value); // Лог: 2
});
```
@@ -471,13 +484,13 @@ batch(() => {
const count = signal(0);
function Unoptimized() {
- // Перерисовывает компонент при изменении `count`:
- return
{count.value}
;
+ // Перерисовывает компонент при изменении `count`:
+ return
{count.value}
;
}
function Optimized() {
- // Текст автоматически обновляется без повторной отрисовки компонента:
- return
{count}
;
+ // Текст автоматически обновляется без повторной отрисовки компонента:
+ return
{count}
;
}
```
@@ -497,13 +510,21 @@ function Optimized() {
const count = signal(0);
```
-При создании сигналов внутри компонента используйте вариант с хуком: `useSignal(initialValue)`.
+Возвращённый сигнал имеет свойство `.value`, которое можно получить или установить для чтения и записи его значения. Чтобы прочитать сигнал без подписки на него, используйте `signal.peek()`.
+
+#### useSignal(initialValue)
-Возвращенный сигнал имеет свойство `.value`, которое можно получить или установить для чтения и записи его значения. Чтобы прочитать сигнал без подписки на него, используйте `signal.peek()`.
+При создании сигналов внутри компонента используйте вариант с хуком: `useSignal(initialValue)`. Он работает аналогично `signal()`, но использует мемоизацию, чтобы гарантировать использование одного и того же экземпляра сигнала при повторных рендерах компонента.
+
+```jsx
+function MyComponent() {
+ const count = useSignal(0);
+}
+```
### computed(fn)
-Создает новый сигнал, который вычисляется на основе значений других сигналов. Возвращенный вычисленный сигнал доступен только для чтения, и его значение автоматически обновляется при изменении любых сигналов, к которым осуществляется доступ из функции обратного вызова.
+Создает новый сигнал, который вычисляется на основе значений других сигналов. Возвращённый вычисленный сигнал доступен только для чтения, и его значение автоматически обновляется при изменении любых сигналов, к которым осуществляется доступ из функции обратного вызова.
```js
const name = signal('Джейн');
@@ -512,8 +533,19 @@ const surname = signal('Доу');
const fullName = computed(() => `${name.value} ${surname.value}`);
```
+#### useComputed(fn)
+
При создании вычисляемых сигналов внутри компонента используйте вариант с хуком: `useComputed(fn)`.
+```jsx
+function MyComponent() {
+ const name = useSignal('Jane');
+ const surname = useSignal('Doe');
+
+ const fullName = useComputed(() => `${name.value} ${surname.value}`);
+}
+```
+
### effect(fn)
Чтобы запустить произвольный код в ответ на изменение сигнала, мы можем использовать `effect(fn)`. Подобно вычисляемым сигналам, эффекты отслеживают, к каким сигналам осуществляется доступ, и повторно запускают обратный вызов при изменении этих сигналов. В отличие от вычисляемых сигналов, `effect()` не возвращает сигнал — это конец последовательности изменений.
@@ -529,8 +561,19 @@ name.value = 'Джон';
// Лог: "Привет, Джон"
```
+#### useSignalEffect(fn)
+
При реагировании на изменения сигнала внутри компонента используйте вариант с хуком: `useSignalEffect(fn)`.
+```jsx
+function MyComponent() {
+ const name = useSignal('Джейн');
+
+ // Отображаем сообщение в консоли при изменении `name`:
+ useSignalEffect(() => console.log('Привет, ', name.value));
+}
+```
+
### batch(fn)
Функцию `batch(fn)` можно использовать для объединения нескольких обновлений значений в одну «фиксацию» в конце предоставленного обратного вызова. Пакеты могут быть вложенными, а изменения сбрасываются только после завершения обратного вызова самого внешнего пакета. Доступ к сигналу, который был изменен в пакете, отразит его обновлённое значение.
@@ -556,7 +599,97 @@ const surname = signal("Doe");
effect(() => {
untracked(() => {
- console.log(`${name.value} ${surname.value}`)
+ console.log(`${name.value} ${surname.value}`)
})
})
```
+
+## Вспомогательные компоненты и хуки
+
+Начиная с версии v2.1.0, пакет `@preact/signals/utils` предоставляет дополнительные вспомогательные компоненты и хуки, упрощающие работу с сигналами.
+
+### Компонент Show
+
+Компонент `` предоставляет декларативный способ условного отображения контента на основе значения сигнала.
+
+```jsx
+import { signal } from '@preact/signals';
+import { Show } from '@preact/signals/utils';
+
+const isVisible = signal(false);
+
+function App() {
+ return (
+ Здесь ничего нет}>
+
Теперь вы меня видите!
+
+ );
+}
+
+// Вы также можете использовать функцию для доступа к значению
+function App() {
+ return {value =>
Значение: {value}
};
+}
+```
+
+### Компонент For
+
+Компонент `` помогает отображать списки из массивов-сигналов с автоматическим кэшированием отрендеренных элементов.
+
+```jsx
+import { signal } from '@preact/signals';
+import { For } from '@preact/signals/utils';
+
+const items = signal(['A', 'B', 'C']);
+
+function App() {
+ return (
+ Нет элементов}>
+ {(item, index) =>
Элемент: {item}
}
+
+ );
+}
+```
+
+### Дополнительные хуки
+
+#### useLiveSignal(signal)
+
+Хук `useLiveSignal(signal)` позволяет создать локальный сигнал, который остаётся синхронизированным с внешним сигналом.
+
+```jsx
+import { signal } from '@preact/signals';
+import { useLiveSignal } from '@preact/signals/utils';
+
+const external = signal(0);
+
+function Component() {
+ const local = useLiveSignal(external);
+ // локальное значение будет автоматически обновляться при изменении внешнего
+}
+```
+
+#### useSignalRef(initialValue)
+
+Хук `useSignalRef(initialValue)` создаёт сигнал, который ведёт себя как реф React со свойством `.current`.
+
+```jsx
+import { useSignalEffect } from '@preact/signals';
+import { useSignalRef } from '@preact/signals/utils';
+
+function Component() {
+ const ref = useSignalRef(null);
+
+ useSignalEffect(() => {
+ if (ref.current) {
+ console.log('Реф получил значение:', ref.current);
+ }
+ });
+
+ return (
+
+ Реф был прикреплён к элементу {ref.current?.tagName}.
+