javascript - إيجابيات وسلبيات استخدام red-saga مع مولدات ES6 مقابل redux-thunk مع ES2017 async / await




reactjs redux-saga (5)

هناك الكثير من الحديث عن أحدث طفل في مدينة Redux الآن ، redux-saga/redux-saga . ويستخدم وظائف مولد للاستماع إلى إجراءات / إيفاد.

قبل أن أضع رأسي حوله ، أود أن أعرف إيجابيات وسلبيات استخدام redux-saga بدلاً من النهج أدناه حيث أستخدم redux-thunk مع async / await.

قد يبدو أحد المكونات كهذا ، حيث يتم إرسال إجراءات كالمعتاد.

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...

Answers

في ما يلي مشروع يجمع بين أفضل الأجزاء (المحترفين) لكل من redux-saga و redux-thunk : يمكنك التعامل مع جميع الآثار الجانبية على Sagas أثناء الحصول على وعد عن طريق dispatching الإجراء https://github.com/diegohaz/redux-saga-thunk : 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)
    })
  }
}

سأضيف تجربتي باستخدام القصة في نظام الإنتاج بالإضافة إلى إجابة مؤلفها بشكل واضح.

برو (باستخدام الملحمة):

  • قابلية الاختبار. من السهل جدًا اختبار sagas حيث تقوم call () بإرجاع كائن نقي. عادةً ما يتطلب اختبار thunks تضمين MockStore داخل الاختبار الخاص بك.

  • ريدو ساغا يأتي مع الكثير من وظائف المساعد مفيدة حول المهام. يبدو لي أن مفهوم الملحمة هو إنشاء نوع من عامل / خيط الخلفية في التطبيق الخاص بك ، والذي يعمل بمثابة قطعة مفقودة في بنية رد الفعل المتفاعل (يجب أن تكون الأكترونات والمخفّفات وظائف نقية.) مما يؤدي إلى النقطة التالية.

  • تقدم Sagas مكانًا مستقلاً للتعامل مع جميع الآثار الجانبية. عادة ما يكون من الأسهل تعديلها وإدارتها من الإجراءات الشديدة في تجربتي.

يخدع:

  • بناء جملة مولد.

  • الكثير من المفاهيم للتعلم.

  • استقرار واجهة برمجة التطبيقات. يبدو أن الملحمة لا تزال تضيف ميزات (مثل القنوات؟) والمجتمع ليس كبيرًا. هناك اهتمام إذا كانت المكتبة بإجراء تحديث غير متخلف غير متخلف يومًا ما.


أسهل طريقة هي استخدام redux-auto .

من وثيقة

قام redux-auto بتثبيت هذه المشكلة غير المتزامنة ببساطة عن طريق السماح لك بإنشاء وظيفة "إجراء" تعيد الوعد. لمرافقة منطق إجراء الوظيفة "الافتراضي" الخاص بك.

  1. لا حاجة للبرامج الوسيطة الأخرى. على سبيل المثال thunk ، الوعد ، الوسيطة ، الملحمة
  2. يسمح لك بسهولة لتمرير الوعد إلى redux وتمكنت من إدارتها لك
  3. يتيح لك المشاركة في تحديد موقع مكالمات الخدمات الخارجية مع المكان الذي سيتم تحويلها إليه
  4. تسمية الملف "init.js" ستطلق عليه مرة واحدة عند بدء التطبيق. هذا جيد لتحميل البيانات من الخادم في البداية

والفكرة هي أن يكون كل إجراء في ملف معين . شارك في تحديد موقع استدعاء الخادم في الملف مع وظائف المخفض لـ "معلق" و "مكتمل" و "مرفوض". هذا يجعل التعامل مع الوعود سهل للغاية.

كما أنه يرفق تلقائيًا كائن مساعد (يُطلق عليه "async") إلى النموذج الأولي لحالتك ، مما يسمح لك بالتتبع في واجهة المستخدم الخاصة بك ، والتحويلات المطلوبة.


في ملحمة redux-saga ، سيكون مكافئ المثال أعلاه

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

أول شيء يجب أن تلاحظه هو أننا نقوم باستدعاء وظائف واجهة برمجة التطبيقات باستخدام yield call(func, ...args) . لا تقوم call بتنفيذ التأثير ، بل تقوم فقط بإنشاء كائن بسيط مثل {type: 'CALL', func, args} . يتم تفويض التنفيذ إلى وسيط red-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 ليس لديهم سيطرة على متى تتوقف عن التعامل مع تلك الإجراءات.

في ملحمة redux-saga ، تسحب المولدات الإجراء التالي. أي أنهم لديهم السيطرة عندما يستمعون لبعض العمل ، ومتى لا. في المثال أعلاه ، يتم وضع تعليمات التدفق داخل حلقة 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}) ، فسيتم إلغاء الاتصال أيضًا. الإلغاء ينتشر إلى الأسفل تلقائيًا.

يمكنك العثور على عرض تجريبي runnable من التدفق أعلاه


هذه هي المشكلة المتعلقة بإعداد ملف المضيفين. أضف السطر التالي إلى ملف hots الخاص بك في Ububtu: / etc / hosts

127.0.0.1   localhost

في windows: c: \ windows \ System32 \ drivers \ etc \ hosts

127.0.0.1   localhost






javascript reactjs redux redux-thunk redux-saga