Store
Como já comentamos anteriormente, utilizamos o VUEX para fazer nossa gestão de estados, se você ainda não está familiarizado, pode ler sobre ele aqui.
Mas vamos dar uma breve explicação de como nós utilizamos.
O que sempre está presente na nossa store:
- State
- Mutations
- Actions
- Getters
- Nossa ‘máquina’ de estados
State/Estados
São os onde os dados ficam armazenados;
Mutations/Mutações
São as funções responsáveis pela mutação dos estados, ou seja, são as únicas que realmente podem ir lá e falar: “Ok estado, mude!!”
Actions/Ações
Funções responsáveis por fazer a conexão com o SDK e pegar as informações necessárias e mandar para as mutações, os dados já tratados se precisarem se alguma lógica a mais, para realizarem a mudança de estado.
Getters
Utilizamos os getters a maiorida das vezes para pegarmos as permissões que o usuário tem ao mexer no escopo.
Máquina de estados As ações assíncronas que são colocadas no store geralmente precisam de um controle de status. Isso é feito com uma máquina de estados. Essa máquina de estados é colocada com o decorator withStatefulBehaviorOn(store, {name: ‘nome do método pra decorar’})
Padrões da Store
Ações com controle de estado
As ações assíncronas que são colocadas no store geralmente precisam de um controle de status. Isso é feito com uma máquina de estados. Essa máquina de estados é colocada com o decorator withStatefulBehaviorOn(store, {name: ‘nome do método pra decorar’})
export default withStatefulBehaviorOn(
createUserStore(sdk),
{ name: "fetchUsers" },
{ name: "nextPage", status: "fetchUsers" },
{ name: "previousPage", status: "fetchUsers" }
{ name: "saveUser" },
{ name: "deleteUser" },
{ name: "deleteMultipleUsers" },
{ name: "getUser" },
{ name: "getUserDetails" },
);
Esse decorator cria no store um atributo chamado actionStatus
:
{
actionStatus: {
fetchUsers: "ok";
saveUser: "ok";
deleteUser: "ok";
deleteMultipleUsers: "ok";
getUser: "ok";
getUserDetails: "ok";
}
}
A máquina de estados possui os seguintes estados:
export const STATUS = {
empty: "",
inProgress: "inProgress",
ok: "ok",
notFound: "notFound",
notReady: "notReady",
invalid: "invalid",
duplicated: "duplicated",
unauthenticated: "unauthenticated",
forbidden: "forbidden",
error: "error",
rule: "rule",
internal: "internal",
};
Com isso, nos componentes de view é possível reagir aos status utilizando um Watcher. Isso geralmente é utilizado para enviar notificações para os usuários ou colocar os componentes exibindo o loading ou erro.
Ações
Nomes de metodos
O store costuma ser dividido em 2 grupos de métodos. Os métodos de init e os métodos disponíveis pós init.
Os métodos init, são utilizados para preparar(limpar dados já existentes, buscar dados relacionados que serão utilizados no caso de uso, etc.) o store para um determinado caso de uso. Não é obrigatório uso do init, apenas se necessário
Por exemplo:
- initList
- initForm
- initDetails
Para os métodos que tem a responsabilidade de buscar dados, é utilizado:
- fetchEntities
Para ações que manipulam uma entidade em específica, é utilizado:
- getEntity
- saveEntity
- deleteEntity
Além disso, também é utilizado o mesmo padrão para os casos mais específicos:
- applyEntity
- importEntity
Também existem as ações que não são relacionadas à busca de dados. Estas ações devem ser nomeadas de acordo com sua intenção. Por exemplo:
- changeService
- changeReadScope
- updateEntity
- clean
Mutações
Nomes de métodos
As mutações que mudam um valor todo da variável do state, deve ser utilizado o prefixo set. Por exemplo:
- setEntities
- setOptions
Para métodos que adicionam valor em um array, deve ser utilizado o prefixo add. Por exemplo:
- addEntity
- addOption
Para atualizações parciais em objetos, deve ser utilizado o prefixo update. Por exemplo:
- updateForm
- updateQuery
Estados
Nomes das variáveis
O estado de um módulo do store, geralmente é composto por objetos de edição, detalhes e listagem, podendo ter mais ou menos informações.
Quando completo, o store utiliza a seguinte nomenclatura para as variáveis do state.
- entity
- entityDetails
- entities
Além disso, existem 2 principais variáveis que auxiliam o cadastro/edição. Normalmente são elas:
- isNew
- isValid
Caso seja necessário buscar dados relacionados, os mesmos podem seguir o padrão do state. Para um dado, e para muitos, respectivamente:
- entity
- entities
Como criar uma store de um módulo
A store fica diretamente dentro do módulo que você está criando.
Vamos utilizar o mesmo exemplo do tutorial do SDK e do Schema, um módulo de produtos com um CRUD simples.
Então, seria algo como:
/products
/components
/views
index.js
store.js
store.spec.js
Na store.js, teríamos:
import { STATUS, isError, isSuccess } from "@/consts";
import { cloneDeep, every, isUndefined } from "lodash";
// Nossas "máquinas" para nos auxiliar quando criamos formulários
import { createForm, updateFields } from "@/utils/formFields";
// Importamos os fields do Schema
import { fields, productSchema } from "./schemas";
// Importamos o SDK
import { sdk } from "@/deps";
import { withStatefulBehaviorOn } from "@/utils/store";
// Esse é o estado puro, que não é alterado
const initialState = {
products: [],
product: createForm(fields),
productDetails: {},
validations: userSchema.getDefaultState(),
isValid: false,
isNew: false,
};
// Esse é o estado que alteramos, ele é um clone do estado inicial
const state = cloneDeep(initialState);
const mutations = {
// Mutação responsável por limpar os estados
clean(state) {
const cleanState = cloneDeep(initialState);
state.product = cleanState.product;
state.isNew = cleanState.isNew;
state.validations = clean.validations;
state.isValid = clean.isValid;
productSchema.reset();
},
setIsNew(state, isNew) {
state.isNew = isNew;
},
setProducts(state, products) {
state.products = products;
},
setProductsDetails(state, productDetails) {
state.productDetails = productDetails;
},
updateForm(state, product) {
updateFields(state.validations, state.product, product);
},
setFieldErrors(state, fields) {
for (let field in fields) {
productSchema.setErrorField(field.name, field.errors);
}
},
validate(state) {
const errors = productSchema.validate({ value: state.product });
state.validations = errors.$subfields;
state.isValid = !errors.$error;
},
};
const actions = (sdk) => ({
// Listagem dos produtos
initList(context) {
const { commit, dispatch } = context;
commit("clean");
dispatch("fetchProducts");
},
// Formulário (repare que é passado o id, caso você está editando)
async initForm({ commit, dispatch }, { id } = {}) {
commit("clean");
commit("setIsNew", isUndefined(id));
commit("updateForm");
if (!isUndefined(id)) {
await dispatch("getProduct", { id });
}
},
// Detalhes
initDetails({ commit, dispatch }, { id }) {
commit("clean");
dispatch("getProductDetails", { id });
},
// Preenchendo o formulário
updateForm({ commit, state }, field) {
commit("updateForm", field);
commit("validate");
},
// Trás todos os produtos existentes
async fetchProducts({ commit }) {
const resp = await sdk.products.getProducts();
if (resp.success) {
commit("setProducts", resp.payload);
}
return resp;
},
// Trás um produto, filtrando pelo ID
async getProduct({ commit }, { id }) {
const resp = await sdk.products.getProduct({ id });
if (resp.success) {
commit("updateForm", resp.payload);
}
return resp;
},
// Trás um produto, filtrando pelo ID
async getProductsDetails({ commit }, { id }) {
const resp = await sdk.products.getProduct({ id });
if (resp.success) {
commit("setProductsDetails", resp.payload);
}
return resp;
},
// Cria ou altera um produto
async saveProducts({ commit, state }) {
const resp = await (state.isNew
? sdk.products.createProduct(state.product)
: sdk.products.updateProduct(state.product));
if (resp.success) {
commit("updateForm", resp.payload);
} else if (resp.httpStatus === HTTP_STATUS.conflict) {
commit("setFieldErrors", resp.errors);
commit("validate");
}
return resp;
},
// Deleta um produto
deleteProduct(_, { id }) {
return sdk.products.deleteProduct({ id });
},
});
// Nossos getters, responsável por trazer as permissões que
// cada usuário tem em cada tela
const getters = {
permissions(_, __, ___, rootGetters) {
return {
...rootGetters.token.getScope("products"),
audit: rootGetters.token.getScope("audit.logs").read,
};
},
};
// Exporta noss store
export function createProductsStore(sdk) {
return {
namespaced: true,
state,
mutations,
getters,
actions: actions(sdk),
};
}
// Nossa máquina de estados
export default withStatefulBehaviorOn(
createProductsStore(sdk),
{ name: "fetchProducts" },
{ name: "getProduct" },
{ name: "saveProduct" },
{ name: "deleteProduct" }
);