Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/reactjs/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/typescript/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Reactjs 如何为React HOC编写类型定义_Reactjs_Typescript_Higher Order Components - Fatal编程技术网

Reactjs 如何为React HOC编写类型定义

Reactjs 如何为React HOC编写类型定义,reactjs,typescript,higher-order-components,Reactjs,Typescript,Higher Order Components,我有一个高阶组件,它为我处理Firestore数据。我对typescript还不太熟悉,我很难让这些类型按我所希望的那样工作 我有几个问题: React.Component不推断类型定义: type WithFirestoreHoC<Props = {}> = ( config: WithFirestoreConfig<Props>, ) => ( WrappedComponent: ComponentType<WithFirestore &

我有一个高阶组件,它为我处理Firestore数据。我对typescript还不太熟悉,我很难让这些类型按我所希望的那样工作

我有几个问题:


React.Component不推断类型定义:

type WithFirestoreHoC<Props = {}> = (
  config: WithFirestoreConfig<Props>,
) => (
  WrappedComponent: ComponentType<WithFirestore & Props>,
) => ComponentClass<Props, { error: Error; queries: {}; loaded: boolean }>;

const withFirestore: WithFirestoreHoC = ({
  queries,
  props: propPickList,
  loading: { delay = 200, timeout = 0 } = {},
}) => WrappedComponent =>
  class WithFirestoreConnect extends Component { ... }
所以WrappedComponent可以知道另一端是否需要查询或文档数据结构

这看起来非常复杂,所以我这里有一个更简单的例子(这是一个创建单个订阅的快捷方式),它至少是我想要的一个很好的垫脚石:

export const withFirestoreDocument: <
  DataType = firestore.DocumentData,
  Props = {}
>(
  query: FirestoreQueryable<DataType>,
) => (
  WrappedComponent: ComponentType<DocumentSnapshotExpanded<DataType>>,
) => WithFirestoreHoC<Props> = query => WrappedComponent =>
  withFirestore({ queries: { _default: query } })(
    mapProps<
      DocumentSnapshotExpanded<DataType> & Props,
      { _default: DocumentSnapshotExpanded<DataType> } & Props
    >(({ _default, ...props }) => ({ ...props, ..._default }))(WrappedComponent),
  );
使用FireStoreDocument导出常量:<
数据类型=firestore.DocumentData,
道具={}
>(
查询:FirestoreQueryable,
) => (
WrappedComponent:ComponentType,
)=>WithFirestoreHoC=query=>WrappedComponent=>
withFirestore({querys:{u默认值:query}})(
地图道具<
文档快照和道具,
{u默认值:DocumentSnapshotExpanded}&Props
>({u default,…props})=>({…props,{u default}))(WrappedComponent),
);

但是我被困在这里,因为我无法从函数的类型定义中获取
mapProp
的类型定义。。。正确的方法是什么?

React.Component不推断类型定义:
Props
作为函数的类型参数,而不是类型别名,然后在使用FireStore定义
时声明它

如何创建动态拾取列表:为拾取列表元素的并集添加一个
PL
类型参数。当您让TypeScript在调用站点推断
PL
时,这将是正确的做法,尽管调用方可以通过将
PL
指定为包含不在实际列表中的元素的联合类型来产生不合理的行为

推断Firestore类型:我不确定您使用FireStoreDocument的
去了哪里。您可以使用另一个
Q
类型参数和一些映射类型和条件类型来执行此操作,以从
Q
生成注入道具的类型

以下是我对FireStore.tsx的
修订版,包括所有新功能、一些无关的修复程序,以使其在我的环境中编译,并在底部添加了一个示例(可能应该放在单独的文件中):

import*as React from'React';
从“react”导入{Component,ComponentClass,ComponentType};
进口{
文件参考,
查询
收藏参考,
文档快照已展开,
Querysnapshot
}来自“/firemodel”;
从“firebase”导入{firestore};
从“lodash”导入{pick、forEach、isEqual、isFunction};
从'modules/providers/util'导入{expandDocSnapshot,expandQuerySnapshot};
从“模块/原子/智能加载器”导入智能加载器;
类型FirestoreQueryable=
|文件参考
|质疑
|收集参考;
类型FirestoryQueryableFunction<
数据类型,
道具
> = (
firestore:firestore,firestore,
道具:道具,
)=>承诺;
类型查询配置=
FirestoreQueryable | FirestoryQueryableFunction;
类型QueryConfig={
[queryName:string]:QueryConfigEntry;
};
类型FirestoreQueryableExpanded=
QE扩展FirestoreQueryable?FirestoreQueryableExpanded1:
QE扩展了FirestoryQueryableFunction?FirestoreQueryableExpanded1:未知;
类型FirestoreQueryableExpanded1=
QE扩展了CollectionReference |查询?QuerySnapshot已展开:
QE扩展了文档引用?DocumentSnapshotExpanded:未知;
与FireStoreConfig的接口{
/**对象,该对象包含要提供给WrappedComponent的查询。
*使用的queryName也是传递快照的道具名称*/
查询:Q;
/**传递给WrappedComponent的白名单道具列表。
*没有列表的配置将白名单所有道具*/
道具?:PL[];
/**加载配置项*/
装货?:{
/**显示加载图标后的毫秒数*/
延迟?:数字;
/**显示超时消息之后的毫秒数*/
超时?:数字;
};
}
键入WithFirestoreHoC=()=>(
配置:使用FireStoreConfig,
) => (
WrappedComponent:ComponentType,
)=>组件类;
使用FireStore的常量:使用FireStoreHoc=
//需要一个额外的函数调用,以便调用方可以指定Props和
//仍然可以推断PL和Q。当需要时,可以将其移除
// https://github.com/Microsoft/TypeScript/issues/10571 实现了。
() =>
//注意:如果未传递'props',则不会对PL和it进行推断
//将默认为其约束,这正是我们想要的行为
//就打字而言。
({
询问,
道具:propPickList,
加载:{delay=200,timeout=0}={},
}:WithFirestoreConfig)=>WrappedComponent=>
使用FireStoreConnect扩展组件初始化{
订阅:{
[queryName:string]:返回类型;
} = {};
状态={
错误:null为错误,
查询:{}与FireStore一样,
加载:false,
};
componentDidMount(){
这是restartSubscription();
}
取消订阅=()=>{
forEach(this.subscriptions,unsubscribe=>unsubscribe());
this.subscriptions={};
};
restartSubscription=()=>{
//开放性问题:
//-确定何时全部加载(使用承诺?)
这个.cancelSubscriptions();
forEach(查询,异步(q:queryconfig,key)=>{
let ref:FirestoreQueryable;
if(isFunction(q)){
//这是一个异步/等待的事实意味着我们可以
//在FirestoreQueryableFunction中创建相关查询
ref=wait q(firestore(),this.props);
}否则{
//由于某些原因,收窄不起作用。
ref=q作为FirestoreQueryable;
}
if(参考firestore.DocumentReference的实例){
this.subscriptions[key]=ref.onSnapsh
export const withFirestoreDocument: <
  DataType = firestore.DocumentData,
  Props = {}
>(
  query: FirestoreQueryable<DataType>,
) => (
  WrappedComponent: ComponentType<DocumentSnapshotExpanded<DataType>>,
) => WithFirestoreHoC<Props> = query => WrappedComponent =>
  withFirestore({ queries: { _default: query } })(
    mapProps<
      DocumentSnapshotExpanded<DataType> & Props,
      { _default: DocumentSnapshotExpanded<DataType> } & Props
    >(({ _default, ...props }) => ({ ...props, ..._default }))(WrappedComponent),
  );
import * as React from 'react';
import { Component, ComponentClass, ComponentType } from 'react';
import {
  DocumentReference,
  Query,
  CollectionReference,
  DocumentSnapshotExpanded,
  QuerySnapshotExpanded
} from './firemodel';
import { firestore } from 'firebase';
import { pick, forEach, isEqual, isFunction } from 'lodash';
import { expandDocSnapshot, expandQuerySnapshot } from 'modules/providers/util';
import SmartLoader from 'modules/atoms/SmartLoader';

type FirestoreQueryable<DataType> =
  | DocumentReference<DataType>
  | Query<DataType>
  | CollectionReference<DataType>;

type FirestoryQueryableFunction<
  DataType,
  Props
> = (
  firestore: firestore.Firestore,
  props: Props,
) => Promise<FirestoreQueryable<DataType>>;

type QueryConfigEntry<Props> =
  FirestoreQueryable<any> | FirestoryQueryableFunction<any, Props>;

type QueryConfig<Props> = {
  [queryName: string]: QueryConfigEntry<Props>;
};

type FirestoreQueryableExpanded<Props, QE extends QueryConfigEntry<Props>> =
  QE extends FirestoreQueryable<any> ? FirestoreQueryableExpanded1<QE> :
  QE extends FirestoryQueryableFunction<any, Props> ? FirestoreQueryableExpanded1<ReturnType<QE>> : unknown;

type FirestoreQueryableExpanded1<QE extends FirestoreQueryable<any>> =
  QE extends CollectionReference<infer DataType> | Query<infer DataType> ? QuerySnapshotExpanded<DataType> :
  QE extends DocumentReference<infer DataType> ? DocumentSnapshotExpanded<DataType> : unknown;

interface WithFirestoreConfig<Props, PL extends keyof Props, Q extends QueryConfig<Props>> {
  /** Object containing the queries to be provided to WrappedComponent.
   * The queryName used is also the prop name the snapshot is passed in. */
  queries: Q;
  /** A list of props to whitelist passing to WrappedComponent.
   * Configs without a list will whitelist all props */
  props?: PL[];
  /** Loading config items */
  loading?: {
    /** Number of ms after which to display the loading icon */
    delay?: number;
    /** Number of ms after which to display the timeout message */
    timeout?: number;
  };
}

type WithFirestoreHoC = <Props>() => <PL extends keyof Props, Q extends QueryConfig<Props>>(
  config: WithFirestoreConfig<Props, PL, Q>,
) => (
  WrappedComponent: ComponentType<WithFirestore<Props, Q> & Pick<Props, PL>>,
) => ComponentClass<Props, { error: Error; queries: {}; loaded: boolean }>;

const withFirestore: WithFirestoreHoC =
  // An extra function call is needed so that callers can specify Props and
  // still have PL and Q inferred.  It can be removed when
  // https://github.com/Microsoft/TypeScript/issues/10571 is implemented.
  <Props extends {}>() =>
  // Note: if `props` is not passed, there will be no inference for PL and it
  // will default to its constraint, which is exactly the behavior we want as
  // far as typing is concerned.
  <PL extends keyof Props, Q extends QueryConfig<Props>>({
    queries,
    props: propPickList,
    loading: { delay = 200, timeout = 0 } = {},
  }: WithFirestoreConfig<Props, PL, Q>) => WrappedComponent =>
  class WithFirestoreConnect extends Component<Props, { error: Error; queries: WithFirestore<Props, Q>; loaded: boolean }> {
    subscriptions: {
      [queryName: string]: ReturnType<FirestoreQueryable<any>['onSnapshot']>;
    } = {};
    state = {
      error: null as Error,
      queries: {} as WithFirestore<Props, Q>,
      loaded: false,
    };
    componentDidMount() {
      this.restartSubscription();
    }

    cancelSubscriptions = () => {
      forEach(this.subscriptions, unsubscribe => unsubscribe());
      this.subscriptions = {};
    };

    restartSubscription = () => {
      // Open questions:
      //   - figuring out when all loaded (use a promise?)
      this.cancelSubscriptions();
      forEach(queries, async (q: QueryConfigEntry<Props>, key) => {
        let ref: FirestoreQueryable<any>;
        if (isFunction(q)) {
          // The fact that this is an async/await means that we can
          // create dependent queries within our FirestoreQueryableFunction
          ref = await q(firestore(), this.props);
        } else {
          // Narrowing is not working for some reason.
          ref = q as FirestoreQueryable<any>;
        }
        if (ref instanceof firestore.DocumentReference) {
          this.subscriptions[key] = ref.onSnapshot(
            snap => {
              this.setState({
                queries: Object.assign({}, this.state.queries, {[key]: expandDocSnapshot(snap)}),
              });
            },
            err => {
              console.error(JSON.stringify(err));
              this.setState({ error: err });
              this.cancelSubscriptions();
            },
          );
        } else if (
          ref instanceof firestore.CollectionReference ||
          ref instanceof firestore.Query
        ) {
          let ref2: {onSnapshot(os: (snap: firestore.QuerySnapshot) => void, oe: (err: Error) => void): () => void; } = ref;
          this.subscriptions[key] = ref2.onSnapshot(
            snap => {
              this.setState({
                queries: Object.assign({}, this.state.queries, {[key]: expandQuerySnapshot(snap)}),
              });
            },
            err => {
              console.error(JSON.stringify(err));
              this.setState({ error: err });
              this.cancelSubscriptions();
            },
          );
        }
      });
    };

    componentDidUpdate(prevProps: Props) {
      if (!isEqual(this.props, prevProps)) {
        this.restartSubscription();
      }
    }
    componentWillUnmount() {
      this.cancelSubscriptions();
    }
    render() {
      if (!this.state.loaded || this.state.error) {
        return (
          <SmartLoader
            error={this.state.error}
            timeout={timeout}
            delay={delay}
          />
        );
      }

      const whitelistedProps = propPickList
        ? pick(this.props, propPickList)
        : this.props;
      // Unsure what's wrong here ~ Matt
      let WrappedComponent2 = WrappedComponent as any;
      return <WrappedComponent2 {...whitelistedProps} {...this.state.queries} />;
    }
  };

export type WithFirestore<Props, Q extends QueryConfig<Props>> = {
  [queryName in keyof Q]: FirestoreQueryableExpanded<Props, Q[queryName]>;
}

export default withFirestore;

// EXAMPLE

interface MyDoc {
  y: number
}
declare let myDocRef: DocumentReference<MyDoc>;
declare let myCollRef: CollectionReference<MyDoc>;
let wrapped = withFirestore<{x: string}>()({
  queries: {
    myDoc: myDocRef,
    myColl: myCollRef
  },
})((props) => { return <>{props.myDoc.data.y + props.myColl.docs[props.x].data.y}</>; });