Типобезопасный NavigationHandler с React-Navigation - PullRequest
2 голосов
/ 27 мая 2020

У меня проблема с Typescript и React Navigation для моего приложения React Native. В моем проекте есть следующая настройка, которая дает мне отличную помощь и автозаполнение при использовании, например, navigation.push().

Типы

import { StackNavigationProp } from '@react-navigation/stack';

export type RootStackParamList = {
    Home: undefined;
    Content: undefined;
    List: undefined;
};

export type StackNavigationProps = StackNavigationProp<RootStackParamList>;

Использование

const navigation = useNavigation<StackNavigationProps>();

Однако я создал функцию-обработчик навигации, которая принимает параметр, передаваемый в navigation.push. Он работает нормально, пока тип параметра имеет тип any.

const navigationHandler = useCallback((targetScreen) => {
    navigation.push(targetScreen);
}, [navigation]);

Но меня не устраивает тип any, и я бы предпочел, чтобы targetScreen на самом деле мог быть просто один из экранов, которые были объявлены в моем типе RootStackParamList. Я пробовал бесчисленным количеством способов удовлетворить метод push объекта навигации, но я застрял.

Следующая попытка была наиболее успешной, но все еще не работает так, как нужно. Это дает мне автозаполнение на доступных экранах, но push по-прежнему недоволен.

Тип:

export type NavigationScreen<T extends RootStackParamList> = {
    [K in keyof T]: K;
}[keyof T];

Использование:

const navigationHandler = useCallback(
    <T extends RootStackParamList>(targetScreen: NavigationScreen<T>) => {
        navigation.push(targetScreen);
},[navigation]);

Ошибка при наведении курсора I получите запрос со следующим:

(параметр) targetScreen: {[K в клавише T]: K; } [keyof T] Аргумент типа '[{[K в keyof T]: K; } [keyof T]] 'не может быть назначен параметру типа' ["Home" | «Контент» | «Список»] | ["Дом" | «Контент» | "Список", не определено] '. Введите '[{[K в тональности T]: K; } [keyof T]] 'нельзя присвоить типу' ["Home" | «Контент» | «Список»] '.
Тип' keyof T 'не может быть назначен типу' "Домой" | «Контент» | «Список» '.
Тип' строка | номер | символ 'не присваивается типу' "Домой" | «Контент» | "Список".
Тип 'строка' не может быть назначен типу '"Домой" | «Контент» | «Список» '.
Тип' keyof T 'не может быть назначен типу' "Список".
Тип 'строка | номер | символ 'не может быть назначен типу' "Список".
Тип 'строка' не может быть назначен типу '"Список".

Как заставить это работать?

1 Ответ

2 голосов
/ 31 мая 2020

Если вас просто интересует использование имени экрана для вашей функции navigationHandler, но не параметров, вы можете сделать так, как @artcorpse предложил в его комментарии к вашему вопросу, и использовать keyof RootStackParamList:

const navigationHandler = useCallback(
    (targetScreen: keyof RootStackParamList) => {
        navigation.push(targetScreen);
    },
    [navigation]
);

Если, с другой стороны, вы хотите разрешить параметры, у вас есть два варианта:

Вариант A: Generi c function

const navigationHandler = useCallback(
    <T extends keyof RootStackParamList>(
        targetScreen: T,
        targetScreenParams?: RootStackParamList[T]
    ) => {
        navigation.push(
            targetScreen as keyof RootStackParamList,
            targetScreenParams
        );
    },
    [navigation]
);

Плюсом этой опции является то, что формы объекта параметров маршрута проверяются на соответствие соответствующим маршрутам, как определено в типе RootStackParamList. Например,

type RootStackParamList = {
    Default: undefined,
    Home: {a: 'a'};
    Profile: { userId: string };
};

navigationHandler('Home', {a: 'a'}); //OK
navigationHandler('Home', {userId: 'a'}); //ERROR

Вариант B: реверсирование уже существующих типов

const navigationHandler2 = useCallback(
    (
        targetScreen: Parameters<typeof navigation.push>[0],
        targetScreenParams?: Parameters<typeof navigation.push>[1]
    ) => {
        navigation.push(targetScreen, targetScreenParams);
    },
    [navigation]
);

Эта опция берет существующие типы, которые вы определили, и пытается извлечь их из метода navigation.push. Обратной стороной является то, что вы не получите вышеупомянутую проверку относительно маршрутов и соответствующих им форм объектов параметров.

Источники:

...