Como configurar e usar TypeScript strict mode de forma eficaz. Padrões práticos que melhoram a qualidade do código sem sacrificar velocidade de desenvolvimento.
Ednei Trabach
Desenvolvedor Full Stack
TypeScript strict mode ativa todas as verificações de tipo mais rigorosas. Embora pareça mais trabalho no início, paga dividendos enormes em manutenibilidade e prevenção de bugs.
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
// ✅ Bom: Tipos explícitos em funções públicas
interface UserProfile {
id: string
name: string
email: string
preferences: UserPreferences
}
async function getUserProfile(userId: string): Promise<UserProfile> {
const response = await fetch(`/api/users/${userId}`)
return response.json()
}
// ❌ Ruim: any implicit
async function getUserProfile(userId: string) {
const response = await fetch(`/api/users/${userId}`)
return response.json() // Retorna any
}
// Type guard para discriminar tipos
function isUserProfile(value: unknown): value is UserProfile {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'name' in value &&
'email' in value
)
}
// Uso seguro
if (isUserProfile(data)) {
console.log(data.name) // TypeScript sabe que é UserProfile
}
// Partial para formulários
interface User {
name: string
email: string
age: number
}
type UserForm = Partial<User>
// Pick para selecionar campos
type UserPreview = Pick<User, 'name' | 'email'>
// Omit para excluir campos
type CreateUser = Omit<User, 'id'>
// Required para tornar tudo obrigatório
type CompleteUser = Required<Partial<User>>
// Hook genérico
function useQuery<T>(
queryFn: () => Promise<T>,
options?: { enabled?: boolean }
) {
const [data, setData] = useState<T | null>(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
const fetch = async () => {
if (options?.enabled === false) return
setLoading(true)
try {
const result = await queryFn()
setData(result)
} catch (err) {
setError(err as Error)
} finally {
setLoading(false)
}
}
return { data, loading, error, refetch: fetch }
}
// Uso
const { data: user } = useQuery(() => getUserProfile('123'))
const { data: posts } = useQuery(() => getUserPosts('123'))
function processValue(value: string | number) {
if (typeof value === 'string') {
return value.toUpperCase() // TypeScript sabe que é string
}
return value * 2 // TypeScript sabe que é number
}
// Discriminated unions
type SuccessResponse = {
status: 'success'
data: UserProfile
}
type ErrorResponse = {
status: 'error'
error: string
}
type ApiResponse = SuccessResponse | ErrorResponse
function handleResponse(response: ApiResponse) {
if (response.status === 'success') {
return response.data // TypeScript sabe que tem data
}
throw new Error(response.error) // TypeScript sabe que tem error
}
// ❌ Evitar quando possível
const element = document.getElementById('app')!
element.classList.add('active')
// ✅ Melhor: verificar
const element = document.getElementById('app')
if (element) {
element.classList.add('active')
}
// ✅ Use quando você tem certeza
const user = getUser() // Sempre retorna usuário
user.email // Seguro usar
// useCounter.ts
import { ref, computed, type Ref } from 'vue'
export function useCounter(initialValue: number = 0) {
const count: Ref<number> = ref(initialValue)
const doubleCount = computed(() => count.value * 2)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
return {
count,
doubleCount,
increment,
decrement,
reset
}
}
<script setup lang="ts">
interface Props {
title: string
count?: number
user: {
name: string
email: string
}
}
const props = withDefaults(defineProps<Props>(), {
count: 0
})
</script>
import { useState, useEffect } from 'react'
interface User {
id: string
name: string
}
function useUser(userId: string) {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
fetchUser(userId).then(setUser).finally(() => setLoading(false))
}, [userId])
return { user, loading }
}
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking"
],
"rules": {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/explicit-function-return-type": "warn",
"@typescript-eslint/no-unused-vars": "error"
}
}
TypeScript strict mode não é sobre ser mais lento - é sobre ser mais rápido prevenindo bugs. Na minha experiência, o investimento inicial em configuração rigorosa se paga 10x em manutenibilidade e velocidade de refatoração.
Comece com strict mode ativado. A curva de aprendizado é real, mas os benefícios são duradouros.
Uma análise prática comparando Composition API e Options API no Vue.js 3. Quando usar cada abordagem e como elas impactam a organização do código.
Guia prático para criar Progressive Web Apps com Vue.js 3, Vite e Workbox. Transformando aplicações web em experiências nativas.