Компоненти вищого порядку в React для початківців

8 хв. читання

Передмова

Я пишу це, тому що кожна інша стаття, включаючи офіційну документацію React про компоненти вищого порядку (Higher-Order Components), спантеличила мене як початківця. Я зрозумів, що компоненти вищого порядку є чимось потрібним, але не розумів, наскільки вони корисні. Ця стаття спрямована на те, щоб прояснити плутанину щодо компонентів вищого порядку (HOC). Перш ніж ми зможемо зрозуміти HOC, потрібно зрозуміти деякі речі про функції у Javascript.

Короткий вступ до стрілкових функцій ES6

У цій статті наведено лише приклади використання стрілкових функцій ES6. Якщо ви раніше ніколи не бачили стрілкових функцій, вони, по суті, еквівалентні звичайним функціям. Наведений нижче код показує відмінності між звичайними функціями та стрілковими.

function () {
  return 42
}

// те ж що:
() => 42

// те ж що:
() => {
  return 42
}

function person(name) {
  return { name: name }
}

// те ж що:
(name) => {
  return { name: name }
}

Для більш повного розуміння, прочитайте документацію про стрілкові функції на MDN.

Функції як значення та часткове застосування

Так само як числа, рядки, булеві значення і т. д., функції є значеннями. Це означає, що ви можете передавати функції, як і будь-які інші дані. Ви можете передавати функцію як аргумент іншої функції:

const execute = (someFunction) => someFunction()

execute(() => alert('Executed'))

І можете повертати функцію з функції.

const getOne = () => () => 1

getOne()()

Причина того, що у нас є дві пари дужок після getOne, полягає в тому, що перше застосування функції повертає іншу функцію. Для ілюстрації:

const getOne = () => () => 1

getOne
//=> () => () => 1

getOne()
//=> () => 1

getOne()()
//=> 1

Корисним моментом у поверненні функції з функції є те, що можна писати функції, які відслідковують початкові вхідні дані. Наприклад, функція нижче, отримує аргументом число і повертає функцію, яка множить цей аргумент на інший:

const multiply = (x) => (y) => x * y

multiply(5)(20)

Цей приклад працює так само як і getOne, кожна пара дужок застосовує деякі вхідні дані до функції. У цьому випадку для x присвоюється 5, а для y20.

const multiply = (x) => (y) => x * y

multiply
//=> (x) => (y) => x * y

multiply(5)
//=> (y) => 5 * y

multiply(5)(20)
//=> 5 * 20

Коли ми викликаємо функцію multiply лише з одним аргументом, ми частково застосовуємо функцію. Коли ми викликаємо multiply(5), то отримуємо функцію, яка множить її вхідні дані на 5. Якщо викликаємо multiply(7), то відбувається множення на 7 і так далі. Ми можемо використовувати часткове застосування для створення нових функцій із заздалегідь заданими вхідними даними:

const multiply = (x) => (y) => x * y

const multiplyByFive = multiply(5)
const multiplyBy100 = multiply(100)

multiplyByFive(20)
//=> 100
multiply(5)(20)
//=> 100

multiplyBy100(5)
//=> 500
multiply(100)(5)
//=> 500

Спочатку це може здатися не надто корисним. Тим не менш, ви можете використовувати часткове застосування для написання коду, який простіше читати та розуміти. Наприклад, ми можемо замінити синтаксис інтерполяції складної функції styled-components чимось набагато простішим.

// до
const Button = styled.button`
  background-color: ${({ theme }) => theme.bgColor}
  color: ${({ theme }) => theme.textColor}
`

<Button theme={themes.primary}>Submit</Button>
// після
const fromTheme = (prop) => ({ theme }) => theme[prop]

const Button = styled.button`
  background-color: ${fromTheme("bgColor")}
  color: ${fromTheme("textColor")}
`

<Button theme={themes.primary}>Submit</Button>

Ми створюємо функцію, яка приймає строку як параметр: fromTheme ("textColor"), який повертає функцію, що приймає об'єкт з властивістю theme: ({theme}) => theme [prop], яку ми потім намагаємося знайти через початковий рядок, який ми передали в "textColor". Ми могли б піти далі і написати такі функції, як backgroundColor та textColor, які частково застосовують функцію fromTheme:

const fromTheme = (prop) => ({ theme }) => theme[prop]
const backgroundColor = fromTheme("bgColor")
const textColor = fromTheme("textColor")

const Button = styled.button`
  background-color: ${backgroundColor}
  color: ${textColor}
`

Функції вищого порядку

Функції вищого порядку – функції, які приймають функцію як аргумент. Ви, можливо, вже знайомі з функціями вищого порядку, якщо ви коли-небудь використовували таку функцію, як map. Якщо ви не знайомі з map, це функція, яка проходить масив і застосовує функцію до кожного його елементу. Наприклад, ви можете піднести до квадрата масив чисел так:

const square = (x) => x * x

[1, 2, 3].map(square)
//=> [ 1, 4, 9 ]

Ми можемо написати власну версію map, щоб проілюструвати цю концепцію:

const map = (fn, array) => {
  const mappedArray = []

  for (let i = 0; i < array.length; i++) {
    mappedArray.push(
      // застосування fn до поточного елементу масиву
      fn(array[i])
    )
  }

  return mappedArray
}

Тепер можна використати нашу функцію map, щоб піднести масив чисел до квадрата:

const square = (x) => x * x

console.log(map(square, [1, 2, 3, 4, 5]))
//=> [ 1, 4, 9, 16, 25 ]

Або щоб повернути масив елементів <li> в React:

const HeroList = ({ heroes }) => (
  <ul>
    {map((hero) => (
      <li key={hero}>{hero}</li>
    ), heroes)}
  </ul>
)

<HeroList heroes=[
  "Wonder Woman",
  "Black Widow",
  "Spider Man",
  "Storm",
  "Deadpool"
]/>
/*=> (
  <ul>
    <li>Wonder Woman</li>
    <li>Black Widow</li>
    <li>Spider Man</li>
    <li>Storm</li>
    <li>Deadpool</li>
  </ul>
)*/

Компоненти вищого порядку

Ми знаємо, що функція вищого порядку – це функція, яка приймає функцію як аргумент. У React будь-яка функція, яка повертає JSX, відома як функціональний компонент(Stateless Functional Component). Базовий функціональний компонент виглядає так:

const Title = (props) => <h1>{props.children}</h1>

<Title>Higher-Order Components(HOCs) for React Newbies</Title>
//=> <h1>Higher-Order Components(HOCs) for React Newbies</h1>

Компонент вищого порядку – це функція, що отримує компонент як аргумент і повертає компонент. Як ви використаєте переданий компонент, повністю залежиться від вас. Ви навіть можете повністю ігнорувати його:

const ignore = (anything) => (props) => <h1>:)</h1>

const IgnoreHeroList = ignore(HeroList)
<IgnoreHeroList />
//=> <h1>:)</h1>

Ви можете написати HOC, який трансформує вхідні дані до верхнього регістру:

const yell = (PassedComponent) =>
  ({ children, ...props }) =>
    <PassedComponent {...props}>
      {children.toUpperCase()}!
    </PassedComponent>

const Title = (props) => <h1>{props.children}</h1>
const AngryTitle = yell(Title)

<AngryTitle>Whatever</AngryTitle>
//=> <h1>WHATEVER!</h1>

Ви також можете повернути Stateful компонент, оскільки класи в Javascript є синтаксичним цукром для функцій. Це дозволяє використовувати методи життєвого циклу React, такі як componentDidMount. Саме тут HOC стають дійсно корисними. Тепер ми можемо робити такі речі, як передача результату HTTP запиту аргументом у функціональний компонент.

const withGists = (PassedComponent) =>
  class WithGists extends React.Component {
    state = {
      gists: []
    }

    componentDidMount() {
      fetch("https://api.github.com/gists/public")
      .then((r) => r.json())
      .then((gists) => this.setState({
        gists: gists
      }))
    }

    render() {
      return (
        <PassedComponent
          {...this.props}
          gists={this.state.gists}
        />
      )
    }
  }


const Gists = ({ gists }) => (
  <pre>{JSON.stringify(gists, null, 2)}</pre>
)

const GistsList = withGists(Gists)

<GistsList />
//=> До завершення api запиту:
// <Gists gists={[]} />
// 
//=> Після завершення api запиту:
// <Gists gists={[
//  { /* … */ },
//  { /* … */ },
//  { /* … */ }
// ]} />

Ви можете викликати withGists з будь-яким компонентом і вона передасть далі результат виклику gists api. Більш деталізований приклад цього можна побачити тут.

Висновок

Redux використовує HOC connect для передачі значень зі сховища вашого додатку до «з'єднаних» компонентів. Він також виконує перевірку помилок і оптимізацію життєвого циклу компоненту, що у випадку написання власноруч призвело б до тон шаблонного коду.

Якщо ви виявили, що пишете багато коду у різних місцях, який робить те ж саме, ви можете переробити цей код у багаторазовий HOC. HOC насправді ефективні і з ними можна створити багато цікавих речей.

Намагайтесь залишати свої HOC простими і писати код, для розуміння якого, не потрібно читати довгу статтю.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.6K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація