В этой статье мы рассмотрим компоненты высшего порядка (Higher-Order Components, HOC) в React.
Что такое HOC?
HOC — это особая техника в React, при которой функция принимает аргумент Component и возвращает новый компонент.
function composeComponent(Component) {
return class extends React.Component {
render() {
return <Component />
}
}
}
В данном примере функция composeComponent
принимает аргумент Component
и возвращает компонент ES6 class. Возвращенный класс отображает аргумент Component. Аргумент Component является компонентом React, который будет отображаться возвращенным компонентом class.
Например:
class CatComponent {
render() {
return <div>Cat Component</div>
}
}
CatComponent отображает следующее:
Cat Component
CatComponet может быть передан функции composeComponent для получения другого компонента:
const composedCatComponent = composeComponent(CatComponent)
composedCatComponent может быть отображен:
<composedCatComponent />
Результат выглядит следующим образом:
Cat Component
Та же функция высшего порядка, как и в JS.
Функция высшего порядка
Функция высшего порядка — это шаблон в JS, при котором одна функция принимает другую функцию и возвращает функцию в качестве результата, что является возможным благодаря композиционному характеру JS. Это означает, что:
- объекты
- массивы
- строки
- числа
- логические значения
- функции
могут быть переданы функциям в качестве аргументов или возвращены из функции.
function mul(x) {
return (y) => {
return x * y
}
}
const mulTwo = mul(2)
mulTwo(2) // 4
mulTwo(3) // 9
mulTwo(4) // 8
mulTwo(5) // 10
Функция mul
возвращает функцию, которая захватывает x
в замыкании. x
теперь доступен для возвращенной функции, а mul
является теперь функцией высшего порядка, поскольку она возвращает функцию. Это означает, что ее можно использовать для создания более специфических функций с использованием различных аргументов.
Например, ее можно использовать для создания функции, которая утраивает свои аргументы:
function mul(x) {
return (y) => {
return x * y
}
}
const triple = mul(3)
triple(2) // 6
triple(3) // 9
triple(4) // 12
triple(5) // 15
В чем преимущества элементов высшего порядка? При постоянном повторении логики нужно найти способ разместить логику в одном месте и использовать ее оттуда. Функции высшего порядка предоставляют шаблон, который можно использовать для этой реализации.
В приведенном выше примере при непрерывном умножении на 3
в приложении можно создать функцию, которая возвращает функцию, умножающую на 3
triple
. Поэтому при необходимости написать код multiplication by 3
, нужно просто вызвать triple
, передающий число для умножения на 3
в качестве параметра.
Общие проблемы при работе с HOC
В чем преимущества использования HOC в приложении React?
В процессе написания программы одна и та же логика может повторяться снова и снова.
Например, приложение для просмотра и редактирования документов. Приложение будет аутентифицировано, поэтому только аутентифицированные пользователи смогут получить доступ к панели инструментов, редактировать, просматривать или удалять документы. Маршруты будут выглядеть следующим образом:
<Route path="/" component={App}>
<Route path="/dashboard" component={Documents}/>
<Route path="document/:id/view" component={ViewDocument} />
<Route path="documents/:id/delete" component={DelDocument} />
<Route path="documents/:id/edit" component={EditDocument}/>
</Route>
Документы нужно аутентифицировать, чтобы только аутентифицированные пользователи могли получить к ним доступ. Для этого необходимо сделать следующее:
class Doucments extends React.Component {
componentwillMount() {
if(!this.props.isAuth){
this.context.router.push("/")
}
}
componentWillUpdate(nextProps) {
if(!nextProps.isAuth) {
this.context.router.push("/")
}
}
render() {
return <div>Documents Paegs!!!</div>
}
}
function mapstateToProps(state) {
isAuth: state.auth
}
export default connect(mapStateToProps)(Documents)
State.auth содержит состояние пользователя. Если пользователь не аутентифицирован, то значение будет false, в обратно случае — true. Функция connect отобразит состояние для объектов props isAuth. Затем при подключении компонента к DOM запускается componentWillMount. Здесь нужно проверить, обладает ли prop isAuth значением true. При значении true метод передается вниз, а при значении false метод передает маршрут “/” индексной страницы маршрутизатору. В результате браузер загружает страницу индекса внутри рендеринга компонента documents, запрещая не аутентифицированным пользователям получить доступ.
Когда компонент выполняет ре-рендеринг после первоначального рендеринга, необходимо выполнить те же действия в componentWillUpdate, чтобы проверить, авторизован ли пользователь. Если нет, загружается страница индекса.
Теперь сделаем то же самое в ViewDocument:
class ViewDoucment extends React.Component {
componentwillMount() {
if(!this.props.isAuth){
this.context.router.push("/")
}
}
componentWillUpdate(nextProps) {
if(!nextProps.isAuth) {
this.context.router.push("/")
}
}
render() {
return <div>View Document Page!!!</div>
}
}
function mapstateToProps(state) {
isAuth: state.auth
}
export default connect(mapStateToProps)(ViewDocument)
И в EditDocument:
class EditDocument extends React.Component {
componentwillMount() {
if(!this.props.isAuth){
this.context.router.push("/")
}
}
componentWillUpdate(nextProps) {
if(!nextProps.isAuth) {
this.context.router.push("/")
}
}
render() {
return <div>Edit Document Page!!!</div>
}
}
function mapstateToProps(state) {
isAuth: state.auth
}
export default connect(mapStateToProps)(EditDocument)
Также в DelDocument:
class DelDocument extends React.Component {
componentwillMount() {
if(!this.props.isAuth){
this.context.router.push("/")
}
}
componentWillUpdate(nextProps) {
if(!nextProps.isAuth) {
this.context.router.push("/")
}
}
render() {
return <div>Delete Document Page!!!</div>
}
}
function mapstateToProps(state) {
isAuth: state.auth
}
export default connect(mapStateToProps)(DelDocument)
Страницы выполняют различные действия, однако реализация одинакова.
Каждый компонент:
- подключается к состоянию с помощью соединения react-redux;
- отображает состояние
auth
для свойствprops
isAuth
; - проверяет аутентификацию в componentWillMount;
- проверяет аутентификацию в componentWillUpdate.
Представьте, что в проекте появится больше компонентов, в каждом из которых нужно выполнить те же действия. Это займет много времени.
Поэтому нужно найти способ для определения логики в одном месте. Лучше всего использовать HOC.
Для этого перемещаем всю логику в функцию, которая будет возвращать компонент:
function requireAuthentication(composedComponent) {
class Authentication extends React.Component {
componentwillMount() {
if(!this.props.isAuth){
this.context.router.push("/")
}
}
componentWillUpdate(nextProps) {
if(!nextProps.isAuth) {
this.context.router.push("/")
}
}
render() {
<ComposedComponent />
}
}
function mapstateToProps(state) {
isAuth: state.auth
}
return connect(mapStateToProps)(Authentication)
}
Все реализации находятся внутри компонента Authentication. Функция requireAuthentication соединяет компонент Authentication с хранилищем и возвращает его. Затем Authentication отображает компонент, переданный через аргумент ComposedCompoennt.
Маршруты теперь выглядят следующим образом:
<Route path="/" component={App}>
<Route path="/dashboard" component={requireAuthentication(Documents)}/>
<Route path="document/:id/view" component={requireAuthentication(ViewDocument)} />
<Route path="documents/:id/delete" component={requireAuthentication(DelDocument)} />
<Route path="documents/:id/edit" component={requireAuthentication(EditDocument)}/>
</Route>
Таким образом, независимо от количества маршрутов в приложении, нужно просто вызвать функцию requireAuthentication с переданным ей компонентом.
Заключение
Функция высшего порядка:
- возвращает функцию;
- используется для решения проблемы DRY.
Компонент высшего порядка:
- принимает компонент в качестве аргумента;
- возвращает другой компонент;
- возвращенный компонент отображает оригинальный компонент;
- используется для решения проблемы DRY.
Спасибо за внимание !!!
Читайте также:
- Увеличиваем производительность приложения React + Redux с библиотекой Reselect
- Экспорт данных в Excel с React
- React Native vs Flutter
Читайте нас в Telegram, VK и Дзен
Перевод статьи Chidume Nnamdi ????: Understanding Higher-Order Components in React