В приложениях с redux многие разработчики испытывают дискомфорт в DX при написании async логики. Например, у нас есть todos-app с бекендом для хранения списков задач пользователей. Для того, чтобы сделать возможным async взаимодействие в redux необходимо написать свою middleware, либо взять из готовых. Как пример, будем использовать redux-thunk. Ниже приведен код, в котором обрабатывается логика добавления todo.
import {
ADD_TODO_SUCCESS,
ADD_TODO_FAILURE,
ADD_TODO_STARTED,
} from './types';
import axios from 'axios';
const url = `https://jsonplaceholder.typicode.com/todos`
export const addTodo = ({ title, userId }) => {
return dispatch => {
dispatch(addTodoStarted());
axios
.post(url, {title, userId, completed: false})
.then(res => {
dispatch(addTodoSuccess(res.data));
})
.catch(err => {
dispatch(addTodoFailure(err.message));
});
};
};
const addTodoSuccess = todo => ({
type: ADD_TODO_SUCCESS,
payload: {
...todo
}
});
const addTodoStarted = () => ({
type: ADD_TODO_STARTED
});
const addTodoFailure = error => ({
type: ADD_TODO_FAILURE,
payload: {
error
}
});
addTodo громоздкая thunk-function вызывающая actionCreators в зависимости от состояния Promise.
Давайте перепишем этот код с использованием Effector.js и Effects
<aside> 💡 Effect
</aside>
// добавить текст описывающий листинг
import {createEffect} from 'effector'
const url = `https://jsonplaceholder.typicode.com/todos`
// импортируем или создаем асинхронную функцию, которая взаимодействует с api
async function requestAddToDo({ title, userId }) {
try {
return await axios.post(url, { title, userId, completed: false })
}
catch (error) {
throw error
}
}
// создаем Effect
const fxAddTodo = createEffect(requestAddToDo)
// создаем Event, который вызовет Effect с переданным объектом
export const addTodo = fxAddTodo.prepend(payload => payload)
// вызываем Event
addTodo({title: 'Done Effector.js docs', userId: 1337 })
Созданный Effect (addTodo) имеет свойства .done .fail .pending .finally. С помощью данных свойств нам не нужно создавать функции **addTodoSuccess, addTodoFailure, addTodoStarted. В конечном итоге отслеживание async действий сводиться к такой записи
// Example 1
const isLoading = createStore(false)
.on(addTodo, () => true)
.on([fxAddTodo.done, fxAddTodo.fail], () => false)
// или .reset(addTodo.done, addTodo.fail)
const error = createStore(null)
.on(fxAddTodo.fail, (_, {error}) => error)
.reset(addTodo.done)
const todosStatus = combine({
loading: isLoading,
error,
})
// Example 2
const error = createStore(null)
.on(fxAddTodo.fail, (_, {error}) => error)
.reset(fxAddTodo.done)
const todosStatus = combine({
loading: fxAddTodo.pending,
error,
})
В Example 1 создается Store isLoading. С помощью него можно отслеживать fetch данных. В последних версиях Effector.js было добавлено свойство pending. Пример его использования Example 2. addTodo.pending заменяет собой создание Store isLoading.
// Добавляем данные в стор и считываем все из него в View
const $todosList = createStore([]).on(fxAddTodo.doneData, (_, todos) => todos);
const $error = createStore(null)
.on(fxAddTodo.fail, (_, {error}) => error)
.reset(fxAddTodo.done)
const $todos = combine({
loading: fxAddTodo.pending,
error: $error
data: $todosList
})
//View.jsx
function View() {
const {data: todos, loading, error} = useStore($todos)
return <ul>{todos.map(todo => <li>{todo.title}</li>)}</ul>
}