Capítulo 3: Implementaciones efectivas de componentes personalizados
3.1 Diseño adaptativo
En la era de dispositivos multiplataforma, nuestras aplicaciones deben presentarse correctamente en pantallas de distintas dimensiones como teléfonos móviles, tablets y computadoras. El diseño adaptativo actúa como una prenda que se ajusta automáticamente al tamaño del cuerpo, permitiendo que nuestros componentes se adapten a cualquier tamaño de pantalla.
Principios fundamentales del diseño adpatativo:
- Sistema de puntos de ruptura: División de intervalos según el ancho de la pantalla
- Diseño flexible: Uso de unidades relativas y proporciones para definir dimensiones
- Contenido adaptable: Ajuste de la visualización según el espacio disponible
- Mejora progresiva: Diseño inicial para pantallas pequeñas con mejora gradual para pantallas grandes
Especificaciones de puntos de ruptura en HarmonyOS:
- xs (pequeño): 0-500vp, principalmente para móviles en modo vertical
- sm (mediano): 500-750vp, principalmente para móviles en modo horizontal y tablets pequeñas
- lg (grande): 750vp en adelante, principalmente para tablets grandes y computadoras
@Component
struct AdaptiveCard {
@Prop titulo: string
@Prop contenido: string
@State puntoRupturaActual: string = 'xs'
build() {
GridRow({
puntosRuptura: {
valor: ['500vp', '750vp'],
referencia: BreakpointsReference.WindowSize
}
}) {
GridCol({
span: {
xs: 12,
sm: 6,
lg: 3
}
}) {
Column() {
Text(this.titulo)
.fontSize(this.puntoRupturaActual === 'xs' ? 14 : 18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 10 })
Text(this.contenido)
.fontSize(this.puntoRupturaActual === 'xs' ? 12 : 14)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.width('100%')
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(10)
.shadow({
radius: 6,
color: '#00000010',
offsetX: 0,
offsetY: 1
})
}
}
.onBreakpointChange((puntoRuptura: string) => {
this.puntoRupturaActual = puntoRuptura
})
}
}
3.2 Optimización del rendimiento
La optimización del rendimiento es similar a ajustar el motor de un automóvil para mejorar su eficiencia. En el desarrollo de componentes, debemos enfocarnos en múltiples aspectos como el rendimiento de renderizado, el uso de memoria y la experiencia del usuario.
Estrategias principales de optimización:
- Reutilización de estilos: Uso de @Styles para evitar código repetido
- Carga perezosa: Implementación de LazyForEach para grandes conjuntos de datos
- Evitar renderizado innecesario: Manejo adecuado del estado para evitar repintados
- Carga anticipada de recursos: Pre-carga de recursos clave para reducir tiempos de espera
Optimización mediante reutilización de estilos
Utilizando el decorador @Styles, podemos encapsular combinaciones de estilos comunes en funciones reutilizables, reduciendo la redundancia de código y mejorando la mantenibilidad:
// Estilo común para tarjetas
@Styles function estiloTarjetaComun() {
.backgroundColor('#FFFFFF')
.borderRadius(10)
.padding(12)
.shadow({
radius: 6,
color: '#00000010',
offsetX: 0,
offsetY: 1
})
}
@Component
struct ComponenteOptimizado {
@Prop datos: any[]
build() {
// Optimización de listas grandes con LazyForEach
LazyForEach(this.datos, (item) => {
Column() {
Text(item.titulo)
.fontSize(14)
.fontWeight(FontWeight.Bold)
Text(item.contenido)
.fontSize(12)
.margin({ top: 6 })
}
.estiloTarjetaComun()
}, (item) => item.id)
}
}
3.3 Manejo de errores y depuración
Durante el desarrollo, debemos considerar diversas situaciones anómalas, permitiendo que los componentes manejen errores de forma elegante sin colapsar. Es como instalar fusibles en una casa que interrumpen el flujo de corriente excesiva para proteger el sistema.
Prácticas recomendadas para manejo de errores:
- Verificación de límites: Validación de parámetros de entrada
- Captura de excepciones: Uso de try-catch para manejar posibles errores
- Visualización de respaldo: Mostrar mensajes de error amigables cuando falle
- Registro de errores: Documentar errores para facilitar la depuración y optimización
@Component
struct ComponenteRobusto {
@State mensajeError: string = ''
@State isError: boolean = false
@State isLoading: boolean = false
private manejarError(error: Error) {
this.mensajeError = error.message
this.isError = true
this.isLoading = false
console.error('Error en el componente:', error)
// Puedes enviar el error a un servidor aquí
// this.reportarError(error)
}
private async realizarOperacion() {
try {
this.isLoading = true
this.isError = false
// Simulación de operación que puede fallar
await new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve('Operación exitosa')
} else {
reject(new Error('Conexión fallida'))
}
}, 1000)
})
this.isLoading = false
} catch (error) {
this.manejarError(error as Error)
}
}
build() {
Column() {
if (this.isError) {
Column() {
Text('😵 Error detectado')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#EF4444')
Text(this.mensajeError)
.fontSize(12)
.fontColor('#6B7280')
.margin({ top: 6 })
.textAlign(TextAlign.Center)
Button('Reintentar')
.onClick(() => {
this.realizarOperacion()
})
.margin({ top: 10 })
}
.justifyContent(FlexAlign.Center)
.height(180)
} else if (this.isLoading) {
Column() {
Text('⏳ Cargando...')
.fontSize(14)
}
.justifyContent(FlexAlign.Center)
.height(180)
} else {
Column() {
Text('✅ Componente funcional')
.fontSize(14)
Button('Ejecutar operación')
.onClick(() => {
this.realizarOperacion()
})
.margin({ top: 10 })
}
}
}
.width('100%')
.padding(18)
}
}
3.4 Pruebas de componentes
Las pruebas son esenciales para garantizar la calidad de los componentes, similar a inspeccionar productos en una fábrica. Con estrategias de prueba adecuadas, podemos detectar problemas antes de que afecten al usuario final.
Estrategias de prueba:
- Pruebas unitarias: Validación de funcionalidad básica y lógica
- Pruebas de integración: Verificación de colaboración entre componentes
- Pruebas UI: Evaluación de interacciones y visualización
- Pruebas de rendimiento: Aálisis del comportamiento con grandes volúmenes de datos
// Diseño de componentes probables
@Component
struct ComponenteProbable {
@Prop idPrueba?: string
@State contador: number = 0
@State estado: 'inactivo' | 'cargando' | 'éxito' | 'error' = 'inactivo'
// Interfaz clara para pruebas
public obtenerContador(): number {
return this.contador
}
public obtenerEstado(): string {
return this.estado
}
build() {
Column() {
Text(`Contador: ${this.contador}`)
.id(this.idPrueba ? `${this.idPrueba}-texto` : undefined)
.fontSize(14)
Text(`Estado: ${this.estado}`)
.id(this.idPrueba ? `${this.idPrueba}-estado` : undefined)
.fontSize(12)
.margin({ top: 6 })
Button('Aumentar')
.id(this.idPrueba ? `${this.idPrueba}-botón` : undefined)
.onClick(() => {
this.contador++
this.estado = 'éxito'
})
.margin({ top: 10 })
}
.padding(14)
}
}
3.5 Resumen y buenas prácticas
Tras tres capítulos de estudio, hemos dominado las técnicas para desarrollar componentes personalizados en HarmonyOS. Resumamos los principios más importantes y buenas prácticas:
Principios de diseño
- Principio de responsabilidad única
- Cada componente debe encargarse de una sola función específica
- Evitar crear componentes complejos "todo en uno"
- Combinar componentes simples para lograr funcionaliddaes complejas
- Principio de reutilización
- Diseñar considerando múltiples escenarios de uso
- Proporcionar flexibilidad mediante configuración de propiedades
- Evitar codificación rígida de lógica específica
- Principio de mantenibilidad
- Usar nomenclatura clara y consistente
- Incluir manejo completo de errores
- Documentar adecuadamente los componentes con comentarios
Puntos clave de optimización
- Optimización de renderizado
- Usar LazyForEach para listas grandes
- Reutilizar estilos con @Styles
- Evitar cálculos complejos dentro del método build
- Gestión de memoria
- Limpiar recursos al destruir componentes
- Evitar crear objetos innecesarios
- Usar caché de imágenes y carga perezosa adecuadamente
- Optimización de la experiencia del usuario
- Incluir estados de carga y error
- Usar animaciones y transiciones adecuadas
- Garantizar consistencia en diferentes dispositivos
Guía de diseño adaptativo
- Diseño de puntos de ruptura
- Usar el sistema estándar de puntos de ruptura de HarmonyOS (xs/sm/lg)
- Priorizar la experiencia en dispositivos móviles
- Mejorar progresivamente la experiencia en pantallas grandes
- Estrategias de diseño
- Usar unidades relativas (vp, porcentajes)
- Adoptar diseños flexibles y sistemas de rejilla
- Considerar adaptación a diferentes orientaciones
- Adaptación de contenido
- Ajustar tamaños de fuente según el tamaño de la pantalla
- Manejar truncamiento de texto y saltos de línea adecuadamente
- Optimizar la visualización de imágenes y contenido multimedia
Siguiendo estas buenas prácticas, podrás crear componentes de alta calidad, de buen rendimiento y fáciles de mantener en HarmonyOS, ofreciendo una excelente experiencia a los usuarios. Recuerda que un buen componente no solo debe ser funcional, sino también fácil de usar y mantener, lo que le dará el máximo valor en el trabajo en equipo.