base.md 22 KB

React: basics.

React - библиотека для построения интерфейса на JS. Представлена Facebook в 2013.

Изначально была задумана для Web-приложений (браузер), но впоследствии появилась библиотека React Native, где встроенные элементы интерфейса мобильных ОС скриптуются на JS в библиотеке-близнеце React.

На React удобно строить SPA.

Virtual DOM.

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

Компонентный подход.

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

JSX.

Отображение компонента в-основном реализуется с помощью синтаксиса JSX, который представляет собой аналог HTML, однако вместо тэгов используются имена классов компонентов, а атрибуты тэга попадают как "Базовые настройки" в объект компонента. При этом в любом месте JSX может быть "включен" JavaScript с любыми выражениями.

JSX транспилируется в вызов функций React для создания компонентов. Всё, что вы видите в JSX попадает в параметры этих функций. Поэтому там допустимы только выражения, но не управляющие конструкции типа for.

Возможность строить интерфейс как "HTML со своими тэгами", причем тэги эти (т. е. компоненты) могут быть любой сложности - сильно упрощает и способствует структуризации кода и интерфейса.

create-react-app

Супер скрипт, который позволяет вам создать каркас приложения React за одну команду. Устанавливается с помощью npm:

npm install -g create-react-app # ставим глобально, что бы (в идеале) появилась команда в PATH
create-react-app projectName # создает проект в текущей директории, подтягивает всё нужное опять же используя npm

После таких действий в папке projectName появятся файлы с проектом. Корнем приложения является файл App.js в папке src.

React minimal

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

class LoginFields extends Component {
    render(){
        return (
            <div>
                <input placeholder='Login' /><br/>
                <input type="password" placeholder='Password' />
            </div>
        )
    }
}

class Form extends Component {
    render() {
        return (
            <div>
                <LoginFields />
                <button>Login</button>
                <button>Cancel</button>
            </div>
        )
    }
}

class App extends Component {
  render() {
    return (
      <div className="App">
        <Form />
      </div>
    );
  }
}

export default App;

В данном примере компонент Form жестко привязан к LoginFields, однако можно сделать форму и более гибкой. Как видно из примера, компонентный подход позволяет строить интерфейс из блоков любого размера, сложности и гибкости. Так как класс является тоже типом данных, а компоненты React являются классами у вас всегда есть возможность "пробросить" компонент в другой компонент как вы передаете коллбэки в функции. Таким образом один компонент будет использовать другой, даже не зная с чем конкретно он имеет дело. Этот подход позволяет добится простоты и гибкости строительства интерфейсов.

Для использования компонента в JSX достаточно тэга с именем класса компонента.

Что такое JSX?

class App extends Component {
  render() {
    return (
      <div className="App">
        <h1>Hello World {Math.random()}</h1>
      </div>
    );
  }
}

export default App;

Синтаксис JSX преобразовывается в выражения вызова функций создания элементов и компонентов React. Код выше аналогичен:

ReactDOM.render(React.createElement('div', {className: 'App'},
                            React.createElement('h1',null,`Hello World ${Math.random()}`)), document.getElementById('root'));

Вы можете проверить это в index.js.

Преобразованием JSX в подобный вызов функций React (транспилированием) занимается транспилер babel, запускаемый npm при сборке проекта.

Соответствие JSX и получаемого JS кода:

Таким образом

  • "Имя тэга" (<div, например) должно являться именем элемента или классом компонента, которые передается первым параметром в React.createElement
  • "Атрибуты тэга" (className="App") собираются в ассоциативный массив, который передается вторым параметром в React.createElement. Очевидно, что коль это настройки компонента, то они рано или поздно попадут в объект и будут влиять на отображение этого компонента.
  • "Вложенные тэги" (h1) добавляются остальными параметрами React.createElement. Их может быть сколько угодно.
  • {} в JSX "включают" Javascript.

JSX: Итого

  • Превращается в выражение вызова, соответственно вложенность практически не ограничена (любая вложенность превращается во вложенность createElement(....createElement(...createElement(...)
  • Результатом является тот или иной элемент React
  • В JS (внутри фигурных скобок) могут быть только выражения, так как этот JS будет вклеен в параметр вызова функции (а значит if или for там неуместен)
  • Метод render компонента просто возвращает объект, созданный транспиленным JSX-ом.
  • JSX придирчив: надо писать именно return ( (нельзя переносить скобку на след. строку).
  • JSX придирчив: его нельзя закомментировать (зато можно "включить" JavaScript в JSX и комментировать в JS)
  • Несмотря на то, что вложенность JSX может быть любой разумной глубины, метод render должен вернуть только одно значение, а значит на верхнем уровне в JSX должен быть один тэг.
  • JSX придирчив: будьте добры ставить / даже в непарных тэгах.
  • Можно вставить массив других элементов среди тэгов JSX. Таким образом вы получаете возможность сделать динамический композитный компонент.

Компоненты

С точки зрения React компонент должен возвращать тот или иной элемент (обычно созданный JSX) и этого достаточно:

function Hello(){
    return <h1>Hello</h1>
}

Однако обычно используются компоненты-классы:

class HelloClass extends Component {
    render(){ //метод, который вызывает React для получения элемента для отрисовки
        return (  //нельзя переносить; только один "тэг" верхнего уровня:
            <div> 
                JSX here
            </div>
        );
    }
}

Props

Для передачи настроек в компоненты используются "атрибуты тэга" в JSX, которые транспилятся в ассоциативный массив, передаваемый в объект компонента как первый параметр компонента-функции или как this.props (параметр конструктора) в объекте:

function Hello(props){
    return <h1>Hello {props.name}!!</h1>
}

Hello.defaultProps = {name: 'NoName'};

class HelloClass extends Component {
    constructor(props){
        super(props)
        console.log(props)
    }

    render(){ //метод, который вызывает React для получения элемента для отрисовки
        return (  //нельзя переносить; только один "тэг" верхнего уровня:
            <div> 
                {this.props.names.map((name,i) => <Hello name={name} key={i} />)}
            </div>
        );
    }

    static get defaultProps(){
        return {names: ["N0NAME"]};
    }
}


class App extends Component {
  render() {
    return (
      <div className="App">
        <Hello name="SuperMan" />
        <HelloClass names={["Captain Obvious", "JSMan", "TrololoMan"]} />
        <Hello />
        <HelloClass />
      </div>
    );
  }
}

export default App;

Props позволяют передавать данные и настройки сверху вниз - от бОльших компонентов к меньшим вложенным компонентам.

Для связи снизу вверх всегда можно передать callback в props.

JSX может делать голову если вы хотите передать что-то кроме строки. Включайте JS в таком случае:

<Counter value={ 0 } stopped={ true } />

Статический геттер defaultProps или же объект в функции-компоненте defaultProps позволяет задать значения props по умолчанию.

Props нельзя менять изнутри компонента, однако можно обновлять снаружи. Изменение props ведет к запуску метода render для обновления представления компонента.

Вы можете добавить сколько угодно вложенных элементов создав массив с ними и вставив его в JSX. Обратите внимание на key - этот идентификатор элемента из массива позволяет React понять когда нужно перерисовывать отображение.

State

Кроме "внешних настроек", навязанных с помощью props, есть внутреннее состояние компонента, под названием state, доступное только из компонента.

  • Значения из state можно менять, но нельзя менять напрямую. Изменения нужно осуществлять через метод setState.
  • state должен использоваться в render при отображении.
  • Изменение state обычно ведет к автоматическому запуску render для актуализации изменений в представлении.
class MinimumInput extends Component {
    constructor(props){
        super(props)
        this.state = { valid: false } //единственное место где можно напрямую писать в state.

        this.check = this.check.bind(this)
    }

    check() { 
        if (this.input.value.length < this.props.min){
            this.setState({valid: false})
        }
        else{
            this.setState({valid: true})
        }
    }


    render(){
        let style = {backgroundColor: this.state.valid ? '' : 'red'};
        return (
            <input onChange={this.check} ref={ c => this.input = c} style={style}/>
        );
    }
}


class App extends Component {
  render() {
    return (
      <div className="App">
        <MinimumInput min={ 5 } />
      </div>
    );
  }
}

export default App;
  • В конструкторе писать в state можно, так как от его изменения еще ничего не зависит;
  • Для передачи функции без потери this используется bind в конструкторе;
  • Для задания состояния (причем не обязательно всего, вы можете менять его частично) используется метод setState
  • render - обычный метод, и вы можете сделать ту или иную логику на JS до return. В данном случае цвет фона зависит от state.
  • Для того, что бы получить ссылку на объект вложенного компонента используется ref - встроенный props, который вызывается при создании компонента. Таким образом можно добраться до значения input в данном примере;

Асинхронный setState.

При вызове setState нет гарантии, что state в этом объекте изменится синхронно (т. е. в тот же момент). React может обработать setState и его отрисовку в компоненте позже, т. е. асинхронно. По этой причине, если новое состояние state зависит от предыдущего, в коде его вычисления нельзя отталкиваться от текущего state, так как он может быть еще не актуализированным предыдущим(и) setState. В качестве решения этой проблемы в сам setState передается функция, которая будет вызвана React в нужный момент времени с актуальными значениями props и state:

class MyCheckBox extends Component {
    constructor(props){
        super(props)
        this.state = { checked: this.props.checked } //единственное место где можно напрямую писать в state.

        this.toggle = this.toggle.bind(this)
    }

    toggle() { 
        this.setState((prevState, props) => { //функция вызовется потом.
            return {
                checked: !prevState.checked
            }
        })
    }


    render(){
        let style = {backgroundColor: this.state.checked ? 'green' : ''};
        return (
            <button onClick={this.toggle} style={style}>{this.props.text}</button>
        );
    }

    static get defaultProps(){
        return {checked: false };
    }
}


class App extends Component {
  render() {
    return (
      <div className="App">
        <MyCheckBox text='Checkbox' />
    </div>
    );
  }
}

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

class LifeCycle extends Component {
    //first time methods:
    constructor(props){ //once
        super(props)
        this.state = {counter: 0};
        console.log('constructor', props, this.state)
    }

    componentWillMount(){ //once
        console.log('componentWillMount')

        this.interval = setInterval(()=>{
            this.setState((prevState, props) => ({counter: prevState.counter +1}))
        },1000)
    }

    render(){ //as many as component should update + once at first time
        console.log('render')
        return (
            <div> life cycle: {this.state.counter}</div>
        );
    }

    componentDidMount(){ //first time render success
        console.log('componentDidMount')
    }

    componentWillUnmount(){ //end of life
        console.log('componentWillUnmount')
    }


    //main loop methods:

    shouldComponentUpdate(nextProps, nextState){ //every time when props or state changed
        console.log('shouldComponentUpdate', nextProps, nextState)
        return Math.random() > 0.7; //here we decide, is this update so major to re-render, or no
    }

    componentWillUpdate(nextProps, nextState){ //before every render (except first)
        console.log('componentWillUpdate', nextProps, nextState)
    }

    componentDidUpdate(prevProps, prevState){ //after render
        console.log('componentDidUpdate', prevProps, prevState)
    }



    componentWillReceiveProps(nextProps){ //when we receive new props
        console.log('componentWillReceiveProps', nextProps)
    }
}

Каждый компонент в процессе своей жизни проходит разные стадии. Существует две основных "ветви" этого пути:

  • Жизнь компонента от начала до конца. Конструктор, подключение, отрисовка, после подключения, отключение.
    • Конструктор. Вызывается однократно, обычно используется для отладки, bind нужных методов, создания изначального state.
    • componentWillMount. Вызывается однократно, тут можно создавать те или иные ресурсы для работы компонента.
    • render. Главный метод, вызывается хотя бы раз за жизнь компонента, а так же может быть вызван при изменении props илиstate`.
    • componentDidMount. Вызывается однократно после первой отрисовки компонента. Сюда можно положить то, что требует полной готовности компонента.
    • componentWillUnmount. Вызывается однократно перед отключением компонента от Virtual DOM.
  • Цикл отображения. Приход новых state или props, оценка, стоят ли эти изменения перерисовки, подготовка, отрисовка, после отрисовки.
    • shouldComponentUpdate. Вызывается каждый раз при приходе нового state или props. Метод должен вернуть true если данные в новых props или state требуют отрисовки и false в противном случае.
    • componentWillUpdate. Вызывается только если shouldComponentUpdate сказал true. Тут можно подготовить те или иные данные перед render
    • render. Отрисовка
    • componentDidUpdate. Те или иные операции после render.