Эта история о компонентах без сохранения состояния . Это означает, что компоненты не имеют this.state = { ... }
. Они имеют дело только с входящими «подпорками» и подкомпонентами.
Во-первых, супер основы
import React, { Component } from 'react' class User extends Component { render() { const { name, highlighted, userSelected } = this.props console.log('Hey User is being rendered for', [name, highlighted]) return <div> <h3 style={{fontStyle: highlighted ? 'italic' : 'normal'}} onClick={event => { userSelected() }} >{name}</h3> </div> } }
Примечание редактора: мы опробуем CodeSandbox для демонстраций в этой статье.
Сообщите нам свое мнение!
Ура! Оно работает. Это действительно просто, но подает пример.
Что следует отметить:
- Это без гражданства. Нет
this.state = { ... }
. - Здесь есть
console.log
чтобы вы могли понять, как он используется. В частности, когда вы выполняете оптимизацию производительности, вы хотите избежать ненужных повторных рендеров, когда реквизиты фактически не изменились. - Обработчик событий там «встроенный». Это удобный синтаксис, потому что код для него близок к элементу, который он обрабатывает, плюс этот синтаксис означает, что вам не нужно делать никаких
.bind(this)
приседаний. - С такими встроенными функциями, это небольшое снижение производительности, так как функция должна создаваться при каждом рендеринге. Подробнее об этом позже.
Это презентационный компонент
Теперь мы понимаем, что вышеприведенный компонент не только не имеет гражданства, но и является тем, что Дан Абрамов называет презентационным компонентом . Это просто имя, но в основном оно легкое, дает немного HTML / DOM и не возится с данными о состоянии.
Таким образом, мы можем сделать это функцией! Ура! Это не только кажется «бедром», но и делает его менее пугающим, потому что его легче рассуждать. Он получает входные данные и, независимо от среды, всегда возвращает один и тот же вывод. Конечно, он «перезванивает», поскольку один из реквизитов является вызываемой функцией.
Итак, давайте перепишем это:
const User = ({ name, highlighted, userSelected }) => { console.log('Hey User is being rendered for', [name, highlighted]) return <div> <h3 style={{fontStyle: highlighted ? 'italic' : 'normal'}} onClick={event => { userSelected() }}>{name}</h3> </div> }
Разве это не прекрасно? Это похоже на чистый JavaScript и что-то, что вы можете написать, не думая об используемой вами платформе.
Они повторяют рендеринг, говорят они 🙁
Предположим, наш маленький User
используется в компоненте, состояние которого меняется со временем. Но государство не влияет на нашу составляющую. Например, что-то вроде этого:
import React, { Component } from 'react' class Users extends Component { constructor(props) { super(props) this.state = { otherData: null, users: [{name: 'John Doe', highlighted: false}] } } async componentDidMount() { try { let response = await fetch('https://api.github.com') let data = await response.json() this.setState({otherData: data}) } catch(err) { throw err } } toggleUserHighlight(user) { this.setState(prevState => { users: prevState.users.map(u => { if (u.name === user.name) { u.highlighted = !u.highlighted } return u }) }) } render() { return <div> <h1>Users</h1> { this.state.users.map(user => { return <User name={user.name} highlighted={user.highlighted} userSelected={() => { this.toggleUserHighlight(user) }}/> }) } </div> } }
Если вы запустите это, вы заметите, что наш маленький компонент перерисовывается, хотя ничего не изменилось! Сейчас это не так уж важно, но в реальных приложениях компоненты имеют тенденцию к росту и усложнению, и каждый ненужный повторный рендеринг приводит к замедлению работы сайта.
Если бы вы отлаживали это приложение сейчас с помощью react-addons-perf
я уверен, что вы нашли бы, что время потрачено впустую, отображая Users->User
. о нет! Что делать?!
Кажется, все указывает на тот факт, что нам нужно использовать shouldComponentUpdate
чтобы переопределить то, как React считает реквизиты разными, когда мы уверены, что это не так. Чтобы добавить хук жизненного цикла React, компонент должен быть классом. Вздох Итак, мы вернемся к исходной реализации на основе классов и добавим новый метод ловушки жизненного цикла:
Вернуться к компоненту класса
import React, { Component } from 'react' class User extends Component { shouldComponentUpdate(nextProps) { // Because we KNOW that only these props would change the output // of this component. return nextProps.name !== this.props.name || nextProps.highlighted !== this.props.highlighted } render() { const { name, highlighted, userSelected } = this.props console.log('Hey User is being rendered for', [name, highlighted]) return <div> <h3 style={{fontStyle: highlighted ? 'italic' : 'normal'}} onClick={event => { userSelected() }} >{name}</h3> </div> } }
Обратите внимание на новое добавление метода shouldComponentUpdate
. Это довольно некрасиво. Мы не только больше не можем использовать функцию, мы также должны вручную перечислить реквизиты, которые могут измениться. Это предполагает смелое предположение, что функция userSelected
prop не меняется. Это маловероятно, но на что-то стоит обратить внимание.
Но обратите внимание, что это только один раз! Даже после повторного рендеринга содержащийся компонент App
. Так что это хорошо для производительности. Но можем ли мы сделать это лучше?
А как насчет React.PureComponent?
В React 15.3 появился новый базовый класс для компонентов. Он называется PureComponent
и у него есть встроенный метод shouldComponentUpdate
который выполняет сравнение с «равным количеством элементов» для каждой пропы. Большой! Если мы используем это, мы можем выбросить наш собственный метод shouldComponentUpdate
который должен был перечислить определенные реквизиты.
import React, { PureComponent } from 'react' class User extends PureComponent { render() { const { name, highlighted, userSelected } = this.props console.log('Hey User is being rendered for', [name, highlighted]) return <div> <h3 style={{fontStyle: highlighted ? 'italic' : 'normal'}} onClick={event => { userSelected() }} >{name}</h3> </div> } }
Попробуйте, и вы будете разочарованы. Он перерисовывается каждый раз. Почему?! Ответ в том, что функция userSelected
воссоздается каждый раз в методе render
. Это означает, что когда PureComponent
основе PureComponent
вызывает собственный shouldComponentUpdate()
он возвращает значение true, поскольку функция всегда отличается, так как она создается каждый раз.
Как правило, решение этой проблемы заключается в связывании функции в конструкторе содержащего компонента. Прежде всего, если бы мы это сделали, это означает, что нам пришлось бы вводить имя метода 5 раз (тогда как раньше это было 1 раз):
-
this.userSelected = this.userSelected.bind(this)
(в конструкторе) -
userSelected() {
(как само определение метода) -
<User userSelected={this.userSelected} ...
(при определении места рендеринга компонентаUser
)
Другая проблема состоит в том, что, как вы можете видеть, при выполнении этого метода userSelected
он опирается на замыкание. В частности, это зависит от user
переменной области из итератора this.state.users.map()
.
По общему признанию, есть решение для этого, и это должно сначала связать метод userSelected
с this
и затем при вызове того метода (из дочернего компонента) передать пользователя (или его имя) назад. Вот одно из таких решений .
recompose
на помощь!
Сначала, чтобы повторить, что мы хотим:
- Написание функциональных компонентов кажется приятнее, потому что они функции. Это сразу говорит читателю кода, что он не содержит никакого состояния. Их легко рассуждать с точки зрения модульного тестирования. И они чувствуют себя менее многословным и более чистым JavaScript (конечно, с JSX).
- Нам лень связывать все методы, которые передаются в дочерние компоненты. Конечно, если методы сложны, было бы неплохо реорганизовать их, а не создавать их на лету. Создание методов на лету означает, что мы можем написать их код прямо там, где они используются, и нам не нужно давать им имя и упоминать их 5 раз в 3 разных местах.
- Дочерние компоненты никогда не должны перерисовываться, если не изменены реквизиты для них. Это может иметь значение не только для крошечных быстрых приложений, но и для реальных приложений, когда у вас есть много-много всего этого, все это лишний рендеринг сжигает процессор, когда его можно избежать.
(На самом деле, в идеале мы хотим, чтобы компоненты отображались только один раз. Почему React не может решить эту проблему за нас? Тогда было бы на 90% меньше постов в блоге о том, «Как быстро реагировать».)
Рекомендовать — это «ремень обслуживания React для функциональных компонентов и компонентов более высокого порядка. Думайте об этом как о lodash для React. ” Согласно документации. В этой библиотеке есть что исследовать, но сейчас мы хотим визуализировать наши функциональные компоненты без их повторного рендеринга, когда реквизиты не меняются.
Наша первая попытка переписать его обратно в функциональный компонент, но с использованием recompose.pure
выглядит следующим образом:
import React from 'react' import { pure } from 'recompose' const User = pure(({ name, highlighted, userSelected }) => { console.log('Hey User is being rendered for', [name, highlighted]) return <div> <h3 style={{fontStyle: highlighted ? 'italic' : 'normal'}} onClick={event => { userSelected() }}>{name}</h3> </div> }) export default User
Как вы могли заметить, если вы запустите это, компонент User
прежнему перерисовываться, даже если реквизиты ( name
и highlighted
клавиши) не меняются.
Давайте возьмем это на одну ступеньку. Вместо использования recompose.pure
мы будем использовать recompose.onlyUpdateForKeys
который является версией recompose.pure
, но вы указываете ключи prop, чтобы сосредоточиться на них явно:
import React from 'react' import { onlyUpdateForKeys } from 'recompose' const User = onlyUpdateForKeys(['name', 'highlighted'])(({ name, highlighted, userSelected }) => { console.log('Hey User is being rendered for', [name, highlighted]) return <div> <h3 style={{fontStyle: highlighted ? 'italic' : 'normal'}} onClick={event => { userSelected() }}>{name}</h3> </div> }) export default User
Когда вы запустите этот файл, вы заметите, что он обновляется только при изменении name
реквизита или highlighted
элемента. Если родительский компонент перерисовывается, компонент User
— нет.
Ура! Мы нашли золото!
обсуждение
Прежде всего, спросите себя, стоит ли оптимизировать производительность ваших компонентов. Возможно, это больше работы, чем стоит. Ваши компоненты в любом случае должны быть легкими, и, возможно, вы можете переместить любые дорогостоящие вычисления из компонентов и либо переместить их в запоминающиеся функции за пределами, либо, возможно, вы можете реорганизовать свои компоненты, чтобы не тратить компоненты рендеринга, когда определенные данные все равно недоступны , Например, в этом случае вы можете не отображать компонент User
до тех пор, пока не закончится fetch
.
Неплохое решение — написать код наиболее удобным для вас способом, затем запустить свою вещь и затем выполнить итерацию, чтобы сделать ее более производительной. В этом случае, чтобы сделать вещи производительными, вам нужно переписать определение функционального компонента из:
const MyComp = (arg1, arg2) => { ... }
… до …
const MyComp = pure((arg1, arg2) => { ... })
В идеале, вместо того, чтобы показывать способы взлома, лучшим решением для всего этого был бы новый патч для React, который является огромным улучшением shallowEqual
который способен «автоматически» расшифровать то, что передается и сравнивается функция и только потому, что она не равна, не означает, что она на самом деле отличается.
Прием! Существует альтернатива среднего уровня необходимости возиться с методами привязки в конструкторах и встроенными функциями, которые создаются заново каждый раз. И это Публичные Классовые Поля . Это функция stage-2
в Babel, поэтому вполне вероятно, что ваша установка поддерживает это. Например, здесь используется вилка, которая не только короче, но и означает, что нам не нужно вручную перечислять все нефункциональные реквизиты. Это решение должно отказаться от закрытия. Тем не менее, все же хорошо понимать и знать о recompose.onlyUpdateForKeys
при необходимости.
Чтобы узнать больше о React, ознакомьтесь с нашим курсом React The ES6 Way .
Эта статья была рецензирована Джеком Франклином . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!