Создание контекста хука с использованием обобщений внутри функционального компонента - PullRequest
1 голос
/ 28 апреля 2020

У меня есть компонент Application, который может получить объект темы с реквизитом. Затем тема сохраняется в контексте с помощью ловушки React.createContext:

import React from 'react'


export interface Theme {
  colors: string[]
  images: string[]
}

interface ThemeContextParam {
  theme: Theme
  setTheme: (newTheme: Theme) => void
}

interface Props {
  theme: Theme
}

// C O N T E X T
export const ThemeContext = React.createContext<ThemeContextParam>({} as ThemeContextParam)

// C O M P O N E N T
const Application: React.FunctionComponent<Props> = (props) => {
  const {
    theme: propsTheme,
    children
  } = props

  const [themeState, setThemeState] = React.useState<Theme>(propsTheme)

  const themeContextProviderValue = {
    theme: themeState,
    setTheme: setThemeState
  }

  return (
    <ThemeContext.Provider value={themeContextProviderValue}>
      {children}
    </ThemeContext.Provider>
  )
}

export default Application

Я инициализирую контекст, вызывая компонент Application:

// C O M P O N E N T
const Theme = (): JSX.Element => {
  return (
    <Application theme={myTheme}>
      <App />
    </Application>
  )
}

Так что я могу использовать такой контекст:

import { ThemeContext } from '../Application'

// C O M P O N E N T
const App = (): JSX.Element => {
  const { theme } = React.useContext(ThemeContext)
  ...

Но теперь я хочу, чтобы мой Theme был обобщенным c, чтобы разработчики могли хранить в контексте все, что они хотят, а не просто объект {colors: string[], images: string[]}. Обобщение c будет передано в компонент Application следующим образом:

<Application<CustomThemeType> theme={myTheme}>

Поэтому я реализую типы обобщений в Application:

import React from 'react'

// I N T E R F A C E S
export interface Theme {
  colors: string[]
  images: string[]
}

interface ThemeContextParam<T extends Theme = Theme> {
  theme: T,
  setTheme: (newTheme: T) => void
}

interface Props<T extends Theme> {
  theme: T
}

// C O N T E X T
export const ThemeContext = React.createContext<ThemeContextParam>({} as ThemeContextParam)

// C O M P O N E N T
const Application = <T extends Theme>(props: Props<T> & { children?: React.ReactNode }): JSX.Element => {
  const {
    theme: propsTheme
    children
  } = props

  const [themeState, setThemeState] = React.useState<T>(propsTheme)

  const themeContextProviderValue = {
    theme: themeState,
    setTheme: setThemeState
  }

  return (
    <ThemeContext.Provider value={themeContextProviderValue}>
      {children}
    </ThemeContext.Provider>
  )
}

Но, как вы можете см. контекст ThemeContext не обрабатывает тип generi c. Если я хочу обработать шаблон c, мне нужно создать его экземпляр в самом компоненте следующим образом:

const Application = <T extends Theme>(props: Props<T> & { children?: React.ReactNode }): JSX.Element => {
  const ThemeContext = React.createContext<ThemeContextParam<T>>({} as ThemeContextParam<T>)

Но в этом случае я не могу export мой ThemeContext.

Итак, кто-то знает, как я могу создать этот контекст, используя обобщенный тип c и экспортировать его?

Ответы [ 2 ]

2 голосов
/ 28 апреля 2020

Я не пробовал (так как вы не предоставили CodeSandbox), но не мог создать функцию-обертку, которая принимает тип c общего типа, чтобы инициализировать вашу контекстную среду и вернуть как компонент, так и context:

function createThemeContext<T extends Theme>() {
  // C O N T E X T
  const ThemeContext = React.createContext<ThemeContextParam<T>>({} as ThemeContextParam<T>)

  // C O M P O N E N T
  const Application = (props: Props<T> & { children?: React.ReactNode }): JSX.Element => {
     ...
  }

  return {
    ThemeContext,
    Application
  };
}

...

const { ThemeContext, Application } = createThemeContext<YourTheme>();

Немного умного рассуждения: я думаю, что общий c контекст является признаком чрезмерного абстрагирования и редко дает хорошую ценность. Я бы предпочел иметь фиксированный интерфейс для определения состояния контекста, поэтому каждый знает, что этот контекст должен делать.

Кроме того, я бы настоятельно рекомендовал возвращать пользовательский хук вместо самого контекста, поэтому использование ясно, что-то вроде:

function useThemeContext() {
  const context = useContext(ThemeContext);
  if (context === undefined) {
    throw new Error(
      'useThemeContext must be used within a ThemeContext provider!'
    );
  }
  return context;
}

Настоятельно рекомендую Кент C. сообщение в блоге Доддса для управления контекстом .

1 голос
/ 28 апреля 2020

Использование, которое вы описали, технически невозможно. Однако у меня есть обходное решение для вашего варианта использования.

Предположим, что ваш ThemeContext предназначен для глобального контекста, а <Application /> - это одиночный объект, который не будет дважды создан в одном приложении, тогда этот обходной путь применяется :

НЕ делайте приложение универсальным c, вместо этого вы экспортируете пустой Theme интерфейс для разработчиков в augment .

/* your module 'johannchopin-awesome-module' */
export interface Theme {}


/* dev's consuming module 'foo' */
import { Application, ThemeContext } from 'johannchopin-awesome-module'

declare module 'johannchopin-awesome-module' {
  interface Theme {
    /* they can fill in whatever fields here */
    foo: string
  }

   // OR:

   interface Theme extends UserDefinedTheme {}
}

const context = React.useContext(ThemeContext)
context.theme.foo // <- they're able to see a 'string' here

Этот обходной путь также используется react-redux lib. Ищите DefaultRootState в исходном коде .

Я установил кодовую коробку, чтобы продемонстрировать использование:

Edit distracted-sanderson-5jj8z


Небольшое объяснение того, почему описанное вами использование невозможно архивировать.

Логически и <Application />, и любое <DownStreamConsumerComponet /> зависит от ThemeContext. Так что у них нет права голоса, как должен вести себя ThemeContext, этот контекст - главный. Если вы передадите дженерик c какой-либо стороне, то в первую очередь эта сторона должна быть ThemeContext.

Однако этого также нельзя сделать из-за того, как работает контекст реагирования. Так что мой обходной путь - это в основном «передача generi c» в ThemeContext с использованием техники слияния деклараций.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...