Redux: exemplos - como implementar namespacing de tipo de ação?

Criado em 23 set. 2015  ·  32Comentários  ·  Fonte: reduxjs/redux

Passei cerca de uma semana tentando encontrar boas implementações de namespacing de tipo de ação e lancei o meu próprio, que é baseado em classes de Transição que contém a definição de um criador de ação, redutor e uma lista de suas transições filhas, mas esta abordagem requer uma maneira muito código padrão e é problemático quando você tem muitas ações muito simples.

Em todos os exemplos que você pode encontrar neste repo e em muitos outros, este problema é sempre ignorado por algum motivo, mas este foi o primeiro problema que eu identifiquei quando comecei a pesquisar no Redux.

Estou faltando alguma coisa aqui? Vocês não têm colisões de nomenclatura de tipo de ação?

Alguém pode atualizar os exemplos e me mostrar como você lida com esse problema ou me indicar a direção certa?

Meu caso específico está relacionado a ter 2 painéis de administração separados no frontend. Um para testadores e outro para clientes que terão seus estados armazenados em seções separadas da loja ("testerAccount", "customerAccount"), mas ambos serão capazes de fazer coisas semelhantes, por exemplo, ADD_VIDEO_UPLOAD, ADD_COMMENT, etc, etc.

Eu realmente aprecio sua ajuda aqui :)

question

Comentários muito úteis

Strings não são inerentemente ruins para namespacing. URLs são strings e parecem funcionar bem.

Todos 32 comentários

Colocar accountType em ações? Veja também examples/real-world/reducers para saber como você pode escrever uma fábrica de redutores e usar o mesmo código muitas vezes, produzindo redutores que respondem a ações diferentes.

Da mesma forma, não se esqueça de que os criadores de ações são apenas funções e você pode criar funções que retornam um monte de outras funções.

Acho que realmente ajudaria se você fornecesse um pequeno exemplo específico do problema que você diz ser ignorado nos exemplos existentes.

Podemos então ajudá-lo a encontrar maneiras de simplificar esse exemplo específico. É muito difícil sugerir algo específico em resposta a uma pergunta sem código.

@gaearon Eu sei onde você está indo com isso, mas não estou tentando criar redutores reutilizáveis, criadores de ação ou coisas DRY, muito pelo contrário.

Estou tentando isolar grupos de ações uns dos outros para que eu possa ter 2 pessoas trabalhando em duas seções diferentes do sistema e ter certeza de que eles não começarão a disparar os redutores uns dos outros acidentalmente devido à falta de namespacing nos nomes de tipo de ação.

Espero que isso torne isso mais claro.

Resolvi esse problema em meu (s) projeto (s) usando https://www.npmjs.com/package/flux-constant em combinação com uma função auxiliar super simples chamada createActionTypes .

Essencialmente, meu código acaba sendo:

// create-action-types.js
var fluxConstant = require('flux-constant');
module.exports = function (types) {
    return fluxConstant.set(types);
};

// in foo-action-types.js
module.exports = createAtionTypes([
    'ADD_FOO',
    'REMOVE_FOO'
]);

// in some-store.js
var fooActionTypes = require('foo-action-types');
function (state, action) {
    switch(action.type) {
        case fooActionTypes.ADD_FOO: 

        case fooActionTypes.REMOVE_FOO:
   }
}

Por que não manter todos os tipos de ação como constantes em um só lugar?
Então eles certamente não podem entrar em conflito porque você não pode exportar o mesmo nome duas vezes.

Para visualizar melhor, digamos que este seja o estado inicial da loja:


let initialState = {
  "testerAccount": {
    "videoUploads": [],
    "messages": []
  },
  "customerAccount": {
    "videoUploads": [],
    "messages": []
  },
  "systemUserAccount": {
    "videoUploads": [],
    "messages": []
  }
};

Como você evitará colisões de nomes de tipo de ação ao implementar redutores separados para adicionar vídeos ou mensagens para cada uma dessas seções?

@gaearon o que você está sugerindo não resolve o cerne do problema, é mais uma abordagem de fita adesiva porque com o tempo você acabará com um arquivo tipo enorme que causará muitos problemas.

Isso causará imediatamente problemas durante as mesclagens de código que mais tarde precisarão ser resolvidos com hacks de nomenclatura, por exemplo:

ADD_VIDEO_UPLOAD, ADD_TESTER_VIDEO_UPLOAD, ADD_VIDEO_UPLOAD_IN_SOME_SECTION, etc.

o que é uma grande dor de cabeça novamente.

@koulmomo Isso é exatamente o que eu estava procurando, obrigado :): +1: Simples e poderoso.

@gaearon Acho que devemos ter pelo menos um exemplo que usa este pacote ou um novo pacote que poderia ser chamado de redux-constant?

Na minha opinião, a abordagem de promoção nos docs / examples, que resolve o problema de namespacing deve ser o novo padrão.

Obrigado por sua ajuda com isso :)

Em seu exemplo, eu realmente não entendo por que não ter um conjunto de tipos, um gerador de redutor e um conjunto de criadores de ação. As ações conteriam accountType propriedade para diferenciá-los. Os criadores de ações aceitariam isso como um parâmetro. A fábrica de redutor aceitaria accountType e retornaria um redutor que só lida com ações com este tipo de conta.

Não acho que flux-constant seja uma ótima solução aqui. Parece depender de cheques de instanceof . O que significa que você não pode serializar suas ações e depois reproduzi-las porque - bam! - seus tipos desserializados não corresponderão aos gerados.

Muitas vezes as pessoas tentam tornar o Flux “mais simples” sem perceber que estão quebrando seus recursos essenciais .

Em seu exemplo, eu realmente não entendo por que não ter um conjunto de tipos, um gerador de redutor e um conjunto de criadores de ação. As ações conteriam a propriedade accountType para diferenciá-las. Os criadores de ações aceitariam isso como um parâmetro. A fábrica de redutor aceitaria accountType e retornaria um redutor que só lida com ações com esse tipo de conta.

Fui enganado pela aparente simetria do seu exemplo. Eu entendo agora que você quis dizer que não há DRY aqui, e a simetria nos recursos é apenas aparente, conforme você disse em https://github.com/rackt/redux/issues/786#issuecomment -142649749.

Não acho que haja necessidade de uma biblioteca aqui. Estabeleça uma convenção! Por exemplo, se seus subprojetos ou recursos são tão separados, crie uma regra para chamar os tipos de ação feature/ACTION_TYPE , por exemplo, testers/UPLOAD_VIDEO ou customers/UPLOAD_VIDEO . Isso é especialmente interessante se esses “grupos” realmente correspondem a pastas reais (ou melhor, pacotes) no disco.

As violações serão fáceis de detectar nas revisões de código. Se você _realmente_ quiser, pode automatizar isso, mas não consigo ver o que isso traz sobre o namespacing manual. Dentro de cada módulo, você ainda deseja declarar todas as constantes em um arquivo para facilitar o controle dos recursos, evitando a duplicação acidental de esforços e como uma documentação.

Eu sou totalmente a favor de adicionar um exemplo huge-apps com divisão de código, tipos de ação com espaço de nomes, etc. Isso seria um problema separado.

Strings não são inerentemente ruins para namespacing. URLs são strings e parecem funcionar bem.

@gaearon Você está certo, esqueci o problema de serialização com a solução baseada em constante de fluxo.

Não tenho problemas com namespaces baseados em string, desde que possamos distinguir facilmente os módulos / namespaces no próprio nome.

A solução baseada na convenção de nomenclatura é algo que eu já vi antes:

https://github.com/erikras/ducks-modular-redux

mas eu esperava que pudesse haver uma maneira melhor ou "correta" de fazer isso, baseada em sua experiência.

Acho que tentarei converter a abordagem da constante de fluxo em algo que você possa serializar de alguma forma.

Por favor, adicione um exemplo de "aplicativos enormes" se você encontrar tempo para isso, porque isso vai economizar muito tempo para outras pessoas e talvez isso leve ao estabelecimento de algum tipo de convenção que poderíamos facilmente seguir mais tarde.

Obrigado novamente :)

Sim, desculpe, infelizmente não estou trabalhando em um grande aplicativo há algum tempo e, mesmo quando o fazia, na verdade gostava que tivéssemos um único arquivo gigante com constantes agrupadas em seções porque fornece uma boa visão geral do que pode acontecer no aplicativo.

Eu procuraria ver esse "problema" explicado com mais detalhes nos documentos. Eu tenho / tive as mesmas perguntas como @pbc. Não penso "Por que não manter todos os tipos de ação como constantes em um só lugar?" funciona se você reutilizar vários módulos em projetos e a recomendação "Estabeleça uma convenção!" parece muito com algo como BEM na terra do CSS. No entanto, mudamos os módulos BEM para CSS e acho que algo semelhante aos módulos CSS, cujos nomes de classe de hash são necessários para tipos de ação em grandes projetos também.

Até onde eu sei, não há como conseguir serialização e ausência de conflitos.

Uma boa convenção para módulos reutilizáveis ​​pode usar "provedores de exclusividade" já existentes, como nomes de domínios reversos ou nome de usuário / repo no github ou nome de módulo registrado no npm.


EDIT: módulos CSS fazem isso definindo uma linguagem customizada em cima do CSS e prefixando os nomes das classes durante o pré-processamento (se resume à convenção de qualquer maneira, mas é gerado).

1 para a pergunta e a discussão. Eu também lutei muito com esse problema exato como @pbc . A confusão para mim é que temos CombineReducers para redutores, o que faz uma bela árvore de estados, mas as ações, por outro lado, parecem mais ou menos globais. Ao que parece, qualquer tipo de namespacing deve ser feito manualmente.

Acho que encontrei uma solução para tipos de ação de string serializáveis. Acho que é uma daquelas situações em que nossas mentes impõem limitações artificiais a algo.

A ideia básica é que as constantes de tipo não precisam estar relacionadas de forma alguma com o valor da string. Portanto, você pode usar valores gerados aleatoriamente, caminhos de arquivo com hash ou qualquer outra coisa exclusiva para o valor de string constante de tipo. Em seu redutor, você importa a constante de tipo por nome e a compara por nome. Esse nome pode ser usado por outras ações e em outros redutores, mas não importa, pois o valor não será o mesmo.

Exemplo aqui: https://gist.github.com/samsch/63a54e868d7fa2b6023a

Isso é sensato. Você ainda deseja reter alguma parte legível por humanos no nome da ação, mas gerar prefixos exclusivos funciona totalmente.

Se você puder provar que eles são únicos, é claro.
A melhor maneira ainda é usar provedores de exclusividade já existentes: nomes de domínio reversos ou nomes de módulo npm.

O namespacing com convenção tem seus perigos quando você está escrevendo módulos que podem ser usados ​​em vários lugares que estão fora de seu controle.

Digamos que você tenha um módulo "Geometria", com ActionType de "Área". Este módulo é usado em dois lugares:

  1. AppA-> Desenho-> Geometria (namespace = "Desenho / Geometria / Área")
  2. AppB-> Trigonometria-> Forma-> Geometria (namespace = "Trigonometria / Forma / Área")

Agora você tem dois namespaces conflitantes, dependendo de onde seu módulo é usado.

  • Não é uma boa ideia codificar este caminho completo no seu módulo Geometria para ActionType "Área".
  • Em vez disso, mantenha o nome simples: "Área".
  • De maneira semelhante à composição do redutor, componha o namespace fazendo com que cada contendo o pai adicione um prefixo.

Estou experimentando o seguinte padrão:

crie um diretório para cada tipo de coleção que contenha:

  • consts
  • redutor
  • componentes
  • sagas

crie consts no seguinte formato:

// song-store/song-store-consts.js
export const ADD = 'SONG_STORE.ADD'
export const REMOVE = 'SONG_STORE.REMOVE'

Ao usar constantes em redutores, saga ou ações import todos eles com * :

// song-store/song-store-actions.js
import * as SONG_STORE from './song-store-consts'

export function addSongStore(name) {
  return {
    type: SONG_STORE.ADD,
    name
  }
}

export function removeSongStore(songStoreId) {
  return {
    type: SONG_STORE.REMOVE,
    songStoreId
  }
}

Infelizmente não é ótimo para tremer de árvores. Seria bom se ES6 permitisse:

import { ADD, REMOVE } as SONG_STORE from './song-store-actions'

Alguém sabe se o Webpack 2 pode inteligentemente agitar import * forma que não agrupe o código para exportações que não são usadas mesmo se importadas com * ?

Este parece ser um problema muito comum e aparece com frequência em nosso escritório:

  • Conflitos de nomes causam comportamento indesejável.
  • Reclamar sobre o boilerplate associado à criação de tipos de ação exclusivos.

Tentamos algumas soluções diferentes, mas nada parecia aderir:

  1. Manter todos os tipos de ação como constantes em um só lugar certamente facilita o gerenciamento, mas descobri que se torna um pouco complicado quando o aplicativo cresce.
  2. Os valores gerados aleatoriamente mencionados por @samsch realmente chamaram minha atenção e certamente funciona, mas sim, perder a parte legível por humanos torna mais difícil conviver com ele.
  3. O comentário acima de @philholden realmente me atraiu, já que normalmente usamos uma arquitetura baseada em recursos e a estrutura de diretório é bastante descritiva.

Depois de experimentar nos últimos meses, decidimos usar a estrutura de diretório para criar um namespace para nossos tipos de ação. Digitar manualmente envelhece muito rápido, então tentei fazer algo com __filename mas isso não funciona devido ao agrupamento de código etc. Então eu fiz meu primeiro plugin Babel que transforma a palavra-chave __filenamespace em uma string estática, que parece estar funcionando bem.

Exemplo

Em App/testerAccount/index.js :

// Something like this
const ADD_VIDEO_UPLOAD = `${__filenamespace}/ADD_VIDEO_UPLOAD`;
const ADD_COMMENT = `${__filenamespace}/ADD_COMMENT`;

// Will be transformed into something like this
const ADD_VIDEO_UPLOAD = 'App/testerAccount/ADD_VIDEO_UPLOAD';
const ADD_COMMENT = 'App/testerAccount/ADD_COMMENT';

Sinta-se à vontade para experimentar, espero que seja útil. Seria fantástico receber qualquer feedback, especialmente de @pbc ou @gaearon como o criador do problema e criador da biblioteca.

https://www.npmjs.com/package/babel-plugin-filenamespace

Eu realmente esperava que alguém oferecesse um "padrão". Não precisamos de símbolos ou objetos para isso, precisamos apenas de uma convenção.

Vamos fazer "featureName$actionType" ou "fileName/ACTION_TYPE" ou "PROJECT.FEATURE.ACTION" ? Se todos pudermos concordar em algo, será mais fácil compartilhar redutores.

Acho que com a escassez de outras convenções disponíveis, Ducks se tornou o padrão de fato.

const ACTION = 'app/feature/ACTION'; é bastante suficiente.

@gaearon sempre mencionou que a relação 1: 1 entre ações e redutores é provavelmente uma má ideia e eu realmente concordo. Mas e o caso em que duas visões diferentes realmente querem chamar o mesmo redutor.
Por exemplo. Uma simples alternância de preferência pode ser habilitada ou desabilitada em dois lugares diferentes:
/ myaccount / toggleNotification
/ dashboard / toggleNotification
então devemos ter dois redutores escritos com o mesmo conteúdo em
reducers / notifications.js
cc: @ samit4me , @philholden


Pensando bem, acho que é uma boa ideia ter dois ou mais redutores dentro de um arquivo fazendo o mesmo trabalho com nomes de ação diferentes. É assim que, apenas vendo o redutor, podemos entender que, de quantos lugares diferentes um determinado estado está sendo modificado.

De qualquer forma, adoraria ouvir os especialistas. Obrigado.

@ivks : vários pensamentos aqui.

Primeiro, você pode despachar a mesma ação de vários locais no aplicativo.

Em segundo lugar, você pode definitivamente ter o mesmo redutor ouvindo vários tipos de ação e tratando-os da mesma maneira.

Terceiro, o aspecto da "relação 1: 1" é sobre ser capaz de ter vários redutores de fatia ouvindo a mesma ação, com cada um atualizando seu bit de estado independentemente. Sim, na maioria das vezes pode haver apenas um redutor que se preocupa com uma determinada ação, mas ter vários redutores respondendo é um caso de uso pretendido para Redux.

@markerikson

Em segundo lugar, você pode definitivamente ter o mesmo redutor ouvindo vários tipos de ação e tratando-os da mesma maneira.

Na verdade, eu estava usando redux-actions e não sabia que ele tinha um método utilitário combineAction que fará a tarefa. Acabei de encontrar e sua declaração acima apenas menciona o mesmo. (deveria ter sido mais claro, desculpe)
Muito obrigado por responder.

Não acho que Ducks defina corretamente em ActionTypes:

PRECISA ter tipos de ação no formato npm-module-or-app / reducer / ACTION_TYPE

O motivo é que uma ação pode ser inscrita por mais de um redutor. Como @mherodev disse, const ACTION = 'app/feature/ACTION'; faz mais sentido, além disso, gostaria de adicionar um esquema para a ação: const ACTION = 'action://app/feature/ACTION'; .

Por que não apenas usar um número inteiro auto-incremental global para identificar os tipos de ação? por exemplo

let id = 0

function generateActionType (label /* for readability */) {
  id++
  return `app/feature/${id}/${label}`
}

Pensei um pouco sobre isso, para ter certeza de não entrar em conflito com outros desenvolvedores, estamos escrevendo uma regra lint, que olha em um diretório (onde temos todos os redutores e cada redutor tem seus próprios tipos de ação como constantes)

Estamos planejando linting de forma que ele relate uma violação se houver duas constantes com o mesmo valor.

Se houver alguma sugestão, por favor me avise :)

Esta página foi útil?
0 / 5 - 0 avaliações