Artigo original: How to Implement Redux in 24 Lines of JavaScript

90% convenção, 10% biblioteca.

O Redux está entre as bibliotecas JavaScript mais importantes já criadas. Inspirado pelo momento atual da técnica, com Flux e Elm , o Redux colocou a programação funcional JavaScript no mapa, introduzindo uma arquitetura escalável de três pontos simples.

Se você é novo no Redux, recomendo a leitura da documentação oficial primeiro.

O Redux é, principalmente, convenção

Considere este aplicativo de contador simples que usa a arquitetura do Redux. Se você gostou dele e quiser vê-lo por dentro, confira o repositório do Github dele.

redux-counter-app-demo

O state vive em uma única árvore

O state (ou estado, em português) do aplicativo é simples assim.

const initialState = { count: 0 };

As ações declaram as mudanças do state

Por convenção do Redux, não modificamos (alteramos/mutamos) diretamente o state.

// NÃO faça isso em um aplicativo Redux
state.count = 1;

Em vez disso, criamos todas as ações que o usuário pode aproveitar no aplicativo.

const actions = {
  increment: { type: 'INCREMENT' },
  decrement: { type: 'DECREMENT' }
};

Reducer interpreta a ação e atualiza o state

A última peça arquitetônica que falta é um redutor (a partir daqui chamado de reducer), que nada mais é que uma função pura que retorna uma nova cópia do seu estado com base no estado e na ação anterior.

  • Se increment for executado, incremente state.count.
  • Se decrement for executado, diminua state.count.
const countReducer = (state = initialState, action) => {
  switch (action.type) {
    case actions.increment.type:
      return {
        count: state.count + 1
      };

    case actions.decrement.type:
      return {
        count: state.count - 1
      };

    default:
      return state;
  }
};

Nada de Redux até agora

Você notou que ainda nem tocamos na biblioteca Redux? Acabamos de criar alguns objetos e uma função. E isso é o que quis dizer com o "principalmente, convenção": 90% do Redux não requer Redux!

Vamos implementar o Redux

Para colocar essa arquitetura em uso, devemos conectá-la a uma store, que é onde estará toda a árvore do seu aplicativo. Por enquanto, porém, implementaremos apenas a função createStore.

Ela fica assim:

import { createStore } from 'redux'

const store = createStore(countReducer);

store.subscribe(() => {
  console.log(store.getState());
});

store.dispatch(actions.increment);
// logs { count: 1 }

store.dispatch(actions.increment);
// logs { count: 2 }

store.dispatch(actions.decrement);
// logs { count: 1 }

Aqui está nosso clichê inicial. Precisaremos de uma lista de ouvintes (em inglês, listeners) e do state inicial fornecido pelo reducer.

const createStore = (yourReducer) => {
    let listeners = [];
    let currentState = yourReducer(undefined, {});
}

Sempre que alguém se inscreve em nossa store, ele é adicionado ao array de listeners. Isso é importante, porque toda vez que alguém envia uma ação, todos os listeners devem ser notificados em um loop.

Chamar o yourReducer com undefined e um objeto vazio retornará o initialState (estado inicial da aplicação) que configuramos acima. Isso nos dá um valor adequado para ser retornado quando chamamos store.getState(). Falando nisso, vamos criar esse método.

store.getState()

Esta é uma função que retorna o estado mais recente da store. Precisaremos disso para atualizar nossa interface do usuário sempre que o usuário clicar em um botão.

const createStore = (yourReducer) => {
    let listeners = [];
    let currentState = yourReducer(undefined, {});
    
    return {
        getState: () => currentState
    };
}

store.dispatch(ação)

Esta é uma função que recebe uma action (ação) como parâmetro. Ele provê a action e o currentState para o yourReducer obter um novo state. Em seguida, o dispatch notifica todos os inscritos na store.

const createStore = (yourReducer) => {
  let listeners = [];
  let currentState = yourReducer(undefined, {});

  return {
    getState: () => currentState,
    dispatch: (action) => {
      currentState = yourReducer(currentState, action);

      listeners.forEach((listener) => {
        listener();
      });
    }
  };
};

store.subscribe(listener)

Esta é uma função que permite que você seja notificado quando a store receber uma ação. É bom usar store.getState() aqui para obter seu estado mais recente e atualizar a interface do usuário.

const createStore = (yourReducer) => {
  let listeners = [];
  let currentState = yourReducer(undefined, {});

  return {
    getState: () => currentState,
    dispatch: (action) => {
      currentState = yourReducer(currentState, action);

      listeners.forEach((listener) => {
        listener();
      });
    },
    subscribe: (newListener) => {
      listeners.push(newListener);

      const unsubscribe = () => {
        listeners = listeners.filter((l) => l !== newListener);
      };

      return unsubscribe;
    }
  };
};

subscribe retorna uma função chamada unsubscribe que você pode chamar quando você não estiver mais interessado em ouvir as atualizações da store.

Tudo junto agora

Vamos conectar isso tudo aos nossos botões e ver o código-fonte final.

// função createStore simplificada
const createStore = (yourReducer) => {
  let listeners = [];
  let currentState = yourReducer(undefined, {});

  return {
    getState: () => currentState,
    dispatch: (action) => {
      currentState = yourReducer(currentState, action);

      listeners.forEach((listener) => {
        listener();
      });
    },
    subscribe: (newListener) => {
      listeners.push(newListener);

      const unsubscribe = () => {
        listeners = listeners.filter((l) => l !== newListener);
      };

      return unsubscribe;
    }
  };
};

// Partes da arquitetura Redux
const initialState = { count: 0 };

const actions = {
  increment: { type: 'INCREMENT' },
  decrement: { type: 'DECREMENT' }
};

const countReducer = (state = initialState, action) => {
  switch (action.type) {
    case actions.increment.type:
      return {
        count: state.count + 1
      };

    case actions.decrement.type:
      return {
        count: state.count - 1
      };

    default:
      return state;
  }
};

const store = createStore(countReducer);

// Elementos do DOM
const incrementButton = document.querySelector('.increment');
const decrementButton = document.querySelector('.decrement');

// Conecta eventos de clique a ações
incrementButton.addEventListener('click', () => {
  store.dispatch(actions.increment);
});

decrementButton.addEventListener('click', () => {
  store.dispatch(actions.decrement);
});

// Inicializa a exibição da interface do usuário
const counterDisplay = document.querySelector('h1');
counterDisplay.innerHTML = parseInt(initialState.count);

// Atualiza a interface do usuário quando uma ação é executada
store.subscribe(() => {
  const state = store.getState();

  counterDisplay.innerHTML = parseInt(state.count);
});

E mais uma vez aqui está nossa interface final.

redux-counter-app-demo-1

Se você estiver interessado no HTML/CSS que usei, aqui está o repositório do GitHub novamente!

Quer um coaching gratuito?

Se você quiser agendar uma ligação gratuita para discutir questões de desenvolvimento em front-end sobre código, entrevistas, carreira ou qualquer outra coisa, siga o autor no Twitter e envie uma mensagem direta para ele.

Depois disso, se você gostar desse primeira conversa com o autor, é possível discutir um treinamento contínuo para ajudá-lo a alcançar seus objetivos de desenvolvimento em front-end!

Vista suas contribuições

Se você está programando todos os dias, especialmente se estiver fazendo commits no GitHub, não seria legal usar esse mapa de contribuição para todos verem?

Gitmerch.com permite que você imprima uma camiseta do seu mapa de contribuições do GitHub! Use o código Yazeed na finalização da compra para obter um desconto.

git-merch-screenshot-1-1
git-merch-screenshot-2-1

Obrigado pela leitura

Para mais conteúdo como este, confira https://yazeedb.com!

Até a próxima vez!