Gestionar el estado de cualquier aplicación de WordPress — cómo maneja y organiza los datos — puede ser todo un reto. A medida que tu proyecto crece, controlar el flujo de datos y garantizar actualizaciones coherentes en todos los componentes resulta cada vez más difícil. El paquete de datos de WordPress puede ayudarte en este sentido, ya que proporciona una solución sólida para la gestión del estado.
Este artículo analiza el paquete de datos de WordPress, explorando sus conceptos clave, estrategias de implementación y mejores prácticas.
Presentación del paquete de datos de WordPress
El paquete de datos de WordPress — oficialmente @wordpress/data
— es una biblioteca de gestión de estados de JavaScript (ES2015 y versiones superiores) que proporciona una forma predecible y centralizada de gestionar el estado de las aplicaciones. Una implementación adecuada puede facilitar la creación de interfaces de usuario complejas y la gestión del flujo de datos en tu aplicación.
El paquete de datos de WordPress se inspira en Redux, una popular biblioteca de gestión de estados del ecosistema React.
Aquí, el módulo de datos funciona dentro del entorno de WordPress y proporciona integraciones con funcionalidades y API específicas de WordPress. Si construyes para el Editor de Bloques de WordPress — o es algo que tienes previsto soportar — el paquete será crucial para gestionar su estado. Si utilizas las mismas herramientas y patrones en tus propios plugins y temas, podrás crear una experiencia de desarrollo más coherente y familiar.
La relación del paquete con Redux
Aunque el paquete de datos de WordPress se inspira en Redux, no es una adaptación directa. Hay muchas adaptaciones para ajustarse al ecosistema de WordPress, con algunas diferencias clave entre ambas soluciones:
- El paquete de datos está diseñado para funcionar a la perfección con las APIs y la funcionalidad de WordPress, algo que Redux vanilla no puede hacer sin esa adaptación.
- En comparación con Redux, el paquete de datos proporciona una API más racionalizada. Esto puede facilitar la puesta en marcha.
- A diferencia de Redux, el paquete de datos incluye soporte integrado para acciones asíncronas. Si trabajas con la API REST de WordPress, esto te resultará útil.
El paquete de datos de WordPress también tiene algunas comparaciones con la API REST. Aunque ambos se ocupan de la gestión de datos, tienen propósitos diferentes:
- La API REST de WordPress proporciona una forma de interactuar con los datos de WordPress a través de HTTP. La utilizarás para aplicaciones externas, configuraciones headless de WordPress y en cualquier lugar donde necesites recuperar y manipular datos.
- El paquete de datos de WordPress proporciona un almacén centralizado para los datos y el estado de la interfaz de usuario. Es una forma de manejar el flujo de datos y las actualizaciones dentro de tu aplicación.
En muchos casos, utilizarás ambos a la vez: la API REST para obtener y actualizar datos en el servidor y el paquete de datos de WordPress para gestionar esos datos dentro de tu aplicación.
Conceptos clave y terminología del paquete de datos de WordPress
El paquete de datos de WordPress ofrece una forma intuitiva de gestionar el estado. Se refiere a los datos dentro de un almacén. Representa el estado actual de tu aplicación y puede incluir tanto el estado de la interfaz de usuario (como si hay un modal abierto) como el estado de los datos (como una lista de entradas).
En este contexto, un almacén es el eje central del paquete de datos de WordPress. Contiene todo el estado del sitio y proporciona los métodos para acceder y actualizar ese estado. En WordPress, puedes tener varios almacenes. Cada uno será responsable de un área específica de tu sitio.
Para gestionar esos almacenes, necesitas un registro. Este objeto central proporciona métodos para registrar nuevos almacenes y acceder a los existentes. Un registro contendrá almacenes, y esos almacenes contendrán el estado de tu aplicación.
Hay varias formas de trabajar con el estado:
- Las acciones describen los cambios en un estado. Son objetos JavaScript planos y son la única forma de activar actualizaciones de estado. Las acciones suelen tener una propiedad
type
, que describe la acción. También puede incluir datos adicionales. - Los selectores extraen partes específicas del estado del almacén. Estas funciones te permiten acceder a los datos de estado sin necesidad de interactuar directamente con la estructura del almacén. Los resolvedores están relacionados y se encargan de la obtención asíncrona de datos. Los utilizas para asegurarte de que puedes acceder a los datos necesarios de un almacén antes de ejecutar un selector.
- Los reductores especifican cómo debe cambiar el estado en respuesta a las acciones. Toman el estado actual y una acción como argumentos y devuelven un nuevo objeto de estado. Las funciones de control permiten a los reductores manejar operaciones asíncronas complejas sin efectos secundarios.
Es necesario que comprendas estos conceptos fundamentales, ya que todos ellos trabajan juntos para crear un sólido sistema de gestión de estados con los almacenes como núcleo.
Almacenes: el eje central del paquete de datos de WordPress
Los almacenes son los contenedores del estado de tu aplicación y proporcionan los métodos para interactuar con él. El paquete de datos de WordPress agrupa otros paquetes, y cada uno de ellos registra almacenes para el directorio de bloques, el Editor de Bloques, el core, la edición de entradas, etc.
Cada almacén tendrá un espacio de nombres único, como core
, core/editor
, y core/notices
. Los plugins de terceros también registrarán almacenes, por lo que deberás elegir espacios de nombres únicos para evitar conflictos. En cualquier caso, los almacenes que registres estarán en el registro por defecto en la mayoría de los casos.
Este objeto central tiene algunas responsabilidades:
- Registrar nuevos almacenes.
- Proporcionar acceso a los almacenes existentes.
- Gestionar las suscripciones a los cambios de estado.
Aunque no sueles tener una interacción directa con el registro, necesitas comprender su papel en la forma en que el paquete de datos organiza la gestión de estados en WordPress.
Interacción básica con los almacenes de datos de WordPress
Si utilizas JavaScript ES2015+ y estás trabajando con un plugin o tema de WordPress, puedes incluirlo como dependencia:
npm install @wordpress/data --save
Dentro de tu código, importarás las funciones necesarias del paquete en la parte superior del archivo:
import { select, dispatch, subscribe } from '@wordpress/data';
Interactuar con almacenes existentes de WordPress requiere que utilices algunas de las funciones que importas. Acceder a los datos de estado con select
, por ejemplo:
const posts = select('core').getPosts();
Lo mismo ocurre para enviar acciones:
dispatch('core').savePost(postData);
La suscripción a cambios de estado utiliza un formato ligeramente diferente, pero el concepto es el mismo:
subscribe(() => {
const newPosts = select('core').getPosts();
// Update your UI based on the new posts
});
Sin embargo, no siempre trabajarás con los almacenes predeterminados. A menudo, trabajarás con almacenes adicionales existentes o registrarás los tuyos propios.
Cómo registrar un almacén de datos de WordPress
Definir la configuración de tu almacén y registrarlo con el paquete de datos de WordPress comienza con la importación de la función register
:
…
import { createReduxStore, register } from '@wordpress/data';
…
Ésta toma un único argumento — el descriptor de tu almacén. A continuación, debes definir un estado predeterminado para el almacén para establecer sus valores por defecto:
…
const DEFAULT_STATE = {
todos: [],
};
…
A continuación, crea un objeto actions
, define una función reducer
para gestionar las actualizaciones de estado, y crea un objeto selectors
con funciones para acceder a los datos de estado:
const actions = {
addTodo: (text) => ({
type: 'ADD_TODO',
text,
}),
};
const reducer = (state = DEFAULT_STATE, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, { text: action.text, completed: false }],
};
default:
return state;
}
};
const selectors = {
getTodos: (state) => state.todos,
};
Para crear la configuración de la tienda, defínela utilizando el objeto createReduxStore
. Esto inicializará las acciones, selectores, control y otras propiedades de tu tienda:
const store = createReduxStore('my-plugin/todos', {
reducer,
actions,
selectors,
});
Lo mínimo que necesita este objeto es un reductor para definir la forma del estado y cómo cambia en respuesta a otras acciones. Por último, registra la tienda, llamando al descriptor de tienda que definas con createReduxStore
:
register(store);
Ahora puedes interactuar con tu tienda personalizada como lo harías con otras:
import { select, dispatch } from '@wordpress/data';
// Add a new todo
dispatch('my-plugin/todos').addTodo('Learn WordPress data package');
// Get all todos
const todos = select('my-plugin/todos').getTodos();
La clave en el uso del paquete de datos de WordPress es cómo utilizas las distintas propiedades y objetos que tienes a tu disposición.
Desglosando las cinco propiedades del paquete de datos de WordPress
Gran parte del uso del paquete de datos de WordPress ocurre de manera «inversa» — definiendo propiedades de almacén de datos de bajo nivel antes que el propio almacén. El objeto createReduxStore
es un ejemplo perfecto, ya que reúne todas las definiciones que haces para crear el descriptor que utilizas para registrar un almacén:
import { createReduxStore } from '@wordpress/data';
const store = createReduxStore( 'demo', {
reducer: ( state = 'OK' ) => state,
selectors: {
getValue: ( state ) => state,
},
} );
Estas otras propiedades también necesitan configuración.
1. Acciones
Las acciones son la forma principal de provocar cambios de estado en tu almacén. Son simples objetos JavaScript que describen lo que debe ocurrir. Como tales, puede ser una buena idea crearlas primero, ya que puedes decidir qué estados quieres recuperar.
const actions = {
addTodo: (text) => ({
type: 'ADD_TODO',
text,
}),
toggleTodo: (index) => ({
type: 'TOGGLE_TODO',
index,
}),
};
Los creadores de acciones toman argumentos opcionales y devolverán un objeto para pasarlo al reductor que definas:
const actions = {
updateStockPrice: (symbol, newPrice) => {
return {
type: 'UPDATE_STOCK_PRICE',
symbol,
newPrice
};
},
Si pasas un descriptor de almacén, puedes enviar creadores de acciones y actualizar el valor del estado:
dispatch('my-plugin/todos').updateStockPrice('¥', '150.37');
Considera los objetos de acción como instrucciones para el reductor sobre cómo realizar cambios de estado. Como mínimo, es probable que quieras definir acciones de creación, actualización, lectura y eliminación (CRUD). También podría ser que tuvieras un archivo JavaScript separado para los tipos de acción y crearas un objeto para todos esos tipos, especialmente si los defines como constantes.
2. Reductores
Aquí merece la pena hablar del reductor, por su papel central junto a las acciones. Su trabajo consiste en especificar cómo debe cambiar el estado en respuesta a las instrucciones que recibe de una acción. Si le pasas las instrucciones de la acción y el estado actual, puede devolver un nuevo objeto de estado y pasarlo a lo largo de la cadena:
const reducer = (state = DEFAULT_STATE, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, { text: action.text, completed: false }],
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map((todo, index) =>
index === action.index ? { ...todo, completed: !todo.completed } : todo
),
};
default:
return state;
}
};
Ten en cuenta que un reductor debe ser una función pura, y no debe mutar el estado que acepta (más bien, debe devolverlo actualizado). Los reductores y las acciones tienen una relación simbiótica en muchos aspectos, por lo que es importante comprender cómo funcionan juntos.
3. Selectores
Para acceder al estado actual desde un almacén registrado, necesitas selectores. Es la forma principal de «exponer» el estado de tu almacén, y ayudan a mantener tus componentes desacoplados de la estructura interna del almacén:
const selectors = {
getTodos: (state) => state.todos,
getTodoCount: (state) => state.todos.length,
};
Puedes llamar a esos selectores con la función select
:
const todoCount = select('my-plugin/todos').getTodoCount();
Sin embargo, un selector no envía esos datos a ninguna parte: simplemente los revela y proporciona acceso a ellos.
Los selectores pueden recibir tantos argumentos como necesites para acceder al estado con precisión. El valor que devuelve es el resultado de lo que consigan esos argumentos dentro del selector que definas. Al igual que con las acciones, puedes optar por tener un archivo aparte para guardar todos tus selectores, ya que podrían ser muchos.
4. Controles
Para guiar el flujo de ejecución de la funcionalidad de tu sitio — o ejecutar la lógica dentro de él — es donde utilizas los controles. Éstos definen el comportamiento de los flujos de ejecución de tus acciones. Considéralos los asistentes del paquete de datos de WordPress, ya que funcionan como intermediarios para recoger el estado que hay que pasar a los resolvers.
Los controles también gestionan los efectos secundarios de tu almacén, como las llamadas a la API o las interacciones con las API del navegador. Te permiten mantener limpios los reductores y, al mismo tiempo, manejar operaciones asíncronas complejas:
const controls = {
FETCH_TODOS: async () => {
const response = await fetch('/api/todos');
return response.json();
},
};
const actions = {
fetchTodos: () => ({ type: 'FETCH_TODOS' }),
};
Este ciclo de obtención y devolución de datos es crucial para todo el proceso. Pero sin una llamada desde una acción, no podrás utilizar esos datos.
5. Resolvers
Los selectores exponen el estado de un almacén, pero no envían explícitamente esos datos a ninguna parte. Los resolvers se encuentran con los selectores (y los controles) para recuperar los datos. Al igual que los controles, también pueden gestionar la obtención asíncrona de datos.
const resolvers = {
getTodos: async () => {
const todos = await controls.FETCH_TODOS();
return actions.receiveTodos(todos);
},
};
El resolver se asegura de que los datos que pides están disponibles en el almacén antes de ejecutar un selector. Esta estrecha conectividad entre el resolver y el selector significa que deben coincidir los nombres. Esto es para que el paquete de datos de WordPress pueda entender qué resolver debe invocar en función de los datos que solicites.
Además, el resolver siempre recibirá los mismos argumentos que pases a una función selectora, y también devolverá, cederá o enviará objetos de acción.
Gestión de errores al utilizar el paquete de datos de WordPress
Debes implementar una gestión adecuada de los errores cuando trabajes con el paquete de datos de WordPress. Si decides tratar con operaciones asíncronas, trabajar con despliegues full stack o hacer llamadas a la API, es aún más vital.
Por ejemplo, si envías acciones que implican operaciones asíncronas, un bloque try-catch podría ser una buena opción:
const StockUpdater = () => {
// Get the dispatch function
const { updateStock, setError, clearError } = useDispatch('my-app/stocks');
const handleUpdateStock = async (stockId, newData) => {
try {
// Clear any existing errors
clearError();
// Attempt to update the stock
await updateStock(stockId, newData);
} catch (error) {
// Dispatch an error action if something goes wrong
setError(error.message);
}
};
return (
<button onClick={() => handleUpdateStock('AAPL', { price: 150 })}>
Update Stock
</button>
);
};
Para los reductores, puedes gestionar acciones de error y actualizar el estado:
const reducer = (state = DEFAULT_STATE, action) => {
switch (action.type) {
// ... other cases
case 'FETCH_TODOS_ERROR':
return {
...state,
error: action.error,
isLoading: false,
};
default:
return state;
}
};
Cuando utilices selectores, puedes incluir la comprobación de errores para gestionar posibles problemas y, a continuación, comprobar si hay errores en tus componentes antes de utilizar los datos.:
const MyComponent = () => {
// Get multiple pieces of state including error information
const { data, isLoading, error } = useSelect((select) => ({
data: select('my-app/stocks').getStockData(),
isLoading: select('my-app/stocks').isLoading(),
error: select('my-app/stocks').getError()
}));
// Handle different states
if (isLoading) {
return <div>Loading...</div>;
}
if (error) {
return (
<div className="error-message">
<p>Error loading stocks: {error.message}</p>
<button onClick={retry}>Try Again</button>
</div>
);
}
return (
<div>
{/* Your normal component render */}
</div>
);
};
Las funciones useSelect
y useDispatch
te dan mucho poder para manejar errores dentro del paquete de datos de WordPress. Con ambas puedes pasar mensajes de error personalizados como argumentos.
Es una buena práctica asegurarte de centralizar tu estado de error durante la configuración inicial, y mantener los límites de error a nivel de componente. Emplear la gestión de errores para los estados de carga también ayudará a mantener tu código claro y coherente.
Cómo integrar el almacén de datos de WordPress con tu sitio web
Hay muchas cosas que el paquete de datos de WordPress puede hacer para ayudarte a gestionar el estado. Aunar todo esto es también una consideración práctica. Analicemos un teletipo bursátil que muestra y actualiza datos financieros en tiempo real.
La primera tarea es crear un almacén para tus datos:
import { createReduxStore, register } from '@wordpress/data';
const DEFAULT_STATE = {
stocks: [],
isLoading: false,
error: null,
};
const actions = {
fetchStocks: () => async ({ dispatch }) => {
dispatch({ type: 'FETCH_STOCKS_START' });
try {
const response = await fetch('/api/stocks');
const stocks = await response.json();
dispatch({ type: 'RECEIVE_STOCKS', stocks });
} catch (error) {
dispatch({ type: 'FETCH_STOCKS_ERROR', error: error.message });
}
},
};
const reducer = (state = DEFAULT_STATE, action) => {
switch (action.type) {
case 'FETCH_STOCKS_START':
return { ...state, isLoading: true, error: null };
case 'RECEIVE_STOCKS':
return { ...state, stocks: action.stocks, isLoading: false };
case 'FETCH_STOCKS_ERROR':
return { ...state, error: action.error, isLoading: false };
default:
return state;
}
};
const selectors = {
getStocks: (state) => state.stocks,
getStocksError: (state) => state.error,
isStocksLoading: (state) => state.isLoading,
};
const store = createReduxStore('my-investing-app/stocks', {
reducer,
actions,
selectors,
});
register(store);
Este proceso configura un estado por defecto que incluye los estados de error y carga, junto con tus acciones, reductores y selectores. Una vez definidos éstos, puedes registrar el almacén.
Visualizar los datos del almacén
Una vez creado el almacén, puedes crear un componente React para mostrar la información que contiene:
import { useSelect, useDispatch } from '@wordpress/data';
import { useEffect } from '@wordpress/element';
const StockTicker = () => {
const stocks = useSelect((select) => select('my-investing-app/stocks').getStocks());
const error = useSelect((select) => select('my-investing-app/stocks').getStocksError());
const isLoading = useSelect((select) => select('my-investing-app/stocks').isStocksLoading());
const { fetchStocks } = useDispatch('my-investing-app/stocks');
useEffect(() => {
fetchStocks();
}, []);
if (isLoading) {
return <p>Loading stock data...</p>;
}
if (error) {
return <p>Error: {error}</p>;
}
return (
<div className="stock-ticker">
<h2>Stock Ticker</h2>
<ul>
{stocks.map((stock) => (
<li key={stock.symbol}>
{stock.symbol}: ${stock.price}
</li>
))}
</ul>
</div>
);
};
Este componente incorpora los hooks useSelect
y useDispatch
(junto con otros) para gestionar el acceso a los datos, el envío de acciones y la gestión del ciclo de vida del componente. También establece mensajes personalizados de error y de estado de carga, además de algo de código para mostrar realmente el teletipo. Una vez hecho esto, ahora tienes que registrar el componente en WordPress.
Registrar el componente con WordPress
Si no lo registras en WordPress, no podrás utilizar los componentes que crees. Esto significa registrarlo como un Bloque, aunque podría ser un widget si diseñas para Temas Clásicos. Este ejemplo utiliza un Bloque.
import { registerBlockType } from '@wordpress/blocks';
import { StockTicker } from './components/StockTicker';
registerBlockType('my-investing-app/stock-ticker', {
title: 'Stock Ticker',
icon: 'chart-line',
category: 'widgets',
edit: StockTicker,
save: () => null, // This will render dynamically
});
Este proceso seguirá el enfoque típico que seguirás para registrar Bloques dentro de WordPress, y no requiere ninguna implementación o configuración especial.
Gestionar las actualizaciones de estado y las interacciones de los usuarios
Una vez registrado el Bloque, tienes que gestionar las interacciones del usuario y las actualizaciones en tiempo real. Para ello necesitarás algunos controles interactivos, junto con HTML y JavaScript personalizados:
const StockControls = () => {
const { addToWatchlist, removeFromWatchlist } = useDispatch('my-investing-app/stocks');
return (
<div className="stock-controls">
<button onClick={() => addToWatchlist('AAPL')}>
Add Apple to Watchlist
</button>
<button onClick={() => removeFromWatchlist('AAPL')}>
Remove from Watchlist
</button>
</div>
);
};
Para las actualizaciones en tiempo real, puedes establecer un intervalo dentro del componente React:
useEffect(() => {
const { updateStockPrice } = dispatch('my-investing-app/stocks');
const interval = setInterval(() => {
stocks.forEach(stock => {
fetchStockPrice(stock.symbol)
.then(price => updateStockPrice(stock.symbol, price));
});
}, 60000);
return () => clearInterval(interval);
}, [stocks]);
Este enfoque mantiene los datos de tu componente sincronizados con tu tienda, al tiempo que mantiene una clara separación de preocupaciones. El paquete de datos de WordPress se encargará de todas las actualizaciones de estado, lo que proporciona coherencia a tu aplicación.
Renderizado del lado del servidor
Por último, puedes configurar la renderización del lado del servidor para asegurarte de que los datos de la tienda están actualizados al cargar la página. Esto requiere algunos conocimientos de PHP:
function my_investing_app_render_stock_ticker($attributes, $content) {
// Fetch the latest stock data from your API
$stocks = fetch_latest_stock_data();
ob_start();
?>
<div class="stock-ticker">
<h2>Stock Ticker</h2>
<ul>
<?php foreach ($stocks as $stock) : ?>
<li><?php echo esc_html($stock['symbol']); ?>: $<?php echo esc_html($stock['price']); ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php
return ob_get_clean();
}
register_block_type('my-investing-app/stock-ticker', array(
'render_callback' => 'my_investing_app_render_stock_ticker'
));
Este enfoque proporciona una integración completa de tu almacén de datos con WordPress, encargándose de todo, desde la renderización inicial hasta las actualizaciones en tiempo real y las interacciones del usuario.
Resumen
El paquete de datos de WordPress es una forma compleja pero robusta de gestionar los estados de la aplicación para tus proyectos. Más allá de los conceptos clave hay un sinfín de funciones, operadores, argumentos y mucho más. Pero recuerda que no todos los datos deben estar en un almacén global — el estado local de los componentes sigue teniendo cabida en tu código.
¿Te ves utilizando el paquete de datos de WordPress de forma habitual, o tienes otro método para gestionar el estado? Comparte tus opiniones con nosotros en los comentarios.
Deja una respuesta