问题描述
我正在使用TypeScript编写React Native应用程序.
I'm writing a React Native application using TypeScript.
我有一个组件EmotionsRater
,它接受两种类型之一:情感或需求.它也应该接受rateNeed
或rateEmotion
类型的函数.我使用|
运算符将这些类型组合为一种称为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.)
相反,您可以使EmotionsRater
和EmotionsRaterItem
在其正在处理的项的类型T
中通用(Emotion
或Need
). (当然,通用组件通常是不完善的,但看起来似乎解决了问题不会在您的情况下发生.)半完整示例:
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中的几个组件传递的函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!