本文介绍了TypeScript:或者通过React中的几个组件传递的函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用TypeScript编写React Native应用程序.

I'm writing a React Native application using TypeScript.

我有一个组件EmotionsRater,它接受两种类型之一:情感或需求.它也应该接受rateNeedrateEmotion类型的函数.我使用|运算符将这些类型组合为一种称为rateBoth的类型.并将此组合类型向下传递给另一个名为EmotionsRaterItem的组件.问题是EmotionsRaterItem然后声明:

I have a component EmotionsRater that accepts one of two types: Emotion or Need. It should also either accept a function of type rateNeed or rateEmotion. I combined these types to one called rateBoth using the | operator. And it passes this combined type down to another component called EmotionsRaterItem. The problem is that EmotionsRaterItem then claims:

Cannot invoke an expression whose type lacks a call signature. Type 'rateBoth' has no compatible call signatures.

我在下面提供了所有相关组件的精简代码.

I provided the boiled down code for all relevant components below.

QuestionsScreen.tsx:

QuestionsScreen.tsx:

// ... imports

export type rateEmotion = (rating: number, emotion: Emotion) => void;
export type rateNeed = (rating: number, emotion: Need) => void;

export interface Props {
  navigation: NavigationScreenProp<any, any>;
}

export interface State {
  readonly emotions: Emotion[];
  readonly needs: Need[];
}

let EMOTIONS_ARRAY: Emotion[] = // ... some array of emotions

let NEEDS_ARRAY: Need[] = // ... some array of needs

export class QuestionsScreen extends Component<Props, State> {
  static navigationOptions = // ... React Navigation Stuff

  readonly state = {
    emotions: EMOTIONS_ARRAY.slice(),
    needs: NEEDS_ARRAY.slice()
  };

  swiper: any;

  componentWillUnmount = () => {
    // ... code to reset the emotions
  };

  toggleEmotion = (emotion: Emotion) => {
    // ... unrelated code for the <EmotionsPicker />
  };

  rateEmotion: rateEmotion = (rating, emotion) => {
    this.setState(prevState => ({
      ...prevState,
      emotions: prevState.emotions.map(val => {
        if (val.name === emotion.name) {
          val.rating = rating;
        }
        return val;
      })
    }));
  };

  rateNeed: rateNeed = (rating, need) => {
    this.setState(prevState => ({
      ...prevState,
      need: prevState.emotions.map(val => {
        if (val.name === need.name) {
          val.rating = rating;
        }
        return val;
      })
    }));
  };

  goToIndex = (targetIndex: number) => {
    const currentIndex = this.swiper.state.index;
    const offset = targetIndex - currentIndex;
    this.swiper.scrollBy(offset);
  };

  render() {
    const { emotions, needs } = this.state;
    return (
      <SafeAreaView style={styles.container} forceInset={{ bottom: "always" }}>
        <Swiper
          style={styles.wrapper}
          showsButtons={false}
          loop={false}
          scrollEnabled={false}
          showsPagination={false}
          ref={component => (this.swiper = component)}
        >
          <EmotionsPicker
            emotions={emotions}
            toggleEmotion={this.toggleEmotion}
            goToIndex={this.goToIndex}
          />
          <EmotionsRater
            emotions={emotions.filter(emotion => emotion.chosen)}
            rateEmotion={this.rateEmotion}
            goToIndex={this.goToIndex}
          />
          <EmotionsRater
            emotions={needs}
            rateEmotion={this.rateNeed}
            goToIndex={this.goToIndex}
            tony={true}
          />
        </Swiper>
      </SafeAreaView>
    );
  }
}

export default QuestionsScreen;

EmotionsRater.tsx:

EmotionsRater.tsx:

// ... imports

export type rateBoth = rateEmotion | rateNeed;

export interface Props {
  emotions: Emotion[] | Need[];
  rateEmotion: rateBoth;
  goToIndex: (targetIndex: number) => void;
  tony?: boolean;
}

export interface DefaultProps {
  readonly tony: boolean;
}

export class EmotionsRater extends PureComponent<Props & DefaultProps> {
  static defaultProps: DefaultProps = {
    tony: false
  };

  keyExtractor = (item: Emotion | Need, index: number): string =>
    item.name + index.toString();

  renderItem = ({ item }: { item: Emotion | Need }) => (
    <EmotionsRaterItem emotion={item} rateEmotion={this.props.rateEmotion} />
  );

  renderHeader = () => {
    const { tony } = this.props;
    return (
      <ListItem
        title={tony ? strings.needsTitle : strings.raterTitle}
        titleStyle={styles.title}
        bottomDivider={true}
        containerStyle={styles.headerContainer}
        leftIcon={tony ? badIcon : goodIcon}
        rightIcon={tony ? goodIcon : badIcon}
      />
    );
  };

  goBack = () => {
    this.props.goToIndex(0);
  };

  goForth = () => {
    this.props.goToIndex(2);
  };

  render() {
    return (
      <View style={styles.container}>
        <FlatList<Emotion | Need>
          style={styles.container}
          keyExtractor={this.keyExtractor}
          renderItem={this.renderItem}
          data={this.props.emotions}
          ListHeaderComponent={this.renderHeader}
          stickyHeaderIndices={[0]}
        />
        <ButtonFooter
          firstButton={{
            disabled: false,
            onPress: this.goBack,
            title: strings.goBack
          }}
          secondButton={{
            disabled: false,
            onPress: this.goForth,
            title: strings.done
          }}
        />
      </View>
    );
  }
}

export default EmotionsRater;

EmotionsRaterItem.tsx:

EmotionsRaterItem.tsx:

// ... imports

export interface Props {
  emotion: Emotion | Need;
  rateEmotion: rateBoth;
}

export interface State {
  readonly rating: number;
}

export class EmotionsRaterItem extends PureComponent<Props, State> {
  readonly state = { rating: this.props.emotion.rating };

  ratingCompleted = (rating: number) => {
    this.setState({ rating });
    this.props.rateEmotion(rating, this.props.emotion);
    // This    ^^^^^^^^^^^ throws the error mentioned in the post.
  };

  render() {
    const { emotion } = this.props;
    const { rating } = this.state;
    const color = getColor(rating);
    return (
      <ListItem
        title={emotion.name}
        bottomDivider={true}
        rightTitle={String(Math.round(rating * 100))}
        rightTitleStyle={{ color: color.hex("rgb") }}
        rightContentContainerStyle={styles.rightContentContainer}
        subtitle={
          <Slider
            value={emotion.rating}
            thumbTintColor={activeColor}
            minimumTrackTintColor={color.hex("rgb")}
            maximumTrackTintColor={color.alpha(0.4).hex("rgba")}
            step={0.01}
            onValueChange={this.ratingCompleted}
          />
        }
      />
    );
  }
}

export default EmotionsRaterItem;

这是怎么回事?为什么TypeScript不知道rateBoth是两个函数之一并且可以调用?

What is going on? Why doesn't TypeScript know that rateBoth is one of two functions and therefore callable?

感谢 Estus 的评论,我在此处添加了代码,而不是要点.

Thanks to Estus's comment I added the code here instead of gists.

推荐答案

如果EmotionsRaterItem具有类型为rateBoth的函数,则该函数要么需要Emotion要么需要Need,但是调用者可以不知道哪种类型是必需的.因此,在当前的TypeScript语义下,无法调用该函数. (您可以想象也许传递一个两者,一个Emotion和一个Need的参数应该可以,但是TypeScript并不那么聪明;请参阅此问题.)

If EmotionsRaterItem has a function of type rateBoth, then that function either requires an Emotion or requires a Need, but the caller does not know which of the type is required. Hence, under current TypeScript semantics, it's impossible to call the function. (You could imagine that maybe passing an argument that is both an Emotion and a Need should work, but TypeScript isn't that smart; see this issue.)

相反,您可以使EmotionsRaterEmotionsRaterItem在其正在处理的项的类型T中通用(EmotionNeed). (当然,通用组件通常是不完善的,但看起来似乎解决了问题不会在您的情况下发生.)半完整示例:

Instead, you could make EmotionsRater and EmotionsRaterItem generic in the type T of the item they are working on (either Emotion or Need). (Of course, generic components are unsound in general, but it looks like the problem won't occur in your scenario.) Semi-complete example:

QuestionsScreen.tsx

// ... imports
import { Component } from "react";
import EmotionsRater from "./EmotionsRater";
import * as React from "react";

export interface Emotion {
  emotionBrand: undefined;
  name: string;
  rating: number;
}
export interface Need {
  needBrand: undefined;
  name: string;
  rating: number;
}

export type rateEmotion = (rating: number, emotion: Emotion) => void;
export type rateNeed = (rating: number, emotion: Need) => void;

export interface Props {
  navigation: NavigationScreenProp<any, any>;
}

export interface State {
  readonly emotions: Emotion[];
  readonly needs: Need[];
}

let EMOTIONS_ARRAY: Emotion[] = []; // ... some array of emotions

let NEEDS_ARRAY: Need[] = []; // ... some array of needs

export class QuestionsScreen extends Component<Props, State> {
  static navigationOptions; // ... React Navigation Stuff

  readonly state = {
    emotions: EMOTIONS_ARRAY.slice(),
    needs: NEEDS_ARRAY.slice()
  };

  swiper: any;

  componentWillUnmount = () => {
    // ... code to reset the emotions
  };

  toggleEmotion = (emotion: Emotion) => {
    // ... unrelated code for the <EmotionsPicker />
  };

  rateEmotion: rateEmotion = (rating, emotion) => {
    this.setState(prevState => ({
      ...prevState,
      emotions: prevState.emotions.map(val => {
        if (val.name === emotion.name) {
          val.rating = rating;
        }
        return val;
      })
    }));
  };

  rateNeed: rateNeed = (rating, need) => {
    this.setState(prevState => ({
      ...prevState,
      need: prevState.emotions.map(val => {
        if (val.name === need.name) {
          val.rating = rating;
        }
        return val;
      })
    }));
  };

  goToIndex = (targetIndex: number) => {
    const currentIndex = this.swiper.state.index;
    const offset = targetIndex - currentIndex;
    this.swiper.scrollBy(offset);
  };

  render() {
    const { emotions, needs } = this.state;
    return (
      <SafeAreaView style={styles.container} forceInset={{ bottom: "always" }}>
        <Swiper
          style={styles.wrapper}
          showsButtons={false}
          loop={false}
          scrollEnabled={false}
          showsPagination={false}
          ref={component => (this.swiper = component)}
        >
          <EmotionsPicker
            emotions={emotions}
            toggleEmotion={this.toggleEmotion}
            goToIndex={this.goToIndex}
          />
          <EmotionsRater
            emotions={emotions.filter(emotion => emotion.chosen)}
            rateEmotion={this.rateEmotion}
            goToIndex={this.goToIndex}
          />
          <EmotionsRater
            emotions={needs}
            rateEmotion={this.rateNeed}
            goToIndex={this.goToIndex}
            tony={true}
          />
        </Swiper>
      </SafeAreaView>
    );
  }
}

export default QuestionsScreen;

EmotionsRater.tsx

// ... imports
import { PureComponent } from "react";
import * as React from "react";
import { Emotion, Need } from "./QuestionsScreen";
import EmotionsRaterItem from "./EmotionsRaterItem";

export interface Props<T extends Emotion | Need> {
  emotions: T[];
  rateEmotion: (rating: number, emotion: T) => void;
  goToIndex: (targetIndex: number) => void;
  tony?: boolean;
}

export interface DefaultProps {
  readonly tony: boolean;
}

export class EmotionsRater<T extends Emotion | Need> extends PureComponent<Props<T> & DefaultProps> {
  static defaultProps: DefaultProps = {
    tony: false
  };

  keyExtractor = (item: Emotion | Need, index: number): string =>
    item.name + index.toString();

  renderItem = ({ item }: { item: T }) => (
    <EmotionsRaterItem emotion={item} rateEmotion={this.props.rateEmotion} />
  );

  renderHeader = () => {
    const { tony } = this.props;
    return (
      <ListItem
        title={tony ? strings.needsTitle : strings.raterTitle}
        titleStyle={styles.title}
        bottomDivider={true}
        containerStyle={styles.headerContainer}
        leftIcon={tony ? badIcon : goodIcon}
        rightIcon={tony ? goodIcon : badIcon}
      />
    );
  };

  goBack = () => {
    this.props.goToIndex(0);
  };

  goForth = () => {
    this.props.goToIndex(2);
  };

  render() {
    return (
      <View style={styles.container}>
        <FlatList<T>
          style={styles.container}
          keyExtractor={this.keyExtractor}
          renderItem={this.renderItem}
          data={this.props.emotions}
          ListHeaderComponent={this.renderHeader}
          stickyHeaderIndices={[0]}
        />
        <ButtonFooter
          firstButton={{
            disabled: false,
            onPress: this.goBack,
            title: strings.goBack
          }}
          secondButton={{
            disabled: false,
            onPress: this.goForth,
            title: strings.done
          }}
        />
      </View>
    );
  }
}

export default EmotionsRater;

EmotionsRaterItem.tsx

// ... imports
import { PureComponent } from "react";
import * as React from "react";
import { Emotion, Need } from "./QuestionsScreen";

export interface Props<T extends Emotion | Need> {
  emotion: T;
  rateEmotion: (rating: number, emotion: T) => void;
}

export interface State {
  readonly rating: number;
}

export class EmotionsRaterItem<T extends Emotion | Need> extends PureComponent<Props<T>, State> {
  readonly state = { rating: this.props.emotion.rating };

  ratingCompleted = (rating: number) => {
    this.setState({ rating });
    this.props.rateEmotion(rating, this.props.emotion);
  };

  render() {
    const { emotion } = this.props;
    const { rating } = this.state;
    const color = getColor(rating);
    return (
      <ListItem
        title={emotion.name}
        bottomDivider={true}
        rightTitle={String(Math.round(rating * 100))}
        rightTitleStyle={{ color: color.hex("rgb") }}
        rightContentContainerStyle={styles.rightContentContainer}
        subtitle={
          <Slider
            value={emotion.rating}
            thumbTintColor={activeColor}
            minimumTrackTintColor={color.hex("rgb")}
            maximumTrackTintColor={color.alpha(0.4).hex("rgba")}
            step={0.01}
            onValueChange={this.ratingCompleted}
          />
        }
      />
    );
  }
}

export default EmotionsRaterItem;

这篇关于TypeScript:或者通过React中的几个组件传递的函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

06-06 21:04