Последняя версия React (версия 18) официально доступна с марта 2022 года. Она привнесла множество новых функций и закулисных изменений, которые сделали фреймворк еще более мощным. React продолжает оставаться первым и предпочтительным фреймворком для разработчиков JavaScript и всегда пользуется большим спросом в компаниях.

В React 18 было введено много новых функций, и сегодня мы рассмотрим некоторые из наиболее популярных, потому что эти функции, вероятно, чаще всего используются разработчиками в их проектах. Я расскажу о следующем:

  • Реагировать на приостановку
  • Новый корневой API
  • Новый хук useId
  • Новый хук useTransition

Реагировать на приостановку

При использовании REST API есть два способа получения данных. Вы можете выполнять их синхронно или асинхронно. Синхронные вызовы API известны как блокирующие вызовы, поскольку они не могут ничего вернуть до тех пор, пока запрос не будет завершен или не произойдет какая-либо ошибка при возврате данных. Этот тип вызова API блокирует приложение и не позволяет пользователю выполнять какие-либо действия до тех пор, пока действие не будет выполнено.

С другой стороны, вызов асинхронного API делает полную противоположность этому. Он не блокируется, и ответ возвращается немедленно, в то время как запрос продолжает обрабатываться в фоновом режиме. Ваше приложение остается отзывчивым и не блокируется в ожидании каких-либо событий, поэтому вы можете продолжать использовать его, пока данные извлекаются в фоновом режиме. Это дает пользователю гораздо лучший опыт работы с интерфейсом.

По сути, React Suspense является асинхронным, потому что он заставляет ваши компоненты ждать, пока что-то произойдет, прежде чем они смогут отобразить данные внутри API или структуры данных. Компоненты должны дождаться завершения асинхронного вызова API для получения некоторых данных, прежде чем они будут отображены на экране. За кулисами происходит загрузка данных, и можно показать резервный прелоадер пользователю, чтобы он знал, что что-то происходит во внешнем интерфейсе.

Пример приостановки реакции

Это новый синтаксис, в котором используется React Suspense в сочетании с API React.lazy().
App.js

// The new syntax for React 18 using React.lazy() to delay loading the component so that Suspense works properly.
import { Suspense, lazy } from 'react';
const Pokemon = lazy(() => {
    return import('./Pokemon');
});
const App = () => {
    return (
        <Suspense fallback={<h1>Loading pokemon...</h1>}>
            <Pokemon />
        </Suspense>
    );
};
export default App;

Pokemon.js

// The new syntax for React 18
import React, { useEffect, useState } from 'react';
const Pokemon = () => {
    useEffect(() => {
        const getPokemons = () => {
            const API = 'http://pokeapi.co/api/v2/pokemon?limit=500';
            fetch(API)
                .then((response) => {
                    console.log(response);
                    return response.json();
                })
                .then((data) => {
                    console.log(data.results);
                    setData(data.results);
                })
                .catch((err) => {
                    console.log(err);
                });
        };
        getPokemons();
    }, []);
    const [data, setData] = useState([]);
    return (
        <>
            {/* You don't need a ternary operator with a variable for loading anymore */}
            {data.map((pokemon) => (
                <div key={pokemon.name}>
                    <h2>{pokemon.name}</h2>
                </div>
            ))}
        </>
    );
};
export default Pokemon;

Пример тернарного оператора

Это старый синтаксис, в котором используется тернарный оператор JavaScript в сочетании с переменным состоянием, которое проверяет, загружаются ли данные или уже загружены. Он отображает предварительный загрузчик, когда данные еще не загружены, и данные, когда они завершены.

App.js

// The old syntax
import Pokemon from './Pokemon';
const App = () => {
    return (
        <>
            <Pokemon />
        </>
    );
};
export default App;

Pokemon.js

// The old syntax
import React, { useEffect, useState } from 'react';
const Pokemon = () => {
    useEffect(() => {
        const getPokemons = () => {
            const API = 'http://pokeapi.co/api/v2/pokemon?limit=500';
            fetch(API)
                .then((response) => {
                    console.log(response);
                    return response.json();
                })
                .then((data) => {
                    console.log(data.results);
                    setLoading(false);
                    setData(data.results);
                })
                .catch((err) => {
                    console.log(err);
                });
        };
        getPokemons();
    }, []);
    const [data, setData] = useState([]);
    const [loading, setLoading] = useState(true);
    return (
        <>
            {/* The old syntax using ternary operators with a variable for loading */}
            {loading ? (
                <h1>Loading pokemon...</h1>
            ) : (
                <div>
                    {data.map((pokemon) => (
                        <div key={pokemon.name}>
                            <h2>{pokemon.name}</h2>
                        </div>
                    ))}
                </div>
            )}
        </>
    );
};
export default Pokemon;

Новый корневой API

В React 18 появился новый синтаксис для корневого файла index.js, который дает вам доступ к новым функциям и улучшениям. Предыдущий синтаксис выдаст вам красное предупреждающее сообщение в консоли, потому что ReactDOM.render больше не поддерживается в React 18. Используйте новый синтаксис, чтобы очистить предупреждающее сообщение.

Теперь вместо этого вам необходимо использовать createRoot, и пока вы не переключитесь на новый API, ваше приложение будет вести себя так, как если бы оно работало под управлением React 17. Новый корневой API имеет более чистый и понятный синтаксис, а новый параллельный рендерер становится включенным, так что теперь вы можете получить доступ параллельные функции, которые не мог использовать предыдущий API.

Новый синтаксис

import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

Старый синтаксис

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));

Новый хук useId

React 18 предоставляет удобный способ генерации идентификаторов при создании элементов DOM с помощью нового хука useId. Если у вас несколько идентификаторов, вы даже можете добавить суффикс, например «twitter», чтобы сделать его уникальным.

Дополнительный совет: вы можете сделать метку кликабельной, добавив, например, тег htmlFor.

App.js

import { useId } from 'react';
const App = () => {
    const id = useId();
    return (
        <>
            <div>
                <label htmlFor={`${id}-twitter`}>Do you have a Twitter?</label>
                <input id={`${id}-twitter`} type="checkbox" name="twitter" />
            </div>
            <div>
                <label>Do you have a Instagram?</label>
                <input id={`${id}-instagram`} type="checkbox" name="instagram" />
            </div>
            <div>
                <label>Do you have a YouTube?</label>
                <input id={id} type="checkbox" name="youtube" />
            </div>
        </>
    );
};
export default App;

Новый хук useTransition

В React 18 вы можете использовать новый хук useTransition для загрузки данных перед их отображением на экране. Когда вы используете возвращаемое значение isPending, вы можете создать предварительный загрузчик для каждого перехода загрузки.

Вы можете увидеть это в действии в этом пользовательском слайдере-карусели.

App.js

import { useState, useEffect, useTransition } from 'react';
const App = () => {
    useEffect(() => {
        mountain().then((data) => {
            console.log(data);
            setLoading(false);
            setData(data);
        });
    }, []);
    let [data, setData] = useState([]);
    let [loading, setLoading] = useState(true);
    let [count, setCount] = useState(0);
    let [index, setIndex] = useState(0);
    let [isPending, startTransition] = useTransition();
    const showNext = () => {
        startTransition(() => {
            setCount(count + 1);
        });
        setIndex(count + 1);
        console.log('Count', count + 1);
    };
    const showPrevious = () => {
        startTransition(() => {
            setCount(count - 1);
        });
        setIndex(count - 1);
        console.log('Count', count - 1);
    };
    const mountain = (loaded) => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (loaded) {
                    reject(new Error('Failed to load data'));
                } else {
                    resolve([
                        {
                            id: 0,
                            name: 'Mountain 1',
                            img: 'https://images.unsplash.com/photo-1570641963303-92ce4845ed4c?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1587&q=80',
                        },
                        {
                            id: 1,
                            name: 'Mountain 2',
                            img: 'https://images.unsplash.com/photo-1434394354979-a235cd36269d?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2051&q=80',
                        },
                        {
                            id: 2,
                            name: 'Mountain 3',
                            img: 'https://images.unsplash.com/photo-1472791108553-c9405341e398?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2137&q=80',
                        },
                    ]);
                }
            }, 2000);
        });
    };
    return (
        <>
            {loading ? (
                <h1>Loading...</h1>
            ) : (
                <div>
                    {isPending ? (
                        <div>
                            <h1>Loading the next image</h1>
                        </div>
                    ) : (
                        <div>
                            <h1>{data[index].name}</h1>
                            <img src={data[index].img} alt="Mountain" style={{ width: '100%', height: '300px', maxWidth: '500px' }} />
                        </div>
                    )}
                </div>
            )}
            <button onClick={showPrevious} disabled={count === 0 ? true : false}>
                Previous
            </button>
            <button onClick={showNext} disabled={count === 2 ? true : false}>
                Next
            </button>
        </>
    );
};
export default App;

Последние мысли

Это было краткое введение в новые функции внутри React 18. Загляните в официальный блог, чтобы увидеть всю новую документацию React v18.0.