Привет, товарищи-разработчики,

Я вернулся к другой интересной теме в ReactJS, которая в основном неизвестна или мало используется, но имеет большие гибкости на ходу.

И да, как сказано в заголовке, это «Render Props» в ReactJS.

Термин рендеринг опоры относится к методике совместного использования кода между компонентами React с использованием опоры, значение которой является функцией.

Как указано в определении, несколько компонентов, использующих один и тот же блок кода, могут быть реорганизованы с помощью гибкости Render Props, чтобы один и тот же блок кода можно было переместить в общий родительский компонент и отобразить несколько компонентов под этим родительским компонентом, чтобы все реагирующие компоненты по-прежнему работали в одном Таким образом, объем блока кода все еще не поврежден.

Хм, это звучит как HOC, но имеет больше гибкости, так что один родительский компонент с гибкостью Render Props может обернуть как можно больше дочерних компонентов, даже не зная, что будут делать дочерние компоненты.

Определение простое, но определение без какого-либо примера - это всего лишь теория и может показаться скучной темой. Теперь перейдем к практической части.

Пример использования. Нам необходимо показать пользователю уведомление о любом действии, которое он выполняет на странице в любом разделе. Предположим, у нас есть два компонента: WishList и UserCart, которые находятся на странице, требующей действий пользователя, и нам нужно показать пользователю уведомление об успешном выполнении их действий.

WishList.js

import * as React from 'react'
import wishlist from 'wishlist.svg'
import {Snackbar} from '@material-ui/core'
const WishList = (props) => {
  const [
    showSuccess,
    setShowSuccess] = React.useState(false)
  
  const addToWishList = () => {
    // some backend action to complete
    setShowSuccess(true)
  }
  return (
    <>
      <img src={wishlist} alt="WishList" onClick={addToWishList}/>
      <SnackBar 
        open={showSuccess} 
        message="Added to wishlist"
      />
    </>
  )
}

UserCart.js

import * as React from 'react'
import userCart from 'userCart.svg'
import {Snackbar} from '@material-ui/core'
const UserCart = (props) => {
  const [
    showSuccess,
    setShowSuccess] = React.useState(false)
  
  const addToCart = () => {
    // some backend action to complete
    setShowSuccess(true)
  }
return (
    <>
      <img src={userCart} alt="UserCart" onClick={addToCart}/>
      <SnackBar 
        open={showSuccess} 
        message="Added to cart"
      />
    </>
  )
}

Как вы видите выше, мы можем проверить, что и UserCart.js, и WishList.js, оба имеют внутренний метод для сохранения данных, а при возврате отображают сообщение об успешном выполнении соответствующего действия пользователя. Это всего лишь два компонента, поэтому мы можем продолжить сам код выше. Представьте, что у вас есть больше компонентов, которые требуют действий пользователя, и в этом случае вам нужно вызвать компонент SnackBar из material-ui во всех этих компонентах, что, очевидно, является перегрузкой.

Общий код в этих компонентах:

  1. Импортировать SnackBar компонент
  2. Инициализировать переменную состояния showSuccess значением false
  3. Установите для переменной состояния showSuccess значение true (при некоторых действиях пользователя - конечно, это отличается в разных компонентах)
  4. В части рендеринга, если showSuccess истинно, рендеринг компонента SnackBar с настраиваемым сообщением об успешном выполнении.

Теперь мы определили общий код, так почему бы не переместить приведенный выше список операторов как функцию в общий компонент, например, SuccessHandler.js в качестве родительского компонента и сделать WishList.js и UserCart.js дочерними и вызвать функцию из дочерних компонентов.

Хм, это звучит так, как будто у папы есть шоколадные конфеты, а дети, которые отлично выполнили свой долг, получат один шоколад от папы за успешное выполнение своих обязанностей. Ооооо…. так мило, не правда ли.

Теперь посмотрим, как реализовать SuccessHandler.js.

SuccessHandler.js

import * as React from 'react'
import {Snackbar} from '@material-ui/core'
const SuccessHandler = (props) => {
  const [
    showSuccess,
    setShowSuccess] = React.useState(false)
  let successMessage = ''
  const handleSuccess = (successMessageFromChild) => {
    // assign the custom successMessage to successMessage variable
    successMessage = successMessageFromChild
    setShowSuccess(true)
  }
return (
    <>
      <SnackBar 
        open={showSuccess} 
        message={successMessage}
      />
    </>
  )
}

Теперь мы переместили нашу общую базу кода в SuccessHandler.js, нам нужно еще раз вернуться к тому, как теперь будут написаны WishList.js и UserCart.js.

WishList.js

import * as React from 'react'
import wishlist from 'wishlist.svg'
const WishList = (props) => {
  const [
    showSuccess,
    setShowSuccess] = React.useState(false)
  
  const {handleSuccess} = props
  const addToWishList = () => {
    // some backend action to complete
    handleSuccess('Added to wishlist')
  }
return (
    <>
      <img src={wishlist} alt="WishList" onClick={addToWishList}/>
    </>
  )
}

UserCart.js

import * as React from 'react'
import userCart from 'userCart.svg'
const UserCart = (props) => {
  const [
    showSuccess,
    setShowSuccess] = React.useState(false)
  
  const {handleSuccess} = props
  
  const addToCart = () => {
    // some backend action to complete
    handleSuccess('Added to cart')
  }
 return (
    <>
      <img src={userCart} alt="UserCart" onClick={addToCart}/>
    </>
  )
}

Хм звучит интересно. Компонент SnackBar отсутствует в WishList.js и UserCart.js, а в props из ниоткуда появился новый метод handleSuccess. Но с другой стороны в SuccessHandler.js используется метод handleSuccess. Теперь давайте подумаем, как мы можем связать все эти три компонента вместе, чтобы метод handleSuccess не мог быть неожиданным, а компоненты знали, как его реальное использование.

Ах. У нас есть ответ, и это Render-Props. Ура Render-Props приходит нам на помощь. Давайте посмотрим на компонент страницы Product.js, чтобы узнать, как мы можем использовать Render-Props для решения указанной выше проблемы.

Блок кода в Product.js

<SuccessHandler 
  render={(handleResponse) => (
    <>
      <WishList handleResponse={handleResponse}/>
      <UserCart handleResponse={handleResponse}/>  
   </>
  )}
/>

Значит доказано. Но подождите ... Откуда мы взяли атрибут render (другими словами - props) в компоненте SuccessHandler. Это заставит нас вернуться к SuccessHanlder.js, чтобы немного реструктурировать. (Но не о чем беспокоиться - совсем немного)

SuccessHandler.js

import * as React from 'react'
import {Snackbar} from '@material-ui/core'
const SuccessHandler = (props) => {
  const [
    showSuccess,
    setShowSuccess] = React.useState(false)
let successMessage = ''
const handleSuccess = (successMessageFromChild) => {
    // assign the custom successMessage to successMessage variable
    successMessage = successMessageFromChild
    setShowSuccess(true)
  }
return (
    <>
      {props.render(handleResponse)} // here we use render :)
      <SnackBar 
        open={showSuccess} 
        message={successMessage}
      />
    </>
  )
}

Теперь это имеет смысл. Итак, чтобы объяснить немного больше, давайте перейдем к Product.js

Блок кода в Product.js

<SuccessHandler 
  render={(handleResponse) => (
    <>
      <WishList handleResponse={handleResponse}/>
      <UserCart handleResponse={handleResponse}/>  
   </>
  )}
/>

SuccessHandler.js оборачивает компоненты WishList и UserCart функцией handleResponse, которая передается в качестве свойств дочерним компонентам, и отображает их с помощью функции render. Таким образом, компонент SuccessHandler не должен знать о том, что делают его дочерние элементы, а просто передает им сообщение successMessage, когда кто-либо из них успешно выполнил свой долг.

Следовательно, доказано. Как обычно, Тадаааа …… Лайки и комментарии очень благодарны :)

Более аккуратный SuccessHandler.js может быть таким, как показано ниже

import * as React from 'react'
import {Snackbar} from '@material-ui/core'
const ACTION_TYPES = {
  SET_SUCCESS: 'SET_SUCCESS'
}
const reducer = (state, action) => {
  switch(action.type) {
    case ACTION_TYPES.SET_SUCCESS: 
      return {
        ...state,
        showSuccess: true,
        successMessage: action.successMessage
     }
     default:
       return state
  }
}
const initialState = {
  showSuccess: false,
  successMessage: '',
}
const SuccessHandler = (props) => {
  const [state, dispatch] = React.useReducer(reducer, intialState)
  const {showSuccess, successMessage} = state
  const handleSuccess = (successMessage) => {
    // assign the custom successMessage to successMessage variable
    dispatch({
      successMessage,
      type: ACTION_TYPES.SET_SUCCESS
    })
  }
return (
    <>
      {props.render(handleResponse)} // here we use render :)
      <SnackBar 
        open={showSuccess} 
        message={successMessage}
      />
    </>
  )
}