Создать повторно используемый соединитель Redux (с теми же mapState и mapDispatch) — возникли проблемы с TypeScript

#typescript #redux #react-redux #typescript-typings #higher-order-functions

#typescript #redux #реагировать-redux #typescript-типизации #функции более высокого порядка

Вопрос:

Вот компонент, который при желании может отображать либо строку (не будучи подключенным к хранилищу Redux), либо поле выбора.

 export function Content(props: ContentProps) {
  const { language } = props;

  if (!props.setLanguage) {
    return (
      <div>{language}</div>
    );
  }

  return (
    <Select
      language={language}
      languages={languages}
      onChange={e => {
        props.setLanguage!(e.target.value);
      }}
    />
  );
}
  

Этот код работает отлично

 const mapStateToProps = (state: RootState) => ({
  language: state.language,
});

const mapDispatch = {
  setLanguage,
};

const connector = connect(mapStateToProps, mapDispatch);

const ConnectedContent = connector(Content);

function App() {
  return (
    <ConnectedContent />
  );
}
  

Но дело в том, что мне понадобились бы дополнительные усовершенствования, поэтому соединитель должен быть повторно используемым, что-то вроде

 //type PropsFromRedux = ConnectedProps<typeof connector>;

interface LoadingProps {
  loading?: boolean;
}

const withLoading = <P extends {}>(Component: React.ComponentType<P>) => (props: P amp; LoadingProps) => (
  <>
    {props.loading amp;amp; <div>Loading</div>}
    <Component {...props as P} />
  </>
);

function getConnectedComponent(Component: React.ComponentType) {
  const ComponentWithLoading = withLoading(Component);

  return connector(ComponentWithLoading);
}

const ConnectedContent = getConnectedComponent(Content);
  

Но я не знаю, как правильно добавлять типы / обобщения в функцию getConnectedComponent. С выводом я получаю

 Error:(24, 48) TS2345: Argument of type '(props: ContentProps) => Element' is not assignable to parameter of type 'ComponentType<{}>'.
  Type '(props: ContentProps) => Element' is not assignable to type 'FunctionComponent<{}>'.
    Types of parameters 'props' and 'props' are incompatible.
      Property 'language' is missing in type '{ children?: ReactNode; }' but required in type 'ContentProps'.
  

Рабочий пример включен https://codesandbox.io/s/elegant-snowflake-b5r89
Также отправлено на GitHub https://github.com/hitrov/reusable-redux-connector

Ответ №1:

Я не уверен, что вы подразумеваете под «соединитель должен быть повторно используемым» или какие «дополнительные улучшения» вы имеете в виду. Он уже повторно используется. Функция, возвращаемая из connect(mapState, mapDispatch) , готова принять любой компонент, который вы передали, поэтому он уже выполняет то, что ваш условный getConnectedComponent хочет сделать.

Комментарии:

1. Привет, спасибо за ваш ответ, да, если я экспортирую эту переменную соединителя — это было бы. Но представьте, что мне понадобились бы дополнительные усовершенствования компонента перед передачей его соединителю (например, применить к нему HOC) — и вот почему у меня должна быть функция, которая получает component в качестве аргумента, а затем применяет к нему connector

2. Нет, никаких «улучшений», которые необходимы, и я не знаю, откуда вы взяли эту идею. Любой компонент может быть передан этой connector() функции, и она будет работать. Точка.

3. вы говорите мне, что мне не нужно, пока я делаю =) я добавил приведенный выше пример — представьте, что мне нужно применить withLoading HOC до или после перехода к соединителю. есть ли какой-либо другой способ сделать это?

4. Такое использование withLoading HOC специфично для вашего собственного приложения. Я хочу сказать, что что касается React-Redux, то для успешного вызова больше ничего не нужно делать const ConnectedComponent = connector(MyComponent) . На данный момент я не знаю, в чем собственно вопрос, tbh.