React ofrece múltiples mecanismos para optimizar el rendimiento, estructurar lógica reutilizable y escalar aplicaciones complejas. A continuación se explican patrones clave desde una perspectiva práctica y actualizada, con ejemplos reescritos para claridad técnica y menor similitud léxica.
Caché selectiva de componentes con memoización inteligente
La memoización no es solo sobre evitar renders innecesarios: se trata de aplicar comparaciones semánticas cuando los props son objetos o funciones. En lugar de depender únicamente de React.memo, se recomienda combinarlo con funciones de comparación personalizadas:
const UserProfileCard = React.memo(({ user, onAction }) => (
<article className="profile-card">
<h2>{user.name}</h2>
<button onClick={() => onAction(user.id)}>Editar</button>
</article>
), (prevProps, nextProps) => {
// Evita re-render si solo cambia la referencia de la función
return prevProps.user.id === nextProps.user.id &&
prevProps.user.updatedAt === nextProps.user.updatedAt;
});
Hooks personalizados con comportamiento contextual
Un Hook efectivo encapsula lógica con estado, efectos secundarios y condiciones de uso claras. Por ejemplo, un Hook para manejar formularios con validación integrada:
function useForm(initialState, validator) {
const [data, setData] = useState(initialState);
const [errors, setErrors] = useState({});
const handleChange = useCallback((field, value) => {
setData(prev => ({ ...prev, [field]: value }));
if (errors[field]) {
setErrors(prev => ({ ...prev, [field]: null }));
}
}, [errors]);
const validate = useCallback(() => {
const validation = validator(data);
setErrors(validation);
return Object.keys(validation).length === 0;
}, [data, validator]);
return { data, errors, handleChange, validate };
}
// Uso en componente
function RegistrationForm() {
const { data, errors, handleChange, validate } = useForm(
{ email: '', password: '' },
(values) => ({
email: !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(values.email) ? 'Email inválido' : null,
password: values.password.length < 8 ? 'Mínimo 8 caracteres' : null
})
);
return (
<form onSubmit={(e) => { e.preventDefault(); validate() ? submit() : null; }}>
<input
value={data.email}
onChange={(e) => handleChange('email', e.target.value)}
/>
{errors.email && <span className="error">{errors.email}</span>}
<button type="submit">Enviar</button>
</form>
);
}
Renderizado del lado del servidor con hidratación robusta
Para SSR realista, no basta con renderToString: se requiere sincronización de estado entre servidor y cliente. Un enfoque moderno usa renderToPipeableStream (en React 18+) junto con createRoot y manejo de carga diferida:
// Servidor (Node.js + Express)
app.get('*', async (req, res) => {
const stream = renderToPipeableStream(
<ServerApp url={req.url} />,
{
bootstrapScripts: ['/static/client.js'],
onShellReady() {
res.statusCode = 200;
res.setHeader('Content-type', 'text/html');
stream.pipe(res);
}
}
);
});
Composición mediante render props y contextos especializados
Más allá de HOCs obsoletos, la composición moderna prioriza flexibilidad y tipado explícito. Un patrón efectivo combina Context API con render props para inyectar comportamientos sin acoplamiento fuerte:
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
function ThemedButton({ children, asChild = false }) {
const { theme } = useContext(ThemeContext);
const baseClass = `btn btn--${theme}`;
return asChild ? (
<>{children({ className: baseClass })}</>
) : (
<button className={baseClass}>{children}</button>
);
}
Gestión de estado distribuido sin librerías externas
Con useReducer y useContext, es posible construir un sistema de estado global ligero y escalable. Se evita el overhead de Redux mientras se mantiene previsibilidad:
const AppContext = createContext();
function AppProvider({ children }) {
const [state, dispatch] = useReducer(appReducer, initialState);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
}
// Reducer tipo "ducks"
function appReducer(state, action) {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.payload };
case 'TOGGLE_DARK_MODE':
return { ...state, darkMode: !state.darkMode };
default:
return state;
}
}
Carga diferida con soporte para código dividido y fallbacks dinámicos
El patrón React.lazy debe combinarse con estrategias de precarga y manejo de errores para experiencia robusta:
const LazyDashboard = lazy(() => import('./dashboard/Dashboard').catch(() => ({
default: () => <div className="fallback-error">Error al cargar panel</div>
}));
function DashboardRoute() {
return (
<Suspense fallback={<Spinner size="large" />} >
<LazyDashboard />
</Suspense>
);
}
Integración con elementos personalizados (Web Components)
React puede interoperar con Web Components estándar mediante envolturas que gestionen propiedades y eventos correctamente:
function CustomAlert({ message, visible, onClose }) {
useEffect(() => {
const element = document.querySelector('custom-alert');
if (element) {
element.message = message;
element.visible = visible;
element.addEventListener('close', onClose);
return () => element.removeEventListener('close', onClose);
}
}, [message, visible, onClose]);
return <custom-alert />;
}