Typescript
hooks
react native
mobile development
React Native nos permite crear aplicaciones móviles nativas con la simplicidad declarativa de React. Cuando agregamos TypeScript, ganamos claridad y seguridad: sabemos qué tipo de datos maneja cada componente, y el editor nos ayuda a evitar errores en tiempo real.
En este artículo aprenderás tres pilares esenciales del desarrollo moderno con React Native:
useState
Antes de entender useState
, necesitamos imaginar cómo piensa React. Cada componente es como una pequeña máquina de estados; cuando esos datos cambian, la interfaz debe cambiar automáticamente.
En una app móvil real, el estado puede representar muchas cosas:
En React, estos valores cambiantes se guardan con el hook useState
. Este hook nos permite recordar datos entre renderizados y actualizarlos de forma reactiva, sin manipular la UI manualmente.
1import React, { useState } from 'react'; 2import { View, Text, Button } from 'react-native'; 3 4export default function Counter(): JSX.Element { 5 const [count, setCount] = useState<number>(0); 6 7 return ( 8 <View style={{ padding: 20 }}> 9 <Text style={{ fontSize: 24 }}>Has hecho clic {count} veces</Text> 10 <Button title="Incrementar" onPress={() => setCount(count + 1)} /> 11 </View> 12 ); 13}
Explicación
useState<number>(0)
crea una variable de estado count
que solo puede ser un número.setCount
actualiza el estado y provoca un re-render automático.💡 En Kotlin, esto sería similar a usar un MutableStateFlow<Int>
o un LiveData<Int>
observado por la UI.
useEffect
useEffect
se usa para ejecutar código después de que el componente se renderiza, o cuando cambian ciertos valores. Es el equivalente a onCreate
y onDestroy
en Android, o viewDidLoad
y viewDidDisappear
en iOS, pero expresado de forma declarativa.
1import React, { useEffect } from 'react'; 2import { View, Text } from 'react-native'; 3 4export default function SimpleEffect(): JSX.Element { 5 useEffect(() => { 6 console.log('🟢 Componente montado'); 7 8 return () => { 9 console.log('🔴 Componente desmontado'); 10 }; 11 }, []); 12 13 return ( 14 <View style={{ padding: 20 }}> 15 <Text style={{ fontSize: 20 }}>Observa la consola</Text> 16 </View> 17 ); 18}
Qué ocurre
return
).Equivalencias conceptuales:
Android | React Native | Swift |
---|---|---|
onCreate() | useEffect(() => {...}, []) | viewDidLoad() |
onDestroy() | return () => {...} | viewDidDisappear() |
1import React, { useState, useEffect } from 'react'; 2import { View, Text } from 'react-native'; 3 4export default function Timer(): JSX.Element { 5 const [seconds, setSeconds] = useState<number>(0); 6 7 useEffect(() => { 8 console.log('⏱ Temporizador iniciado'); 9 10 const interval = setInterval(() => { 11 setSeconds((prev) => prev + 1); 12 }, 1000); 13 14 return () => { 15 console.log('🧹 Temporizador detenido'); 16 clearInterval(interval); 17 }; 18 }, []); 19 20 return ( 21 <View style={{ padding: 20 }}> 22 <Text style={{ fontSize: 22 }}>Segundos: {seconds}</Text> 23 </View> 24 ); 25}
En Kotlin sería:
1override fun onCreate(savedInstanceState: Bundle?) { 2 timer = Timer() 3 timer.scheduleAtFixedRate(object : TimerTask() { 4 override fun run() { 5 seconds++ 6 } 7 }, 0, 1000) 8} 9 10override fun onDestroy() { 11 timer.cancel() 12}
Y en Swift:
1override func viewDidLoad() { 2 timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in 3 seconds += 1 4 } 5} 6 7override func viewDidDisappear(_ animated: Bool) { 8 timer.invalidate() 9}
🪄 En React Native lo haces con una sola estructura, más declarativa y predecible.
1import React, { useState, useEffect } from 'react'; 2import { View, Text, Button } from 'react-native'; 3 4export default function DependentEffect(): JSX.Element { 5 const [count, setCount] = useState<number>(0); 6 7 useEffect(() => { 8 console.log(`El contador cambió a: ${count}`); 9 }, [count]); // solo se ejecuta cuando cambia count 10 11 return ( 12 <View style={{ padding: 20 }}> 13 <Text style={{ fontSize: 20 }}>Contador: {count}</Text> 14 <Button title="Incrementar" onPress={() => setCount(count + 1)} /> 15 </View> 16 ); 17}
💡 En Kotlin podrías compararlo con observar un LiveData<Int>
y en Swift con una propiedad didSet
.
El renderizado condicional permite mostrar diferentes vistas según el estado.
1import React, { useState } from 'react'; 2import { View, Text, Button } from 'react-native'; 3 4export default function LoginExample(): JSX.Element { 5 const [loggedIn, setLoggedIn] = useState<boolean>(false); 6 7 return ( 8 <View style={{ padding: 20 }}> 9 {loggedIn ? ( 10 <> 11 <Text style={{ fontSize: 20 }}>¡Bienvenido de nuevo!</Text> 12 <Button title="Cerrar sesión" onPress={() => setLoggedIn(false)} /> 13 </> 14 ) : ( 15 <> 16 <Text style={{ fontSize: 20 }}>Por favor, inicia sesión</Text> 17 <Button title="Iniciar sesión" onPress={() => setLoggedIn(true)} /> 18 </> 19 )} 20 </View> 21 ); 22}
Esto es similar a usar un if
o un when
en Kotlin, pero dentro del propio JSX, mezclando lógica y presentación.
Para renderizar listas en React Native se usa FlatList
, optimizada para rendimiento.
1import React from 'react'; 2import { View, Text, FlatList } from 'react-native'; 3 4interface Task { 5 id: string; 6 text: string; 7} 8 9export default function TodoList(): JSX.Element { 10 const tasks: Task[] = [ 11 { id: '1', text: 'Aprender useState' }, 12 { id: '2', text: 'Practicar useEffect' }, 13 { id: '3', text: 'Dominar el renderizado condicional' }, 14 ]; 15 16 return ( 17 <View style={{ padding: 20 }}> 18 <FlatList 19 data={tasks} 20 keyExtractor={(item) => item.id} 21 renderItem={({ item }) => ( 22 <Text style={{ fontSize: 18, marginVertical: 4 }}>• {item.text}</Text> 23 )} 24 /> 25 </View> 26 ); 27}
Concepto | Kotlin | Swift / SwiftUI | React Native |
---|---|---|---|
Estado | LiveData , MutableStateFlow | @State , @Published | useState |
Efectos de ciclo de vida | onCreate() , onDestroy() | .onAppear {} , .onDisappear {} | useEffect |
Vista condicional | if , when | if , switch | {condición ? <A/> : <B/>} |
Listas dinámicas | RecyclerView | List | FlatList |
React Native con TypeScript te enseña a pensar en estados y efectos, no en instrucciones. El flujo de datos es unidireccional, y los componentes reaccionan automáticamente a los cambios, sin que tú tengas que “decirles qué hacer”.