Привет, товарищи-разработчики,
Я вернулся к другой интересной теме в 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 во всех этих компонентах, что, очевидно, является перегрузкой.
Общий код в этих компонентах:
- Импортировать
SnackBar
компонент - Инициализировать переменную состояния
showSuccess
значением false - Установите для переменной состояния
showSuccess
значение true (при некоторых действиях пользователя - конечно, это отличается в разных компонентах) - В части рендеринга, если
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} /> </> ) }