В этой статье предполагается, что у вас есть базовые знания о JavaScript и React.
- Реакция:
Higher Order Component(HOC)
– это функция, которая принимает реакцию КОМПОНЕНТ какparam
и возвращает КОМПОНЕНТ. - JavaScript:
Higher Order Function(HOF)
– это функция, которая принимает ФУНКЦИЮ в качестве параметра, ИЛИ возвращает ФУНКЦИЮ.
export function withThemeContext(ComponentToWrap: React.FC): React.FC { return function Wrapper(props): JSX.Element { const [themeType, setThemeType] = useState<ThemeType>('light'); return ( <ThemeContext.Provider value={{ themeType, setThemeType}}> <ComponentToWrap {...props} /> </ThemeContext.Provider> ); }; }
В этом фрагменте кода упаковано довольно много концепций, мы постараемся их разобрать.
Итак, чтобы понять React HOC, вам нужно хорошо разбираться в функциях высшего порядка JavaScript, потому что HOC — это всего лишь разновидность HOF. Я рекомендую прочитать эту статью, чтобы понять, что компоненты React Function по сути являются функциями JavaScript.
withThemeContext
: это функция, которая принимает Функция( React.FC
) в качестве параметра и возвращает другую Функция<. /em>(который называется Wrapper
типа React.FC
).
Функция, возвращаемая из withThemeContext
, также является функцией. Эта функция оборачивает входной компонент с помощью ThemeContext
, чтобы мы могли получить доступ к теме внутри компонента и его дочерних компонентов. Эта статья не о том, как работает контекст React, если вы не знакомы с ним, просто представьте, что этот withThemeContext
HOC предоставляет некоторую функцию компоненту Wrapped.
Применение
function MyComponent() { // my component logic } const MyComponentWithThemeContext = withThemeContext(MyComponent); // Now MyComponentWithThemeContext with the new Wrapper component returned from // withThemeContext function // app.tsx function App(): JSX.Element { return <MyComponentWithThemeContext /> } // index.ts ReactDOM.render(<App/>, document.getElementById('app'));
Вы можете спросить, почему бы нам не добавить логику оболочки в сам MyComponent
, это возможно. Однако представьте, что в вашем приложении слишком много такой логики, и обработка их в одном компоненте очень быстро усложняется. При таком подходе у HOC есть только одна цель, в этом конкретном примере withThemeContext
несет только одну ответственность за управление темой, которая придерживается принципа дизайна Single Responsibility.
Чтобы лучше понять важность этого шаблона, давайте предположим, что вы хотите добавить ErrorBoundary в дерево компонентов для обработки любых необработанных исключений в вашем приложении. Что бы вы сделали? Если вы измените какой-либо из этих компонентов, НЕТ. Вот где HOC становится удобным, иногда вы можете добавить больше функций в свое приложение, даже не касаясь ни одного из существующих компонентов, React Error Boundary — один из хороших примеров. У меня есть статья на эту тему здесь, если интересно.
export function withErrorBoundary(ComponentToWrap: React.FC): React.FC { return function Wrapper(props): JSX.Element { return <ErrorBoundary> <ComponentToWrap {...props} /> </ErrorBoundary>; }; } const CompWithThemeContext = withThemeContext(MyComponent); const CompWithErrorBoundary = withErrorBoundary(ComponentWithThemeContext); // did you notice how the output of the first hoc passed to the other hoc // this mean we are able to compose independent features together to build up application logic // DECOMPOSITION then COMPOSITION = FLEXIBILITY + REUSABILITY + MANAGABILITY // let's say now you have one more hoc, // just implement it independently and follow the same pattern. like so const CompWithAnotherFeature = withAnotherFeature(MyComponent); const CompWithThemeContext = withThemeContext(CompWithAnotherFeature); const CompWithErrorBoundary = withErrorBoundary(ComponentWithThemeContext); // the above three lines are equivalent to the following const Component = withErrorBoundary(withThemeContext(withAnotherFeature(MyComponent)));
Собираем все вместе
// types.ts export type ThemeType = 'dark' | 'light'; export interface IThemeContext { themeType: ThemeType; setThemeType: (themeType: ThemeType) => void; } // themeContext.ts import { IThemeContext, ThemeType } from './types'; import React from 'react'; // create a reat context of type IThemeContext const ThemeContext = React.createContext<IThemeContext>({} as IThemeContext); // withThemeContext.tsx export function withThemeContext(ComponentToWrap: React.FC): React.FC { return function Wrapper(props): JSX.Element { const [themeType, setThemeType] = useState<ThemeType>('light'); return ( <ThemeContext.Provider value={{ themeType, setThemeType}}> <ComponentToWrap {...props} /> </ThemeContext.Provider> ); }; } import React, { useContext } from 'react'; import { ThemeContext } from './themeContext'; // This button provide the user to choose a theme function ChangeThemeButton() { const { setThemeType } = useContext(ThemeContext); const onDarkClick = useCallback(() => { setThemeType('dark') }, []); const onLightClick = useCallback(() => { setThemeType('light') }, []); return ( <div> <button onClick={onLightClick}>Change to light</button> <button onClick={onDarkClick}>Change to dark</button> </div> ); } // withErrorBoundary.ts export function withErrorBoundary(ComponentToWrap: React.FC): React.FC { return function Wrapper(props): JSX.Element { return <ErrorBoundary> // please check my other article for ErrorBoundary <ComponentToWrap {...props} /> </ErrorBoundary>; }; } // myComposer.ts const CompWithAnotherFeature = withAnotherFeature(MyComponent); const CompWithThemeContext = withThemeContext(CompWithAnotherFeature); const CompWithErrorBoundary = withErrorBoundary(ComponentWithThemeContext); export { CompWithErrorBoundary as MyComponent }; // just renaming before exporting // app.tsx import React from 'react'; import { MyComponent } from './myComposer'; // please note this is the renamed component export function App() { return <MyComponent /> } // index.ts import ReactDOM from 'react-dom'; ReactDOM.render(<App/>, document.getElementById('app'));
Заключение
В этой статье мы обсуждали в основном HOC, однако, чтобы сделать сценарий более практичным, я использовал некоторые другие концепции, с которыми вы, возможно, не знакомы. то есть React Context API, хуки React, которые будут обсуждаться на других сессиях. А еще есть интересная тема про HOC vs Custom hooks. В большинстве случаев Custom Hooks могут заменить HOC гораздо лучше, но не полностью.