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
- DOM : jQuery é imperativo / React é declarativo
- Mônadas : IO é imperativa / Livre é declarativa
- Efeitos redux :
redux-thunk
é imperativo / redux-saga
é declarativo
Quando 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.
Visto pelas lentes dos efeitos colaterais, as zombarias são uma bandeira de que seu código é impuro e, aos olhos do programador funcional, prova que algo está errado. Em vez de baixar uma biblioteca para nos ajudar a verificar se o iceberg está intacto, devemos navegar nele. Um cara de TDD / Java hardcore uma vez me perguntou como você zomba de Clojure. A resposta é que geralmente não o fazemos. Geralmente vemos isso como um sinal de que precisamos refatorar nosso código.
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 :
no FP moderno, não devemos escrever programas - devemos escrever descrições de programas, que podemos então introspectar, transformar e interpretar à vontade.
(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
<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div>
function loadUserProfile(userId) {
return dispatch => fetch(`http://data.com/${userId}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'USER_PROFILE_LOADED', data }),
err => dispatch({ type: 'USER_PROFILE_LOAD_FAILED', err })
);
}
redux-saga
<div onClick={e => dispatch({ type: 'USER_NAME_CLICKED', payload: 123 })}>Robert</div>
function* loadUserProfileOnNameClick() {
yield* takeLatest("USER_NAME_CLICKED", fetchUser);
}
function* fetchUser(action) {
try {
const userProfile = yield fetch(`http://data.com/${action.payload.userId }`)
yield put({ type: 'USER_PROFILE_LOADED', userProfile })
}
catch(err) {
yield put({ type: 'USER_PROFILE_LOAD_FAILED', err })
}
}
Esta saga se traduz em:
toda vez que um nome de usuário é clicado, busque o perfil do usuário e despache um evento com o perfil carregado.
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 usado takeEvery
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 componentes dispatch
), 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 isso NEXT_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 pergaminho
Alguns 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 chamado redux-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.
const loadUserProfileOnNameClickEpic = action$ =>
action$.ofType('USER_NAME_CLICKED')
.switchMap(action =>
Observable.ajax(`http://data.com/${action.payload.userId}`)
.map(userProfile => ({
type: 'USER_PROFILE_LOADED',
userProfile
}))
.catch(err => Observable.of({
type: 'USER_PROFILE_LOAD_FAILED',
err
}))
);
Alguns recursos úteis para redux-saga
2017 aconselha
- Não use Redux-saga apenas por uma questão de usá-lo. Apenas chamadas de API testáveis não valem a pena.
- Não remova thunks do seu projeto para os casos mais simples.
- Não hesite em enviar thunks
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.
const unsubscribe = store.addDispatchListener(action => {
if (action.type === 'ping') {
store.dispatch({ type: 'pong' });
}
});