Redux: Middleware está quebrado

Criado em 6 jun. 2018  ·  3Comentários  ·  Fonte: reduxjs/redux

Erro

Um padrão importante de middleware foi quebrado. A maioria das soluções de middleware da IMO encapsula suas próprias preocupações e são independentes da existência de outro middleware. Uma mudança foi feita recentemente para quebrar um padrão que causava apenas um pequeno problema para um subconjunto específico de middleware: aqueles que exibem um comportamento diferente ao interagir com outro middleware durante o caminho de inicialização applyMiddleware .

aqui estão alguns exemplos:

// Make state aware of browser media queries
const mediaQueries = (mediaQueries = defaultMediaQueries): Middleware => store => {
  if (typeof window !== 'undefined') {
    const reverseMap = createReverseMap(mediaQueries)

    const handleMediaChange = ({media, matches}: MediaChangedActionPayload) =>
      store.dispatch(mediaChanged(reverseMap[media], matches))

    const addMatchListener = (media: string) => {
      const match = window.matchMedia(media)
      match.addListener(handleMediaChange)
      return match.matches
    }

    values(mediaQueries).forEach(media => 
      handleMediaChange({media, matches: addMatchListener(media)}))
  }

  return next => (action: any) => next(action)
}

Outro exemplo:

// Make state aware of user adblockers
const adBlockDetection: Middleware = store => {
  if (typeof document !== 'undefined' && !document.getElementById('XMpADSwgbPUC')) {
    store.dispatch({type: AD_BLOCKER_DETECTED, payload: true})
  }
  return next => (action: any) => next(action)
}

Outro exemplo:

// Make state aware of socket connectivity and allow synchronisation of actions
const socketMiddleware = ({actionCreator, url, dataSync}) => store => {
  const socket = new WebSocket(url.replace(/(http|https)/, 'ws'))

  socket.addEventListener('message', ({data}) => {
    store.dispatch(JSON.parse(data))
  })

  socket.addEventListener('open', () => {
    socket.send(syncCreator(CONNECT_TO_DOMAIN, store.getState(), null, socket))
    store.dispatch(actionCreator({connected: true}))
  })

  socket.addEventListener('close', reason => {
    store.dispatch(actionCreator({connected: false}))
  })

  return next => action => {
    if (dataSync.hasOwnProperty(action.type)) {
      socket.send(syncCreator(action.type, store.getState(), action.payload, socket))
    }
    return next(action)
  }
}

Qual é o comportamento atual?

O seguinte erro é gerado durante applyMiddleware , essencialmente interrompendo o aplicativo:

Uncaught Error: Dispatching while constructing your middleware is not allowed. Other middleware would not be applied to this dispatch

Refatorar o middleware acima para ser fechado pela função next => (como abaixo) não faz diferença (mas obviamente também não é uma solução viável).

// Still broken:
const mediaQueries = (mediaQueries = defaultMediaQueries): Middleware => store => next => {
  // ...middlewareCodeGoesHere...
  return (action: any) => next(action)
}

Qual é o comportamento esperado?

Não quebre o aplicativo. Registre um aviso se quiser, para que eu possa ignorá-lo, porque em 100% dos meus casos de uso _não me importo se outro middleware pode lidar com esse dispatch_. Eu suspeito fortemente que existem (ou deveriam haver) muito poucos motivos legítimos pelos quais qualquer despacho de dentro de um contexto de middleware deveria interagir com qualquer outro middleware na pilha e, se o fizesse, eu consideraria que um cheiro de código (colocaria dependências de ordem implícitas no middleware, no mínimo).

Quais versões do Redux e quais navegador e sistema operacional são afetados por esse problema?

Trabalhou até este PR .

Comentários muito úteis

@markerikson Sim, eu vi esse problema. O principal problema que o novo design "resolve" é este:

Aqui está o problema: esse listener é acionado quando você está aplicando o middleware, portanto, chamar store.dispatch está chamando o despacho não-middlewared e nosso middleware analítico não verá nenhum evento .

Este é o IMO, um detalhe de implementação que o desenvolvedor do aplicativo deve lidar, não algo que deve _destruir o funcionamento da maioria dos middlewares_. Com um aviso em vez de lançar um erro, o desenvolvedor do aplicativo pode identificar o problema e refatorar seu próprio middleware para resolvê-lo.

Em seu estado atual, todo middleware existente, incluindo middleware de terceiros, que usa esse padrão de envio interno (basicamente qualquer coisa que precise instanciar ouvintes em qualquer coisa), está quebrado.

Ficar com 3.x não é uma opção para nós, infelizmente, já que usamos Typescript, e apenas 4.x tem as digitações corretas e, em geral, preferimos não ficar presos a uma versão legada de uma parte central de nosso lógica do aplicativo, mesmo que a digitação não seja um problema.

E, na minha opinião, até mesmo a própria mensagem de erro sugere o quão sem importância ela realmente é: o "problema" é que Other middleware would not be applied to this dispatch , que muito raramente é realmente desejado e, até agora, tem sido um problema apenas para um específico caso de uso, em que o middleware analítico dependia de ações despachadas de um roteador.

É importante notar que a "correção" no redux agora quebra o código que também pretendia consertar . Ele quebra o middleware do roteador. Agora, os autores desse middleware (e de todos os outros middleware semelhantes) precisam liberar versões 4.x compatíveis de suas libs, que agora precisam expor os detalhes de implementação de uma ação "pronta para armazenar" despachada manualmente, que o consumidor deve implementar.

O uso de bibliotecas de middleware mudou de simplesmente importar o pacote e inserir na matriz de middlewares, para agora, adicionalmente, exigir que os autores do aplicativo encontrem alguma maneira hacky de despachar uma ação arbitrária de "armazenamento pronto" algum tempo depois que a loja terminar de ser criada, mas antes de ser usado. E agora TODOS os seus middlewares devem aderir ao mesmo tipo de ação, caso contrário, os autores do aplicativo precisarão despachar várias ações "prontas para armazenar", com diferentes tipos correspondendo a qualquer API que os autores do middleware implementaram.

Isso é uma verdadeira bagunça, e a "cura" é claramente pior do que a "doença" neste caso.

Todos 3 comentários

O processo init do middleware foi explicitamente alterado no 4.0 no PR que você vinculou. Esta foi uma decisão de design deliberada e destinava-se a resolver o nº 1240.

Não tenho certeza se tenho uma sugestão imediata para o seu caso de uso, além de tentar alterar a lógica do seu middleware para esperar por alguma ação "store is ready" despachada manualmente. Você também pode considerar ficar com o 3.x.

@markerikson Sim, eu vi esse problema. O principal problema que o novo design "resolve" é este:

Aqui está o problema: esse listener é acionado quando você está aplicando o middleware, portanto, chamar store.dispatch está chamando o despacho não-middlewared e nosso middleware analítico não verá nenhum evento .

Este é o IMO, um detalhe de implementação que o desenvolvedor do aplicativo deve lidar, não algo que deve _destruir o funcionamento da maioria dos middlewares_. Com um aviso em vez de lançar um erro, o desenvolvedor do aplicativo pode identificar o problema e refatorar seu próprio middleware para resolvê-lo.

Em seu estado atual, todo middleware existente, incluindo middleware de terceiros, que usa esse padrão de envio interno (basicamente qualquer coisa que precise instanciar ouvintes em qualquer coisa), está quebrado.

Ficar com 3.x não é uma opção para nós, infelizmente, já que usamos Typescript, e apenas 4.x tem as digitações corretas e, em geral, preferimos não ficar presos a uma versão legada de uma parte central de nosso lógica do aplicativo, mesmo que a digitação não seja um problema.

E, na minha opinião, até mesmo a própria mensagem de erro sugere o quão sem importância ela realmente é: o "problema" é que Other middleware would not be applied to this dispatch , que muito raramente é realmente desejado e, até agora, tem sido um problema apenas para um específico caso de uso, em que o middleware analítico dependia de ações despachadas de um roteador.

É importante notar que a "correção" no redux agora quebra o código que também pretendia consertar . Ele quebra o middleware do roteador. Agora, os autores desse middleware (e de todos os outros middleware semelhantes) precisam liberar versões 4.x compatíveis de suas libs, que agora precisam expor os detalhes de implementação de uma ação "pronta para armazenar" despachada manualmente, que o consumidor deve implementar.

O uso de bibliotecas de middleware mudou de simplesmente importar o pacote e inserir na matriz de middlewares, para agora, adicionalmente, exigir que os autores do aplicativo encontrem alguma maneira hacky de despachar uma ação arbitrária de "armazenamento pronto" algum tempo depois que a loja terminar de ser criada, mas antes de ser usado. E agora TODOS os seus middlewares devem aderir ao mesmo tipo de ação, caso contrário, os autores do aplicativo precisarão despachar várias ações "prontas para armazenar", com diferentes tipos correspondendo a qualquer API que os autores do middleware implementaram.

Isso é uma verdadeira bagunça, e a "cura" é claramente pior do que a "doença" neste caso.

Ainda é possível fazer todos os seus exemplos funcionarem. Apenas despache após o applyMiddleware. Talvez em algum tipo de função setupMiddleware(store) ou um realçador de loja.

O erro foi introduzido intencionalmente como a alternativa para permitir que os usuários encontrem um caso extremo que tem uma causa não óbvia (a menos que você esteja familiarizado com os detalhes internos de applyMiddleware ). Redux toma a posição de prevenir o erro do usuário sempre que possível, então isso não vai embora.

Uma alternativa pode ser algum tipo de retorno de chamada que o middleware pode registrar como uma espécie de evento afterApply . Assim, você terá a garantia de despachar no horário correto, depois que tudo estiver aplicado e em ordem. Seria interessante pensar bem.

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

Questões relacionadas

caojinli picture caojinli  ·  3Comentários

vslinko picture vslinko  ·  3Comentários

ilearnio picture ilearnio  ·  3Comentários

parallelthought picture parallelthought  ·  3Comentários

ramakay picture ramakay  ·  3Comentários