Если EmotionsRaterItem
имеет функцию типа rateBoth
, то для этой функции либо требуется Emotion
, либо Need
, но вызывающая сторона не знает, какой из типов требуется.Следовательно, в соответствии с текущей семантикой TypeScript невозможно вызвать функцию.(Вы можете представить, что, возможно, передача аргумента, который и , Emotion
и Need
, должен работать, но TypeScript не настолько умен, см. эту проблему .)
Вместо этого вы можете сделать EmotionsRater
и EmotionsRaterItem
универсальными в типе T
элемента, над которым они работают (либо Emotion
, либо Need
).(Конечно, общие компоненты не работают в общем , но похоже, что проблема не возникнет в вашем сценарии.) Полу-полный пример:
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;