Автор оригинала: FreeCodeCamp Community Member.
Респолагаемая компонентная структура представляет собой важное обязательное условие для стабильного применения реагирования. Вы можете добиться этого, написав свой код функциональным способом с использованием компонентов более высокого порядка (HOCS). Если вы придерживаетесь этого шаблона, вы получите многоразовые компоненты, которые оба читаются, так и просты в тесте, так как каждый компонент отвечает только за одну задачу.
В этой статье я хотел бы поделиться своим опытом, поэтому вы можете легко использовать этот подход в собственных приложениях. Вы не только научитесь узнать, как улучшить ваши презентационные компоненты, используя одну или несколько HOC, но вы также поймете принципы позади этой картины.
Почему этот пост так долго?
Когда я начал изучать HOCS SAY, у меня не было проблем с поиском ресурсов, касающихся этой темы. Однако многие из них приняли определенные предыдущие знания сложных тем, таких как функциональные принципы программирования (FP). В результате мне было сложно понять, что происходит под капотом и как работает состав нескольких HOCS.
Это был этот опыт, который мотивировал меня, чтобы написать эту статью в более широком и более дружелюбным путем. Таким образом, он охватывает не только HOC, но и принципы ФП и основные идеи, которые нужно понимать, чтобы иметь возможность развязать мощность компонентов высшего порядка.
Эта статья также основана на Моя первая технология конференции Talk Я дал на международной конференции JavaScript (IJS) 2017 в Мюнхене. Вы можете найти все исходный код на Github Отказ
Начиная
Давайте начнем, глядя на какой-код:
const starWarsChars = [ { name:'Luke', side:'light' }, { name:'Darth Vader', side:'dark' }, { name:'Obi-wan Kenobi', side:'light'}, { name:'Palpatine', side:'dark'},]
class FilteredList extends React.Component { constructor(props) { super(props) this.state = { value: this.props.defaultState } } updateState(value) { this.setState({ value }) } render() { const otherSide = this.state.value === 'dark' ? 'light' : 'dark' const transformedProps = this.props.list.filter(char => char.side === this.state.value) return ({transformedProps.map(char =>) }})}Character: {char.name}Side: {char.side}
ReactDOM.render (, document.getElementById('app'))
FilteredList
это огромный компонент, который делает так много вещей. Он поддерживает состояние и фильтрует Список
персонажей звездных войн в зависимости от их стороны. Кроме того, он отображает список символов с помощью кнопки на экран.
Это заботится обо всех логике и презентации и из-за этого, вряд ли когда-либо многоразовая.
Если вы решите повторно использовать этот компонент в другом месте, вам всегда нужно использовать все логику и интерфейс компонента. Вы не можете просто выбрать вишневые функции, которые вам действительно нужно для конкретного сценария. Вместо этого вы будете вынуждены переписать уже существующую часть поведения в качестве другого компонента.
В результате такой повторный код будет трудно поддерживать, особенно в более широком приложении.
В конце этой статьи мы сможем написать полностью многоразовую версию этого кода, используя принципы функционального программирования (FP).
Следите за обновлениями.
Вкус принципы функционального программирования
Чтобы показать вам, почему вы должны придерживаться принципов FP в приложении React React, мне нужно немного поговорить о основных принципах самого FP.
Идея состоит в том, чтобы разложить программу в простых многоразовые функции Отказ
Итак, это все о функциях. Быть более точным, это все о Простые функции Отказ Это означает, что каждая функция должна быть несетена только за одну задачу. Чем проще функция, тем более многоразовая она есть.
Функции высшего порядка
В JavaScript вы можете использовать функцию, как любое другое значение. Он может быть передан как аргумент для функции или его можно вернуть. Функция, что Возвращает или создает новую функцию называется функцией высшего порядка.
const numbers = [1, 5, 8, 10, 21]const createAddingFunction = number => arr => arr.map(num => num + number)const numbersPlusOne = createAddingFunction(1)console.log(numbersPlusOne(numbers)) // [2, 6, 9, 11, 22]
CreateAddingFunctions
является функцией более высокого порядка. Требуется Номер
и создает новую функцию, ожидающую передачу массива. В примере мы передаем это 1
и верните новую функцию, ожидая массива. Мы храним его как Numbersplusone
Отказ Тогда мы передаем Числа
массив к этому. Функция затем итерации по элементам массива и увеличивается друг на друга.
Как вы можете видеть, мы говорим JavaScript Engine что Мы хотим сделать – мы хотим отображать элементы массива. Этот код является самоснабжением. Вы просто видите код, и вы немедленно знаете, что происходит. Такой код называется декларативный Отказ Функциональное программирование – это все о декларативном коде.
Избегайте побочных эффектов
Как функциональный программист, вы хотите избежать побочных эффектов в ваших функциях как можно больше. Другими словами, функция не должна меняться ничего, что не является локальной для самой функции. Вы можете легко использовать такую функцию, в любом месте вашего приложения. Функции без побочных эффектов называются чистый. Они всегда возвращают один и тот же выход, учитывая те же аргументы.
Если вы хотите написать чистые функции, вы также должны избегать мутации ваших значений. Это называется принципом Непомышленность Отказ Однако это не значит, что вы не изменяете свои ценности. Это означает, что когда вы хотите изменить значение, вы создаете новый, а не мутировать оригинальный.
Однако в JavaScript, такие значения, как объекты и массивы, являются смежными. Чтобы уважать принцип неизменности, мы можем рассматривать значения как неизменными.
Например, придерживаясь этого принципа, вы не сможете случайно мутировать объект, который был передан на функцию в качестве его параметра.
// pure functionconst numbers = [1, 5, 8, 10, 21]const createAddingFunction = number => arr => arr.map(num => num + number)const numbersPlusOne = createAddingFunction(1)console.log(numbersPlusOne(numbers)) //[2, 6, 9, 11, 22]console.log(numbers) // [1, 5, 8, 10, 21]
// impure functionconst numbers = [1, 5, 8, 10, 21]const numbersPlusOne = numbers => { for(let i = 0; i < numbers.length; i++) { numbers[i] = numbers[i] + 1 } return numbers}numbersPlusOne(numbers) // [2, 6, 9, 11, 22]console.log(numbers) // [2, 6, 9, 11, 22]
Здесь у нас есть пример чистого (так же, как в предыдущем примере) и нечистому функции. В первом случае тот факт, что мы прошли массив в чистую функцию, не повлияло на Числа
массив в любом случае.
Однако во втором сценарии массив был мутирован внутри нечистого функции. Такое поведение может сделать ваш код довольно непредсказуемым. И особенно в функциональном программировании REALM, мы хотим избежать этого.
Состав
К настоящему времени мы знаем, что мы должны создавать простые чистые функции. Однако, что, если нам нужно поведение, которое настолько сложно, что нельзя хранить в одной функции? Мы могли бы достичь этого путем объединения нескольких функций в новую составную функцию с использованием композиции.
const number = 15const increment = num => num + 5const decrement = num =>; num - 3const multiply = num => num * 2
const operation = increment(decrement(multiply(number)))console.log(operation) //32
Композиция означает, что мы проходим выход первого вызова функции в качестве входа во второй вызов функции, его вывод на третью функцию и т. Д. В результате мы получаем составную функцию.
В нашем примере у нас есть Номер
и три функции. Мы охватываем их всеми внутри друг друга, и мы получаем составную функцию, ожидая Номер
аргумент Используя композицию, нам не нужно создавать переменные для хранения результата единых функций.
Комбинированный
Чтобы действительно увидеть преимущества всех этих принципов FP, вам нужно объединить их вместе.
В идеале ваша заявка должна быть состоит из Чистые функции чьи данные рассматриваются как неизменный. Это означает, что они не изменяют их верхний объем, и поэтому вы можете использовать их в любой части вашей программы. Каждая функция должна быть ответственна за одну задачу и должна быть разделена от других. Вы можете использовать их, как они или могут составить их вместе, чтобы добиться более сложного поведения.
Придерживаясь принципами FP, вы получите простые многоразовые функции, которые могут быть состоны вместе.
Функциональное программирование и реагировать
Теперь, когда мы знакомы с основными принципами FP, мы можем взглянуть на то, как использовать их в наше преимущество в реакции.
Реагистрационные приложения состоят из компонентов. Но что именно является компонентом?
// Class-based componentclass Button extends React.Component { render(){ return }}
// Functional componentconst Button = (props) =>
Поскольку класс является просто синтаксическим сахаром по функциям, а функциональный компонент в основном является функцией, Компоненты просто функции Отказ Это функция, которая принимает входные данные (реквизиты) и возвращает дерево элементов React Elements (UI), который отображается на экран. Однако ему не нужно возвращать UI все время. Он может вернуть компонент, а также мы увидим позже.
Так jog ui это просто Состав функций Отказ Это звучит ужасно, как FP, верно?
Умные и презентационные компоненты
Компонент обычно состоит из логики и презентации. Однако, если принять решение написать все наши компоненты как таковые, мы бы в конечном итоге с десятками компонентов, имеющих только одну цель. С другой стороны, если мы попробуем Отделить эти проблемы Мы сможем создать простые многоразовые компоненты. После этой идеи мы должны предпочесть определять наши компоненты как Smart (логика) и презентация (UI).
презентационный Компонент заботится обо всех интерфейсе UI. У этого обычно есть форма Функциональный Компонент, который является просто представляющим методом. Вы можете думать о них как о функциях.
Компонент, содержащий в основном логику, называется Умный Отказ Обычно он обрабатывает манипуляции для данных, вызовы API и обработчики событий. Это часто будет определено как Класс Поскольку он предоставляет нам больше функциональных возможностей (таких как внутреннее состояние и жизненный цикл).
Каждый компонент должен нести ответственность за одну задачу и написано так, как правило, оно может быть использовано повторно на протяжении всего приложения. Такая задача должна быть либо логикой (интеллектуальной компонентой), либо презентацией (компонентом презентации). Комбинация как в одном компоненте должна быть минимизирована.
- Компонент смарт-класса
class DisplayList extends Component { constructor(props) { super(props) this.state = { starWarsChars: [ { name:'Luke Skywalker', side:'light' }, { name:'Darth Vader', side:'dark' }, { name:'Obi-wan Kenobi', side:'light' }, { name:'Palpatine', side:'dark' }, ] } } render() { return ({this.state.starWarsChars.map(char =>) }})}Character: {char.name}Side: {char.side}
ReactDOM.render(, document.getElementById('app'))
- Презентационная функциональная компонент
const starWarsChars = [ { name:'Luke', side:'light' }, { name:'Darth Vader', side:'dark' }, { name:'Obi-wan Kenobi', side:'light'}, { name:'Palpatine', side:'dark'},]
const DisplayList = ({ list }) =>{list.map(char =>)}Character: {char.name}Side: {char.side}
ReactDOM.render (, document.getElementById('app'))
Давайте посмотрим на функциональный компонент. Это довольно многоразовая, так как он только заботится о пользовательском интерфейсе. Итак, если вы хотите отобразить список персонажей Star Wars в другом месте в вашем приложении, вы можете легко повторно использовать этот компонент. У него также нет побочных эффектов, так как он никак не влияет на его внешнюю область.
Вы можете увидеть, что функциональный компонент – это просто чистая функция который принимает объект реквизита и возвращает один и тот же UI, учитывая одинаковые реквизиты.
Это не только реагирует на применение композиции функций в целом, но это также может быть Состав чистых функций Отказ
Как мы уже узнали, чистые функции являются основными строительными блоками FP. Поэтому, если мы предпочитаем использовать функциональные компоненты, мы сможем Применить различные методы FP такие как компоненты высшего порядка в нашем коде.
Добавление больше логики
Давайте посмотрим на наш функциональный компонент снова. Он принимает список персонажей Star Wars в качестве опоры и оказывает их на экран. Он довольно многоразовый, так как он не содержит никакой логики.
Теперь, что если мы хотели отображать только персонажи, принадлежащие к темной стороне? Самое простое решение будет фильтровать Список
опоры внутри компонента.
const FilteredList = ({ list, side }) => { const filteredList = list.filter(char => char.side === side) return ({filteredList.map(char =>)})}Character: {char.name}Side: {char.side}
ReactDOM.render (, document.getElementById('app'))
Это сделает трюк. Мы переименованы DisplysList
к FilteredList
Так как теперь он содержит функциональность фильтрации. Мы также сейчас передаем сторона
опора в соответствии с каким списком будет отфильтрован.
Тем не менее, это идеальное решение? Как вы можете видеть, FilteredList
Компонент больше не используемый. Из-за функции фильтра, похороненной внутри него, этот компонент вряд ли можно использовать повторно.
Если мы хотели отображать персонажей в другом месте в нашем приложении без каких-либо фильтрации, нам нужно было бы создать другой компонент. Кроме того, если мы хотели использовать функцию фильтра в других компонентах, нам придется дублировать это поведение.
К счастью, есть более элегантное и декларативное решение Это позволяет нам поддерживать нашу презентационную компонент многоразовой. Мы можем отфильтровать список символов, прежде чем она передается в виде опоры DisplysList
составная часть.
const withFilterProps = BaseComponent => ({ list, side }) => { const transformedProps = list.filter(char => char.side === side) return}
const renderDisplayList = ({ list }) =>{list.map(char =>)}Character: {char.name}Side: {char.side}
const FilteredList = withFilterProps(renderDisplayList)
ReactDOM.render (, document.getElementById('app'))
Мы переименованы наш функциональный компонент RenderDisPlaylist
Чтобы это было очевидно, что он несет только для рендеринга пользовательского интерфейса.
Во-первых, давайте посмотрим на FilteredList
составная часть. Этот компонент создается путем прохождения нашего функционального компонента RenderDisPlaylist
к сфильтерыprops
Функция более высокого порядка. Когда это произойдет, мы вернемся к функциональному компоненту и храните его как Filteterdlist
В ожидании прохождения объекта реквизита.
Мы видим FilteredList
Компонент в конце примера, передавая реквизиты. Он фильтрует список символов из реквизитов в соответствии с сторона
пропры Затем отфильтрованный список пропускается как опоры для RenderDisPlayList,
Что впоследствии оказывает список символов на экран.
Представляем компоненты высшего порядка
Давайте теперь поговорим о природе функции высшего порядка сфильтерыprops
Отказ В словаре React, такая функция называется компонентом более высокого порядка (HOC). Так же, как функция более высокого порядка создает новую функцию, HOC создает новый компонент.
HOC – это Функция что принимает Компонент и Возвращает новый компонент, который отображает пропущенную Отказ Этот новый компонент усиливается с дополнительными функциональными возможностями.
const HoC = BaseComponent => EnhancedComponent
В нашем примере сфильтерыprops
HOC берет RenderDisPlaylist
Компонент и возвращает новый функциональный компонент, который отображает RenderDisPlaylist
Отказ RenderDisPlaylist
Компонент усиливается с помощью логики фильтрующей реквизиты.
Поскольку мы абстрагировали всю логику к HOC, наш базовый функциональный компонент только для рендеринга пользовательского интерфейса и снова повторно используется.
HOC является специальным типом функции, которая управляет презентационной составляющей и повышает его с расширенными функциями. Думать о них как обертки для ваших функциональных компонентов.
Благодаря шаблону HOC вы можете улучшить ваши простые функциональные компоненты любую логику, которую вы хотите. Это сила узора HOC. Вы можете редактировать/обновить/преобразовывать реквизиты, поддерживать внутреннее состояние или влиять на рендеринг компонента вне вашего презентационного компонента.
Придерживаясь этого шаблона позволит вам использовать только функциональные компоненты в качестве базовых компонентов на протяжении всего вашего приложения и избавиться от всех компонентов класса.
Если мы снова рассмотрим различие между интеллектуальными и презентационными компонентами, базовый компонент всегда будет презентационным (поскольку это просто чистая функция). С другой стороны, HOC возьмет роль Умный Компонент, поскольку он касается только логики, которая затем передается до презентационного компонента. Тем не менее, если вам не нужно поведение, специфичное для класса, вы также можете определить HOC как функциональный компонент (как вы только что видели).
Поскольку вы сделали это далеко, давайте немного замедлились и поговорим о еде:)
Мясолот или блин
В начале этой статьи мы увидели, что этот сложный компонент повторно использует, который заботится обо всех логике и презентации.
class FilteredList extends React.Component { constructor(props) { super(props) this.state = { value: this.props.defaultState } } updateState(value) { this.setState({ value }) } render() { const otherSide = this.state.value === 'dark' ? 'light' : 'dark' const transformedProps = this.props.list.filter(char => char.side === this.state.value) return ({transformedProps.map(char =>) }})}Character: {char.name}Side: {char.side}
ReactDOM.render (, document.getElementById('app'))
Вы можете подумать об этом компоненте как Meatloaf Отказ
При подготовке мяса, вы берете мясо, панирумы, чеснок, лук и яйца, смешайте их вместе, положите сырой мясо в духовку и подождите, пока он не приготовлен. Нет никакого способа, чтобы вы могли взять яйца или лук из мяса, так как все безвозвратно объединяется.
Это так же, как компонент, который представляет собой смесь Логика и интерфейс. Вы просто не можете что-то взять из этого. Вам нужно использовать его как есть или нет вообще.
Попробуйте подумать о презентационных компонентах как Блинчики Отказ
Однако простые блины без каких-либо украшений довольно скучны, и никто не ест их так в любом случае. Так что вы хотите их украсить. Вы можете залить на них сироп клена или положить некоторые ягоды или шоколад на них. Так много возможных украшений слоев для вас использовать!
В приложении React эти украшения эти украшения представлены HOCS. Таким образом, как вы украсите блин в соответствии с вашим вкусом, вы также украшаете презентационный компонент с помощью HOC с помощью функциональности, которые вы хотите. В результате Вы можете повторно использовать конкретный презентационный компонент в разных местах вашего приложения и украсить его с помощью HOC, который вы хотите для конкретного случая.
Тем не менее, вы не можете сделать это с компонентом, который отвечает за всю логику и презентацию, так как все безвозвратно объединяется.
Я надеюсь, что эта метафора дала вам лучшее понимание шаблона HOC. Если нет, по крайней мере я заставил тебя голодать:).
Сделайте все компоненты многоразовые снова
Теперь, что мы знаем, как создать HOC, мы посмотрим, как сделать его многоразовым.
Изготовление компонентов многоразовые средства для разделить их от данных Отказ Это означает, что они не должны зависеть от определенной структуры реквизита. Прилипание к многоразовым компонентам помогает вам избежать ненужного дублирования. Вы просто передаете другой набор реквизитов каждый раз.
Используя шаблон HOC в предыдущем примере, мы переместили всю логику в HOC, и просто позволяйте базовым компонентом рендерировать пользовательский интерфейс. В результате наша Предметующий компонент стал многоразовым Поскольку он просто принимает данные как реквизиты и оказывает его на экран.
Но было бы довольно сложно повторно использовать наш HOC, так как он слишком спеденция.
const withFilterProps = BaseComponent => ({ list, side }) => { const transformedProps = list.filter(char => char.side === side) return}
Это может быть применено только в случаях, когда Список
и сторона
реквизиты присутствуют. Вы не хотите такого рода специфики в вашем приложении, поскольку вы хотите многоразовые HOCS, которые можно использовать в различных сценариях.
Давайте сделаем HOC многоразовый.
const withTransformProps = transformFunc => { const ConfiguredComponent = BaseComponent => { return baseProps => { const transformedProps = transformFunc(baseProps) return} } return ConfiguredComponent}
const renderDisplayList = ({ list }) =>{list.map(char =>)}Character: {char.name}Side: {char.side}
const FilteredList = withTransformProps( ({ list, side }) => ({ list: list.filter(FilteredListchar => char.side === side) }))(renderDisplayList)
ReactDOM.render (, document.getElementById('app'))
Этот код все еще делает то же самое, что и предыдущий пример HOC. Мы фильтруем реквизиты с использованием компонента HOC, а затем пройти их в базовый компонент. Тем не менее, старое имя вводит в заблуждение, поскольку HOC больше не ограничен только логикой фильтрации, поэтому мы переименованы в нее СтрансформProps
Отказ
Мы также больше не заботимся о структуре реквизита. Мы вновь проезжаем TransformFunc
как Функция конфигурации к СтрансформProps
Отказ Эта функция отвечает за преобразование реквизитов.
Давайте посмотрим на FilteredList
Улучшенный компонент. Это создается при прохождении функции конфигурации (ответственная за преобразование реквизита) к СтрансформProps
Отказ Мы возвращаем специализированную HOC с функцией трансформации, хранящейся внутри закрытия. Мы храним его как ConfiguredComponent
Отказ Ожидается, что Basecomponent
быть прошедшим. Когда RenderDisPlaylist
Передается к нему, мы вернемся к функциональному компоненту, который ждет прохождения реквизитов. Мы храним этот улучшенный компонент как FilteredList
Отказ
Реквизиты проходят, когда мы оказываем FilteredList
составная часть. Затем функция преобразования, которую мы прошли ранее, принимают реквизиты и фильтруют символы в соответствии со стороны. Возвращенное значение затем передается как опоры для RenderDisPlaylist
Базовый компонент, который отображает отфильтрованные символы начала войны на экран.
Однако наш синтаксис HOC довольно многословный. Нам не нужно хранить специализированную HOC как ConfiguredComponent
внутри переменной.
const withTransformProps = mapperFunc => BaseComponent => baseProps => { const transformedProps = mapperFunc(baseProps) return}
Это решение намного чище.
Идея такого подхода – это есть многоразовый HOC, который можно настроить для любого сценария В котором мы хотим что-то сделать с реквизитами, прежде чем они передаются на базовый компонент. Это мощная абстракция, не так ли?
В нашем примере мы прошли пользовательскую функцию фильтрации, которая может отличаться для каждого случая использования. И если бы мы позже решили, что мы хотим изменить некоторые из поведения HOC, нам просто нужно изменить его в единый многоразовый компонент, а не во многих различных местах нашего приложения.
const HoC = config => BaseComponent => EnhancedComponent
HOC и базовый компонент оба многоразовый и Независимый друг друга. HOC не знает, где идет его данные, и презентационный компонент понятия не имеет представления, откуда его данные.
Писать многоразовые HOCS и презентационные компоненты помогут вам избежать ненужного повторения и заставить вас писать простые компоненты. В результате вы будете писать очиститель, ремонструйную и читаемый код.
Поздравляю! К настоящему времени вы должны иметь возможность писать многоразовые компоненты высшего порядка самостоятельно.
В следующих разделах вы узнаете разницу между классом HOC и функциональным. Мы также проведем хорошее количество времени, понимая, как работает композиция нескольких компонентов высшего порядка. Все это позволит нам улучшить наши базовые компоненты еще большее поведением, которое можно легко использовать в нашем приложении.
Функциональные или классовые HOCS?
Давайте немного поговорим о разнице между функциональными HOCS и классовыми. Когда ему удобнее придерживаться первого и когда вы должны идти на последнее?
Поскольку мы хотим следовать принципам FP, мы должны использовать Функциональные компоненты как можно больше. Мы уже делаем это с презентационными компонентами, как мы видели выше. И мы должны сделать это с HOC также.
Функциональный HOC
Функциональный HOC просто обернут базовый компонент, впрыскивает его новым реквизитом вместе с оригинальными и возвращает новый компонент. Он не изменяет оригинальный компонент, изменив свой прототип в качестве классов. Мы видели такое HOC выше. Вот быстрое напоминание:
const withTransformProps = mapperFunc => BaseComponent => baseProps => { const transformedProps = mapperFunc(baseProps) return}
Этот HOC не имеет никаких побочных эффектов. Это ничего не мутирует. Это чистая функция.
При создании HOC мы должны определить его как функциональный компонент, если это возможно.
Классовые HOCS
Тем не менее, рано или поздно вам нужно получить доступ к внутренним методам состояния или жизненного цикла в вашем компоненте. Вы не можете добиться этого без классов, так как это поведение унаследовано от Ract.component , который не может быть доступен в функциональном компоненте. Итак, давайте определим HOC на основе классов.
const withSimpleState = defaultState => BaseComponent => { return class WithSimpleState extends React.Component { constructor(props) { super(props) this.state = { value: defaultState } this.updateState = this.updateState.bind(this) } updateState(value) { this.setState({ value }) } render() { return () } }}
const renderDisplayList = ({ list, stateValue, stateHandler })=> { const filteredList = list.filter(char => char.side === stateValue) const otherSide = stateValue === 'dark' ? 'light' : 'dark' return (<;button onClick={() => stateHandler(otherSide)}>Switch {filteredList.map(char =>)})}Character: {char.name}Side: {char.side}
const FilteredList = withSimpleState('dark')(renderDisplayList)
ReactDOM.render (, document.getElementById('app'))
Наш новый классовый Hoc с симулестатом
ожидает параметра конфигурации defaultstate
что довольно неясно. Это также поддерживает государство, названное ценность
и определяет обработчик событий UpdateState
Это может установить значение состояния. Наконец, он передает государственные утилиты вместе с оригинальными реквизитами к базовому компоненту.
RenderDisPlaylist
Теперь содержит логику фильтрации, которая была ранее сохранена внутри СтрансформProps
HOC, так что это больше не многоразовая.
Давайте посмотрим на FilteredList
составная часть. Во-первых, мы передаем строку конфигурации темный
к с симулестатом
И верните специализированное HOC в ожидании базового компонента. Итак, мы передаем это RenderDisPlaylist
Компонент и верните компонент класса, ожидающий прохождения реквизитов. Мы храним этот компонент как FilteredList
Отказ
В конце примера мы оказываем компонент, передавая его реквизиту. Когда это произойдет, компонент класса устанавливает состояние ценность
к темный
и передает состояние и его обработчик к RenderDisPlaylist
Компонент вместе с Список
пропры
RenderDisPlaylist
Затем фильтрует Список
опоры в соответствии с пропущенным значением состояния и устанавливает Другие
Переменная. Наконец, он отображает отфильтрованный список на экран вместе с кнопкой с прикрепленным элементом состояния. Когда кнопка нажала, состояние установлено на Другие
Переменная.
Это имеет значение?
Как вы только что видели, наш новый HOC с симулестатом
Возвращает класс вместо функционального компонента. Вы можете сказать, что это не похоже на чистая функция Так как он содержит нечистые определенное классовое поведение (состояние). Тем не менее, давайте приблизимся.
с симулестатом
не имеет побочных эффектов. Это ничего не мутирует. Это просто берет базовый компонент и возвращает новый. Хотя он содержит нечистый код, связанный с классом, сам HOC все еще является чистой функцией, поскольку «чистота функции судит снаружи, независимо от того, что происходит внутри ». Мы в основном скрываем определенный классовый неисправный код внутри чистой функции HOC.
HOC (чистая функция) позволяет нам инкапсулировать код, связанный с упорным классом внутри него.
Если вы окажетесь в ситуации, когда вы просто не можете написать функциональный компонент, поскольку вам нужно поведение, связанное с классом, оберните нежамому коду внутри HOC, что вместо этого является чистая функция, так же, как мы сделали в примере.
Что дальше?
Если вы снова проверяете наш пример, вы увидите, что у нас есть новая проблема. RenderDisPlaylist
Компонент больше не используемый, так как мы переместили логику фильтрации внутри него.
Чтобы сделать его многоразовым снова, нам нужно переместить логику обратно в СтрансформProps
HOC. Чтобы достичь этого, нам нужно выяснить, как использовать СтрансформProps
и с симулестатом
HOCS с базовым компонентом одновременно и разрешить RenderDisPlaylist
Чтобы снова нести ответственность за презентацию. Мы можем добиться этого поведения с использованием композиции.
Состав
Мы уже говорили о принципе композиции в начале. Это позволяет нам объединить несколько функций в новую составную функцию. Вот быстрое напоминание:
const number = 15const increment = num => num + 5const decrement = num => num - 3const multiply = num => num * 2
const operation = increment(decrement(multiply(number)))console.log(operation) //32
У нас есть число и три функции. Мы обертываем их всеми внутри друг друга, и мы получаем соединительную функцию, к которой мы передаем номер.
Это работает нормально. Однако читабельность может ухудшаться, если мы хотели составить еще больше функций. К счастью, мы можем определить функциональное программирование составить
функция, чтобы помочь нам. Имейте в виду, что он составит функции из Право налево Отказ
const compose = (...funcs) => value => funcs.reduceRight((acc, func) => func(acc) , value)
const number = 15const increment = num => num + 5const decrement = num =>; num - 3const multiply = num => num * 2
const funcComposition = compose( increment, decrement, multiply)
const result = funcComposition(number)console.log(result) //32
Мы больше не должны явно обернуть функции внутри друг друга. Вместо этого мы передаем их все как аргументы для составить
функция. Когда мы сделаем это, мы вернемся к новой сложной функции, ожидая ценность
аргумент, который будет передан. Мы храним его как Funccomposition
Отказ
Наконец, мы передаем Номер
Как ценность
к Funccomposition
функция. Когда это происходит, составить
проходит ценность
к Умножьте
(главная) функция. Возвращенное значение затем передается как вход в уменьшение
Функция и так далее, пока все функции в композиции не были вызваны. Мы храним окончательное значение как Результат
Отказ
Состав HOCS.
Давайте посмотрим, как мы могли бы составить
несколько HOC. Мы уже узнали, что наши многоразовые HOCS должны быть ответственны только за одну задачу. Тем не менее, что, если нам нужно было реализовать сложную логику, которая не может быть сохранена в одном HOC? Чтобы добиться этого, мы хотим быть в состоянии Объедините несколько HOC вместе и оберните их вокруг базового компонента.
Во-первых, давайте посмотрим на композицию HOC без составить
Помощник, так как легче понять, что происходит.
const withTransformProps = mapperFunc => BaseComponent => baseProps => { const transformedProps = mapperFunc(baseProps) return}
const withSimpleState = defaultState => BaseComponent => { return class WithSimpleState extends React.Component { constructor(props) { super(props) this.state = { value: defaultState } this.updateState = this.updateState.bind(this) } updateState(value) { this.setState({ value }) } render() { return () } }}
const renderDisplayList = ({ list, stateHandler, otherSide }) => ({list.map(char =>))}Character: {char.name}Side: {char.side}
const FilteredList = withTransformProps(({ list, stateValue, stateHandler }) => { const otherSide = stateValue === 'dark' ? 'light' : 'dark' return { stateHandler, otherSide, list: list.filter(char => char.side === stateValue), }})(renderDisplayList)
const ToggleableFilteredList = withSimpleState('dark')(FilteredList)
ReactDOM.render (, document.getElementById('app'))
Здесь ничего нового. Мы видели все этот код раньше. Новое, что мы составляем два HOCS – с симулестатом
который предоставляет нам государственные утилиты и СтрансформProps
что дает нам функциональность трансформации реквизитов.
У нас есть два улучшенных компонента здесь: FilteredList
и ToGGLableFilteredList
Отказ
Во-первых, мы улучшаем RenderDisPlaylist
Компонент с СтрансформProps
HOC и хранить его как FilteredList
Отказ Во-вторых, мы улучшаем новый FilteredList
Компонент с использованием с симулестатом
HOC и хранить его как ToGGLableFilteredList
Отказ
ToGGLableFilteredList
это компонент, усиленный двумя HOCS, которые были составлены вместе.
Вот подробное описание композиции HOC:
- Мы передаем функцию трансформации реквизитов к
СтрансформProps
HOC и верните специализированное HOC в ожидании прохождения базового компонента. - Мы передаем это
RenderDisPlaylist
Предметующий компонент и верните новый функциональный компонент, ожидающий аргумента реквизита. - Мы храним этот улучшенный компонент как
FilteredList
Отказ - Мы передаем
темный
Строка кс симулестатом
HOC и верните специализированное HOC в ожидании прохождения базового компонента. - Мы проходим наши улучшенные
FilteredList
Компонент в качестве базового компонента, и мы вернемся к компоненту класса, ожидая реквизита. - Мы храним это Компонент высшего порядка компонент Как
ToGGLableFilteredList
Отказ - Мы видим
ToGGLableFilteredList
Компонент, передавСписок
реквизит к этому. ToGGLableFilteredList
этоFilteredList
Компонент, усиленныйс симулестатом
HOC. Таким образом, реквизиты сначала передаются на компонент класса, который был возвращен этим HOC. Внутри его реквизиты усиливаются с состоянием и его обработчиком. Эти реквизиты вместе с оригинальными затем передаются наFilteredList
как базовый компонент.FilteredList
этоRenderDisPlaylist
Компонент, усиленныйСтрансформProps
HOC. Таким образом, реквизиты сначала передаются на функциональный компонент, который был возвращен этим HOC. Внутри этого прошлоСписок
опоры фильтруют с использованием функции преобразования. Эти реквизиты вместе с другими опорами затем передаются в базовый компонентRenderDisPlaylist
Отказ- Наконец,
RenderDisPlaylist
Компонент отображает список символов с помощью кнопки переключения на экран.
Композиция позволяет нам повысить наш базовый компонент с функциональными возможностями, агрегированными из нескольких HOCS.
В нашем примере мы передали новое поведение от с симулестатом
и СтрансформProps
HOCS к RenderDisPlaylist
Базовый компонент.
Как вы только что видели, Реквисы – единственный язык, который HOCS использует, чтобы поговорить друг с другом внутри композиции Отказ Каждый HOC выполняет определенное действие, которое приводит к улучшению или модификации объекта реквизита.
Рефакторист
Хотя наш состав HOC работает, сам синтаксис довольно многословный. Мы можем сделать это проще, избавившись от ToGGLableFilteredList
Переменная и просто оберните HOC внутри друг друга.
const FilteredList = withSimpleState('dark')( withTransformProps(({ list, stateValue, stateHandler }) => { const otherSide = stateValue === 'dark' ? 'light' : 'dark' return { stateHandler, otherSide, list: list.filter(char => char.side === stateValue), } })(renderDisplayList))
Этот код немного лучше. Однако мы все еще вручную упаковывая все компоненты. Представьте, что вы хотели добавить еще больше HOC для этой композиции. В таком случае наша композиция станет трудной для чтения и понимать. Просто представьте все эти скобки!
Использование композиции
Поскольку этот разговор о принципах FP, давайте будем использовать составить
помощник.
const compose = (...hocs) => BaseComponent => hocs.reduceRight((acc, hoc) => hoc(acc) , BaseComponent)
const enhance = compose( withSimpleState('dark'), withTransformProps(({ list, stateValue, stateHandler }) => { const otherSide = stateValue === 'dark' ? 'light' : 'dark' return { stateHandler, otherSide, list: list.filter(char => char.side === stateValue), } }))
const FilteredList = enhance(renderDisplayList)
Мы больше не должны явно обернуть HOC внутри друг друга. Вместо этого мы передаем их все как аргументы для составить
функция. Когда мы сделаем это, мы вернемся к новой сложной функции, ожидая Basecomponent
аргумент, который будет передан. Мы храним эту функцию как Усилить
Отказ Тогда мы просто проходим RenderDisPlaylist
как базовый компонент к нему и составить
сделает все компонентную упаковку для нас.
Блины снова
Я хотел бы вернуться к нашему блинчик аналогия. Раньше мы украшали наши блины только с одним ароматным слоем. Но, как мы все знаем, блины вкус намного лучше, когда вы объединяете больше ароматов вместе. Как насчет блин с расплавленным шоколадом и бананом или с кремом и карамелью? Ты знаешь о чем я говорю…
Так же, как вы можете украсить свой блин, используя один или несколько украшений в зависимости от ваших вкусов, вы можете украсить вашу презентационную компонент с одним или несколькими HOC, чтобы получить комбинацию логики, которую вы хотите для вашего конкретного использования.
Если вам нужна сложная логика для вашего презентационного компонента, вам не нужно хранить его все внутри одного компонента или в одном HOC. Вместо этого вы просто объединяете несколько простых HOC и усиливают свой презентационный компонент с ними.
Пересчитать
Пока что вы видели несколько простых HOC. Однако эта модель настолько мощна, что она использовалась во многих библиотеках на основе реагирования (таких как React-redux, React Router, пересматривает).
Я хотел бы поговорить больше о Рекомендовать библиотеку , что дает нам десятки HOCS. Он использует HOCS для всего от состояния и жизненного цикла до условного рендеринга и манипуляций реквизита.
Давайте переписываем наш пример композиции HOC, используя заранее определенные HOC от компенсации.
import { withState, mapProps, compose } from 'recompose';
const enhance = compose( withState('stateValue', 'stateHandler', 'dark'), mapProps(({ list, stateValue, stateHandler }) => { const otherSide = stateValue === 'dark' ? 'light' : 'dark' return { stateHandler, otherSide, list: list.filter(char => char.side === stateValue), } }),)
const FilteredList = enhance(renderDisplayList)
ReactDOM.render (, document.getElementById('app'))
Наши два пользовательских HOCS с симулестатом
и СтрансформProps
уже предопределены в отзыве, как С сознанием
и MapProops
Отказ Более того, библиотека также предоставляет нам предопределенную составить
функция. Итак, действительно просто просто использовать эти существующие реализации, а не определять нашего собственного.
Версия рецепсии композиции HOC не отличается от наших. Просто С сознанием
HOC теперь более многоразовый, поскольку требуется три аргумента, где вы можете установить значение по умолчанию состояния, имя состояния и имя его обработчика. MapProops
Работает так же, как наша реализация. Нам нужно только пройти функцию конфигурации.
В результате нам не нужно определять HOCS, которые предоставляют нам общее поведение.
Больше улучшений
Мы можем улучшить нашу композицию, используя даже больше, так как есть еще одна проблема, которую мы еще не обращались.
const renderDisplayList = ({ list, stateHandler, otherSide }) => ({list.map(char =>))}Character: {char.name}Side: {char.side}
Если мы проверим RenderDisPlaylist
Компонент снова, мы видим, что его функция обработчика кликов при каждом воссоздании компонента повторно отображается. И мы хотим предотвратить любое ненужное воссоздание, поскольку оно может препятствовать выполнению нашей заявки. К счастью, мы можем добавить с хандлеров
HOC для нашей композиции для решения этой проблемы.
import { withState, mapProps, withHandlers, compose } from 'recompose';
const renderDisplayList = ({ list, handleSetState }) => ({list.map(char =>))}Character: {char.name}Side: {char.side}
const enhance = compose( withState('stateValue', 'stateHandler', 'dark'), mapProps(({ list, stateValue, stateHandler }) => { const otherSide = stateValue === 'dark' ? 'light' : 'dark' return { stateHandler, otherSide, list: list.filter(char => char.side === stateValue), } }), withHandlers({ handleSetState: ({ stateHandler, otherSide }) => () => stateHandler(otherSide) }))
const FilteredList = enhance(renderDisplayList)
ReactDOM.render (, document.getElementById('app'))
с хандлеров
HOC принимает объект функций в качестве аргумента конфигурации. В нашем примере мы проходим объект с одной функцией Ручная смолаеттация
Отказ Когда это произойдет, мы вернемся на HOC, ожидая базовый компонент и пропустить реквизиты. Когда мы пройдем их, внешняя функция в каждом клавише пропущенного объекта принимает объект реквизита как аргумент.
В нашем случае Ручная смолаеттация
Функция получает Государственный анхендр
и Другие
реквизит. Мы вернем новую функцию, которая затем вводится в реквизиты и передается до RenderDisPlaylist
составная часть.
Ручная смолаеттация
Затем привязан к кнопке таким образом, чтобы он не требует своего воссоздания во время повторного рендеринга каждого компонента, поскольку с хандлеров
Убедится в том, что идентичность его обработчиков сохранилась через рендеры. В результате обработчики воссозданы Только Когда реквизиты прошли к с хандлеров
менять.
Конечно, возможное воссоздание нашей простой функции обработчика Click не мешает большому характеристикам. с хандлеров
гораздо полезнее, когда вам нужно оптимизировать более высокое количество сложных обработчиков.
Это также означает, что это хорошее место для хранения всех обработчиков, используемых внутри вашего презентационного компонента. Таким образом, это сразу очевидно для тех, кто смотрит на ваш компонент, какие обработчики используются внутри него. В результате разработчик довольно просто добавить или удалить определенный обработчик. Это намного лучше, чем искать все обработчики внутри компонента вручную.
Предоставляя нам многие многоразовые HOCS, компенсируют композицию HOC и использование HOCS вообще гораздо проще, поскольку нам не нужно писать все HOCS сами.
В приложениях Real-World вы не будете использовать эти предопределенные HOC часто, так как они охватывают наиболее типичные случаи использования. И в том случае, если вам нужна определенная логика, которая должна быть передана несколькими компонентами, вы сами определяете HOC.
Заключение
Благодаря принципам функционального программирования мы смогли преобразовать этот не многоразовый огромный компонент с самого начала …
class FilteredList extends React.Component { constructor(props) { super(props) this.state = { value: this.props.defaultState } } updateState(value) { this.setState({ value }) } render() { const otherSide = this.state.value === 'dark' ? 'light' : 'dark' const transformedProps = this.props.list.filter(char => char.side === this.state.value) return ({transformedProps.map(char =>) }})}Character: {char.name}Side: {char.side}
ReactDOM.render (, document.getElementById('app'))
… в этот многоразовый, читаемый и ремонторуемый компонент компонент.
import { withState, mapProps, withHandlers, compose } from 'recompose';
const renderDisplayList = ({ list, handleSetState }) => ({list.map(char =>))}Character: {char.name}Side: {char.side}
const enhance = compose( withState('stateValue', 'stateHandler', 'dark'), mapProps(({ list, stateValue, stateHandler }) => { const otherSide = stateValue === 'dark' ? 'light' : 'dark' return { stateHandler, otherSide, list: list.filter(char => char.side === stateValue), } }), withHandlers({ handleSetState: ({ stateHandler, otherSide }) => () => stateHandler(otherSide) }))
const FilteredList = enhance(renderDisplayList)
ReactDOM.render (, document.getElementById('app'))
Мы используем эти принципы во время разработки приложений довольно часто. Наша цель – максимально использовать простые многоразовые компоненты. Узор HOC помогает нам достичь этого, поскольку его идея состоит в том, чтобы переместить логику в HOC и позволить презентационному функциональному компоненту позаботиться о рендеринге UI. В результате нам больше не нужно использовать классы для наших презентационных компонентов, только для HOCS, если нам нужно поведение в классе.
В результате наше приложение состоит из куча презентационных компонентов, которые мы можем повторно использовать в нашем приложении, и мы можем улучшить их, используя одну или несколько многоразовых HOC, чтобы получить логику, нам нужно для определенного сценария (например, выделенный HOC для Получение данных).
Классная функция о нашем подходе состоит в том, что, если вы взглянуте на определенную композицию HOC, вы сразу же знаете, какую логику использует логику. Вам просто нужно проверить составить
Функция, где вы можете увидеть всю логику, содержащуюся в HOCS. Если вы решите добавить больше логики, вы просто вставляете новый HOC в составить
функция. Кроме того, если вы хотите увидеть, какие обработчики используют компонент, вам просто нужно проверить с хандлеров
HOC.
Еще одна прохладная вещь о HOCS – это то, что они не привязаны к реакции. Это означает, что вы можете использовать их в ваших других приложениях, которые не были написаны в реакции.
Поздравляю! Ты сделал это.
Если вам понравилась эта статья, дайте ему несколько хлопов Отказ Я бы очень признателен за это, и больше людей смогут также увидеть этот пост.
Этот пост был Первоначально опубликовано в моем блоге Отказ
Если у вас есть какие-либо вопросы, критика, наблюдения или советы по улучшению, не стесняйтесь написать комментарий ниже или добраться до меня через Twitter Отказ
Дэвид Копал (@coding_lawyer) | Twitter Последние твиты из Дэвида Копала (@coding_lawyer). Страстный программист, докладчик, бывший адвокат, любовь, чтобы узнать новый … twitter.com.