De acordo com os documentos, "Sem o middleware, o armazenamento Redux suporta apenas o fluxo de dados síncrono" . Não entendo por que esse é o caso. Por que o componente de contêiner não pode chamar a API assíncrona e dispatch
as ações?
Por exemplo, imagine uma interface simples: um campo e um botão. Quando o usuário aperta o botão, o campo é preenchido com dados de um servidor remoto.
import * as React from 'react';
import * as Redux from 'redux';
import { Provider, connect } from 'react-redux';
const ActionTypes = {
STARTED_UPDATING: 'STARTED_UPDATING',
UPDATED: 'UPDATED'
};
class AsyncApi {
static getFieldValue() {
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve(Math.floor(Math.random() * 100));
}, 1000);
});
return promise;
}
}
class App extends React.Component {
render() {
return (
<div>
<input value={this.props.field}/>
<button disabled={this.props.isWaiting} onClick={this.props.update}>Fetch</button>
{this.props.isWaiting && <div>Waiting...</div>}
</div>
);
}
}
App.propTypes = {
dispatch: React.PropTypes.func,
field: React.PropTypes.any,
isWaiting: React.PropTypes.bool
};
const reducer = (state = { field: 'No data', isWaiting: false }, action) => {
switch (action.type) {
case ActionTypes.STARTED_UPDATING:
return { ...state, isWaiting: true };
case ActionTypes.UPDATED:
return { ...state, isWaiting: false, field: action.payload };
default:
return state;
}
};
const store = Redux.createStore(reducer);
const ConnectedApp = connect(
(state) => {
return { ...state };
},
(dispatch) => {
return {
update: () => {
dispatch({
type: ActionTypes.STARTED_UPDATING
});
AsyncApi.getFieldValue()
.then(result => dispatch({
type: ActionTypes.UPDATED,
payload: result
}));
}
};
})(App);
export default class extends React.Component {
render() {
return <Provider store={store}><ConnectedApp/></Provider>;
}
}
Quando o componente exportado é renderizado, posso clicar no botão e a entrada é atualizada corretamente.
Observe a update
função na connect
chamada. Ele despacha uma ação que informa ao aplicativo que está atualizando e executa uma chamada assíncrona. Após o término da chamada, o valor fornecido é despachado como carga de outra ação.
O que há de errado com essa abordagem? Por que eu gostaria de usar o Redux Thunk ou o Redux Promise, como sugere a documentação?
EDIT: Procurei no repositório Redux por pistas e descobri que os Action Creators eram exigidos como funções puras no passado. Por exemplo, aqui está um usuário tentando fornecer uma explicação melhor para o fluxo de dados assíncrono:
O criador da ação em si ainda é uma função pura, mas a função thunk que ele retorna não precisa ser, e pode fazer nossas chamadas assíncronas
Os criadores de ações não precisam mais ser puros. Portanto, o middleware thunk / promessa era definitivamente necessário no passado, mas parece que esse não é mais o caso?
fonte
Respostas:
Não há nada de errado com essa abordagem. É apenas inconveniente em um aplicativo grande, porque você terá componentes diferentes executando as mesmas ações, poderá renunciar a algumas ações ou manter algum estado local, como IDs de incremento automático, perto dos criadores de ações, etc. Portanto, é mais fácil o ponto de vista de manutenção para extrair criadores de ação em funções separadas.
Você pode ler minha resposta em "Como despachar uma ação Redux com um tempo limite" para obter uma explicação mais detalhada.
Middleware como Redux Thunk ou Redux Promise oferecem apenas "açúcar de sintaxe" para o envio de thunks ou promessas, mas você não precisa usá-lo.
Portanto, sem nenhum middleware, seu criador de ações pode parecer
Mas com o Thunk Middleware, você pode escrever assim:
Portanto, não há grande diferença. Uma coisa que eu gosto na última abordagem é que o componente não se importa que o criador da ação seja assíncrono. É apenas chamado
dispatch
normalmente, também pode ser usadomapDispatchToProps
para vincular esse criador de ação a uma sintaxe curta etc. Os componentes não sabem como os criadores de ação são implementados e você pode alternar entre diferentes abordagens assíncronas (Redux Thunk, Redux Promise, Redux Saga ) sem alterar os componentes. Por outro lado, com a abordagem explícita anterior, seus componentes sabem exatamente que uma chamada específica é assíncrona e precisadispatch
ser aprovada por alguma convenção (por exemplo, como um parâmetro de sincronização).Pense também em como esse código será alterado. Digamos que queremos ter uma segunda função de carregamento de dados e combiná-los em um único criador de ações.
Com a primeira abordagem, precisamos estar atentos ao tipo de criador de ação que estamos chamando:
Com o Redux Thunk, os criadores de ações podem
dispatch
resultar de outros criadores de ações e nem pensar se são síncronos ou assíncronos:Com essa abordagem, se você desejar posteriormente que seus criadores de ação analisem o estado atual do Redux, basta usar o segundo
getState
argumento passado aos thunks sem modificar o código de chamada:Se você precisar alterá-lo para que seja síncrono, também poderá fazer isso sem alterar nenhum código de chamada:
Portanto, o benefício do uso de middleware como Redux Thunk ou Redux Promise é que os componentes não sabem como os criadores de ação são implementados e se eles se importam com o estado Redux, sejam síncronos ou assíncronos e se chamam ou não outros criadores de ação . A desvantagem é um pouco indireta, mas acreditamos que vale a pena em aplicações reais.
Por fim, Redux Thunk e amigos é apenas uma abordagem possível para solicitações assíncronas em aplicativos Redux. Outra abordagem interessante é o Redux Saga, que permite definir daemons de longa duração (“sagas”) que executam ações à medida que chegam e transformam ou executam solicitações antes de gerar ações. Isso move a lógica dos criadores de ação para as sagas. Você pode dar uma olhada e depois escolher o que melhor combina com você.
Isto está incorreto. Os documentos disseram isso, mas estavam errados.
Os criadores de ação nunca precisaram ser funções puras.
Corrigimos os documentos para refletir isso.
fonte
alert
depoisdispatch()
da ação.loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch
. Por que preciso passar na expedição? Se por convenção há apenas uma única loja global, por que não faço referência diretamente a ela estore.dispatch
sempre que preciso, por exemploloadData
?store
instância diferente para cada solicitação, para não poder defini-la com antecedência.Você não
Mas ... você deve usar o redux-saga :)
A resposta de Dan Abramov está certa,
redux-thunk
mas vou falar um pouco mais sobre a redux-saga que é bem parecida, mas mais poderosa.Imperativo VS declarativo
redux-thunk
é imperativo /redux-saga
é declarativoQuando você tem um thunk em suas mãos, como uma mônada de IO ou uma promessa, não pode saber facilmente o que ele fará depois de executar. A única maneira de testar um thunk é executá-lo e zombar do expedidor (ou de todo o mundo exterior, se ele interagir com mais coisas ...).
Se você estiver usando zombarias, não estará fazendo programação funcional.
Fonte
As sagas (como foram implementadas
redux-saga
) são declarativas e, como os componentes Mônada livre ou React, são muito mais fáceis de testar sem qualquer simulação.Veja também este artigo :
(Na verdade, Redux-saga é como um híbrido: o fluxo é imperativo, mas os efeitos são declarativos)
Confusão: ações / eventos / comandos ...
Há muita confusão no mundo frontend sobre como alguns conceitos de back-end como CQRS / EventSourcing e Flux / Redux podem estar relacionados, principalmente porque no Flux usamos o termo "ação", que às vezes pode representar tanto código imperativo (
LOAD_USER
) quanto eventos (USER_LOADED
) Acredito que, como na fonte de eventos, você só deve despachar eventos.Usando sagas na prática
Imagine um aplicativo com um link para um perfil de usuário. A maneira idiomática de lidar com isso com cada middleware seria:
redux-thunk
redux-saga
Esta saga se traduz em:
Como você pode ver, existem algumas vantagens
redux-saga
.O uso de
takeLatest
licenças para expressar que você só está interessado em obter os dados do último nome de usuário (lida com problemas de simultaneidade caso o usuário clique muito rapidamente em muitos nomes de usuário). Esse tipo de coisa é difícil com thunks. Você poderia ter usadotakeEvery
se não quiser esse comportamento.Você mantém os criadores de ação puros. Observe que ainda é útil manter os actionCreators (em sagas
put
e componentesdispatch
), pois pode ajudá-lo a adicionar a validação de ações (asserções / fluxo / texto datilografado) no futuro.Seu código se torna muito mais testável, pois os efeitos são declarativos
Você não precisa mais acionar chamadas semelhantes a rpc
actions.loadUser()
. Sua interface do usuário só precisa despachar o que ACONTECEU. Só disparamos eventos (sempre no passado!) E não mais ações. Isso significa que você pode criar "patos" dissociados ou contextos limitados e que a saga pode atuar como o ponto de acoplamento entre esses componentes modulares.Isso significa que suas visualizações são mais fáceis de gerenciar, porque não precisam mais conter a camada de conversão entre o que aconteceu e o que deve acontecer como efeito
Por exemplo, imagine uma exibição de rolagem infinita.
CONTAINER_SCROLLED
pode levar a issoNEXT_PAGE_LOADED
, mas é realmente da responsabilidade do contêiner rolável decidir se devemos ou não carregar outra página? Então ele precisa estar ciente de coisas mais complicadas, como se a última página foi carregada com êxito ou se já existe uma página que tenta carregar ou se não há mais itens para carregar? Acho que não: para máxima reutilização, o contêiner rolável deve apenas descrever que foi rolado. O carregamento de uma página é um "efeito comercial" desse pergaminhoAlguns podem argumentar que os geradores podem ocultar inerentemente o estado fora do repositório redux com variáveis locais, mas se você começar a orquestrar coisas complexas dentro de thunks iniciando temporizadores etc., você terá o mesmo problema de qualquer maneira. E há um
select
efeito que agora permite obter algum estado da sua loja Redux.As sagas podem ser viajadas no tempo e também permitem o registro de fluxo complexo e ferramentas de desenvolvimento que estão sendo trabalhadas no momento. Aqui está um log de fluxo assíncrono simples que já está implementado:
Dissociação
As sagas não estão apenas substituindo os redux thunks. Eles vêm de back-end / sistemas distribuídos / fornecimento de eventos.
É um equívoco muito comum que as sagas estejam aqui apenas para substituir seus thunks redux por uma melhor testabilidade. Na verdade, este é apenas um detalhe de implementação do redux-saga. O uso de efeitos declarativos é melhor que thunks para testabilidade, mas o padrão de saga pode ser implementado sobre o código imperativo ou declarativo.
Em primeiro lugar, a saga é um software que permite coordenar transações de longa duração (consistência eventual) e transações em diferentes contextos limitados (jargão de design orientado a domínio).
Para simplificar isso no mundo frontend, imagine que há widget1 e widget2. Quando se clica em algum botão no widget1, ele deve ter um efeito no widget2. Em vez de acoplar os 2 widgets juntos (por exemplo, o widget1 despacha uma ação que visa o widget2), o widget1 despacha apenas que seu botão foi clicado. Em seguida, a saga escuta esse botão, clique e atualize o widget2, desanexando um novo evento que o widget2 tenha conhecimento.
Isso adiciona um nível de indireção desnecessário para aplicativos simples, mas facilita a escalabilidade de aplicativos complexos. Agora você pode publicar o widget1 e o widget2 em diferentes repositórios npm para que eles nunca precisem se conhecer, sem que eles compartilhem um registro global de ações. Os 2 widgets agora são contextos limitados que podem viver separadamente. Eles não precisam ser consistentes e podem ser reutilizados em outros aplicativos. A saga é o ponto de acoplamento entre os dois widgets que os coordenam de maneira significativa para o seu negócio.
Alguns bons artigos sobre como estruturar seu aplicativo Redux, nos quais você pode usar o Redux-saga por razões de desacoplamento:
Um caso de uso concreto: sistema de notificação
Desejo que meus componentes possam acionar a exibição de notificações no aplicativo. Mas não quero que meus componentes sejam altamente acoplados ao sistema de notificação que possui suas próprias regras de negócios (no máximo 3 notificações exibidas ao mesmo tempo, enfileiramento de notificações, 4 segundos de exibição etc ...).
Não quero que meus componentes JSX decidam quando uma notificação será exibida / ocultada. Eu apenas lhe dou a capacidade de solicitar uma notificação e deixar as regras complexas dentro da saga. Esse tipo de coisa é bastante difícil de implementar com thunks ou promessas.
Eu descrevi aqui como isso pode ser feito com saga
Por que é chamado de saga?
O termo saga vem do mundo back-end. Inicialmente, apresentei Yassine (o autor da Redux-saga) a esse termo em uma longa discussão .
Inicialmente, esse termo foi introduzido com um artigo , o padrão saga deveria ser usado para lidar com a consistência eventual nas transações distribuídas, mas seu uso foi estendido a uma definição mais ampla pelos desenvolvedores de back-end, para que agora também cubra o "gerenciador de processos" padrão (de alguma forma, o padrão original da saga é uma forma especializada de gerenciador de processos).
Hoje, o termo "saga" é confuso, pois pode descrever duas coisas diferentes. Como é usado no redux-saga, ele não descreve uma maneira de lidar com transações distribuídas, mas uma maneira de coordenar ações no seu aplicativo.
redux-saga
também poderia ter sido chamadoredux-process-manager
.Veja também:
Alternativas
Se você não gosta da idéia de usar geradores, mas está interessado no padrão saga e em suas propriedades de desacoplamento, também pode obter o mesmo com redux-observable, que usa o nome
epic
para descrever exatamente o mesmo padrão, mas com o RxJS. Se você já conhece o Rx, vai se sentir em casa.Alguns recursos úteis para redux-saga
2017 aconselha
yield put(someActionThunk)
se fizer sentido.Se você tem medo de usar o Redux-saga (ou Redux-observable), mas precisa apenas do padrão de dissociação, verifique redux-dispatch-subscribe : ele permite ouvir os despachos e acionar novos despachos no ouvinte.
fonte
ADD_ITEM
, é imperativo porque você despacha uma ação que visa ter um efeito em sua loja: você espera que a ação faça alguma coisa. Ser declarativo adota a filosofia da fonte de eventos: você não envia ações para acionar alterações em seus aplicativos, mas envia eventos passados para descrever o que aconteceu em seu aplicativo. O envio de um evento deve ser suficiente para considerar que o estado do aplicativo foi alterado. O fato de que há uma loja Redux que reage ao evento é um detalhe de implementação opcionalA resposta curta : parece uma abordagem totalmente razoável para o problema da assincronia para mim. Com algumas ressalvas.
Eu tinha uma linha de pensamento muito semelhante ao trabalhar em um novo projeto que começamos no meu trabalho. Eu era um grande fã do elegante sistema de baunilha Redux para atualizar a loja e renderizar componentes de uma maneira que fica fora dos ângulos de uma árvore de componentes do React. Pareceu-me estranho conectar-me a esse
dispatch
mecanismo elegante para lidar com assincronia.Acabei adotando uma abordagem realmente semelhante à que você tem em uma biblioteca que considerei fora do nosso projeto, que chamamos de react-redux-controller .
Acabei não seguindo a abordagem exata que você tem acima por alguns motivos:
dispatch
si mesmas por meio de escopo lexical. Isso limita as opções de refatoração uma vez que aconnect
declaração fica fora de controle - e ela parece bastante complicada com apenas esseupdate
método. Portanto, você precisa de algum sistema para permitir que você componha essas funções do despachante se você as dividir em módulos separados.Em conjunto, é necessário montar algum sistema para permitir que
dispatch
a loja seja injetada em suas funções de despacho, juntamente com os parâmetros do evento. Conheço três abordagens razoáveis para essa injeção de dependência:dispatch
abordagens de middleware, mas presumo que elas sejam basicamente as mesmas.connect
, em vez de precisar trabalhar diretamente com a loja normalizada bruta.this
contexto, através de uma variedade de mecanismos possíveis.Atualizar
Ocorre-me que parte desse enigma é uma limitação do react-redux . O primeiro argumento para
connect
obter um instantâneo de estado, mas não despachar. O segundo argumento é despachado, mas não o estado. Nenhum argumento recebe uma conversão que se fecha sobre o estado atual, por poder ver o estado atualizado no momento de uma continuação / retorno de chamada.fonte
O objetivo de Abramov - e o ideal para todos - é simplesmente encapsular a complexidade (e chamadas assíncronas) no local onde é mais apropriado .
Qual é o melhor lugar para fazer isso no fluxo de dados padrão do Redux? E se:
fonte
Para responder à pergunta que é feita no começo:
Lembre-se de que esses documentos são para Redux, não Redux plus React. As lojas Redux conectadas aos componentes React podem fazer exatamente o que você diz, mas uma loja Jane Redux simples sem middleware não aceita argumentos para
dispatch
exceção de objetos simples e antigos.Sem o middleware, é claro que você ainda pode
Mas é um caso semelhante em que a assincronia é envolvida no Redux, e não no Redux. Portanto, o middleware permite assincronia, modificando o que pode ser passado diretamente para
dispatch
.Dito isto, o espírito da sua sugestão é, eu acho, válido. Certamente existem outras maneiras de lidar com assincronia em um aplicativo Redux + React.
Um benefício do uso de middleware é que você pode continuar usando os criadores de ação normalmente, sem se preocupar exatamente com como eles estão conectados. Por exemplo, usando
redux-thunk
, o código que você escreveu seria muito parecido comque não parece tão diferente do original - é apenas embaralhado um pouco - e
connect
não sabe queupdateThing
é (ou precisa ser) assíncrono.Se você também quis oferecer suporte a promessas , observáveis , sagas ou criadores de ação loucos e altamente declarativos , então o Redux pode fazer isso apenas alterando o que você passa
dispatch
(ou seja, o que você retorna dos criadores de ação). Não é necessário estragar os componentes (ouconnect
chamadas) do React .fonte
OK, vamos começar a ver como o middleware funciona primeiro, que responde bastante à pergunta, este é o código fonte da função pplyMiddleWare no Redux:
Veja esta parte, veja como nosso despacho se torna uma função .
OK, é assim que o Redux-thunk como um dos middlewares mais usados para o Redux se apresenta:
Então, como você vê, ele retornará uma função em vez de uma ação, significa que você pode esperar e chamá-lo sempre que quiser, pois é uma função ...
Então, o que diabos é thunk? É assim que é introduzido na Wikipedia:
Veja como é fácil o conceito e como ele pode ajudá-lo a gerenciar suas ações assíncronas ...
É algo que você pode viver sem ele, mas lembre-se de que na programação sempre há maneiras melhores, mais organizadas e adequadas de fazer as coisas ...
fonte
Usar o Redux-saga é o melhor middleware na implementação do React-redux.
Ex: store.js
E então saga.js
E então action.js
E então reducer.js
E então main.js
tente isso .. está funcionando
fonte
Existem criadores de ações síncronas e, em seguida, criadores de ações assíncronas.
Um criador de ação síncrona é aquele que, quando o chamamos, retorna imediatamente um objeto Action com todos os dados relevantes anexados a esse objeto e está pronto para ser processado por nossos redutores.
Os criadores de ação assíncrona são aqueles em que será necessário um pouco de tempo antes de estar pronto para enviar uma ação.
Por definição, sempre que você tiver um criador de ações que faça uma solicitação de rede, ele sempre será qualificado como criador de ações assíncronas.
Se você deseja ter criadores de ação assíncronos dentro de um aplicativo Redux, é necessário instalar algo chamado middleware que permitirá que você lide com esses criadores de ação assíncronos.
Você pode verificar isso na mensagem de erro que indica o uso de middleware personalizado para ações assíncronas.
Então, o que é um middleware e por que precisamos dele para fluxo assíncrono no Redux?
No contexto do middleware redux, como o redux-thunk, um middleware nos ajuda a lidar com criadores de ação assíncronos, pois é algo que o Redux não pode lidar imediatamente.
Com um middleware integrado ao ciclo Redux, ainda estamos chamando criadores de ação, que retornará uma ação que será despachada, mas agora quando despacharmos uma ação, em vez de enviá-la diretamente para todos os nossos redutores, iremos dizer que uma ação será enviada através de todos os diferentes middlewares dentro do aplicativo.
Dentro de um único aplicativo Redux, podemos ter o middleware que desejar. Na maioria das vezes, nos projetos em que trabalhamos, teremos um ou dois middlewares conectados à nossa loja Redux.
Um middleware é uma função JavaScript simples que será chamada a cada ação que despacharmos. Dentro dessa função, um middleware tem a oportunidade de impedir que uma ação seja despachada para qualquer um dos redutores, ele pode modificar uma ação ou apenas mexer com uma ação de qualquer maneira que você, por exemplo, poderíamos criar um middleware que console logs cada ação que você despacha apenas para o seu prazer.
Há um número tremendo de middleware de código aberto que você pode instalar como dependências no seu projeto.
Você não está limitado apenas a usar o middleware de código aberto ou a instalá-lo como dependências. Você pode escrever seu próprio middleware personalizado e usá-lo dentro da sua loja Redux.
Um dos usos mais populares do middleware (e chegando à sua resposta) é para lidar com criadores de ação assíncronos, provavelmente o middleware mais popular existente no mercado é o redux-thunk e trata-se de ajudá-lo a lidar com criadores de ação assíncronos.
Existem muitos outros tipos de middleware que também ajudam a lidar com criadores de ações assíncronas.
fonte
Para responder à pergunta:
Eu diria por pelo menos duas razões:
A primeira razão é a separação de preocupações, não é tarefa do
action creator
chamarapi
e recuperar dados, você precisa passar dois argumentos para o seuaction creator function
,action type
oe umpayload
.A segunda razão é porque
redux store
está aguardando um objeto simples com o tipo de ação obrigatória e, opcionalmente, apayload
(mas aqui você também deve passar a carga útil).O criador da ação deve ser um objeto simples, como abaixo:
E o trabalho de
Redux-Thunk midleware
paradispache
o resultado do seuapi call
para o apropriadoaction
.fonte
Ao trabalhar em um projeto corporativo, existem muitos requisitos disponíveis no middleware, como (saga), não disponíveis no fluxo assíncrono simples, abaixo estão alguns:
A lista é longa, basta revisar a seção avançada na documentação da saga
fonte