06Sep

logo_og

Теперь для поддерживаемости вашего кода нужно указать какие же props/каких типов может ожидать наш компонент.

В файл comments.js добавляем:

Comment.propTypes = {  
    comment: PropTypes.shape({  
        text: PropTypes.string.isRequired,  
        user: PropTypes.string  
    })  

Мы ожидаем что нам придут comments, мы пишем об этом на 2 строчке сверху в коде.

.shape – описывает какого вида могут быть данные.

В нем должен быть text и user, к примеру. Таким образом вы описываете что хотите получить.

Также добавим такую запись в Article.js

Изменим для начала первую строку на

import React, { Component, PropTypes } from 'react'

Класс будет выглядеть таким образом:

class Article extends Component {  
    state = {  
        isOpen: false  
    }  
  
    static propTypes = {  
        article: PropTypes.object.isRequired  
    }  

Заметим, что из компонента в компонент у нас повторяется один и тот же функционал, т.е. Article и Comment List выполняют одну и туже логику в плане открытия/закрытия статей/комментариев. Та же самая toggleOpen, скорее всего, повторится еще много раз в наших компонентах. Давайте переиспользуем наш код. Самый простой метод это – mixins.

Создадим файл mixins/toggleOpen.js и опишем что он будет подмешивать в наш компонент.

export default {  
    getInitialState() {  
        return {  
            isOpen: false  
        }  
    },  
    toggleOpen() {  
        this.setState({  
            isOpen: !this.state.isOpen  
        })  
    }  
}  

Этот синтаксис поддерживается в старых компонентах React, но не поддерживается в ES6 и в Functional Components. Также возможно встретить старый синтаксис такой как этот, назовем файл ArticleOld.

import React from 'react'  
import CommentList from './CommentList'  
import toggleOpen from './mixins/toggleOpen'  
  
const ArticleOld = React.createClass({  
    mixins: [toggleOpen],  
    render() {  
        const { article: { title, text, comments } } = this.props  
        const { isOpen } = this.state  
        const body = isOpen ? <section>{ text } <CommentList comments = {comments} /></section> : null  
  
        return (  
            <div>  
                <h1 onClick = {this.toggleOpen}>{ title }</h1>  
                {body}  
            </div>  
        )  
    }  
})  
  
export default ArticleOld

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

Теперь поговорим о декораторах. Что это такое? Это функция, которой мы передаем свой компонент чтобы декорировать его, т.е. добавить к нему функциональность, а она в свою очередь возвращает уже новый компонент. Давайте напишем такой декоратор для комментариев.

import React from 'react'  
  
export default (Component) => class DecoratedComponent extends React.Component {  
    state = {  
        isOpen: false  
    }  
  
    toggleOpen = (ev) => {  
        ev.preventDefault()  
        this.setState({  
            isOpen: !this.state.isOpen  
        })  
    }  
  
    render() {  
        return <Component {...this.props} isOpen = {this.state.isOpen} toggleOpen = {this.toggleOpen}/>  
    }  
}  

В нашем случае будет возвращаться старый компонент добавляя к нему функциональность.

Также сделаем импорт декоратора в CommentList

import toggleOpen from './decorators/toggleOpen'

Наш класс мы будем описывать так же, как и делали до этого, но возвращать будем уже «обертку» над ним. Изменим последнюю строчку.

export default toggleOpen(CommentList)

В декоратор вынесем State, toggleOpen, для этого уберем их из файла CommentList. Для того чтобы наш компонент знал о состояниях, которые мы описываем, мы передаем состояния как props в строке 16. Таким образом в наш компонент попадет не только комментарии, но и isOpen, toggleOpen . Добавим запись об этом в CommentList

const { comments, isOpen, toggleOpen } = this.props

Также удалим строки

const { isOpen } = this.state

А строчку c OnClick изменим так:

<a href="#" onClick = {toggleOpen}>{linkText}</a>

Такой подход более удобный и явный. Так будет выглядеть CommentList

import React, { Component, PropTypes } from 'react'  
import Comment from './Comment'  
import toggleOpen from './decorators/toggleOpen'  
  
class CommentList extends Component {  
    render() {  
        const { comments, isOpen, toggleOpen } = this.props  
        if (!comments || !comments.length) return <h3>no comments yet</h3>  
  
        const commentItems = comments.map(comment => <li key = {comment.id}><Comment comment = {comment}/></li>)  
        const body = isOpen ? <ul>{commentItems}</ul> : null  
        const linkText = isOpen ? 'close comments' : 'show comments'  
        return (  
            <div>  
                <a href="#" onClick = {toggleOpen}>{linkText}</a>  
                {body}  
            </div>  
        )  
    }  
}  
  
export default toggleOpen(CommentList)  

Написанные нами компоненты достаточно простые. Давайте рассмотрим некий жизненный цикл компонентов в React.
165

Первый этап – это момент до инициализации компонента, до его появления в DOM дереве. На схеме этот блок отмечен красным квадратом. Например, где-то было принято решение о render этого компонента, возможно это происходит впервые. Начнем со второй ступени этого блока, getInitialState (в новом синтаксисе это просто state). State может зависеть от props., но это довольно плохая практика.

После создания начального состояния вашего компонента вызовется componentWillMount (), в нем будет доступно все this.props, this.state, все что нужно чтобы сделать render вашего компонента.

Далее идет render, строится виртуальный DOM, далее, когда мы готовы его вставить в реальный DOM вызывается componentDidMount.

Иногда как в случае с CommentList наш компонент содержит еще несколько компонентов, например, Comment в нашем случае, они в свою очередь тоже имеют эти Lifecycle методы. Каков же порядок выполнения? Сначала отработает родительский componentWillMount, потом render, потом дочерние componentWillMount, потом метод render закончился и начинает встраиваться в DOM дерево. Сначала отработает componentDidMount дочерних компонентов и только когда все дочерние компоненты будут готовы, будет вызван родительский componentDidMount.

Следующий этап component Lifecycle это этап обновления, выделен зеленым. Т.е. у него будет меняться state, приходить новые props, и на это мы тоже можем подписываться. Иногда состояние компонента зависит от props, т.е. вам необходимо следить, когда они обновляются и менять state для поддержки его в актуальном состоянии.

Поэтому в ComponentWillReceiveProps вы можете вызвать setState и тогда в ComponentWillUpdate у вас будут и новые props и новые state, т.о. приложение не будет делать render 2 раза. Далее произойдет render и ComponentDidUpdate (где мы уже обновились). В ComponentDidUpdate есть доступ и к старым, и к новым props, новые будут храниться в this, а старые просто как аргумент (prevProps, nextProps).

ComponentWillReceiveProps – тут есть доступ до старых Props и к новым;

shouldComponentUpdate – об этом методе поговорим в следующих уроках

ComponentWillUpdate – тут есть доступ и к новым props и к новым state

Так происходит до тех пор, пока компонент не обновиться полностью и произойдет

componentWillUnmount – это значит что компонент все еще находиться в DOM, но скоро мы его от туда уберем. Это распространенная ситуация для очистки каких-либо подписок.

Для лучшего понимания процесса работы Lifecycle добавим следующий код в class CommentList нашего компонента CommentList:

componentWillMount() {  
    console.log('---', this.props)  
}  
componentDidMount() {  
    console.log('---', 'mounted')  
}  
  
componentWillReceiveProps(nextProps) {  
    console.log('---', this.props.isOpen, nextProps.isOpen)  
}  
  
componentWillUnmount() {  
    console.log('---', 'unmounting')  

Результаты работы можно наблюдать в консоли.

На протяжении Lifecycle в компонент находящийся в DOM дереве могут приходить новые props, а он в свою очередь будет постоянно обновляться. Вспомним о ключах их прошлых уроков, которые мы писали для наших Articles. Если мы не используем их, то каждый раз, когда статья будет удаляться из списка наш компонент будет обновляться, т.е. не будет проходить все этапы цикла. Но если меняется ключ это уже причина для переинициализации всего компонента полностью. Если где-то меняется ключ значит React уберет предыдущий и вставит новый, это удобно если вы используете сторонние библиотеки такие как D3, чтобы не следить за всеми обновлениями вручную, вы просто убираете старое и строите новое.

Иногда необходимо работать с реальным DOM, сам React знает его структуру, необходимо только запросить ее у него. Для этого используется ref.

Например, добавим ref в CommentList для элемента с Link:

<a href="#" onClick = {toggleOpen} ref="toggler">{linkText}</a>

Также напишем следующий код в этом же файле:

componentDidMount() {
    console.log('---', 'mounted', this.refs.toggler)

Таким образом мы получили доступ до реального DOM элемента с Link и можем с ним работать. Как работать с JQuery компонентами? Создадим файл для этого JqueryComponent. Пишем следующее:

import React, { Component, PropTypes } from 'react'  
  
class JqueryComponent extends Component {  
    static propTypes = {  
  
    };  
  
    componentDidMount() {  
        console.log('---',this.refs.component)  
    }  
  
    render() {  
        return (  
            <div ref="component"/>  
        )  
    }  
}  
  
export default JqueryComponent  

К примеру, в такую конструкцию можно будет инициализировать JQuery плагин. Это не приветствуется, но иногда просто необходимо чтобы не реализовывать сложные решения самому.

Поговорим далее о потоках данных. Не всегда мы можем организовать его в одном направлении, иногда будет использоваться обратный поток данных. По умолчанию в React все данные передаются сверху вниз, но иногда есть необходимость управления родительским компонентом из дочернего. Например, мы хотим, чтобы в один момент времени была открыта одна статья, а другие были закрыты автоматически. Т.е. информация должна храниться сверху в родителе, т.е. необходимо организовать reverse data flow, т.к. компоненты про друг друга ничего не знают.

Для начала мы уже не можем использовать Functional Component Article List, так как у нас появиться Functional list. Начнем работу над кодом ArticleList.

Изменяем код импорта в первой строчке, создаем class вместо функции, добавляем render. Также не забудьте изменить import на 2 строке. Теперь задаем state на 5 строчке. Сначала нам необходимо знать какая статья открыта, опишем это на 6 строчке. Также необходим метод, который может открывать и закрывать статью – создадим его на 9 строчке. В Article, передадим состояние и функцию открытия статьи – 20 строчка.

import React, { Component }  from 'react'  
import Article from './Article'  
  
class ArticleList extends Component {  
    state = {  
        openArticleId: null  
    }  
  
    openArticle = id => ev => {  
        if (ev) ev.preventDefault()  
        this.setState({  
            openArticleId: id  
        })  
    }  
  
    render() {  
        const { articles } = this.props  
  
        const listItems = articles.map((article) => <li key={article.id}>  
            <Article article = {article}  
                isOpen = {article.id == this.state.openArticleId}  
                openArticle = {this.openArticle(article.id)}  
            />  
        </li>)  
        return (  
            <div>  
                <h1>Article list</h1>  
                <ul>  
                    {listItems}  
                </ul>  
            </div>  
        )  
    }  
}  

Теперь нужно изменить Article удалив ненужный код, получая все необходимое из props:

import React, { Component, PropTypes } from 'react'  
import CommentList from './CommentList'  
  
class Article extends Component {  
  
    static propTypes = {  
        article: PropTypes.object.isRequired  
    }  
/* 
    constructor(props) { 
        super(props) 
        this.state = { 
            isOpen: false 
        } 
    } 
*/  
    render() {  
        const { isOpen, openArticle, article: { title, text, comments } } = this.props  
            const body = isOpen ? <section>{ text } <CommentList comments = {comments} /></section> : null  
  
            return (  
                <div>  
                    <h1 onClick = {openArticle}>{ title }</h1>  
                    {body}  
                </div>  
            )  
    }  
}  
  
export default Article 

Пожалуйста загрузите исходный код урока отсюда.

Задание для самостоятельной работы.

Нужно создать миксин и декоратор для уже описанного нами функционала: отображение только одной открытой статьи, а также возможность закрытия всех статей и отображения только заголовков.
keep-calm-there-s-more-to-come

We are looking forward to meeting you on our website soshace.com

Уроки React. Урок 1, Введение.

Окунемся немного в прошлое и разберемся как вообще появился React, и зачем вообще нам нужны фреймворки. Вернемся лет на 10 назад к примеру, когда был только чистый JavaScript, зачастую он выполнял простейшие задачи, такие как валидация формы и.т.п. Ключевая концепция – это абстрагироваться от неких обыденных проблем, и решать более сложные, задачи.