Вопрос по reactjs, redux-thunk, redux, redux-saga, javascript – Плюсы / минусы использования redux-saga с генераторами ES6 по сравнению с redux-thunk с ES2017 async / await

410

Сейчас много говорят о последнем мальчике в городе редуксе,перевождь-сага / перевождь-эпопея, Он использует функции генератора для прослушивания / диспетчеризации действий.

Прежде чем обернуть голову, я хотел бы знать плюсы / минусы использованияredux-saga вместо подхода ниже, где я используюredux-thunk с асинхронным ожиданием

Компонент может выглядеть так, диспетчеризировать действия как обычно.

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);

Тогда мои действия выглядят примерно так:

// 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...
@ZhenyangHua это сокращение для привязки функции к объекту (this) акаthis.onClick = this.onClick.bind(this), Более длинную форму обычно рекомендуется использовать в конструкторе, так как сокращение повторяет привязку при каждом рендере. hampusohlsson
@hampusohlsson Вы нашли сагу о редуксе полезной? После объяснения я не смог найти никакой пользы. Hosar
Что:: до вашегоthis.onClick делать? Downhillski
Понимаю. Спасибо! Я вижу, что люди используютbind() много пройтиthis к функции, но я начал использовать() => method() сейчас. Downhillski

Ваш Ответ

7   ответов
0

на своем опыте, Sagas предоставляет разработчикам более структурированный способ написания кода, который намного проще тестировать и труднее ошибиться.

Да, это немного странно, но большинство разработчиков достаточно понимают это за один день. Я всегда говорю людям не беспокоиться о том, чтоyield делает для начала и что, как только вы напишите пару тестов, он придет к вам.

Я видел пару проектов, в которых thunks рассматривались так, как если бы они были контроллерами из паттерна MVC, и это быстро превращается в необратимый беспорядок.

Мой совет - использовать Sagas там, где вам нужны триггеры типа B, относящиеся к одному событию. Я считаю, что для всего, что может затрагивать несколько действий, проще написать клиентское промежуточное ПО и использовать мета-свойство действия FSA для его запуска.

-1

async / await - нет. Так что для примера из вопроса, это действительно не имеет смысла, что выбрать. Но для более сложных потоков иногда нет лучшего решения, чем использование генераторов.

Таким образом, другая идея могла бы состоять в том, чтобы использовать генераторы с редуктором, но для меня это похоже на попытку изобрести велосипед с квадратными колесами.

И конечно, генераторы проще тестировать.

-1

перевождь-авто.

из документа

redux-auto исправил эту асинхронную проблему, просто позволив вам создать функцию «action», которая возвращает обещание. Для сопровождения вашей «стандартной» функции логика действий.

Нет необходимости в другом асинхронном промежуточном ПО Redux. например Thunk, обещание промежуточного программного обеспечения, сагаЛегко позволяет передать обещание в редуксеи вам это удалосьПозволяет совмещать внешние вызовы службы с тем, где они будут преобразованыПрисвоение имени файлу init.js вызовет его один раз при запуске приложения. Это хорошо для загрузки данных с сервера при запуске

Идея состоит в том, чтобы каждыйдействие в определенном файле, совместное размещение вызова сервера в файле с функциями редуктора для «ожидающих», «выполненных» и «отклоненных». Это делает обработку обещаний очень легкой.

Он также автоматически присоединяетвспомогательный объект (называется «асинхронный») к прототипу вашего состояния, позволяющему отслеживать в вашем пользовательском интерфейсе запрашиваемые переходы.

Я сделал +1, даже если это не имеет значения ответ, потому что разные решения тоже должны быть рассмотрены amorenew
Я думаю, что есть, потому что он не раскрыл, что он является автором проекта jreptak
20

а (с использованием саг и thunk):

Саги отлично подходят для тестирования:

Вам не нужно имитировать функции, обернутые эффектамиПоэтому тесты чистые, удобочитаемые и простые в написанииПри использовании саг создатели действий в основном возвращают обычные литералы объектов. Это также легче протестировать и утверждать, в отличие от обещаний Тунка.

Саги более могущественны. Все, что вы можете сделать в создателе действий одного толка, вы также можете сделать в одной саге, но не наоборот (или, по крайней мере, нелегко). Например:

дождаться отправки действия / действий (take)отменить существующую процедуру (cancel, takeLatest, race)несколько подпрограмм могут прослушивать одно и то же действие (take, takeEvery...)

Sagas также предлагает другие полезные функции, которые обобщают некоторые общие шаблоны приложений:

channels прослушивать внешние источники событий (например, websockets)модель вилки (fork, spawn)дроссель...

Саги отличный и мощный инструмент. Однако с властью приходит ответственность. Когда ваше приложение растет, вы можете легко потеряться, выяснив, кто ожидает отправки действия или что все происходит при отправке какого-либо действия. С другой стороны, это проще и легче рассуждать. Выбор того или другого зависит от многих аспектов, таких как тип и размер проекта, какие виды побочных эффектов должен обрабатывать ваш проект или предпочтения команды разработчиков. В любом случае просто сделайте ваше приложение простым и предсказуемым.

0

который сочетает в себе лучшие части (плюсы) обоихredux-saga а такжеredux-thunk: вы можете справиться со всеми побочными эффектами саг, получая обещание отdispatching соответствующее действие:https://github.com/diegohaz/redux-saga-thunk

class MyComponent extends React.Component {
  componentWillMount() {
    // `doSomething` dispatches an action which is handled by some saga
    this.props.doSomething().then((detail) => {
      console.log('Yaay!', detail)
    }).catch((error) => {
      console.log('Oops!', error)
    })
  }
}
@ Maxincredible52 Это не верно для рендеринга на стороне сервера. Diego Haz
с помощьюthen() внутри React компонент против парадигмы. Вы должны обработать измененное состояние вcomponentDidUpdate вместо того, чтобы ждать обещания, которое будет решено. user4315230
@ Maxincredible52, почему это против парадигмы, где ты это прочитал? Я обычно делаю аналогично @Diego Haz, но делаю это в componentDidMount (в соответствии с документами React сетевые вызовы должны выполняться там), поэтому мы имеемcomponentDidlMount() { this.props.doSomething().then((detail) => { this.setState({isReady: true})} } user3711421
По моему опыту, точка зрения Макса все еще верна для рендеринга на стороне сервера. Это, вероятно, должно быть обработано где-то на уровне маршрутизации. ThinkingInBits
82

полнение к довольно полному ответу автора библиотеки.

Pro (используя сагу):

Тестируемость. Тестировать саги очень легко, так как call () возвращает чистый объект. Тестирование Thunks обычно требует, чтобы вы включили mockStore внутри вашего теста.

Redux-Saga поставляется с множеством полезных вспомогательных функций о задачах. Мне кажется, что идея саги заключается в создании своего рода фонового рабочего процесса / потока для вашего приложения, который выступает в качестве недостающего элемента в архитектуре реагирующего редукса (actionCreators и редукторы должны быть чистыми функциями.) Что приводит к следующему пункту.

Саги предлагают независимое место для обработки всех побочных эффектов. В моем опыте, как правило, легче модифицировать и управлять чем громоздкими действиями.

Против:

Синтаксис генератора.

Много идей для изучения.

API стабильность. Кажется, что Redx-сага все еще добавляет функции (например, каналы?), И сообщество не такое большое. Существует опасение, если библиотека когда-нибудь сделает несовместимое обновление.

Да, у FWIW redux-saga теперь 12 тысяч звезд, у redux-thunk 8 тысяч Brian Burns
Просто хочу кое-что прокомментировать, создатель действия не должен быть чистой функцией, о которой неоднократно заявлял сам Дэн. Marson Mao
Я собираюсь добавить еще один вызов саг, это то, что сагиполностью отделен от действий и создателей действий по умолчанию. Хотя Thunks напрямую связывают создателей действий со своими побочными эффектами, саги оставляют создателей действий полностью отделенными от саг, которые их слушают. Это имеет технические преимущества, но может значительно усложнить понимание кода и может размыть некоторые однонаправленные концепции. theaceofthespade
Сага имеет больше стартов, чем Thunk, и ее последний коммит тоже после Thunk amorenew
На данный момент, как и в случае расширения сообщества, очень рекомендуется использовать Redux-Sagas. Также API стал более зрелым. Рассмотрите возможность удаления Con дляAPI stability как обновление, чтобы отразить текущую ситуацию. Denialos
413

эквивалент приведенного выше примера будет

export function* loginSaga() {
  while(true) {
    const { user, pass } = yield take(LOGIN_REQUEST)
    try {
      let { data } = yield call(request.post, '/login', { user, pass });
      yield fork(loadUserData, data.uid);
      yield put({ type: LOGIN_SUCCESS, data });
    } catch(error) {
      yield put({ type: LOGIN_ERROR, error });
    }  
  }
}

export function* loadUserData(uid) {
  try {
    yield put({ type: USERDATA_REQUEST });
    let { data } = yield call(request.get, `/users/${uid}`);
    yield put({ type: USERDATA_SUCCESS, data });
  } catch(error) {
    yield put({ type: USERDATA_ERROR, error });
  }
}

Первое, на что нужно обратить внимание, это то, что мы вызываем функции API, используя формуyield call(func, ...args). call не выполняет эффект, он просто создает простой объект, такой как{type: 'CALL', func, args}, Выполнение делегируется промежуточному программному обеспечению redux-saga, которое заботится о выполнении функции и возобновлении генератора с его результатом.

Основным преимуществом является то, что вы можете протестировать генератор за пределами Redux, используя простые проверки на равенство

const iterator = loginSaga()

assert.deepEqual(iterator.next().value, take(LOGIN_REQUEST))

// resume the generator with some dummy action
const mockAction = {user: '...', pass: '...'}
assert.deepEqual(
  iterator.next(mockAction).value, 
  call(request.post, '/login', mockAction)
)

// simulate an error result
const mockError = 'invalid user/password'
assert.deepEqual(
  iterator.throw(mockError).value, 
  put({ type: LOGIN_ERROR, error: mockError })
)

Обратите внимание, что мы высмеиваем результат вызова API, просто вставляя проверенные данные вnext метод итератора. Дразнить данные намного проще, чем дразнить функции.

Второе, на что стоит обратить внимание, это призыв кyield take(ACTION), Thunks вызывается создателем действия при каждом новом действии (например,LOGIN_REQUEST). то есть действия постояннотолкнул к thunks, и thunks не имеют никакого контроля над тем, когда прекратить обрабатывать эти действия.

В редукс-саге, генераторывытащить следующее действие. то есть они имеют контроль, когда прислушиваться к какому-либо действию, а когда нет. В приведенном выше примере инструкции потока размещены внутриwhile(true) цикл, так что он будет прослушивать каждое поступающее действие, что в некоторой степени имитирует поведение толчка.

Тяговый подход позволяет реализовать сложные потоки управления. Предположим, например, что мы хотим добавить следующие требования

Обработка действий пользователя LOGOUT

при первом успешном входе в систему сервер возвращает токен, срок действия которого истекает с некоторой задержкой, хранящейся вexpires_in поле. Придется обновить авторизацию в фоновом режиме на каждомexpires_in миллисекунды

Примите во внимание, что при ожидании результата вызовов API (либо первоначального входа в систему, либо обновления) пользователь может выйти из промежуточного режима.

Как бы вы реализовали это с помощью Thunks; одновременно обеспечивая полное тестовое покрытие для всего потока? Вот как это может выглядеть с Sagas:

function* authorize(credentials) {
  const token = yield call(api.authorize, credentials)
  yield put( login.success(token) )
  return token
}

function* authAndRefreshTokenOnExpiry(name, password) {
  let token = yield call(authorize, {name, password})
  while(true) {
    yield call(delay, token.expires_in)
    token = yield call(authorize, {token})
  }
}

function* watchAuth() {
  while(true) {
    try {
      const {name, password} = yield take(LOGIN_REQUEST)

      yield race([
        take(LOGOUT),
        call(authAndRefreshTokenOnExpiry, name, password)
      ])

      // user logged out, next while iteration will wait for the
      // next LOGIN_REQUEST action

    } catch(error) {
      yield put( login.error(error) )
    }
  }
}

В приведенном выше примере мы выражаем наше требование параллелизма, используяrace, Еслиtake(LOGOUT) выигрывает гонку (то есть пользователь нажимает кнопку выхода из системы). Гонка автоматически отменитauthAndRefreshTokenOnExpiry фоновое задание И еслиauthAndRefreshTokenOnExpiry был заблокирован в серединеcall(authorize, {token}) позвоните, это также будет отменено. Отмена распространяется вниз автоматически.

Вы можете найтиработоспособная демонстрация вышеуказанного потока

@justingordon Исправлено, исправлено с 2016 года 08 11. Denialos
@ syg, я согласен, что колл, форк, бери и пут могут быть более семантически дружелюбными. Однако именно те функции, подобные глаголам, делают все побочные эффекты тестируемыми. Downhillski
redux-thunk Код вполне читабелен и самоочевиден. Ноredux-sagas одно действительно нечитаемо, в основном из-за функций, подобных глаголам:call, fork, take, put... syg
эти "странные" глаголы также помогают вам осмыслить отношение саги к сообщениям, исходящим от избыточности. вы можетепринимать типы сообщений вне избыточности - часто для запуска следующей итерации, и вы можетеположил новые сообщения обратно, чтобы транслировать результат вашего побочного эффекта. worc

Похожие вопросы