# OTUS ## Javascript Basic Вопросы? ### [React](https://ru.reactjs.org/) #### Проблемы шаблонизации - скорость разработки приложения - скорость работы приложения #### React - декларативный - основан на [компонентах](https://reactjs.org/docs/components-and-props.html) - научитесь однажды — пишите где угодно Особенности **React** - с компонентами (но не [Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components)) - оптимизация обновлений через [vDOM](https://reactjs.org/docs/faq-internals.html) - минимальный синтаксис шаблонов — [JSX](https://facebook.github.io/jsx/) Идея скорости — [**Virtual DOM**](https://reactjs.org/docs/faq-internals.html) - архитектура React — отрисовать минимально необходимые изменения - работа с DOM напрямую — медленно - vDOM — модель настоящего DOM на легких js-объектах
### React - абстрактная идея - может использоваться на разных платформах (не только Web, но и нативные системы и что-угодно еще представляющее UI - [React Native](https://reactnative.dev/), [React for terminal](https://github.com/vadimdemedes/ink)) - сам React никак не связан ни с Web ни с DOM - для работы с DOM(HTML) используется отдельный пакет — [react-dom](https://reactjs.org/docs/react-dom.html) ### Из чего состоит приложение на React? [React-элементы](https://ru.reactjs.org/docs/glossary.html) — это составляющие блоки React-приложений. Их **можно перепутать с более известной концепцией «компонентов»**, но в отличие от компонента, элемент описывает то, что вы хотите увидеть на экране. React-элементы иммутабельны. Представление React элементов через [`createElement`](https://ru.reactjs.org/docs/react-api.html#createelement) ```jsx React.createElement(type, [props], [...children]); ``` ```tsx [1-30] React.createElement( "div", { className: "wrapper", style: "margin: 5px", }, React.createElement( "button", { onClick: function onClick() { return alert("Click"); }, className: "btn", }, "Click me" ) ); ``` ```jsx [1-30] // jsx: // https://bit.ly/3gCKQOI
alert("Click")} className="btn"> Click me
``` [React-компоненты](https://ru.reactjs.org/docs/glossary.html) — это маленькие, повторно используемые части кода, которые возвращают React-элементы для отображения на странице. Самый простой React-компонент — JavaScript функция, которая возвращает элементы React. ```tsx [1-50] // компонент const Button = ({ name }) => { return React.createElement( "button", { onClick: function onClick() { return alert("Click"); }, className: "btn", }, name ); }; // применение компонента React.createElement( "div", { className: "wrapper", style: "margin: 5px", }, React.createElement(Button, { name: "Click me", }) ); ``` ```jsx [1-30] const Button = ({ name }) => (
alert("Click")} className="btn"> {name}
); // применение компонента
; ``` Чтобы [отрисовать элемент на странице](https://ru.reactjs.org/docs/rendering-elements.html) нужен пакет [`react-dom`](https://ru.reactjs.org/docs/react-dom.html) ```jsx [1-10] const element =
Hello, world
; const root = ReactDOM.createRoot(document.getElementById("root")); root.render(element); ``` ### [Пример работы с React элементами](https://stackblitz.com/edit/basic-react-dom-novgwc?file=index.js) Для поддержки синтаксиса `JSX` нужен пакет [@babel/preset-react](https://babeljs.io/docs/en/babel-preset-react). Но при этом вполне можно обходиться и [вообще без JSX](https://ru.reactjs.org/docs/react-without-jsx.html) ### Вопросы? ### JSX `JSX` — синтаксический сахар для функции `React.createElement` > JSX — расширение языка JavaScript. Мы рекомендуем использовать его, когда требуется объяснить React, как должен выглядеть UI. JSX напоминает язык шаблонов, наделённый силой JavaScript. [Знакомство с JSX](https://ru.reactjs.org/docs/introducing-jsx.html) [JSX в деталях](https://ru.reactjs.org/docs/jsx-in-depth.html) #### [Немного о правилах JSX](https://stackblitz.com/edit/react-basic-jsx-otus?file=jsxExamples.jsx) ```tsx [1-30] // 1. Объявление компонентов // 1.1 Как функция const Cmp1 = (props) =>
{props.name}
; // 1.2 Как класс class Cmp2 extends React.Component { render() { return
; } } ``` ```tsx [1-30] // 2. Использование компонентов // 2.1 С закрывающим тегов
// 2.2 С самозакрывающимся тегом (если нет дочерних элементов)
``` ```tsx [1-30] // 3. Передача свойств в компонент // 3.1 Все свойства пишутся в {}
// 3.2 Строковые литералы передаются как-есть
// 3.3 Булевые true свойства можно передавать просто указанием
``` ```tsx [1-30] // 4 Комментарий // 4.1 Вокруг компонентов/их методов - обычный js // так const Cmp3 = (props /* или так */) =>
{props.name}
; // 4.2 Внутри jsx const Cmp4 = (props) => (
{/* вот так */} {props.name}
); ``` ```tsx [1-30] // 5 Выражения // jsx - subset js, выражения пишутся внутри {} (как в примере с свойствами компонентов) const Cmp5 = (props) => (
{props.disabled ?
No way
:
Go
} {props.names.map((name) => (
{name}
))} {props.someFlag &&
}
); ``` ```tsx // Важно! (ну или было важно) import React from "react"; ``` Начиная с React 17 появилась [возможность](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) использования новой JSX трансформации ### Вопросы? ### Тестирование - Подход от реализации - [Enzyme](https://enzymejs.github.io/enzyme/) - Подход от пользователя - [RTL](https://testing-library.com/docs/react-testing-library/intro/) ### React-testing-library > The React Testing Library is a very light-weight solution for testing React components. It provides light utility functions on top of react-dom and react-dom/test-utils ```js [1-30] import React from "react"; import { render, screen } from "@testing-library/react"; import App from "./App"; describe("App", () => { test("renders App component", () => { // Render component render(
); // Debug info screen.debug(); // For debugging using testing-playground, // screen exposes this convenient method // which logs a URL that can be opened in a browser screen.logTestingPlaygroundURL(); }); }); ``` #### Вывод > RTL используется для взаимодействия с вашими компонентами React так, как это делает человек. То, что видит человек, - это просто визуализированный HTML из ваших компонентов React, поэтому вы видите эту структуру HTML как результат ```html [1-30]
Hello React
``` ### Выбор элементов [jest-dom](https://github.com/testing-library/jest-dom/) ```js [1-30] import React from "react"; import { render, screen } from "@testing-library/react"; import App from "./App"; describe("App", () => { test("renders App component", () => { render(
); expect(screen.getByText("Search:")).toBeInTheDocument(); }); }); ``` ```js [1-30] import React from "react"; import { render, screen } from "@testing-library/react"; import App from "./App"; describe("App", () => { test("renders App component", () => { render(
); // неявная проверка // getByText выбрасывает исключение если элемент не найден screen.getByText("Search:"); // явная проверка - рекомендуется expect(screen.getByText("Search:")).toBeInTheDocument(); }); }); ``` ```js [1-30] import React from "react"; import { render, screen } from "@testing-library/react"; import App from "./App"; describe("App", () => { test("renders App component", () => { render(
); // упадет expect(screen.getByText("Search")).toBeInTheDocument(); // пройдет expect(screen.getByText("Search:")).toBeInTheDocument(); // пройдет expect(screen.getByText(/Search/)).toBeInTheDocument(); }); }); ``` ### Возможности для поиска `getByRole` используется для поиска по [aria-label](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label). Но, [у некоторых HTML элементов есть неявные роли](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles) ```js [1-30] import React from "react"; import { render, screen } from "@testing-library/react"; import App from "./App"; describe("App", () => { test("renders App component", () => { render(
); screen.getByRole(""); }); }); ``` ```[1-30] Unable to find an accessible element with the role "" Here are the accessible roles: document: Name "":
-------------------------------------------------- textbox: Name "Search:":
-------------------------------------------------- ``` ```js import React from "react"; import { render, screen } from "@testing-library/react"; import App from "./App"; describe("App", () => { test("renders App component", () => { render(
); expect(screen.getByRole("textbox")).toBeInTheDocument(); }); }); ``` ### Есть более специфичные способы поиска: `getByLabelText`: ```html
``` `getByPlaceholderText`: ```html
``` `getByAltText`: ```html
``` `getByDisplayValue`: ```html
``` И другие - getByText - getByRole - getByLabelText - getByPlaceholderText - getByAltText - getByDisplayValue ### getBy vs queryBy ```js [1-30] import React from "react"; import { render, screen } from "@testing-library/react"; import App from "./App"; describe("App", () => { test("renders App component", () => { render(
); screen.debug(); // fails expect(screen.getByText(/Searches for JavaScript/)).toBeNull(); }); }); ``` **getBy** выбрасывает исключение если элемент не найден. Для работы и проверки элементов, которых нет - можно использовать **queryBy** ```js [1-30] import React from "react"; import { render, screen } from "@testing-library/react"; import App from "./App"; describe("App", () => { test("renders App component", () => { render(
); expect(screen.queryByText(/Searches for JavaScript/)).toBeNull(); }); }); ``` ### findBy делает асинхронный поиск (И ожидание) элементов ```js [1-30] import React from "react"; import { render, screen } from "@testing-library/react"; import App from "./App"; describe("App", () => { test("renders App component", async () => { render(
); expect(screen.queryByText(/Signed in as/)).toBeNull(); expect(await screen.findByText(/Signed in as/)).toBeInTheDocument(); }); }); ``` Для коллекций элементов есть - getAllBy - queryAllBy - findAllBy ### Assertive Functions [jest-dom](https://github.com/testing-library/jest-dom/) - toBeDisabled - toBeEnabled - toBeEmpty - toBeEmptyDOMElement - toBeInTheDocument - toBeInvalid - toBeRequired - and etc... ### [Testing-playground](https://testing-playground.com/) ### Вопросы? ### FIRE EVENT ```js [1-30] import React from "react"; import { render, screen, fireEvent } from "@testing-library/react"; import App from "./App"; describe("App", () => { test("renders App component", () => { render(
); screen.debug(); fireEvent.change(screen.getByRole("textbox"), { target: { value: "JavaScript" }, }); screen.debug(); }); }); ``` ```js [1-30] import React from "react"; import App from "./App"; describe("App", () => { test("renders App component", async () => { render(
); // wait for the user to resolve // needs only be used in our special case await screen.findByText(/Signed in as/); expect(screen.queryByText(/Searches for JavaScript/)).toBeNull(); fireEvent.change(screen.getByRole("textbox"), { target: { value: "JavaScript" }, }); expect(screen.getByText(/Searches for JavaScript/)).toBeInTheDocument(); }); }); ``` ### React Testing Library: User Event > userEvent API имитирует реальное поведение браузера более точно, чем fireEvent API. Например, fireEvent.change() запускает только событие изменения, тогда как userEvent.type запускает событие изменения, а также события keyDown, keyPress и keyUp. ```js [1-30] import React from "react"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import App from "./App"; describe("App", () => { test("renders App component", async () => { render(
); // wait for the user to resolve await screen.findByText(/Signed in as/); expect(screen.queryByText(/Searches for JavaScript/)).toBeNull(); await userEvent.type(screen.getByRole("textbox"), "JavaScript"); expect(screen.getByText(/Searches for JavaScript/)).toBeInTheDocument(); }); }); ``` ### Обработчики событий ```js import React from "react"; function Search({ value, onChange, children }) { return (
{children}
); } ``` ```js [1-30] import React from "react"; import Search from "./Search"; // FireEvent describe("Search", () => { test("calls the onChange callback handler", () => { const onChange = jest.fn(); render(
Search:
); fireEvent.change(screen.getByRole("textbox"), { target: { value: "JavaScript" }, }); expect(onChange).toHaveBeenCalledTimes(1); }); }); ``` ```js [1-30] import React from "react"; import Search from "./Search"; // UserEvent describe("Search", () => { test("calls the onChange callback handler", async () => { const onChange = jest.fn(); render(
Search:
); await userEvent.type(screen.getByRole("textbox"), "JavaScript"); expect(onChange).toHaveBeenCalledTimes(10); }); }); ``` Вопросы? ### CRA [CRA](https://create-react-app.dev/) - инструмент от [facebook](https://github.com/facebook/create-react-app) для старта разработки приложений ``` npx create-react-app my-app cd my-app npm start ``` c TS: ``` npx create-react-app my-app --template typescript ``` Скрывает от пользователя все настройка за [`react-scripts`](https://github.com/facebook/create-react-app/tree/master/packages/react-scripts) Для открытия настроек есть команда **eject** ### На курсе мы НЕ будем использовать CRA! Вопросы? ### [Storybook](https://storybook.js.org/) ### [Loki](https://loki.js.org/) ### Дополнительные материалы 0. https://ru.reactjs.org/docs/getting-started.html 1. https://pomb.us/build-your-own-react/ 2. https://jasonformat.com/wtf-is-jsx/ 3. https://github.com/pomber/didact 4. [Paul O Shannessy - Building React From Scratch](https://www.youtube.com/watch?v=_MAD4Oly9yg) 5. [YT: Автотесты. Модульное тестирование – Дмитрий Андриянов](https://www.youtube.com/watch?v=DFLXBdfnAeE) 6. [Начало работы со Storybook](https://www.youtube.com/watch?v=lUf8qC_xFHo)