Gerenciar o estado de qualquer aplicativo WordPress – ou seja, como ele lida e organiza os dados — pode ser desafiador. À medida que o projeto cresce, acompanhar o fluxo de dados e garantir atualizações consistentes entre os componentes se torna cada vez mais difícil. O pacote de dados do WordPress pode ajudar nesse aspecto, pois oferece uma solução robusta para o gerenciamento de estado.

Este artigo analisa o pacote de dados do WordPress, explorando seus principais conceitos, estratégias de implementação e práticas recomendadas.

Introdução ao pacote de dados do WordPress

O pacote de dados do WordPress – oficialmente @wordpress/data – é uma biblioteca de gerenciamento de estado em JavaScript (ES2015 e superior) que oferece uma maneira previsível e centralizada de gerenciar o estado do aplicativo. A implementação correta pode ajudar a facilitar a criação de interfaces de usuário complexas e a lidar com o fluxo de dados em todo o seu aplicativo.

O pacote de dados do WordPress se inspira no Redux, uma biblioteca de gerenciamento de estado popular no ecossistema React.

A página inicial do Redux apresenta uma seção de cabeçalho roxa que contém o logotipo e o título do Redux, juntamente com um slogan e um botão **Get Started**. Abaixo disso, há quatro seções principais com ícones que destacam os recursos. Cada seção inclui um texto descritivo explicando as capacidades do Redux, como gerenciamento de estado de aplicações, ferramentas de depuração e compatibilidade com ecossistemas. A barra de navegação superior contém links para várias outras páginas do site e funcionalidade de busca.
O site oficial do Redux.

Aqui, o módulo de dados funciona no ambiente do WordPress e fornece integrações com APIs e funcionalidades específicas do WordPress. Se você cria algo no Editor de Blocos do WordPress — ou precisa oferecer suporte a ele — o pacote será crucial para gerenciar seu estado. Usando as mesmas ferramentas e padrões em seus próprios plugins e temas, você pode criar uma experiência de desenvolvimento mais consistente e familiar.

A relação do pacote com o Redux

Embora o pacote de dados do WordPress se inspire no Redux, ele não é uma porta direta. Há muitas adaptações para se adequar ao ecossistema do WordPress, com algumas diferenças importantes entre as duas soluções:

  • O pacote de dados foi projetado para funcionar perfeitamente com as APIs e a funcionalidade do WordPress, o que o Redux vanilla não pode fazer sem essa adaptação.
  • Em comparação com o Redux, o pacote de dados oferece uma API mais simplificada. Isso pode facilitar os primeiros passos.
  • Ao contrário do Redux, o pacote de dados inclui suporte integrado para ações assíncronas. Se você trabalha com a API REST do WordPress, isso será útil.

O pacote de dados do WordPress também tem algumas comparações com a API REST. Embora ambos lidem com o gerenciamento de dados, eles têm finalidades diferentes:

  • A API REST do WordPress oferece uma maneira de você interagir com os dados do WordPress por HTTP. Você a usará para aplicativos externos, configurações do WordPress headless e em qualquer lugar em que precise recuperar e manipular dados.
  • O pacote de dados do WordPress fornece um armazenamento centralizado para dados e estado da interface do usuário. É uma maneira de lidar com o fluxo de dados e as atualizações em seu aplicativo.

Em muitos casos, você usará os dois juntos: A API REST para buscar e atualizar dados no servidor e o pacote de dados do WordPress para gerenciar esses dados no aplicativo.

Principais conceitos e terminologia do pacote de dados do WordPress

O pacote de dados do WordPress oferece uma maneira intuitiva de gerenciar estados. Isso se refere aos dados em um store. Ele representa a condição atual do seu aplicativo e pode incluir o estado da interface do usuário (por exemplo, se há um modal aberto) e o estado dos dados (como uma lista de artigos).

O estado dos seus dados de artigos é uma área que o pacote de dados do WordPress gerencia.
O estado dos seus dados de artigos é uma área que o pacote de dados do WordPress gerencia.

Nesse contexto, um store é o núcleo central do pacote de dados do WordPress. Ele armazena todo o estado do site e fornece os métodos necessários para acessar e atualizar esse estado. No WordPress, é possível ter múltiplos stores, cada um responsável por uma área específica do seu site.

Para gerenciar esses stores, você precisa de um registry. Esse objeto central oferece métodos para registrar novos stores e acessar os existentes. Um registry armazenará os stores, e esses stores armazenarão o estado do seu aplicativo.

Existem algumas maneiras de trabalhar com estados:

  • Actions descrevem as mudanças em um estado. São objetos simples em JavaScript e a única forma de acionar atualizações no estado. Normalmente, actions têm uma propriedade type, que descreve a ação, e podem incluir dados adicionais.
  • Selectors extraem partes específicas do estado do store. Essas funções permitem que você acesse dados de estado sem a necessidade de interação direta com a estrutura do store. Resolvers estão relacionados e lidam com a busca assíncrona de dados. Eles garantem que você possa acessar os dados necessários em um store antes de executar um selector.
  • Reducers especificam como o estado deve mudar em resposta para actions. Eles recebem o estado atual e uma ação como argumentos e retornam um novo objeto de estado. Funções de controle permitem que os reducers lidem com operações assíncronas complexas sem efeitos colaterais.

Você precisa entender esses conceitos fundamentais, pois todos eles trabalham juntos para criar um sistema robusto de gerenciamento de estado com lojas em seu coração.

Stores: O núcleo central do pacote de dados do WordPress

Os stores são os contêineres para o estado do seu aplicativo e fornecem os métodos para interagir com ele. O pacote de dados do WordPress reúne alguns outros pacotes, e cada um deles registra stores para o diretório de blocos, Editor de Blocos, núcleo, edição de artigos e mais.

Cada store terá um namespace único, como core, core/editor e core/notices. Plugins de terceiros também registrarão stores, então é necessário escolher namespaces únicos para evitar conflitos. No entanto, os stores que você registrar estarão no registro padrão.

Esse objeto central tem algumas responsabilidades:

  • Registrar novos stores.
    Fornecer acesso aos stores existentes.
    Gerenciar assinaturas para mudanças no estado.

Embora você não interaja diretamente com o registry com frequência, é importante entender seu papel na organização do gerenciamento de estado pelo pacote de dados no WordPress.

Interação básica com os data stores do WordPress

Se você usa JavaScript ES2015+ e está trabalhando com um plugin ou tema do WordPress, pode incluí-lo como uma dependência:

npm install @wordpress/data --save

No seu código, você importará as funções necessárias do pacote na parte superior do arquivo:

import { select, dispatch, subscribe } from '@wordpress/data';

Para interagir com os stores existentes no WordPress, você utilizará algumas das funções importadas. Por exemplo, para acessar dados de estado com select:

const posts = select('core').getPosts();

O mesmo acontece com o envio de actions:

dispatch('core').savePost(postData);

A assinatura de mudanças de estado utiliza um formato ligeiramente diferente, mas o conceito permanece o mesmo:

subscribe(() => {
  const newPosts = select('core').getPosts();
  // Update your UI based on the new posts
});

No entanto, você nem sempre trabalhará com os stores padrão. Muitas vezes, será necessário usar stores adicionais existentes ou registrar seus próprios.

Como registrar um store de dados no WordPress

Definir a configuração do seu store e registrá-lo no pacote de dados do WordPress começa com a importação da função register:

…
import { createReduxStore, register } from '@wordpress/data';
…

Isso requer um único argumento: O descriptor do seu store. O próximo passo é definir um estado padrão para o store, configurando os valores iniciais:

…
const DEFAULT_STATE = {
  todos: [],
};
…

Em seguida, crie um objeto actions, defina uma função reducer para tratar as atualizações de estado e crie um objeto selectors com funções para acessar os dados 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 criar a configuração do store, defina-a usando o objeto createReduxStore. Isso inicializará as actions, selectors, control e outras propriedades do seu store:

const store = createReduxStore('my-plugin/todos', {
  reducer,
  actions,
  selectors,
});

O requisito mínimo desse objeto é um reducer, que define a estrutura do estado e como ele muda em resposta a outras ações. Por fim, registre o store chamando o descriptor que você definiu com o createReduxStore:

register(store);

Agora você pode interagir com seu store personalizado como faria com outros:

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();

A chave para usar o pacote de dados do WordPress é como você usa as diferentes propriedades e objetos à sua disposição.

Detalhando as cinco propriedades do armazenamento de dados do WordPress

Grande parte do uso do pacote de dados do WordPress acontece “de trás para frente” – definindo propriedades de baixo nível do armazenamento de dados antes do próprio armazenamento. O objeto createReduxStore é um exemplo perfeito, pois reúne todas as definições que você faz para criar o descriptor que usa para registrar um armazenamento:

import { createReduxStore } from '@wordpress/data';
  const store = createReduxStore( 'demo', {
    reducer: ( state = 'OK' ) => state,
    selectors: {
    getValue: ( state ) => state,
    },
  } );

Essas outras propriedades também precisam de instalação e configuração.

1. Actions

As actions são a principal maneira de acionar mudanças de estado no seu store. Elas são objetos simples em JavaScript que descrevem o que deve acontecer. Por isso, é uma boa prática defini-las primeiro, já que você pode decidir quais estados deseja manipular.

const actions = {
  addTodo: (text) => ({
    type: 'ADD_TODO',
    text,
  }),
  toggleTodo: (index) => ({
    type: 'TOGGLE_TODO',
    index,
  }),
};

Os criadores de actions podem receber argumentos opcionais e retornar um objeto que será enviado ao reducer definido:

const actions = {
  updateStockPrice: (symbol, newPrice) => {
  return {
    type: 'UPDATE_STOCK_PRICE',
    symbol,
    newPrice
  };
},

Se você passar um descriptor de store, poderá despachar os criadores de actions e atualizar o valor do estado:

dispatch('my-plugin/todos').updateStockPrice('¥', '150.37');

Considere os objetos de action como instruções para o reducer sobre como realizar mudanças no estado. No mínimo, é recomendável definir actions para criar, atualizar, ler e excluir (CRUD). Você também pode criar um arquivo JavaScript separado para armazenar os tipos de action e organizá-los em um objeto, especialmente se forem definidos como constantes.

2. Reducer

O reducer tem um papel central junto às actions. Sua função é especificar como o estado deve mudar em resposta às instruções recebidas de uma action. Quando recebe as instruções de uma action e o estado atual, ele retorna um novo objeto de estado e o passa adiante na chain:

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;
    }
};

É importante lembrar que um reducer deve ser uma função pura. Ele não deve alterar o estado original recebido, mas sim retornar uma cópia atualizada com as mudanças aplicadas. Os reducers e as actions possuem uma relação simbiótica, o que torna essencial compreender como eles trabalham juntos para criar um fluxo eficiente de gerenciamento de estado.

3. Selectors

Os selectors são utilizados para acessar o estado atual de um store registrado. Eles são a principal forma de “expor” o estado do store, ajudando a manter os componentes desacoplados da estrutura interna do store:

const selectors = {
  getTodos: (state) => state.todos,
  getTodoCount: (state) => state.todos.length,
};

Você pode chamar esses selectors com a função select:

const todoCount = select('my-plugin/todos').getTodoCount();

Um selector não envia dados para lugar algum; ele apenas os revela e fornece acesso.

Os selectors podem receber quantos argumentos forem necessários para acessar o estado com precisão. O valor retornado será o resultado do que esses argumentos definirem dentro do selector. Assim como nas actions, você pode criar um arquivo separado para armazenar todos os seus selectors, especialmente se houver muitos.

4. Controls

Os controls guiam o fluxo de execução das funcionalidades do site ou executam a lógica dentro delas. Eles definem o comportamento dos fluxos de execução das actions. No pacote de dados do WordPress, os controls funcionam como intermediários, reunindo o estado para passar aos resolvers.

Os controls também lidam com efeitos colaterais no seu store, como chamadas de APIs ou interações com APIs do navegador. Eles permitem manter os reducers limpos, enquanto possibilitam lidar com operações assíncronas complexas:

const controls = {
  FETCH_TODOS: async () => {
    const response = await fetch('/api/todos');
    return response.json();
  },
};

const actions = {
  fetchTodos: () => ({ type: 'FETCH_TODOS' }),
};

Este ciclo de busca e retorno de dados é essencial para todo o processo. No entanto, sem uma chamada a partir de uma action, você não poderá usar esses dados.

5. Resolvers

Os selectors expõem o estado de um store, mas não enviam os dados explicitamente para lugar algum. É aí que entram os resolvers, que encontram os selectors (e os controls) para buscar os dados. Assim como os controls, os resolvers também podem lidar com busca assíncrona de dados:

const resolvers = {
  getTodos: async () => {
    const todos = await controls.FETCH_TODOS();
    return actions.receiveTodos(todos);
  },
};

O resolver garante que os dados solicitados estejam disponíveis no store antes de executar um selector. Essa conectividade entre o resolver e o selector exige que seus nomes correspondam, permitindo ao pacote de dados do WordPress identificar qual resolver invocar com base nos dados solicitados.

Além disso, o resolver sempre receberá os mesmos argumentos que você passa para uma função de selector, e também pode retornar, produzir ou despachar objetos de action.

Tratamento de erros ao usar o pacote de dados do WordPress

É fundamental implementar um tratamento de erros adequado ao trabalhar com o pacote de dados do WordPress. Isso é especialmente importante ao lidar com operações assíncronas, implantações full stack ou chamadas de API.

Por exemplo, se você despachar actions que envolvem operações assíncronas, o uso de um bloco try-catch pode ser uma boa opção:

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>
  );
};

Nos reducers, você pode lidar com ações de erro e atualizar o 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;
  }
};

Ao usar selectors, você pode incluir verificações de erro para lidar com problemas potenciais e então verificar a presença de erros nos seus componentes antes de usar os dados. Isso garante que erros sejam identificados e tratados de forma preventiva, evitando comportamentos inesperados no aplicativo.

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>
  );
};

As funções useSelect e useDispatch dão a você muito poder para lidar com erros no pacote de dados do WordPress. Com ambas, você pode passar mensagens de erro personalizadas como argumentos.

É uma boa prática garantir que você centralize seu estado de erro durante a configuração inicial e manter limites de erros no nível do componente. Implementar o tratamento de erros para estados de carregamento também ajuda a manter o código claro e consistente.

Como integrar o data store do WordPress ao seu site

O pacote de dados do WordPress oferece muitas funcionalidades para ajudar a gerenciar estados. Consolidar tudo isso é uma consideração prática. Vamos analisar um exemplo de um ticker financeiro que exibe e atualiza dados financeiros em tempo real.

A primeira tarefa é criar um store para seus dados:

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);

Esse processo configura um estado padrão que inclui estados de erro e de carregamento, juntamente com suas actions, reducers e selectors. Após definir esses elementos, você pode registrar o store.

Exibindo os dados do store

Com o store configurado, você pode criar um componente React para exibir as informações contidas nele:

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 utiliza os hooks useSelect e useDispatch (além de outros) para lidar com acesso aos dados, despachar ações e gerenciar o ciclo de vida do componente. Ele também define mensagens personalizadas para estados de erro e carregamento, além de incluir o código para exibir o ticker. Com isso, o próximo passo é registrar o componente no WordPress.

Registrando o componente no WordPress

Sem o registro no WordPress, você não poderá usar os componentes criados. Isso significa registrá-lo como um Bloco, embora também possa ser registrado como um widget caso você esteja desenvolvendo para Temas Clássicos. Este exemplo utiliza um Bloco:

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
});

Esse processo segue o método típico para registrar Blocos no WordPress e não exige implementações ou configurações especiais.

Gerenciando atualizações de estado e interações do usuário

Depois de registrar o bloco, você precisa gerenciar as interações do usuário e as atualizações em tempo real. Para isso, você precisará de alguns controles interativos, além de HTML e 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 atualizações em tempo real, você pode configurar um intervalo no 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]);

Essa abordagem mantém os dados do componente sincronizados com o store, enquanto preserva uma separação clara de responsabilidades. O pacote de dados do WordPress gerenciará todas as atualizações de estado, garantindo consistência no aplicativo.

Renderização no lado do servidor

Por fim, você pode configurar a renderização no lado do servidor para garantir que os dados de estoque estejam atualizados no carregamento da página. Isso requer algum conhecimento 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'
));

Essa abordagem integra completamente o seu data store ao WordPress, lidando com tudo, desde a renderização inicial até atualizações em tempo real e interações do usuário.

Resumo

O pacote de dados do WordPress é uma ferramenta complexa, mas poderosa, para gerenciar estados de aplicativos em seus projetos. Além dos conceitos-chave, existe uma vasta gama de funções, operadores, argumentos e outros elementos a explorar. No entanto, lembre-se de que nem todos os dados precisam estar em um store global — o estado local dos componentes ainda tem seu lugar no código.

Você se imagina usando o pacote de dados do WordPress regularmente, ou prefere outro método de gerenciamento de estado? Compartilhe sua opinião conosco nos comentários abaixo.

Steve Bonisteel Kinsta

Steve Bonisteel é um Editor Técnico na Kinsta que começou sua carreira de escritor como jornalista impresso, cobrindo ambulâncias e caminhões de bombeiros. Ele tem coberto tecnologia relacionada à Internet desde o final dos anos 1990.