Há muita conversa sobre o garoto mais recente da cidade de redux agora, redux-saga / redux-saga . Ele usa funções de gerador para ouvir / despachar ações.
Antes de entender, gostaria de saber os prós / contras do uso, em redux-saga
vez da abordagem abaixo, na qual estou usando o redux-thunk
async / wait.
Um componente pode se parecer com isso, despachar ações como de costume.
import { login } from 'redux/auth';
class LoginForm extends Component {
onClick(e) {
e.preventDefault();
const { user, pass } = this.refs;
this.props.dispatch(login(user.value, pass.value));
}
render() {
return (<div>
<input type="text" ref="user" />
<input type="password" ref="pass" />
<button onClick={::this.onClick}>Sign In</button>
</div>);
}
}
export default connect((state) => ({}))(LoginForm);
Então, minhas ações são mais ou menos assim:
// auth.js
import request from 'axios';
import { loadUserData } from './user';
// define constants
// define initial state
// export default reducer
export const login = (user, pass) => async (dispatch) => {
try {
dispatch({ type: LOGIN_REQUEST });
let { data } = await request.post('/login', { user, pass });
await dispatch(loadUserData(data.uid));
dispatch({ type: LOGIN_SUCCESS, data });
} catch(error) {
dispatch({ type: LOGIN_ERROR, error });
}
}
// more actions...
// user.js
import request from 'axios';
// define constants
// define initial state
// export default reducer
export const loadUserData = (uid) => async (dispatch) => {
try {
dispatch({ type: USERDATA_REQUEST });
let { data } = await request.get(`/users/${uid}`);
dispatch({ type: USERDATA_SUCCESS, data });
} catch(error) {
dispatch({ type: USERDATA_ERROR, error });
}
}
// more actions...
javascript
reactjs
redux
redux-thunk
redux-saga
hampusohlsson
fonte
fonte
::
antes de vocêthis.onClick
fazer?this
), também conhecido comothis.onClick = this.onClick.bind(this)
. A forma mais longa é geralmente recomendada no construtor, pois a mão curta é ligada novamente a cada renderização.bind()
muito para passarthis
para a função, mas comecei a usar() => method()
agora.Respostas:
Na redux-saga, o equivalente ao exemplo acima seria
A primeira coisa a notar é que nós estamos chamando as funções da API utilizando o formulário
yield call(func, ...args)
.call
não executa o efeito, apenas cria um objeto simples como{type: 'CALL', func, args}
. A execução é delegada ao middleware redux-saga, que cuida da execução da função e da retomada do gerador com seu resultado.A principal vantagem é que você pode testar o gerador fora do Redux usando simples verificações de igualdade
Observe que estamos simulando o resultado da chamada da API simplesmente injetando os dados simulados no
next
método do iterador. Zombar de dados é muito mais simples que funções de zombaria.A segunda coisa a notar é a chamada para
yield take(ACTION)
. Thunks são chamados pelo criador da ação em cada nova ação (por exemploLOGIN_REQUEST
). ou seja, as ações são continuamente enviadas para thunks, e os thunks não têm controle sobre quando parar de manipular essas ações.Na redux-saga, os geradores executam a próxima ação. isto é, eles têm controle sobre quando ouvir alguma ação e quando não. No exemplo acima, as instruções de fluxo são colocadas dentro de um
while(true)
loop, para que ele escute cada ação recebida, o que imita um pouco o comportamento de empurrar a thunk.A abordagem pull permite a implementação de fluxos de controle complexos. Suponha, por exemplo, que desejemos adicionar os seguintes requisitos
Lidar com a ação do usuário LOGOUT
após o primeiro login bem-sucedido, o servidor retorna um token que expira em algum atraso armazenado em um
expires_in
campo. Teremos que atualizar a autorização em segundo plano a cadaexpires_in
milissegundoLeve em consideração que, ao aguardar o resultado das chamadas da API (login inicial ou atualização), o usuário poderá efetuar logout no meio.
Como você implementaria isso com thunks; enquanto também fornece cobertura de teste completa para todo o fluxo? Aqui está como isso pode parecer com Sagas:
No exemplo acima, estamos expressando nosso requisito de simultaneidade usando
race
. Setake(LOGOUT)
vencer a corrida (ou seja, o usuário clicou no botão Logout). A corrida cancelará automaticamente aauthAndRefreshTokenOnExpiry
tarefa em segundo plano. E se oauthAndRefreshTokenOnExpiry
foi bloqueado no meio de umacall(authorize, {token})
chamada, também será cancelado. O cancelamento se propaga para baixo automaticamente.Você pode encontrar uma demonstração executável do fluxo acima
fonte
delay
função? Ah, encontrei: github.com/yelouafi/redux-saga/blob/…redux-thunk
código é bastante legível e auto-explicado. Masredux-sagas
é realmente ilegível, principalmente por causa daqueles verbo-como funções:call
,fork
,take
,put
...Acrescentarei minha experiência usando saga no sistema de produção, além da resposta bastante completa do autor da biblioteca.
Pro (usando saga):
Testabilidade. É muito fácil testar sagas, pois call () retorna um objeto puro. Testar thunks normalmente exige que você inclua uma mockStore em seu teste.
O redux-saga vem com muitas funções auxiliares úteis sobre tarefas. Parece-me que o conceito de saga é criar algum tipo de trabalhador / thread em segundo plano para o seu aplicativo, que atua como uma peça que falta na arquitetura de redux de reação (actionCreators e reducers devem ser funções puras.) O que leva ao próximo ponto.
Sagas oferecem lugar independente para lidar com todos os efeitos colaterais. Geralmente, é mais fácil modificar e gerenciar do que ações thunk na minha experiência.
Vigarista:
Sintaxe do gerador.
Muitos conceitos para aprender.
Estabilidade da API. Parece que a redux-saga ainda está adicionando recursos (por exemplo, canais?) E a comunidade não é tão grande. Existe uma preocupação se a biblioteca fizer uma atualização não compatível com versões anteriores algum dia.
fonte
API stability
como uma atualização para refletir a situação atual.Gostaria apenas de adicionar alguns comentários da minha experiência pessoal (usando sagas e thunk):
As sagas são ótimas para testar:
As sagas são mais poderosas. Tudo o que você pode fazer no criador de ações de um thunk também pode fazer em uma saga, mas não vice-versa (ou pelo menos não facilmente). Por exemplo:
take
)cancel
,takeLatest
,race
)take
,takeEvery
...)O Sagas também oferece outras funcionalidades úteis, que generalizam alguns padrões de aplicativos comuns:
channels
para ouvir em fontes externas de eventos (por exemplo, websockets)fork
,spawn
)Sagas são uma ferramenta grande e poderosa. No entanto, com o poder vem a responsabilidade. Quando seu aplicativo cresce, você pode se perder facilmente, descobrindo quem está esperando a ação ser despachada ou o que acontece quando uma ação está sendo despachada. Por outro lado, o thunk é mais simples e mais fácil de raciocinar. A escolha de um ou de outro depende de muitos aspectos, como tipo e tamanho do projeto, que tipos de efeito colateral seu projeto deve lidar ou preferência da equipe de desenvolvimento. De qualquer forma, basta manter seu aplicativo simples e previsível.
fonte
Apenas alguma experiência pessoal:
Para estilo de codificação e legibilidade, uma das vantagens mais significativas do uso de redux-saga no passado é evitar o inferno de retorno de chamada em redux-thunk - não é necessário usar muitos aninhamentos e / ou capturar mais. Mas agora, com a popularidade do async / waiting thunk, também se pode escrever código assíncrono no estilo de sincronização ao usar o redux-thunk, o que pode ser considerado uma melhoria no redux-think.
Pode ser necessário escrever muito mais código padrão ao usar o redux-saga, especialmente no Typescript. Por exemplo, se alguém quiser implementar uma função de busca assíncrona, o tratamento de dados e erros pode ser realizado diretamente em uma unidade de thunk no action.js com uma única ação FETCH. Mas, no redux-saga, pode ser necessário definir as ações FETCH_START, FETCH_SUCCESS e FETCH_FAILURE e todas as verificações de tipo relacionadas, porque um dos recursos do redux-saga é usar esse tipo de mecanismo rico de "token" para criar efeitos e instruir redux store para testes fáceis. É claro que se poderia escrever uma saga sem usar essas ações, mas isso a tornaria semelhante a um thunk.
Em termos de estrutura de arquivos, o redux-saga parece ser mais explícito em muitos casos. Pode-se encontrar facilmente um código relacionado à assíncrona em todos os sagas.ts, mas no redux-thunk, seria necessário vê-lo em ações.
Testes fáceis podem ser outro recurso ponderado na redux-saga. Isso é realmente conveniente. Mas uma coisa que precisa ser esclarecida é que o teste de chamada de redux-saga não executaria a chamada de API real no teste; portanto, seria necessário especificar o resultado da amostra para as etapas que podem ser usadas após a chamada de API. Portanto, antes de escrever em redux-saga, seria melhor planejar uma saga e suas sagas.spec.ts correspondentes em detalhes.
O Redux-saga também oferece muitos recursos avançados, como executar tarefas em paralelo, auxiliares de simultaneidade como takeLatest / takeEvery, fork / spawn, que são muito mais poderosos que os thunks.
Concluindo, pessoalmente, eu gostaria de dizer: em muitos casos normais e aplicativos de tamanho pequeno a médio, use o estilo assíncrono / aguardado redux-thunk. Isso pouparia muitos códigos / ações / typedefs padrão, e você não precisaria trocar muitos sagas.ts diferentes e manter uma árvore de sagas específica. Mas se você estiver desenvolvendo um aplicativo grande com lógica assíncrona muito complexa e a necessidade de recursos como padrão de simultaneidade / paralelo, ou tiver uma alta demanda por testes e manutenção (especialmente no desenvolvimento orientado a testes), o redux-sagas possivelmente salvará sua vida .
De qualquer forma, o redux-saga não é mais difícil e complexo do que o próprio redux, e não possui uma chamada curva de aprendizado íngreme porque possui conceitos e APIs bem limitados. Passar um pouco de tempo aprendendo redux-saga pode se beneficiar um dia no futuro.
fonte
Tendo analisado alguns projetos React / Redux diferentes em larga escala, na minha experiência, o Sagas fornece aos desenvolvedores uma maneira mais estruturada de escrever código, que é muito mais fácil de testar e mais difícil de errar.
Sim, é um pouco estranho para começar, mas a maioria dos desenvolvedores entende o suficiente em um dia. Eu sempre digo às pessoas para não se preocuparem com o que
yield
fazer para começar e que, depois de escrever alguns testes, ele chegará até você.Eu já vi alguns projetos em que os thunks foram tratados como se fossem controladores do MVC patten e isso rapidamente se tornou uma bagunça insustentável.
Meu conselho é usar Sagas, onde você precisa de A dispara coisas do tipo B relacionadas a um único evento. Para qualquer coisa que possa abranger várias ações, acho mais simples escrever o middleware do cliente e usar a propriedade meta de uma ação da FSA para acioná-lo.
fonte
Thunks versus Sagas
Redux-Thunk
eRedux-Saga
diferem em algumas maneiras importantes, ambas são bibliotecas de middleware para Redux (o middleware Redux é um código que intercepta ações que entram na loja por meio do método dispatch ()).Uma ação pode ser literalmente qualquer coisa, mas se você estiver seguindo as práticas recomendadas, uma ação é um objeto javascript simples com um campo de tipo e campos opcionais de carga útil, meta e erro. por exemplo
Redux-Thunk
Além de despachar ações padrão, o
Redux-Thunk
middleware permite despachar funções especiais, chamadasthunks
.Thunks (no Redux) geralmente têm a seguinte estrutura:
Ou seja, a
thunk
é uma função que (opcionalmente) pega alguns parâmetros e retorna outra função. A função interna leva umadispatch function
e umagetState
função - ambos os quais serão fornecidos peloRedux-Thunk
middleware.Redux-Saga
Redux-Saga
O middleware permite que você expresse lógicas complexas de aplicativos como funções puras chamadas sagas. As funções puras são desejáveis do ponto de vista de teste porque são previsíveis e repetíveis, o que as torna relativamente fáceis de testar.As sagas são implementadas através de funções especiais chamadas funções de gerador. Estes são um novo recurso do
ES6 JavaScript
. Basicamente, a execução entra e sai de um gerador em todos os lugares em que você vê uma declaração de rendimento. Pense em umayield
declaração como fazendo com que o gerador faça uma pausa e retorne o valor gerado. Posteriormente, o chamador pode retomar o gerador na declaração a seguir ayield
.Uma função geradora é definida como esta. Observe o asterisco após a palavra-chave da função.
Uma vez que a saga de login é registrada
Redux-Saga
. Mas então ayield
tomada na primeira linha pausará a saga até que uma ação do tipo'LOGIN_REQUEST'
seja despachada para a loja. Quando isso acontecer, a execução continuará.Para mais detalhes, consulte este artigo .
fonte
Uma nota rápida. Os geradores são canceláveis, assíncronos / aguardam - não. Portanto, para um exemplo da pergunta, realmente não faz sentido o que escolher. Mas, para fluxos mais complicados, às vezes não há solução melhor do que usar geradores.
Então, outra idéia poderia ser é usar geradores com redux-thunk, mas, para mim, parece como tentar inventar uma bicicleta com rodas quadradas.
E, é claro, os geradores são mais fáceis de testar.
fonte
Aqui está um projeto que combina as melhores partes (profissionais) de ambos
redux-saga
eredux-thunk
: você pode lidar com todos os efeitos colaterais das sagas enquanto recebe uma promessa peladispatching
ação correspondente: https://github.com/diegohaz/redux-saga-thunkfonte
then()
dentro de um componente React é contra o paradigma. Você deve lidar com o estado alterado emcomponentDidUpdate
vez de esperar que uma promessa seja resolvida.componentDidlMount() { this.props.doSomething().then((detail) => { this.setState({isReady: true})} }
Uma maneira mais fácil é usar o redux-auto .
da documantasion
A idéia é ter cada ação em um arquivo específico . co-localizando a chamada do servidor no arquivo com funções redutoras para "pendente", "cumprida" e "rejeitada". Isso facilita muito o manuseio das promessas.
Também anexa automaticamente um objeto auxiliar (chamado "assíncrono") ao protótipo do seu estado, permitindo rastrear na sua interface do usuário as transições solicitadas.
fonte