Reagir Native + Redux: Implementando Redux Saga para um fluxo síncrono

Por: Jeff Lewis

Jeff Lewis Blocked Unblock Seguir Seguindo 8 de julho

Notas:

O que é o Redux Saga?

O Redux Saga é uma biblioteca que é usada como um middleware para o Redux. Um middleware Redux é um código que intercepta ações que entram na loja através do método dispatch() e pode realizar tarefas com base na ação recebida . As sagas observam todas as ações (observador / observador) que são despachadas da loja. Antes que a ação seja passada para os props , a outra função (worker) processará o que fazer com a ação no fluxo assíncrono.

O Redux Saga usa um recurso do ES6 chamado Generators , que permite escrever código assíncrono. É importante saber como funciona o fluxo do Redux Saga, especificamente o que acontece com as Sagas (Generator Functions) . Funções do Gerador permitem operações de enfileiramento usando yield , tornando-o assíncrono.

Por que usar o Redux Saga?

O Redux Saga ajuda o Redux a gerenciar os efeitos colaterais dos aplicativos. Isso torna seu código mais eficiente de executar, mais fácil de testar e permite um melhor tratamento de erros.

1. Melhor Manuseio de Ações e Operações Assíncronas

Os geradores fazem fila de forma assíncrona, o que é importante quando se lida com solicitações / respostas de API, comunicação com o Firebase ou processos sensíveis ao tempo.

2. Mais fácil de ler sem o inferno do retorno de chamada

O Redux Saga usa geradores para tarefas de fila, o que remove o inferno de retorno de chamada que vem com o Redux-Thunk ao usar o Promises.

3. Melhor tratamento de erros

O Redux Saga requer 2 Sagas (funções geradoras) para cada pedaço de estado: trabalhador e observador. A saga que gerencia o processo está encarregada de lidar com o erro, então cada parte do Estado do Repositório do Redux tem seu próprio erro.

4. Gerencia o Fluxo Complexo

O Redux Saga como um middleware nos permite interceptar ações antes que elas sejam passadas para o componente por mapStateToProps . As Sagas (funções geradoras) trabalham juntas para executar de forma assíncrona em um estilo modular.

5. Efeitos Declarativos

O usuário do Redux Saga Watchers, Efeitos Declarativos ( takeEvery , takeLatest , takeLeading , etc.), permite o controle da quantidade de solicitações. Por exemplo, um usuário está tentando fazer login e está quebrando o botão de login continuamente. Isso enviaria uma solicitação toda vez que uma solicitação fosse feita.

Podemos usar o Efeito Declarativo, como takeLatest para aceitar apenas o último pedido.

6. Fácil de testar

Com o uso de geradores ES6 e Efeitos Declarativos ( takeEvery , takeLatest , takeLeading , e etc.), construindo os testes são muito mais fáceis e limpos.

Exemplo de aplicativo + código

Github Repo: https://github.com/jefelewis/redux-saga-test

A. Visão geral do aplicativo

O aplicativo terá um redutor: counterReducer . Em nosso arquivo store.js , importaremos o redutor root e a saga root e os integraremos em nossa Redux Store com applyMiddleware () .

Para testar se o Redux Saga está funcionando em nosso aplicativo, podemos pressionar o botão “-” (Diminuir) quantas vezes quisermos e nossa contagem diminuirá. Nosso botão “+” (Aumento) é diferente porque temos um atraso assíncrono de 4 segundos. Ambas as nossas funções de Aumento e Diminuição são assíncronas devido às Sagas, mas o atraso de 4 segundos é para fins de demonstração.

Além disso, estamos usando o takeLatest em nossa watchIncreaseCounter , então somente a última ação será usada. Você pode clicar no botão “+” (Aumentar) quantas vezes quiser, mas o contador só aumentará em 1 quando 4 segundos se esgotarem após o último clique no botão.

B. Captura de tela da aplicação

Counter.js

C. Estrutura do Arquivo de Aplicativo

Este exemplo utilizará 7 arquivos:

  1. App.js (React Native App)
  2. Counter.js (tela do contador)
  3. store.js (loja Redux)
  4. index.js (redutor de raiz Redux)
  5. counterReducer.js (Redux Counter Reducer)
  6. index.js (Redux Root Saga)
  7. counterSaga (Redux Counter Saga)

D. Arquivos App

App.js

 // Importações: dependências 
importar Reagir de 'reagir';
import {Provider} de 'react-redux';
 // Importações: Telas 
importar contador de './screens/Counter';
 // Importações: Redux Store 
import {store} de './store/store';
 // Reagir aplicativo nativo 
função padrão de exportação App () {
Retorna (
// Redux: loja global
<Loja do provedor = {store}>
<Counter />
</ Provedor>
);
}

Counter.js

 // Importações: dependências 
import React, {Component} from 'react'; import {Botão, Dimensões, SafeAreaView, StyleSheet, Text, TouchableOpacity, View} de 'react-native';
import {connect} de 'react-redux';
 // Dimensões da tela 
const {altura, largura} = Dimensions.get ('window');
 // Screen: Counter 
contador de classe estende React.Component {
render () {
Retorna (
<Estilo SafeAreaView = {styles.container}>
<Estilo de texto = {styles.counterTitle}> Contador </ Text>
 <Ver estilo = {styles.counterContainer}> 
<TouchableOpacity onPress = {this.props.reduxIncreaseCounter}>
<Estilo de texto = {styles.buttonText}> + </ Text
</ TouchableOpacity>
 <Estilo de texto = {styles.counterText}> {this.props.counter} </ Text> 
 <TouchableOpacity onPress = {this.props.reduxDecreaseCounter}> 
<Estilo de texto = {styles.buttonText}> - </ Text
</ TouchableOpacity>
</ View>
</ SafeAreaView>
)
}
}
 // Styles 
estilos de const = StyleSheet.create ({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}
counterContainer: {
display: 'flex'
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
}
counterTitle: {
fontFamily: 'System',
fontSize: 32,
fontWeight: '700',
cor: '# 000',
}
counterText: {
fontFamily: 'System',
fontSize: 36,
fontWeight: '400',
cor: '# 000',
}
botão de texto: {
fontFamily: 'System',
fontSize: 50,
fontWeight: '300',
cor: '# 007AFF',
marginLeft: 40,
marginRight: 40,
}
});
 // Mapear o estado para adereços (o Redux Store passa do estado para o componente) 
const mapStateToProps = (estado) => {
console.log ('State:');
console.log (estado);
 // Redux Store -> Componente 
Retorna {
contador: state.counter.counter,
};
};
 // Mapear Despacho para Adereços (Despachar Ações para Redutores. Então, Modificar os Dados e Atribuí-los aos Seus Adendos) 
const mapDispatchToProps = (envio) => {
// Açao
Retorna {
// Aumentar contador
reduxIncreaseCounter: () => despacho ({
tipo: 'INCREASE_COUNTER',
valor: 1,
}),
// Diminuir Contador
reduxDecreaseCounter: () => despacho ({
tipo: 'DECREASE_COUNTER',
valor: 1,
}),
};
};
 // Exportações 
exportar padrão connect (mapStateToProps, mapDispatchToProps) (Contador);

store.js

 // Importações: dependências 
import {createStore, applyMiddleware} de 'redux';
import {createLogger} do 'redux-logger';
import createSagaMiddleware de 'redux-saga';
 // Importações: Redux Root Reducer 
import rootReducer de '../reducers/index';
 // Importações: Redux Root Saga 
import {rootSaga} de '../sagas/index';
 // Middleware: Redux Saga 
const sagaMiddleware = createSagaMiddleware ();
 // Redux: loja 
const store = createStore (
rootReducer,
applyMiddleware (
sagaMiddleware,
createLogger (),
)
);
 // Middleware: Redux Saga 
sagaMiddleware.run (rootSaga);
 // Exportações 
export {
loja,
}

index.js (redutor de raiz)

 // Importações: dependências 
import {combineReducers} de 'redux';
 // Importações: Redutores 
import counterReducer de './counterReducer';
 // Redux: Redutor de Raiz 
const rootReducer = combineReducers ({
contador: counterReducer,
});
 // Exportações 
rootReducer padrão de exportação;

counterReducer.js

 // Estado inicial 
const initialState = {
contador: 0,
};
 // Redux: Counter Reducer 
const counterReducer = (state = initialState, ação) => {
switch (action.type) {
case 'INCREASE_COUNTER_ASYNC': {
Retorna {
...Estado,
contador: state.counter + action.value,
};
}
 case 'DECREASE_COUNTER': { 
Retorna {
...Estado,
contador: state.counter - action.value,
};
}
 padrão: { 
estado de retorno;
}
}
};
 // Exportações 
exportar counterReducer padrão;

index.js (Root Saga)

 // Importações: dependências 
import {all, fork} de 'redux-saga / effects';
 // Importações: Redux Sagas 
import {watchIncreaseCounter, watchDecreaseCounter} de './counterSaga';
 // Redux Saga: Root Saga 
função de exportação * rootSaga () {
render tudo ([
fork (watchIncreaseCounter),
garfo (watchDecreaseCounter),
]);
};

counterSaga.js

 // Importações: dependências 
import {delay, takeAvery, takeLatest, put} de 'redux-saga / effects';
 // Worker: Aumenta o Counter Async (atrasado em 4 segundos) 
function * increaseCounterAsync () {
experimentar {
// Atraso 4 segundos
atraso no rendimento (4000);
 // Despachar ação para o Redux Store 
rendimento put ({
tipo: 'INCREASE_COUNTER_ASYNC',
valor: 1,
});
}
catch (erro) {
console.log (erro);
}
};
 // Watcher: Aumentar o contador assíncrono 
função de exportação * watchIncreaseCounter () {
// Tome apenas a última ação
yield takeLatest ('INCREASE_COUNTER', increaseCounterAsync);
};
 // Worker: Diminuir Contador 
function * decreaseCounter () {
experimentar {
// Despachar ação para o Redux Store
rendimento put ({
tipo: 'DECREASE_COUNTER_ASYNC',
valor: 1,
});
}
catch (erro) {
console.log (erro);
}
};
 // Watcher: Diminuir Contador 
função de exportação * watchDecreaseCounter () {
// Tome apenas a última ação
yield takeLatest ('DECREASE_COUNTER', reduceCounter);
};

A Saga Continua

E é isso! O Redux Saga está agora trabalhando em seu aplicativo e sua Loja Redux agora é assíncrona.

Ninguém é perfeito. Se você encontrou algum erro, deseja sugerir melhorias ou expandir um tópico, sinta-se à vontade para me enviar uma mensagem. Não se esqueça de incluir quaisquer melhorias ou corrigir quaisquer problemas.