Всем привет! Сейчас мы пробежимся по нашему домашнему заданию, а также дадим важные комментарии , которые помогут Вам в дальнейшей работе с 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
. Поэтому будьте внимательны и используйте это только там где это реально нужно.
Внимательно изучите то, что было реализовано нами в домашнем задании и мы пойдем дальше. До скорой встречи!
We are looking forward to meeting you on our website soshace.com