React

Наталья Дружинина

Немного истории

Эволюция задач перед JS

Простые задачи

Взаимодействие

Взаимодействие

Взаимодействие

Взаимодействие

Более сложные задачи

Single Page Application  (SPA)

Веб-приложение или сайт, который загружает только одну страницу и все последующие запросы обрабатываются без полной перезагрузки страницы.

Взаимодействие

Взаимодействие

Взаимодействие

Взаимодействие

Isomorphic (Universal) Web App

Приложение, которое может работать как на стороне сервера, так и на стороне клиента.

Взаимодействие

Концепции React

  1. Декларативность
  2. Компонентный подход
  3. Эффективная абстракция над DOM
  4. Изоморфный код
  5. Реактивный рендеринг
  6. Нисходящий поток данных

Без Virtual DOM

С Virtual DOM

Первый компонент

Первый компонент

Первый компонент

Первый компонент
Functional (Stateless) component


                        import React from 'react';

                        function Form() {
                            return (
                                <div className="form">
                                    <input placeholder="Ключ заметки" />
                                    <textarea placeholder="Текст заметки" />
                                    <button>Добавить новую заметку</button>
                                </div>
                            );
                        }
                    

Первый компонент
Class (Stateful) component


                        import React from 'react';

                        class Form extends React.Component {
                            render() {
                                return (
                                    <div className="form">
                                        <input placeholder="Ключ заметки" />
                                        <textarea placeholder="Текст заметки" />
                                        <button>Добавить новую заметку</button>
                                    </div>
                                );
                            }
                        }
                    

Первый компонент

<div id="root"></div>

                        import React from 'react';
                        import ReactDOM from 'react-dom';

                        import Form from './Form';

                        ReactDOM.render(<Form />, document.getElementById('root'));
                    

JSX


                        <div>
                            <img className="logo" src="./images/logo.svg" />
                            <h1 className="title">Список заметок</h1>
                        </div>
                    

JS


                        React.createElement(
                            "div",
                            null,
                            React.createElement("img", { className: "logo", src: "./images/logo.svg" }),
                            React.createElement("h1", { className: "title" }, "Список заметок")
                        );
                    
JSX Live Compiler

Все html атрибуты именуются в camelCase стиле

HTML

                        <input class="name" tabindex="2" />
                    
JSX

                        <input className="name" tabIndex="2" />
                    

Все элементы должны быть закрыты

HTML

                        <div>
                            <span>Введите ваше имя:</span><br>
                            <input type="text">
                        </div>
                    
JSX

                        <div>
                            <span>Введите ваше имя:</span><br />
                            <input type="text" />
                        </div>
                    

Имена пользовательских компонентов должны начинаться с заглавной буквы


                                    <Article />
                                
React.createElement(Article, null);

                                    <article />
                                
React.createElement("article", null);

Может быть только один корневой элемент


                        function MyComponent() {
                            return (
                                <p>Первый абзац</p>
                                <p>Второй абзац</p>
                            );
                        }
                    
SyntaxError: Adjacent JSX elements must be wrapped in an enclosing tag

Может быть только один корневой элемент


                        function MyComponent() {
                            return (
                                <div>
                                    <p>Первый абзац</p>
                                    <p>Второй абзац</p>
                                </div>
                            );
                        }
                    

Может быть только один корневой элемент


                        function MyComponent() {
                            return (
                                <React.Fragment>
                                    <p>Первый абзац</p>
                                    <p>Второй абзац</p>
                                </React.Fragment>
                            );
                        }
                    

Может быть только один корневой элемент


                        function MyComponent() {
                            return (
                                <>
                                    <p>Первый абзац</p>
                                    <p>Второй абзац</p>
                                </>
                            );
                        }
                    

Javascript выражения должны быть заключены в { }


                        const user = { name: 'Наташа' };

                        function Greeting() {
                            return <div>Привет, {user.name}</div>;
                        }
                    

Всё содержимое экранируется


                        const someHtml = '<strong>Наташа</strong>';

                        function Greeting() {
                            return <div>Привет, {someHtml}</div>;
                        }
                    

Всё содержимое экранируется


                        const someHtml = '<strong>Наташа</strong>';

                        function Greeting() {
                            return (
                                <div dangerouslySetInnerHTML={{ __html: someHtml }} />
                            );
                        }
                    

Объединение компонентов

Объединение компонентов

Объединение компонентов


                        import Form from './Form';
                        import Notes from './Notes';

                        function NotesApp() {
                            return (
                                <div className="notes-app">
                                    <Notes />
                                    <Form />
                                </div>
                            );
                        }
                    

Атрибуты

Props

Атрибуты (Props)

Атрибуты (Props)

Атрибуты (Props)

Атрибуты (Props)


                        function SomeComponent() {
                            return (
                                <OtherComponent
                                    text="Text"
                                    id={2}
                                    data={[1, 3, 4]}
                                />
                            );
                        }
                    

Атрибуты (Props)


                        function Notes() {
                            return (
                                <div className="notes">
                                    <Note name="Books" text="Books to read" />
                                    <Note name="Music" text="Music to listen" />
                                    <Note name="Films" text="Films to watch" />
                                </div>
                            );
                        }
                    

Атрибуты (Props)


                        function Note({ name, text }) {
                            return (
                                <div className="note">
                                    <h1>{name}</h1>
                                    <p>{text}</p>
                                </div>
                            );
                        }
                    

Атрибуты (Props)


                        export default class Note extends React.Component {
                            render() {
                                const { name, text } = this.props;

                                return (
                                    <div className="note">
                                        <h1>{name}</h1>
                                        <p>{text}</p>
                                    </div>
                                );
                            }
                        }
                    

Типы атрибутов и значения по-умолчанию


                        import PropTypes from 'prop-types';

                        function Note({ name, text }) {
                            return ...
                        }

                        Note.propTypes = {
                            name: PropTypes.string,
                            text: PropTypes.string.isRequired
                        };

                        Note.defaultProps = {
                            name: 'Note name'
                        };
                    

Типы атрибутов и значения по-умолчанию


                        import PropTypes from 'prop-types';

                        export default class Note extends React.Component {
                            static propTypes = {
                                name: ,
                                text: PropTypes.string.isRequired
                            }

                            static defaultProps = { name: 'Note name' }

                            render() {
                                const { name, text  } = this.props;

                                return ...
                            }
                        }
                    

Контекст

Context

Контекст (Context)

Контекст (Context)


                        import React from 'react';

                        const ThemeContext = React.createContext('light');

                        export default ThemeContext;
                    

Контекст (Context)


                        import ThemeContext from './ThemeContext.js';

                        function NotesApp() {
                            return (
                                <ThemeContext.Provider value="dark">
                                    ...
                                </ThemeContext.Provider>
                            );
                        }
                    

Контекст (Context)


                        import ThemeContext from './ThemeContext.js';

                        function Note() {
                            return (
                                <ThemeContext.Consumer>
                                    {theme => {
                                        return <div className={theme}>...</div>;
                                    }}
                                </ThemeContext.Consumer>
                            );
                        }
                    

Потомки

Children

Потомки (Children)


                        function Notes() {
                            return (
                                <div className="notes">
                                    <Note name="Books">
                                        <span>Books to read</span>
                                        <button>Like!</button>
                                    </Note>
                                </div>
                            );
                        }
                    

Потомки (Children)


                        function Note({ name, children }) {
                            return (
                                <div className="note">
                                    <h1>{name}</h1>
                                    <p>{children}</p>
                                </div>
                            );
                        }
                    

Состояние компонента

State

Состояние компонента (State)

  1. Может быть только у классов
  2. Компонент реагирует на изменения состояния
  3. Инициализация происходит в конструкторе
  4. Изменять состояние напрямую нельзя

Состояние компонента (State)


                        export default class Note extends React.Component {
                            constructor(props) {
                                super(props);

                                this.state = { viewType: 'short' };
                                this.viewFull = () => this.setState({ viewType: 'full' })
                            }

                            render() {
                                ...
                            }
                        }
                    

Состояние компонента (State)


                        export default class Note extends React.Component {
                            state = { viewType: 'short' }

                            viewFull = () => this.setState({ viewType: 'full' })

                            render() {
                                const { viewType } = this.state;

                                return (
                                    <div className="note">
                                        <h1>Название заметки</h1>
                                        {viewType === 'short'
                                            ? (
                                                <React.Fragment>
                                                    <p>Краткое содержание</p>
                                                    <button onClick={this.viewFull}>Читать подробнее</button>
                                                </React.Fragment>
                                            ) : <p>Полный текст</p>
                                        }
                                    </div>
                                );
                            }
                        }
                    

Состояние компонента (State)


                        export default class Note extends React.Component {
                            state = { viewType: 'short' }

                            viewFull = () => this.setState({ viewType: 'full' })

                            render() {
                                const { viewType } = this.state;

                                return (
                                    <div className="note">
                                        <h1>Название заметки</h1>
                                        {viewType === 'short'
                                            ? (
                                                <React.Fragment>
                                                    <p>Краткое содержание</p>
                                                    <button onClick={this.viewFull}>Читать подробнее</button>
                                                </React.Fragment>
                                            ) : <p>Полный текст</p>
                                        }
                                    </div>
                                );
                            }
                        }
                    

Состояние компонента (State)


                        export default class Note extends React.Component {
                            state = { viewType: 'short' }

                            viewFull = () => this.setState({ viewType: 'full' })

                            render() {
                                const { viewType } = this.state;

                                return (
                                    <div className="note">
                                        <h1>Название заметки</h1>
                                        {viewType === 'short'
                                            ? (
                                                <React.Fragment>
                                                    <p>Краткое содержание</p>
                                                    <button onClick={this.viewFull}>Читать подробнее</button>
                                                </React.Fragment>
                                            ) : <p>Полный текст</p>
                                        }
                                    </div>
                                );
                            }
                        }
                    

Состояние компонента (State)


                        export default class Note extends React.Component {
                            state = { viewType: 'short' }

                            viewFull = () => this.setState({ viewType: 'full' })

                            render() {
                                const { viewType } = this.state;

                                return (
                                    <div className="note">
                                        <h1>Название заметки</h1>
                                        {viewType === 'short'
                                            ? (
                                                <React.Fragment>
                                                    <p>Краткое содержание</p>
                                                    <button onClick={this.viewFull}>Читать подробнее</button>
                                                </React.Fragment>
                                            ) : <p>Полный текст</p>
                                        }
                                    </div>
                                );
                            }
                        }
                    

Жизненный цикл компонента

Component life-cycle

Этапы жизненного цикла

  1. Монтирование компонента (Mounting)
  2. Изменение атрибутов или состояния (Updating)
  3. Удаление компонента (Unmounting)

А как в функциональных компонентах?

Хуки

Хуки — это функции, с помощью которых вы можете «подцепиться» к состоянию и методам жизненного цикла React из функциональных компонентов.

Хук состояния


                        import React, { useState } from 'react';

                        function Example() {
                            // Объявление переменной состояния, которую мы назовём "count"
                            const [count, setCount] = useState(0);

                            return (
                                <div>
                                    <p>Вы нажали {count} раз</p>
                                    <button onClick={() => setCount(count + 1)}>
                                        Нажми на меня
                                    </button>
                                </div>
                            );
                        }
                    

Хук состояния


                        import React, { useState } from 'react';

                        function ExampleWithManyStates() {
                            // Объявляем несколько переменных состояния
                            const [age, setAge] = useState(42);
                            const [fruit, setFruit] = useState('банан');
                            const [todos, setTodos] = useState([{ text: 'Изучить хуки' }]);
                            // ...
                        }
                    

Жизненный цикл на хуках

Хук эффекта


                        import React, { useEffect, useState } from 'react';

                        function Example() {
                            const [count, setCount] = useState(0);

                            // Аналогично componentDidMount и componentDidUpdate:
                            useEffect(() => { document.title = `Вы нажали ${count} раз`; });

                            return (
                                <div>
                                    <p>Вы нажали {count} раз</p>
                                    <button onClick={() => setCount(count + 1)}>
                                        Нажми на меня
                                    </button>
                                </div>
                            );
                        }
                    

Работа со списками

Работа со списками


                        function NotesList({ notes }) {
                            return notes.map(note => (
                                <Note name={note.name} text={note.text} />
                            ));
                        }
                    

Each child in an array or iterator should have a unique "key" prop.
Check the render method of NotesList. See https://fb.me/react-warning-keys for more information.

Работа со списками


                        function NotesList({ notes }) {
                            return notes.map(note => (
                                <Note key={note.id} name={note.name} text={note.text} />
                            ));
                        }
                    

Keys

  • Key должен однозначно определять элемент списка
  • Повторяющиеся key в рамках одного списка недопустимы
  • Использовать индекс элемента в массиве лучше только тогда, когда другого выхода нет

Работа с формами

Работа с формами

  • Как получить данные из формы?
  • Как обрабатывать события?

Виды компонентов

  1. Управляемые          (Controlled)
  2. Неуправляемые    (Uncontrolled)

Неуправляемые компоненты
(Uncontrolled components)

DOM управляет текущим состоянием компонента

Ссылка на DOM элемент (Ref)


                        class Form extends React.Component {
                            inputRef = React.createRef();

                            handleSubmit = () => this.makeSomeApiRequest(
                                this.inputRef.current.value
                            )

                            render() {
                                return (
                                    <form onSubmit={this.handleSubmit}>
                                        <input ref={this.inputRef} />
                                        <button>Отправить</button>
                                    </form>
                                );
                            }
                        }
                    

Ссылка на DOM элемент (Ref)


                        class Form extends React.Component {
                            inputRef = React.createRef();

                            handleSubmit = () => this.makeSomeApiRequest(
                                this.inputRef.current.value
                            )

                            render() {
                                return (
                                    <form onSubmit={this.handleSubmit}>
                                        <input ref={this.inputRef} />
                                        <button>Отправить</button>
                                    </form>
                                );
                            }
                        }
                    

Ссылка на DOM элемент (Ref)


                        class Form extends React.Component {
                            inputRef = React.createRef();

                            handleSubmit = () => this.makeSomeApiRequest(
                                this.inputRef.current.value
                            )

                            render() {
                                return (
                                    <form onSubmit={this.handleSubmit}>
                                        <input ref={this.inputRef} />
                                        <button>Отправить</button>
                                    </form>
                                );
                            }
                        }
                    

Неуправляемые компоненты
(Uncontrolled components)

  • Простота
  • Один обработчик на всю форму
  • Контроль из кода

Управляемые компоненты
(Controlled components)

React управляет текущим состоянием компонента

Управляемые компоненты  (Controlled components)


                        class Form extends React.Component {
                            state = { value: '' }

                            handleSubmit = () => this.makeSomeApiRequest(this.state.value)
                            handleChange = event => this.setState({
                                value: event.target.value
                            })

                            render() {
                                return (
                                    <div>
                                        <input value={this.state.value} onChange={this.handleChange} />
                                        <button onClick={this.handleSubmit}>
                                            Отправить
                                        <button>
                                    </div>
                                );
                            }
                        }
                    

Управляемые компоненты  (Controlled components)


                        class Form extends React.Component {
                            state = { value: '' }

                            handleSubmit = () => this.makeSomeApiRequest(this.state.value)
                            handleChange = event => this.setState({
                                value: event.target.value
                            })

                            render() {
                                return (
                                    <div>
                                        <input value={this.state.value} onChange={this.handleChange} />
                                        <button onClick={this.handleSubmit}>
                                            Отправить
                                        <button>
                                    </div>
                                );
                            }
                        }
                    

Управляемые компоненты  (Controlled components)


                        class Form extends React.Component {
                            state = { value: '' }

                            handleSubmit = () => this.makeSomeApiRequest(this.state.value)
                            handleChange = event => this.setState({
                                value: event.target.value
                            })

                            render() {
                                return (
                                    <div>
                                        <input value={this.state.value} onChange={this.handleChange} />
                                        <button onClick={this.handleSubmit}>
                                            Отправить
                                        <button>
                                    </div>
                                );
                            }
                        }
                    

Управляемые компоненты
(Controlled components)

  • Состояние управляется через интерфейс
  • Полный контроль над изменениями
  • Более сложное взаимодействие

Сборка

webpack


                        $ npm i webpack webpack-cli babel-loader babel-preset-react
                    

                        // webpack.config.js

                        const path = require('path');

                        module.exports = {
                            entry: './index.js',
                            output: {
                                path: path.join(__dirname, './build')
                            },
                            module: {
                                rules: [{
                                    test: /\.js$/,
                                    loader: 'babel-loader',
                                    options: {
                                        presets: ['react']
                                    }
                                }]
                            }
                        };
                    

                        $ webpack --config webpack.config.js
                    

create-react-app


                        $ npx create-react-app notes-app
                        $ cd notes-app
                        $ npm start
                        
                        Compiled successfully!

                        You can now view notes-app in the browser.
                            Local:            http://localhost:3000/

                        Note that the development build is not optimized.
                        To create a production build, use yarn build.
                    

create-react-app


                        notes-app
                        ├── README.md
                        ├── node_modules
                        ├── package.json
                        ├── .gitignore
                        ├── public
                        │   ├── favicon.ico
                        │   ├── index.html
                        │   └── manifest.json
                        └── src
                            ├── App.css
                            ├── App.js
                            ├── App.test.js
                            ├── index.css
                            ├── index.js
                            ├── logo.svg
                            └── registerServiceWorker.js
                    

create-react-app


                        notes-app
                        ├── README.md
                        ├── node_modules
                        ├── package.json
                        ├── .gitignore
                        ├── public
                        │   ├── favicon.ico
                        │   ├── index.html
                        │   └── manifest.json
                        └── src
                            ├── App.css
                            ├── App.js
                            ├── App.test.js
                            ├── index.css
                            ├── index.js
                            ├── logo.svg
                            └── registerServiceWorker.js
                    

create-react-app

  • Очень быстрый старт
  • Активное развитие и поддержка от разработчиков React
  • Сложности при интеграции со своим сервером

create-next-app


                        $ npx create-next-app notes-app
                        $ cd notes-app
                        $ npm run dev
                        
                        [ ready ] compiled successfully - ready on http://localhost:3000
                    

next.js

  • Очень быстрый старт
  • Активное развитие и поддержка
  • Множество точек для расширения и кастомизации

next.js Свой сервер


                        const { parse } = require('url');

                        const next = require('next');
                        const server = require('express')();

                        const app = next({ dev: process.env.NODE_ENV !== 'production' });

                        const render = pageName => (req, res) => app.render(req, res, `/${pageName}`);
                        const handleRequest = (req, res) =>
                            app.getRequestHandler()(req, res, parse(req.url, true));

                        app.prepare().then(() => {
                            server
                                .get('/notes', render('index'))
                                .get('/notes/:note', render('note'))
                                .get('*', handleRequest)
                                .listen(3000, () => console.log('Listening on http://localhost:3000'));
                        });
                    

Почитать

reactjs.org
awesome-react
Index as a key is an anti-pattern

Посмотреть

How React Context API works
The Beginner's Guide to ReactJS
Start Learning React

Вопросы?