O Expo Router pode gerar tipos de rota estáticos a partir da sua árvore de arquivos app/, fornecendo verificações em tempo de compilação para <Link>, router.push e hooks de parâmetros. Os parâmetros de rota ainda cruzam um limite de confiança - eles se originam de URLs, notificações e intenções do sistema operacional - portanto, combine os tipos gerados com validação em tempo de execução.
// app/user/[id].tsximport { Link, router, useLocalSearchParams, type Href } from "expo-router";import { Text, View } from "react-native";import { z } from "zod";const Params = z.object({ id: z.string().regex(/^\d+$/),});export default function UserScreen() { const raw = useLocalSearchParams<"/user/[id]">(); const { id } = Params.parse(raw); // portão de tempo de execução - os parâmetros são strings return ( <View> <Text>Usuário {id}</Text> <Link href={{ pathname: "/user/[id]", params: { id: "42" } }}>Usuário 42</Link> </View> );}// Navegação imperativa - Href pega erros de digitaçãoexport function openUser(userId: string) { const href = { pathname: "/user/[id]", params: { id: userId } } satisfies Href; router.push(href);}
Quando usar isso: Você usa rotas baseadas em arquivos do Expo Router e deseja autocompletar em href, segmentos de caminho tipados em telas e validação antes de confiar em parâmetros de deep link.
// app/search/[...terms].tsx - segmentos catch-all são string[]import { useGlobalSearchParams } from "expo-router";import { Text, View } from "react-native";import { z } from "zod";const TermsParams = z.object({ terms: z .union([z.string(), z.array(z.string())]) .transform((v) => (Array.isArray(v) ? v : [v])) .pipe(z.array(z.string()).min(1)),});export default function SearchScreen() { const raw = useGlobalSearchParams<"/search/[...terms]">(); const parsed = TermsParams.safeParse(raw); if (!parsed.success) { return <Text>Termos de busca ausentes</Text>; } return ( <View> <Text>Buscando: {parsed.data.terms.join(" · ")}</Text> </View> );}
O que isso demonstra:
useLocalSearchParams<"/places/[placeId]">() tipa as chaves de caminho; parâmetros de consulta são em grande parte manuais, a menos que declarados através do segundo genérico.
Zod valida segmentos de caminho UUID e abas de consulta enum após o TypeScript verificar a forma da rota.
router.push e <Link href={{ pathname, params }}> compartilham a mesma forma de objeto Href para rotas dinâmicas.
Rotas catch-all ([...terms]) precisam de transformações porque o Expo Router pode entregar string | string[].
Com experiments.typedRoutes: true, o Expo CLI gera tipos quando você executa npx expo start (ou npx expo customize tsconfig.json em CI).
As definições geradas ficam em .expo/types/router.d.ts e são importadas através de expo-env.d.ts - não edite ou confirme esses arquivos.
Href aceita caminhos estáticos ("/places") e formas HrefObject para segmentos dinâmicos ({ pathname: "/places/[placeId]", params: { placeId } }).
useLocalSearchParams<Route>() tipa os nomes dos segmentos de caminho para essa tela; as chaves de consulta são em grande parte manuais, a menos que declaradas através do segundo genérico.
Todos os valores de parâmetros chegam como strings (ou string[] para catch-alls e chaves repetidas) - o TypeScript não pode provar a coerção em tempo de execução.
Caminhos relativos (./profile) não fazem parte de Href; construa caminhos absolutos com useSegments() quando precisar de navegação relativa à aba.
import { type Href, useRouter, useSegments } from "expo-router";// Navegação de perfil relativa à aba sem strings href relativasfunction ProfileShortcut() { const [group] = useSegments(); const router = useRouter(); const href = `/${group}/profile` as Href; // cast após construir a partir dos segmentos return null; // conectar a um Pressable na UI real}// Restringir navegação imperativa por trás de um helperfunction navToPlace(placeId: string, tab: "overview" | "photos" = "overview") { const router = useRouter(); router.push({ pathname: "/places/[placeId]", params: { placeId, tab }, });}
Caminho de string para rotas dinâmicas - <Link href="/places/[placeId]" /> pode passar na verificação de tipos em configurações flexíveis, mas omite params obrigatórios. Correção: Use objetos { pathname, params } para cada segmento [param].
Confiar em parâmetros tipados como números - useLocalSearchParams<"/product/[id]">() ainda retorna id: string. Correção: Analise com z.coerce.number() ou Number() por trás da validação.
Tipos desatualizados após mover arquivos - Renomear rotas sem reiniciar o Metro deixa uniões Href desatualizadas. Correção: Reinicie npx expo start ou execute o comando de personalização em CI antes de tsc.
Parâmetros de consulta assumidos como auto-tipados - Apenas segmentos do sistema de arquivos aparecem nos tipos gerados. Correção: Adicione um segundo genérico a useLocalSearchParams ou um esquema Zod para as chaves ?tab= e ?ref=.
Surpresas de tipo catch-all - [...terms] pode ser string quando um segmento e string[] quando vários. Correção: Normalize para um array antes de juntar ou exibir.
Strings href relativas - ./settings é intencionalmente excluído de Href. Correção: Prefira com useSegments() ou codifique caminhos absolutos.
Parâmetros opcionais em push - Passar params: { id: undefined } pode converter "undefined" em string na URL. Correção: Omita chaves ou filtre antes da navegação.
Defina experiments.typedRoutes como true em app.json, execute npx expo customize tsconfig.json e, em seguida, inicie o servidor de desenvolvimento com npx expo start. Os tipos gerados são atualizados quando os arquivos de rota mudam.
Onde os tipos de rota gerados são armazenados?
O Expo CLI grava em .expo/types/ e os referencia a partir de um expo-env.d.ts ignorado pelo git na raiz do projeto. Não confirme ou edite manualmente esses arquivos.
Por que rotas dinâmicas devem usar objetos href?
Segmentos dinâmicos precisam de um mapa params ao lado de pathname. A forma de objeto garante que cada token [placeId] ou [id] receba um valor e corresponda à união Href gerada.
Como tipar parâmetros de consulta?
Passe um segundo genérico para useLocalSearchParams:
Qual a diferença entre useLocalSearchParams e useGlobalSearchParams?
useLocalSearchParams retorna parâmetros para o padrão de rota da tela atual. useGlobalSearchParams retorna os parâmetros mesclados para toda a árvore de navegação - útil para rotas catch-all e layouts aninhados, mas mais amplo em escopo.
Os parâmetros de rota são tipados como strings?
Sim. Segmentos de URL e valores de consulta são strings na fronteira nativa. Genéricos TypeScript nomeiam as chaves; Zod (ou similar) prova as formas de valor em tempo de execução.
Como navego imperativamente com tipos?
Importe router de expo-router ou chame useRouter(). Ambos aceitam caminhos Href: router.push("/places") para rotas estáticas e router.push({ pathname: "/places/[placeId]", params: { placeId } }) para rotas dinâmicas.
Posso usar rotas tipadas sem iniciar o Metro?
Execute npx expo customize tsconfig.json em CI antes de tsc para que os tipos existam sem um servidor de desenvolvimento. Para arquivos de rota novos, ainda regenere quando a árvore app/ mudar.
Armadilha: Por que o autocompletar não mostra minha nova rota?
Os tipos são gerados ao iniciar o servidor de desenvolvimento. Se você adicionou um arquivo enquanto o Metro estava ocioso, reinicie npx expo start. Confirme se experiments.typedRoutes é true e tsconfig.json inclui expo-env.d.ts.
Como as rotas catch-all tipam seus parâmetros?
Um arquivo search/[...terms].tsx mapeia para useLocalSearchParams<"/search/[...terms]">() com terms como string | string[]. Normalize para um array antes de juntar ou exibir.
Devo validar parâmetros se o TypeScript já os verifica?
Sim. Rotas tipadas provam a forma da rota, não a entrada do usuário. Deep links, códigos QR e intenções do sistema operacional podem fornecer valores malformados - valide na fronteira da tela.
Como os grupos de rotas afetam os tipos?
Grupos como (tabs) são omitidos da URL, mas aparecem nos caminhos dos arquivos. Os tipos gerados incluem segmentos de grupo quando aplicável - use a string literal exata que o TypeScript sugere ao passar genéricos para hooks.
Como construo links relativos à aba sem href relativo?
Leia os segmentos atuais com useSegments() e construa um caminho absoluto:
const [group] = useSegments();<Link href={`/${group}/profile` as Href}>Perfil</Link>
O Zod pode compartilhar esquemas entre API e parâmetros de rota?
Sim. Reutilize primitivos (z.string().uuid()) em esquemas de resposta de API e esquemas de parâmetros de rota. Mantenha objetos específicos da tela separados para evitar acoplar formas de busca a formas de URL.
E se eu migrar de listas de parâmetros do React Navigation?
Mapeie cada tela para uma rota de arquivo, habilite rotas tipadas e, em seguida, substitua gradualmente as entradas manuais RootStackParamList. Veja Tipagem Gradual em Aplicativos Brownfield para o ritmo de migração.