【发布时间】:2019-03-13 22:44:49
【问题描述】:
我正在使用 TypeScript 编写一个 React Native 应用程序。
我有一个组件EmotionsRater,它接受以下两种类型之一:情感或需要。它还应该接受rateNeed 或rateEmotion 类型的函数。我使用| 运算符将这些类型组合成一种称为rateBoth 的类型。并将这种组合类型传递给另一个名为EmotionsRaterItem 的组件。问题是EmotionsRaterItem 然后声称:
Cannot invoke an expression whose type lacks a call signature. Type 'rateBoth' has no compatible call signatures.
我在下面提供了所有相关组件的精简代码。
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:
// ... 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:
// ... 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 是两个函数之一,因此是可调用的?
编辑: 感谢Estus 的评论,我在这里添加了代码而不是要点。
【问题讨论】:
-
问题应包含理解所需的所有代码。要点可以消失,但问题主体不能。如果 sn-ps 太大,请考虑通过删除不相关的部分使其更小,请参阅stackoverflow.com/help/mcve。如果你觉得整体展示可能会有好处,可以考虑提供一个工作演示,stackblitz 和 codeandbox 都提供 React+TS 设置。
-
@estus 我将代码添加到问题中。感谢您的帮助!
标签: reactjs typescript react-native typescript-typings typescript-types