01Nov

react_12
Всем привет! Сейчас мы пробежимся по нашему домашнему заданию, а также дадим важные комментарии , которые помогут Вам в дальнейшей работе с React.  Для удобства Вам понабиться видеть перед собой код, поэтому перейдите на нужный commit и мы продолжим. Начнем с connect.connect нужен нам, чтобы обратиться к store. Добавим небольшую ремарку, что если Вы можете передать данные используя обычные props – передавайте. Например в containers/Articles.js  мы обращаемся к store чтобы из его state достать необходимые нам статьи:

export default connect(({ articles, filters }) => {
    return {
        articles: filterArticles(articles, filters)
    }
})(Articles)

А в Article/index.js мы, например,  conncet не используем. Компонент render‘иться сам по себе.  А в случае с  containers/CommentList.js мы опять делаем этот компонент “умным”, используя connect, так как в противном случае мы просто до него не сможем добраться . У нас нет этих комментариев в state, они хранятся  в storeи до них нужно как-то добраться, поэтому единственным правильным способом является использование  conncet .

export default connect((state, { article }) => {
    return {
        commentObjects: article.comments.map(id => state.comments.get(id))
    }
}, {
    addComment
})(toggleOpen(CommentList))

Есть способ достать комментарии с помощью рукописного helper прямо из статьи, к примеру в методе render нашего CommentList.js. Делая это мы получаем не чистую функцию render и у нас появляется риск попасть в разнообразные неприятные ситуации, к примеру, когда вы делаете action , который обновляет комментарии, но при этом затрагивает статьи,  в результате –  этот компонент вообще не обновиться. Это происходит потому, что единственное обновление будет происходить на самом верхнем уровне, и оно будет связано с обновлением отфильтрованных статей в containers.js/Articles.js .

export default connect(({ articles, filters }) => {
    return {
        articles: filterArticles(articles, filters)
    }
})(Articles)

Redux проверит, совпадение списка отфильтрованных статей с предыдущим и  сделает вывод, что ничего обновлять не надо. Поэтому  connect мы добавляем в том случае, когда нам нужно обратиться за данными которых у нас сейчас нету. Мы стоим перед выбором: взять эти данные где-то на верхнем уровне или добавить connect уровнем ниже – это вопрос баланса который тяжело объяснить, т.к. в React нет какой-то логической формулы по этому вопросу.

Детально на выполнении домашнего задания мы останавливаться не будем, вы можете посмотреть пример выполнения в репозитории. Сейчас мы с Вами остановимся только на некоторых аспектах выполнения этой задачи, поэтому перейдите на нужный commit по ссылке сверху и мы пойдем дальше.

Как мы работаем с формами? Посмотрим на файл components/NewCommentForm.js . В state мы храним значения для наших inputsв данном случае это user и text.

 state = {
        user: '',
        text: ''
    }

Далее мы передаем значения из state в inputs,

 render() {
        const { text, user } = this.state
        return (
            <form onSubmit = {this.handleSubmit}>
                <input value={text} onChange={this.handleChange('text')}/>
                <input value={user} onChange={this.handleChange('user')}/>
                <input type="submit" value="add comment"/>
            </form>
        )
    }

и на событии onChangeмы передаем callback , который умеет менять это значение, в нашем примере это делается вот таким образом:

   handleChange = element => ev => {
        this.setState({
            [element]: ev.target.value
        })
    }

Далее мы легко можем обнулить эти данные, просто вызвав setState с пустыми строками, когда комментарии добавлены и у нас будут пустые inputs:

  this.setState({
            user: '',
            text: ''
        })

Теперь несколько слов о middleware. Их нужно писать таким образом чтобы было легко переиспользовать их в будущем. Потому что это такая часть архитектуры приложения, через которое будет происходить все взаимодействие. Каждый Ваш action будет проходить через эту middleware с того момента как вы ее подключили. Поэтому хорошо бы использовать ее много раз, а не для одного конкретного случая. Например, не стоит писать middleware для добавления id комментариев. Middleware для добавления случайного id может выглядеть так:

export default store => next => action => {
    const { withRandomId, ...rest} = action
    if (!withRandomId) return next(action)

    next({
        ...rest,
        randomId: Date.now() + Math.random()
    })
}

Вся прелесть React+Redux в его прозрачности, весь этот One-way data flow прекрасен тем что вы всегда знаете поэтапно, что у Вас происходит.  Вы знаете что в тот момент как только произошел submit на добавление комментариев они пошли у нас в Action creator, далее они попали в цепочку Ваших middleware и в каждой из них у Вас произошли очевидные изменения, в одной у Вас добавиться id , потом это все залоггируется (через logger.js ), потом это передастся в reducer там также только чистые функции, поэтому Вам будет легко контролировать процесс.  Потом все это приведет к обновлению Вашего DOM. Поэтому для debuggingВам просто необходимо проверить каждый из шагов. Если же добавлять middleware  для разных, но по сути однотипных  actions то в дальнейшем отследить процесс будет очень тяжело. Делайте middleware  более прозрачным способом.

Вернемся к нашему middleware . Мы с Вами в AC/comments.jsдобавляем флажок – withRandomIdпотом в middleware  мы проверяем, если у нас есть этот флажок мы добавляем случайный id и пускаем этот action дальше. Потом в reducer/articles.js мы достаем этот случайный id прямо из action :

export default (articles = defaultArticles, action) => {
    const { type, payload, randomId } = action

и работаем с ним:

case ADD_COMMENT:
            return articles.updateIn([payload.articleId, 'comments'], comments => comments.concat(randomId))

И последнее. Взгляните на reducer/articles.jsа именно на Record :

const Article = Record({
    "id": "",
    "date": "",
    "title": "",
    "text": "",
    "comments": []
})

Это у нас immutable структура. Если с примитивами Вы ничего не сделаете, то вот с comments можете. Например используя push для comments вы  мутируете их, используя этот метод т.к. это простой массив. Этим действием вы теряете все то, что дают immutable структуры. Вы меняете что то внутри, а Immutable.js думает, что на самом деле ничего не поменялось.  Т.е. здесь у нас вместо concat делается push :

return articles.updateIn([payload.articleId, 'comments'], comments => comments.concat(randomId))

в результате ничего не возвращается. Сами данные изменились, но ничего не произойдет и в return все останется также.

Также несколько слов о вреде преждевременной оптимизации. Дело в том, что еще в прошлых уроках в containers/CommentList.js 
мы с Вами использовали много lifecycle hooks и в том числе shouldComponentUpdate() – он проверял, если isOpen меняется, то мы обновляем этот  CommentListесли isOpen не меняется, то мы явно говорим React не перестраивать  компонент. Это оптимизация для того чтобы не делать render компонента лишний раз, но это довольно опасная штука, не следует ее делать на каждом шагу.
Мы с Вами писали shouldComponentUpdate()и проверяли меняется isOpen или нет. На тот момент это была прекрасная оптимизация, так как когда у нас менялось что-либо не затрагивающие CommentList который умел только открываться и закрываться нас это не волновало. Но приложение усложнилось, к компоненту добавляется дополнительная логика и в результате если мы добавим shouldComponentUpdate снова:

class CommentList extends Component {
    shouldComponentUpdate(nextProps) {
        return this.props.isOpen != nextProps.isOpen
    }

комментарии добавляться не будут пока мы не будем закрывать/открывать комментарии. Почему так происходит? Из-за преждевременной оптимизации. Комментарии меняются, а isOpen нет. В результате компонент не перестраивается, т.к. его “больше интересует” isOpen . Поэтому будьте внимательны и используйте это только там где это реально нужно.

Внимательно изучите то, что было реализовано нами в домашнем задании и мы пойдем дальше. До скорой встречи!

see-you-soon

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

Уроки Express.js . Логгер, Конфигурация, Шаблонизация с EJS. Часть 2.

Favicon – это все connect Middleware, он смотрит, если url имеет вид favicon.ico, то он читает favicon и выдает, а иначе передает управления дальше. Логгер выводит запись о том, что у нас за запрос пришел. Например, если сейчас запустить приложение, то логгер что-то выведет, если мы зайдем на:

Leave a Reply