В этой статье мы обсудим, как использовать компоненты более высокого порядка, чтобы ваши приложения React были аккуратными, хорошо структурированными и простыми в обслуживании. Мы обсудим, как чистые функции поддерживают чистоту кода и как эти же принципы можно применить к компонентам React.
Чистые функции
Функция считается чистой, если она обладает следующими свойствами:
все данные, с которыми он работает, объявлены как аргументы
он не мутирует данные, которые ему дали, или любые другие данные (это часто называют побочными эффектами)
при одном и том же входе он всегда будет возвращать один и тот же результат.
Например, addфункция ниже чистая:
function add (x, y) {
return x + y;
}
Однако функция badAddниже нечиста:
let y = 2;
function badAdd (x) {
return x + y;
}
Эта функция не является чистой, поскольку она ссылается на данные, которые не были переданы ей напрямую. В результате можно вызвать эту функцию с одним и тем же входом и получить другой результат:
let y = 2;
badAdd (3) // 5
y = 3;
badAdd (3) // 6
Чтобы узнать больше о чистых функциях, вы можете прочитать «Введение в разумно чистое программирование «Марка Брауна.
Хотя чистые функции очень полезны и значительно упрощают отладку и тестирование приложения, иногда вам нужно создавать нечистые функции, которые имеют побочные эффекты, или изменять поведение существующей функции, к которой вы не можете получить прямой доступ (функция из библиотеки, например). Чтобы включить это, нам нужно взглянуть на функции более высокого порядка.
Функции высшего порядка
Функция высшего порядка — это функция, которая при вызове возвращает другую функцию. Часто они также принимают функцию в качестве аргумента, но это не требуется для того, чтобы функция считалась более высокого порядка.
Допустим, у нас есть наша addфункция сверху, и мы хотим написать некоторый код, чтобы при ее вызове мы записывали результат в консоль перед возвратом результата. Мы не можем редактировать addфункцию, поэтому вместо этого мы можем создать новую функцию:
function addAndLog (x, y) {
const result = add (x, y) ;
console.log (`Result: ${result}`) ;
return result;
}
Мы решили, что регистрация результатов функций полезна, и теперь мы хотим сделать то же самое с subtractфункцией. Вместо того, чтобы дублировать приведенное выше, мы могли бы написать функцию более высокого порядка, которая может принимать функцию и возвращать новую функцию, которая вызывает данную функцию и регистрирует результат, прежде чем возвращать его:
function logAndReturn (func) {
return function (...args) {
const result = func (...args)
console.log (‘Result’, result) ;
return result;
}
}
Теперь мы можем взять эту функцию и использовать ее для добавления логирования в addи subtract:
const addAndLog = logAndReturn (add) ;
addAndLog (4, 4) // 8 is returned, «Result 8’ is logged
const subtractAndLog = logAndReturn (subtract) ;
subtractAndLog (4, 3) // 1 is returned, «Result 1’ is logged;
logAndReturnявляется функцией высшего порядка, потому что она принимает функцию в качестве аргумента и возвращает новую функцию, которую мы можем вызвать. Они действительно полезны для обертывания существующих функций, поведение которых вы не можете изменить. Дополнительную информацию об этом можно найти в статье М. Дэвида Грина «Функции высшего порядка в JavaScript «, в которой эта тема рассматривается гораздо подробнее.
Компоненты высшего порядка
Переходя к земле React, мы можем использовать ту же логику, что и выше, чтобы взять существующие компоненты React и придать им дополнительное поведение.
Примечание: с появлением React Hooks, выпущенного в React 16.8, функции более высокого порядка стали немного менее полезными, поскольку хуки позволяли делиться поведением без необходимости в дополнительных компонентах. Тем не менее, они
В этом разделе мы собираемся использовать React Router, решение маршрутизации
Компонент React Router Link
React Router предоставляет
Это действительно полезная функция, и в нашем гипотетическом приложении мы решаем, что всегда хотим использовать это свойство. Однако после этого мы быстро обнаруживаем, что это делает все наши
Обратите внимание, что нам приходится каждый раз повторять свойство имени класса. Это не только делает наши компоненты многословными, но также означает, что если мы решим изменить имя класса, нам придется сделать это во многих местах.
Вместо этого мы можем написать компонент, который обертывает
const AppLink = (props) => {
return (
{props.children}
) ;
};
И теперь мы можем использовать этот компонент, который приводит в порядок наши ссылки:
В экосистеме React эти компоненты известны как компоненты более высокого порядка, потому что они берут существующий компонент и слегка манипулируют им, не изменяя существующий компонент. Вы также можете думать о них как о
Улучшенные компоненты высшего порядка
Вышеупомянутый компонент работает, но мы можем сделать намного лучше. Компонент
Принятие нескольких свойств
Компонент
this.props.to, который является
this.props.children, который представляет собой текст, отображаемый пользователю
Однако
Распространение JSX
JSX,
const props = { a: 1, b: 2};
Использование {...props}распространяет каждый ключ в объекте и передает его Fooкак отдельное свойство.
Мы можем использовать этот трюк с
const AppLink = (props) => {
return
}
Теперь
Порядок свойств в React
Представьте, что для одной конкретной ссылки на вашей странице вам нужно использовать другой файл activeClassName. Вы пытаетесь передать его в
Однако это не работает. Причина в том, что свойства упорядочиваются при рендеринге
return
;
Когда у вас есть одно и то же свойство несколько раз в компоненте React, выигрывает последнее объявление. Это означает, что наше последнее activeClassName="active-link"объявление всегда будет выигрывать, так как оно размещено после {...this.props}. Чтобы исправить это, мы можем изменить порядок свойств, чтобы мы распространялись this.propsпоследними. Это означает, что мы устанавливаем разумные значения по умолчанию, которые хотели бы использовать, но пользователь может переопределить их, если ему действительно нужно:
return
;
Создавая компоненты более высокого порядка, которые обертывают существующие, но с дополнительным поведением, мы сохраняем нашу кодовую базу чистой и защищаемся от будущих изменений, не повторяя свойства и сохраняя их значения только в одном месте.
Создатели компонентов высшего порядка
Часто у вас будет ряд компонентов, которые вам нужно обернуть одним и тем же поведением. Это очень похоже на ранее в этой статье, когда мы обернули addи subtractдобавили к ним ведение журнала.
Давайте представим, что в вашем приложении есть объект, содержащий информацию о текущем пользователе, прошедшем аутентификацию в системе. Вам нужно, чтобы некоторые из ваших компонентов React имели доступ к этой информации, но вместо того, чтобы слепо делать ее доступной для каждого компонента, вы хотите быть более строгим в отношении того, какие компоненты получают информацию.
Способ решить эту проблему — создать функцию, которую мы можем вызывать с помощью компонента React. Затем функция вернет новый компонент React, который будет отображать данный компонент, но с дополнительным свойством, которое даст ему доступ к информации о пользователе.
Звучит довольно сложно, но с помощью кода это можно упростить:
function wrapWithUser (Component) {
// information that we don’t want everything to access
const secretUserInfo = {
name: ‘Jack Franklin’,
favouriteColour: ‘blue’
};
// return a newly generated React component
// using a functional, stateless component
return function (props) {
// pass in the user variable as a property, along with
// all the other props that we might be given
return
}
}
Функция берет компонент React (что легко заметить, поскольку компоненты React обычно имеют заглавные буквы в начале) и возвращает новую функцию, которая отрисовывает компонент, который ей был передан, с дополнительным свойством user, для которого задано значение secretUserInfo.
Теперь давайте возьмем компонент,
const AppHeader = function (props) {
if (props.user) {
return Logged in as {props.user.name};
} else {
return You need to login;
}
}
Последний шаг — подключить этот компонент так, чтобы он получил this.props.user. Мы можем создать новый компонент, передав его в нашу wrapWithUserфункцию:
const ConnectedAppHeader = wrapWithUser (AppHeader) ;
Теперь у нас есть
Я решил назвать компонент, ConnectedAppHeaderпотому что думаю о нем как о связанном с
Этот шаблон очень распространен в библиотеках React, особенно в Redux, поэтому знание того, как он работает и почему он используется, поможет вам по мере роста вашего приложения, и вы полагаетесь на другие сторонние библиотеки, которые используют этот подход.
Вывод
В этой статье показано, как, применяя принципы функционального программирования, такие как чистые функции и компоненты более высокого порядка, к React, вы можете создать кодовую базу, которую легче поддерживать и с которой легче работать на ежедневной основе.
Создавая компоненты более высокого порядка, вы можете хранить данные, определенные только в одном месте, что упрощает рефакторинг. Создатели функций более высокого порядка позволяют сохранять большую часть данных в секрете и предоставлять фрагменты данных только тем компонентам, которые действительно в них нуждаются. Делая это, вы делаете очевидным, какие компоненты используют какие биты данных, и по мере роста вашего приложения вы обнаружите, что это полезно.