Redux: محاولة وضع استدعاءات API في المكان الصحيح

تم إنشاؤها على ٢٠ يوليو ٢٠١٥  ·  115تعليقات  ·  مصدر: reduxjs/redux

أحاول إجراء عملية تسجيل دخول ناجح / خطأ ، لكن شاغلي الرئيسي هو أين يمكنني وضع هذا المنطق.

أنا أستخدم حاليًا actions -> reducer (switch case with action calling API) -> success/error on response triggering another action .

تكمن مشكلة هذا النهج في أن المخفض لا يعمل عندما أستدعى الإجراء من استدعاء API.

هل فاتني شيء؟

المخفض

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';
import Immutable from 'immutable';
import LoginApiCall from '../utils/login-request';

const initialState = new Immutable.Map({
  email: '',
  password: '',
}).asMutable();

export default function user(state = initialState, action) {
  switch (action.type) {
    case LOGIN_ATTEMPT:
      console.log(action.user);
      LoginApiCall.login(action.user);
      return state;
    case LOGGED_FAILED:
      console.log('failed from reducer');
      return state;
    case LOGGED_SUCCESSFULLY:
      console.log('success', action);
      console.log('success from reducer');
      break;
    default:
      return state;
  }
}

أجراءات

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';

export function loginError(error) {
  return dispatch => {
    dispatch({ error, type: LOGGED_FAILED });
  };
}

/*
 * Should add the route like parameter in this method
*/
export function loginSuccess(response) {
  return dispatch => {
    dispatch({ response, type: LOGGED_SUCCESSFULLY });
    // router.transitionTo('/dashboard'); // will fire CHANGE_ROUTE in its change handler
  };
}

export function loginRequest(email, password) {
  const user = {email: email, password: password};
  return dispatch => {
    dispatch({ user, type: LOGIN_ATTEMPT });
  };
}

مكالمات API

 // Use there fetch polyfill
 // The main idea is create a helper in order to handle success/error status
import * as LoginActions from '../actions/LoginActions';

const LoginApiCall = {
  login(userData) {
    fetch('http://localhost/login', {
      method: 'post',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        email: userData.email,
        password: userData.password,
      }),
    })
    .then(response => {
      if (response.status >= 200 && response.status < 300) {
        console.log(response);
        LoginActions.loginSuccess(response);
      } else {
        const error = new Error(response.statusText);
        error.response = response;
        LoginActions.loginError();
        throw error;
      }
    })
    .catch(error => { console.log('request failed', error); });
  },
};

export default LoginApiCall;
docs question

التعليق الأكثر فائدة

أخيرًا ، أين تضع استدعاء API لتسجيل الدخول بعد ذلك؟

هذا هو بالضبط ما هو منشئ الإجراء dispatch => {} . آثار جانبية!

إنه مجرد صانع عمل آخر. ضعها مع الإجراءات الأخرى:

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';

export function loginError(error) {
  return { error, type: LOGGED_FAILED };
}

export function loginSuccess(response) {
  return dispatch => {
    dispatch({ response, type: LOGGED_SUCCESSFULLY });
    router.transitionTo('/dashboard');
  };
}

export function loginRequest(email, password) {
  const user = {email: email, password: password};
  return { user, type: LOGIN_ATTEMPT };
}

export function login(userData) {
  return dispatch =>
    fetch('http://localhost/login', {
      method: 'post',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        email: userData.email,
        password: userData.password,
      }),
    })
    .then(response => {
      if (response.status >= 200 && response.status < 300) {
        console.log(response);
        dispatch(loginSuccess(response));
      } else {
        const error = new Error(response.statusText);
        error.response = response;
        dispatch(loginError(error));
        throw error;
      }
    })
    .catch(error => { console.log('request failed', error); });
}

في المكونات الخاصة بك ، فقط اتصل

this.props.login(); // assuming it was bound with bindActionCreators before

// --or--

this.props.dispatch(login()); // assuming you only have dispatch from Connector

ال 115 كومينتر

هذا صحيح تقريبًا ، لكن المشكلة هي أنه لا يمكنك استدعاء صانعي الإجراءات الخالصة وتوقع حدوث الأشياء. لا تنس أن صانعي الإجراءات هم مجرد وظائف تحدد _ ما الذي يجب إرساله.

// CounterActions
export function increment() {
  return { type: INCREMENT }
}


// Some other file
import { increment } from './CounterActions';
store.dispatch(increment()); <--- This will work assuming you have a reference to the Store
increment(); <---- THIS DOESN'T DO ANYTHING! You're just calling your function and ignoring result.


// SomeComponent
import { increment } from './CounterActions';

@connect(state => state.counter) // will inject store's dispatch into props
class SomeComponent {
  render() {
    return <OtherComponent {...bindActionCreators(CounterActions, this.props.dispatch)} />
  }
}


// OtherComponent
class OtherComponent {
  handleClick() {
    // this is correct:
    this.props.increment(); // <---- it was bound to dispatch in SomeComponent

    // THIS DOESN'T DO ANYTHING:
    CounterActions.increment(); // <---- it's just your functions as is! it's not bound to the Store.
  }
}

الآن ، دعنا ننتقل إلى مثالك. أول شيء أريد توضيحه هو أنك لست بحاجة إلى نموذج غير متزامن dispatch => {} إذا قمت بإرسال إجراء واحد فقط بشكل متزامن ولم يكن لديك آثار جانبية (صحيح لـ loginError و loginRequest ).

هذه:

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';

export function loginError(error) {
  return dispatch => {
    dispatch({ error, type: LOGGED_FAILED });
  };
}

export function loginSuccess(response) {
  return dispatch => {
    dispatch({ response, type: LOGGED_SUCCESSFULLY });
    // router.transitionTo('/dashboard');
  };
}

export function loginRequest(email, password) {
  const user = {email: email, password: password};
  return dispatch => {
    dispatch({ user, type: LOGIN_ATTEMPT });
  };
}

يمكن تبسيطها كـ

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';

export function loginError(error) {
  return { error, type: LOGGED_FAILED };
}

// You'll have a side effect here so (dispatch) => {} form is a good idea
export function loginSuccess(response) {
  return dispatch => {
    dispatch({ response, type: LOGGED_SUCCESSFULLY });
    // router.transitionTo('/dashboard');
  };
}

export function loginRequest(email, password) {
  const user = {email: email, password: password};
  return { user, type: LOGIN_ATTEMPT };
}

ثانيًا ، من المفترض أن تكون مخفضات السرعة وظائف نقية ليس لها آثار جانبية. لا تحاول استدعاء API الخاص بك من المخفض.

هذه:

const initialState = new Immutable.Map({
  email: '',
  password: '',
  isLoggingIn: false,
  isLoggedIn: false,
  error: null
}).asMutable(); // <---------------------- why asMutable?

export default function user(state = initialState, action) {
  switch (action.type) {
    case LOGIN_ATTEMPT:
      console.log(action.user);
      LoginApiCall.login(action.user); // <------------------------ no side effects in reducers! :-(
      return state;
    case LOGGED_FAILED:
      console.log('failed from reducer');
      return state;
    case LOGGED_SUCCESSFULLY:
      console.log('success', action);
      console.log('success from reducer');
      break;
    default:
      return state;
  }

ربما يجب أن تبدو أكثر مثل

const initialState = new Immutable.Map({
  email: '',
  password: '',
  isLoggingIn: false,
  isLoggedIn: false,
  error: null
});

export default function user(state = initialState, action) {
  switch (action.type) {
    case LOGIN_ATTEMPT:
      return state.merge({
        isLoggingIn: true,
        isLoggedIn: false,
        email: action.email,
        password: action.password // Note you shouldn't store user's password in real apps
      });
    case LOGGED_FAILED:
      return state.merge({
        error: action.error,
        isLoggingIn: false,
        isLoggedIn: false
      });
    case LOGGED_SUCCESSFULLY:
      return state.merge({
        error: null,
        isLoggingIn: false,
        isLoggedIn: true
      });
      break;
    default:
      return state;
  }

أخيرًا ، أين تضع استدعاء API لتسجيل الدخول بعد ذلك؟

هذا هو بالضبط ما هو منشئ الإجراء dispatch => {} . آثار جانبية!

إنه مجرد صانع عمل آخر. ضعها مع الإجراءات الأخرى:

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';

export function loginError(error) {
  return { error, type: LOGGED_FAILED };
}

export function loginSuccess(response) {
  return dispatch => {
    dispatch({ response, type: LOGGED_SUCCESSFULLY });
    router.transitionTo('/dashboard');
  };
}

export function loginRequest(email, password) {
  const user = {email: email, password: password};
  return { user, type: LOGIN_ATTEMPT };
}

export function login(userData) {
  return dispatch =>
    fetch('http://localhost/login', {
      method: 'post',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        email: userData.email,
        password: userData.password,
      }),
    })
    .then(response => {
      if (response.status >= 200 && response.status < 300) {
        console.log(response);
        dispatch(loginSuccess(response));
      } else {
        const error = new Error(response.statusText);
        error.response = response;
        dispatch(loginError(error));
        throw error;
      }
    })
    .catch(error => { console.log('request failed', error); });
}

في المكونات الخاصة بك ، فقط اتصل

this.props.login(); // assuming it was bound with bindActionCreators before

// --or--

this.props.dispatch(login()); // assuming you only have dispatch from Connector

أخيرًا ، إذا وجدت نفسك كثيرًا ما تكتب منشئي الحركة الكبار مثل هذا ، فمن الجيد كتابة برمجية وسيطة مخصصة للمكالمات غير المتزامنة المتوافقة مع الوعود هي كل ما تستخدمه في حالة عدم التزامن.

التقنية التي وصفتها أعلاه (منشئو الإجراءات بتوقيع dispatch => {} ) مضمنة الآن في Redux ، لكن الإصدار 1.0 سيكون متاحًا فقط كحزمة منفصلة تسمى redux-thunk . أثناء تواجدك فيه ، يمكنك أيضًا التحقق من برمجيات الوعد بإعادة الوسيط أو وعد الاستعادة .

gaearon : التصفيق: كان هذا تفسيرًا رائعًا! ؛) يجب بالتأكيد جعله في المستندات.

gearon شرح رائع! :غنيمة:

gaearon ماذا لو كنت بحاجة إلى تنفيذ بعض المنطق لتحديد استدعاء API الذي يجب استدعاءه؟ أليس منطق المجال الذي يجب أن يكون في مكان واحد (مخفضات)؟ يبدو لي أن الحفاظ على ذلك في العمل هو كسر مصدر واحد للحقيقة. بالإضافة إلى ذلك ، تحتاج في الغالب إلى تحديد معلمات استدعاء واجهة برمجة التطبيقات ببعض القيمة من حالة التطبيق. أنت على الأرجح تريد أيضًا اختبار المنطق بطريقة ما. وجدنا أن إجراء استدعاءات API في Reducers (التدفق الذري) مفيد للغاية وقابل للاختبار في مشروع واسع النطاق.

وجدنا أن إجراء استدعاءات API في Reducers (التدفق الذري) مفيد للغاية وقابل للاختبار في مشروع واسع النطاق.

هذا يكسر الرقم القياسي / إعادة التشغيل. يصعب اختبار الوظائف ذات الآثار الجانبية أكثر من الوظائف البحتة بحكم التعريف. يمكنك استخدام Redux مثل هذا ولكنه مخالف تمامًا لتصميمه. :-)

يبدو لي أن الحفاظ على ذلك في العمل هو كسر مصدر واحد للحقيقة.

"مصدر واحد للحقيقة" يعني أن البيانات تعيش في مكان واحد ، وليس لها نسخ مستقلة. لا يعني ذلك "يجب أن يكون كل منطق المجال في مكان واحد".

ماذا لو كنت بحاجة إلى تنفيذ بعض المنطق لتحديد استدعاء API الذي يجب استدعاءه؟ أليس منطق المجال الذي يجب أن يكون في مكان واحد (مخفضات)؟

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

تحدث أي استدعاءات لواجهة برمجة التطبيقات (أو منطق يحدد واجهة برمجة التطبيقات التي تسمى) قبل المخفضات. هذا هو سبب دعم Redux للبرمجيات الوسيطة. تتيح لك البرامج الوسيطة Thunk الموضحة أعلاه استخدام الشروط وحتى القراءة من الحالة:

// Simple pure action creator
function loginFailure(error) {
  return { type: LOGIN_FAILURE, error };
}

// Another simple pure action creator
function loginSuccess(userId) {
  return { type: LOGIN_SUCCESS, userId };
}

// Another simple pure action creator
function logout() {
  return { type: LOGOUT };  
}


// Side effect: uses thunk middleware
function login() {
  return dispatch => {
    MyAwesomeAPI.performLogin().then(
      json => dispatch(loginSuccess(json.userId)),
      error => dispatch(loginFailure(error))
    )
  };
}


// Side effect *and* reads state
function toggleLoginState() {
  return (dispatch, getState) => {
    const { isLoggedIn } = getState().loginState;
    if (isLoggedIn) {
      dispatch(login());
    } else {
      dispatch(logout());
    }
  };
}

// Component
this.props.toggleLoginState(); // Doesn't care how it happens

تم تصميم منشئو الإجراءات والبرمجيات الوسيطة لأداء الآثار الجانبية وإكمال بعضها البعض.
المخفضات هي مجرد آلات حالة وليس لها علاقة بالتزامن.

تحدد المخفضات كيف يتم تحويل الحالة من خلال الإجراءات.

هذه بالفعل نقطة صحيحة للغاية ، شكرًا.

سأعيد فتحه للأجيال القادمة حتى يتم عرض نسخة من هذا في المستندات.

: +1:

أود أن أزعم أنه في حين أن المخفضات هي آلات الدولة ، فإن صناع الفعل كذلك بشكل عام (ولكن ضمنًا)

لنفترض أن المستخدم نقر على زر "إرسال" مرتين. في المرة الأولى سترسل طلب HTTP إلى الخادم (في منشئ الإجراء) وتغيير الحالة إلى "إرسال" (في المخفض). في المرة الثانية لا تريد أن تفعل ذلك.

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

لذلك يتم تقسيم تدفق التحكم الخاص بك بشكل فعال إلى جهازي حالة واحد منهما ضمني ومليء بالآثار الجانبية.

أود أن أقول إن نهج Flux هذا يعالج غالبية حالات استخدام تطبيق الويب النموذجي بشكل جيد للغاية. ولكن عندما يكون لديك منطق تحكم معقد ، فقد يصبح ذلك مشكلة. جميع الأمثلة الموجودة ليس لديها منطق تحكم معقد ، لذلك هذه المشكلة مخفية إلى حد ما.

https://github.com/Day8/re-frame هو مثال على نهج مختلف حيث يكون لديهم معالج حدث واحد يعمل باعتباره FSM الوحيد.

ولكن بعد ذلك يكون للمخفضات آثار جانبية. لذا فمن المثير للاهتمام كيف يتعاملون مع ميزة "إعادة التشغيل" في مثل هذه الحالات - سألهم في https://github.com/Day8/re-frame/issues/86

بشكل عام ، أعتقد أن هذه مشكلة حقيقية وليست مفاجأة أن تظهر مرارًا وتكرارًا. من المثير للاهتمام معرفة الحلول التي ستظهر في النهاية.

ابقنا على اطلاع حول إعادة الإطار والآثار الجانبية!

يوجد في كتابي 3 أنواع من الحالات في أي تطبيق ويب Redux لائق.

1) اعرض المكونات (React this.state). يجب تجنب ذلك ولكن في بعض الأحيان أريد فقط تنفيذ بعض السلوك الذي يمكن إعادة استخدامه بمجرد إعادة استخدام مكون بشكل مستقل عن Redux.

2) حالة التطبيق المشتركة ، أي Redux.state. إنها حالة تستخدمها طبقة العرض لتقديم تطبيق إلى مستخدم وعرض المكونات التي تستخدمها "للتواصل" بين بعضها البعض. ويمكن استخدام هذه الحالة "للتواصل" بين ActionCreators و Middlewares ووجهات النظر لأن قراراتهم يمكن أن تعتمد على هذه الحالة. ومن ثم فإن "المشتركة" مهمة. لا يجب أن تكون حالة تطبيق كاملة. أو أعتقد أنه من غير العملي تنفيذ كل شيء مثل هذا النوع من الدولة (من حيث الإجراءات).

3) الحالة التي تحتفظ بها الوحدات النمطية / libs / الخدمات المعقدة الأخرى ذات التأثير الجانبي. أود أن أكتب هذه للتعامل مع السيناريوهات التي يصفها فلادار. أو رد فعل جهاز التوجيه هو مثال آخر. يمكنك معاملته على أنه صندوق أسود له حالته الخاصة أو يمكنك أن تقرر رفع جزء من حالة جهاز التوجيه المتفاعل إلى الحالة المشتركة للتطبيق (Redux.state). إذا كنت سأكتب وحدة HTTP معقدة من شأنها التعامل بذكاء مع جميع طلباتي وتوقيتاتها ، فأنا عادة غير مهتم بطلب تفاصيل التوقيت في Redux.state. سأستخدم هذه الوحدة فقط من ActionCreators / Middlewares ، ربما مع Redux.state للحصول على Redux.state جديد.

إذا كنت أرغب في كتابة مكون عرض يوضح توقيتات طلبي ، فسوف يتعين علي نقل هذه الحالة إلى Redux.state.

vladar هل ما تعنيه أن AC / MW هي أجهزة حالة ضمنية؟ ذلك لأنهم لا يحتفظون بالدولة في حد ذاتها لكنهم لا يزالون معتمدين على الحالة الموجودة في مكان آخر وأنهم يستطيعون تحديد منطق التحكم كيف تتطور هذه الحالات في الوقت المناسب؟ في بعض الحالات ، أعتقد أنه لا يزال من الممكن تنفيذها كإغلاقات وتحافظ على حالتها الخاصة ، لتصبح آلات دولة صريحة؟

بدلاً من ذلك ، يمكنني تسمية Redux.state بأنها "دولة عامة" ، في حين أن الولايات الأخرى خاصة. إن كيفية تصميم تطبيقي هو عمل لتقرير ما يجب الاحتفاظ به كدولة خاصة وماذا كدولة عامة. يبدو لي أنها فرصة رائعة للتغليف ، وبالتالي لا أرى مشكلة في تقسيم الحالة إلى أماكن مختلفة بقدر ما لا يصبح تأثيرها على بعضها البعض أمرًا جحيمًا.

تضمين التغريدة

ولكن بعد ذلك يكون للمخفضات آثار جانبية. لذلك من المثير للاهتمام كيف يتعاملون مع ميزة "إعادة التشغيل" في مثل هذه الحالات

لتحقيق إعادة سهلة ، يجب أن يكون الرمز محددًا. هذا ما تحققه Redux من خلال طلب مخفضات نقية. في التأثير ، يقسم Redux.state التطبيق إلى أجزاء غير حتمية (غير متزامنة) وأجزاء حتمية. يمكنك إعادة تشغيل السلوك فوق Redux.state بافتراض أنك لا تفعل شيئًا مجنونًا في عرض المكونات. الهدف الأول المهم لتحقيق الكود الحتمي هو نقل الكود غير المتزامن بعيدًا وترجمته إلى رمز متزامن عبر سجل الإجراءات. هذا ما تفعله هندسة Flux بشكل عام. لكن بشكل عام ، هذا ليس كافيًا ، ولا يزال بإمكان الشفرات الأخرى ذات التأثير الجانبي أو المتحول كسر المعالجة الحتمية.

إن تحقيق القدرة على إعادة التشغيل باستخدام مخفضات التأثيرات الجانبية أمر صعب من الناحية العملية ، أو حتى مستحيلًا ، أو أنه سيعمل جزئيًا فقط مع العديد من حالات الزاوية مع القليل من التأثير العملي على الأرجح.

لتحقيق سفر زمني سهل ، نقوم بتخزين لقطات من حالة التطبيق بدلاً من إعادة تشغيل الإجراءات (وهو أمر صعب دائمًا) كما هو الحال في Redux.

لتحقيق سفر زمني سهل ، نقوم بتخزين لقطات من حالة التطبيق بدلاً من إعادة تشغيل الإجراءات (وهو أمر صعب دائمًا) كما هو الحال في Redux.

تقوم Redux DevTools بتخزين كل من اللقطات والإجراءات. إذا لم يكن لديك إجراءات ، فلا يمكنك إعادة تحميل علب التروس. إنها أقوى ميزة لسير العمل تتيحها DevTools.

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

ذلك لأنهم لا يحتفظون بالدولة في حد ذاتها لكنهم لا يزالون معتمدين على الحالة الموجودة في مكان آخر وأنهم يستطيعون تحديد منطق التحكم كيف تتطور هذه الحالات في الوقت المناسب؟

نعم ، هذا بالضبط ما أعنيه. ولكن قد ينتهي بك الأمر أيضًا إلى تكرار منطق التحكم الخاص بك. بعض التعليمات البرمجية لتوضيح المشكلة (من المثال أعلاه بنقرة إرسال مزدوجة).

function submit(data) {
  return (dispatch, getState) => {
    const { isSubmitting } = getState().isSubmitting;
    if (!isSubmitting) {
      dispatch(started(data));

      MyAPI.submit(data).then(
        json => dispatch(success(json)),
        error => dispatch(failure(error))
      )
    }
  }
}

function started(data) {
   return { type: SUBMIT_STARTED, data };
}

function success(result) {
  return { type: SUBMIT_SUCCESS, result };
}

function failure(error) {
  return { type: SUBMIT_FAILURE, error };
}

وبعد ذلك يكون لديك المخفض للتحقق من حالة "isSubmitting" مرة أخرى

const initialState = new Immutable.Map({
  isSubmitting: false,
  pendingData: null,
  error: null
});

export default function form(state = initialState, action) {
  switch (action.type) {
    case SUBMIT_STARTED:
      if (!state.isSubmitting) {
        return state.merge({
          isSubmitting: true,
          pendingData: action.data
        });
      } else {
        return state;
      }
  }
}

لذلك ينتهي بك الأمر إلى وجود نفس عمليات التحقق المنطقية في مكانين. من الواضح أن التكرار في هذا المثال ضئيل للغاية ، ولكن بالنسبة للسيناريوهات الأكثر تعقيدًا ، فقد لا يكون جميلًا.

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

(آسف إذا كانت هذه هي النقطة التي كنت تثيرها ؛-)

أتفق مع gaeron لأن! isSubmitting مشفر بالفعل في حقيقة أنه تم إرسال الإجراء SUBMIT_STARTED. عندما يكون الإجراء نتيجة لمنطق ما ، فلا يجب تكرار هذا المنطق في المخفض. إذن ، فإن مسؤولية المخفض هي أنه عندما يتلقى SUBMIT_STARTED في أي وقت ، فإنه لا يفكر ويقوم فقط بتحديث الحالة مع حمولة الإجراء لأن شخصًا آخر قد تحمل مسؤولية تقرير SUBMIT_STARTED.

يجب أن يهدف المرء دائمًا إلى وجود شيء واحد يتحمل مسؤولية قرار واحد.

يمكن للمخفض بعد ذلك البناء على حقيقة SUBMIT_STARTED وتوسيعها بمنطق إضافي ولكن يجب أن يكون هذا المنطق مختلفًا. Fe:

case SUBMIT_STARTED:
  if (goodMood) loading...
  else nothing_happens

أستطيع أن أتخيل أنه قد يكون من الصعب أحيانًا تحديد من يجب أن يكون مسؤولاً عن ماذا. ActionCreator ، Middleware ، وحدة معقدة مستقلة ، مخفض؟

سأهدف إلى الحصول على أكبر قدر ممكن من القرارات في المخفضات مع إبقائها نقية ، لأنه يمكن إعادة تحميلها على الساخن. إذا كانت هناك حاجة لاتخاذ قرارات بشأن الآثار الجانبية / غير المتزامن ، فسيذهبون إلى AC / MW / somewhere_else. يمكن أن تعتمد أيضًا على كيفية تطور أفضل الممارسات فيما يتعلق بإعادة استخدام الكود ، أي المخفضات مقابل البرامج الوسيطة.

لا يمكن للتيار المتردد تغيير الحالة. هو التقديم مباشرة. ومن ثم ، إذا كان منطقه يعتمد عليه ، يحتاج AC إلى ضمانات تلك الحالة. سيكون التقديم متزامنًا مع منطقه. إذا أراد منطق AC ضبط state.is تم تقديمه إلى true مع البيانات الجديدة وعاد القراءات ، فإنه يتوقع أن يتم تعيينه على هذا النحو. لا ينبغي لأي منطق آخر أن يغير هذه الفرضية. ACTION هو مزامنة بدائية بحد ذاتها. تحدد الإجراءات بشكل أساسي البروتوكول ويجب أن تكون البروتوكولات محددة جيدًا وواضحة.

لكني أعتقد أنني أعرف ما تحاول قوله. Redux.state هي حالة مشتركة. الحالة المشتركة صعبة دائمًا. من السهل كتابة التعليمات البرمجية في المخفضات التي تغير الحالة بطريقة غير متوقعة بواسطة منطق التيار المتردد. ومن ثم يمكنني أن أتخيل أنه قد يكون من الصعب الحفاظ على المنطق بين التيار المتردد والمخفضات متزامنة في بعض السيناريوهات المعقدة للغاية. يمكن أن يؤدي هذا إلى أخطاء قد يكون من الصعب متابعتها وتصحيحها. أعتقد أنه في مثل هذه السيناريوهات ، من الممكن دائمًا انهيار المنطق في AC / Middleware وجعل المخفضات تتصرف بغباء فقط بناءً على إجراءات محددة جيدًا بدون منطق أو القليل.

يمكن لأي شخص دائمًا إضافة مخفض جديد يكسر بعض التيار المتردد القديم. يجب على المرء أن يفكر مليًا قبل جعل التيار المتردد معتمدًا على Redux.state.

لدي منشئ إجراء يتطلب رمز مصادقة يتم إرجاعه بواسطة طلب واجهة برمجة التطبيقات مع بيانات اعتماد المستخدم. هناك حد زمني لهذا الرمز المميز ويجب تحديثه وإدارته بواسطة شيء ما. يبدو أن ما تقترحه يشير إلى أن هذه الحالة لا تنتمي إلى حالة Redux.state؟

أين يجب أن تذهب؟ من الناحية المفاهيمية ، هل يتم تمريرها إلى مُنشئ متجر Redux كحالة خارجية غير قابلة للتغيير؟

ومن ثم يمكنني أن أتخيل أنه قد يكون من الصعب الحفاظ على المنطق بين التيار المتردد والمخفضات متزامنة في بعض السيناريوهات المعقدة للغاية.

نعم. أنا لا أقول إنه توقف للعرض ، لكنه قد يصبح غير متسق. الهدف من آلة الدولة هو الرد على الأحداث - بغض النظر عن ترتيب ظهورها - والبقاء متسقة.

إذا كنت تعتمد على الترتيب الصحيح للأحداث من التيار المتردد - فأنت تقوم ضمنيًا بنقل أجزاء من محرك الحالة إلى التيار المتردد وإقرانها بالمخفض.

ربما يكون الأمر مجرد مسألة تفضيل ، لكنني أشعر بتحسن كبير عندما يعمل المتجر / المخفض دائمًا باستمرار ولا يعتمد على شيء خارجي للحفاظ على الاتساق في مكان آخر.

يجب على المرء أن يفكر مليًا قبل جعل التيار المتردد معتمدًا على Redux.state.

أعتقد أنه لا يمكنك تجنبه في الحالات المعقدة (دون نقل بعض الآثار الجانبية إلى مخفضات / معالجات الأحداث).

يبدو أنها مقايضة: "إسقاط ميزة إعادة التحميل السريع أو إعادة التشغيل" مقابل "إدارة الحالة في مكان واحد". في حالة لاحقة ، يمكنك كتابة FSMs أو Statecharts حقيقية مثل مقترحات إعادة الإطار ، في الحالة السابقة ، يكون منطق التحكم المخصص أكثر كما لدينا في Flux الآن.

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

لم تتم مناقشة معالجة الآثار الجانبية كثيرًا أو فاتني ذلك ، لكن ما أفترضه هو أنه يتم التعامل معها في البرامج الوسيطة. وهناك فرق رئيسي. Middlewares التفاف معالجات الأحداث (مخفضات) ، يؤلف معهم من الخارج ، بينما Redux Middlewares لا يلف المخفضات. وبعبارة أخرى ، فإن إعادة الإطار تؤلف آثارًا جانبية مباشرة على مخفضات نقية بينما تبقيها Redux منفصلة.

هذا يعني أنه لا يمكن تسجيل أحداث Re-frame وإعادة تشغيلها بسهولة.

عندما كنت أتحدث عن التناقضات المحتملة ، اعتبرت أن الحالة المشتركة هي السبب الجذري وأعتقد أن Re-frame لها نفس المخاطر.

الفرق هو في اقتران Middlewares مع مخفضات. عندما تنتهي البرامج الوسيطة في إعادة الإطار ، فإنها تمرر النتيجة مباشرة إلى معالجات الأحداث (المخفضات) ، لا يتم تسجيل هذه النتيجة كحدث / إجراء ، وبالتالي لا يمكن إعادة عرضها. أود أن أقول إن Redux يضع الخطافات بينهما بدلاً من ذلك في شكل إجراءات ، ومن ثم أعتقد أنه يمكنني تقنيًا تحقيق نفس ما تفعله Re-frame ، مع مزيد من المرونة على حساب المزيد من الكتابة (إنشاء الإجراءات) وربما مساحة أكبر للقيام بذلك خطأ.

في النهاية ، لا يوجد شيء يمنعني Redux من تنفيذ نفس النهج تمامًا كما في Re-frame. يمكنني إنشاء أي وظيفة تأخذ المخفض كمعامل ، وإجراء بعض المعالجة فيه ثم استدعاء وظيفة المخفض مباشرة مع النتيجة وتسميتها Reducer Middlewares أو أي شيء آخر. يتعلق الأمر فقط بما إذا كنت أريد أن أجعل الأمر أسهل قليلاً على حساب فقدان DevTools والمزيد من الاقتران.

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

في الواقع ، كنت غير صحيح بعض الشيء. لا يكمن الاختلاف في تطبيقات Redux و Re-frame ، ولكن في الحقيقة في "حيث تضع آثارك الجانبية".

إذا قمت بذلك في معالجات الأحداث (المخفضات) ولم تسمح لمنشئي الإجراءات بالتأثير على تدفق التحكم ، فلا يوجد فرق تقنيًا - يمكنك استخدام مخططات fsm / statecharts في Redux أيضًا والحصول على التحكم في التدفق في مكان واحد.

لكن في الواقع - هناك فرق. أوضح gaearon ذلك جيدًا: تتوقع Redux أن تكون

تنص إعادة الإطار أيضًا على أن معالجات الأحداث نقية ، ولكن حتى في الملف التمهيدي يذكرون أنه يتم بدء طلبات HTTP في معالجات الأحداث. هذا غير واضح بالنسبة لي ، لكن يبدو أن توقعاتهم مختلفة: يمكنك وضع الآثار الجانبية الخاصة بك في معالجي الأحداث.

ثم من المثير للاهتمام كيف يتعاملون مع إعادة التشغيل (وهذا ما سألته على https://github.com/Day8/re-frame/issues/86).

أفترض أن الطريقة الوحيدة للقيام بذلك عندما يكون لمعالجات الأحداث الخاصة بك آثارًا جانبية هو ما ذكره

لا يزال بإمكان إعادة التحميل السريع العمل ، إلا أنه لا يمكن أن يؤثر على الأحداث "الماضية" - فقط ينتج فرعًا جديدًا للحالة في الوقت المناسب.

لذا ربما يكون الاختلاف في التصميم دقيقًا ، لكنه مهم بالنسبة لي.

أعتقد أنهم ينفذون طلبات HTTP من خلال تأليف معالجات أحداث خالصة بأدوات وسيطة ذات تأثيرات جانبية. يعيد هذا التكوين معالج الأحداث الجديد الذي لم يعد نقيًا ولكن يبقى العنصر الداخلي نقيًا. لا أعتقد أنهم يقترحون بناء معالجات للأحداث تجمع بين طفرة (الحالة) app-db والآثار الجانبية. يجعل الكود أكثر قابلية للاختبار.

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

أعتقد أنني أستطيع أن أقول إن البرامج الوسيطة Re-frame هي استباقية بينما تسمح Redux بالتفاعل (أي مخفض مخصص يمكنه الاستماع إلى نتيجة البرامج الوسيطة / التيار المتردد). شيء واحد بدأت أدركه هو أن التيار المتردد كما هو مستخدم في الغالب في Flux والأدوات الوسيطة هو نفس الشيء مثل وحدات التحكم.

كما فهمت ، يقترح @ tomkis1 السفر عبر

حسنًا ، مع إدراك أن معالجات التخفيضات / الأحداث ترجع حالة جديدة كاملة ، ومن ثم قلت نفس الشيء.

merk سأحاول كتابة رسالتي حول هذا الموضوع ربما يوم الاثنين لأنني ربما لا أصل إلى هنا خلال عطلة نهاية الأسبوع.

توصي إعادة صياغة المستندات بالتحدث إلى الخادم في معالجات الأحداث: https://github.com/Day8/re-frame#talking -to-a-server

هذا هو المكان الذي تبتعد فيه عن Redux. وهذا الاختلاف أعمق مما يبدو للوهلة الأولى.

حسنًا ، أخفق في رؤية الفرق من Redux:

يجب أن تنظم معالجات الأحداث البادئة أن معالجات النجاح أو الفشل لطلبات HTTP هذه تقوم ببساطة بإرسال حدث جديد . يجب ألا يحاولوا أبدًا تعديل app-db بأنفسهم. يتم ذلك دائمًا في معالج.

استبدل الحدث بـ Action. Redux فقط له اسم خاص لمعالج الحدث الخالص - مخفض. أعتقد أنني أفتقد شيئًا ما أو أن عقلي متوقف في نهاية هذا الأسبوع.

أستطيع أن أشم فرقًا أكثر أهمية في كيفية تنفيذ الأحداث - في قائمة الانتظار وغير متزامنة. أعتقد أن Redux ينفذ إجراءات متزامنة وقفل الإجراءات من خلال العمل لضمان اتساق الحالة. يمكن أن يؤثر أيضًا على إمكانية إعادة التشغيل لأنني لست متأكدًا مما إذا كان حفظ الأحداث وإعادة تشغيلها مرارًا وتكرارًا سيؤدي إلى نفس الحالة النهائية. بغض النظر عن أنه لا يمكنني فعل ذلك فقط لأنني لا أعرف الأحداث التي تؤدي إلى آثار جانبية على عكس Redux حيث تؤدي الإجراءات دائمًا إلى رمز خالص.

vladap لتلخيص الاختلاف كما أراه:

1.

  • في Redux ، تبدأ طلب الخادم في منشئ العمل (راجع gaearon الرد على السؤال الأصلي لهذه المشكلة) ؛ يجب أن تكون مخفضاتك نقية
  • في إعادة الإطار تفعل ذلك في معالج الحدث (المخفض) ؛ قد يكون لمعالجي الأحداث (مخفضات) آثار جانبية

2.

  • في الحالة الأولى ، يتم تقسيم تدفق التحكم بين التيار المتردد والمخفض.
  • في الحالة الثانية ، يكون كل تدفق التحكم في مكان واحد - معالج الحدث (المخفض).

3.

  • إذا تم تقسيم تدفق التحكم - فأنت بحاجة إلى التنسيق بين التيار المتردد والمخفض (حتى لو كان ذلك ضمنيًا): يقرأ التيار المتردد الحالة الناتجة عن المخفض ؛ يفترض المخفض الترتيب الصحيح للأحداث من التيار المتردد. من الناحية الفنية ، تقوم بإدخال اقتران التيار المتردد والمخفض ، لأن التغييرات في المخفض قد تؤثر أيضًا على التيار المتردد والعكس صحيح.
  • إذا كان تدفق التحكم في مكان واحد - فأنت لست بحاجة إلى تنسيق إضافي + يمكنك استخدام أدوات مثل FSMs أو Statecharts في معالجات / مخفضات الأحداث


    1. تتيح طريقة Redux لفرض مخفضات نقية ونقل الآثار الجانبية إلى مكيفات الهواء إعادة التحميل وإعادة التشغيل على الساخن

  • طريقة إعادة الإطار لوجود آثار جانبية في معالجات الأحداث (مخفضات) تغلق الباب لإعادة التشغيل كما يحدث في Redux (عن طريق إعادة تقييم المخفضات) ، ولكن لا تزال الخيارات الأخرى (ربما أقل قوة) ممكنة - كما ذكر @ tomkis1

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

أعتقد أنني بحاجة إلى إعداد بعض الأمثلة الواقعية لمثل هذا السيناريو المعقد لتوضيح وجهة نظري.

قد أدفعك إلى الجنون ، أنا آسف لذلك لكننا لا نتفق على شيء أساسي واحد. ربما يجب أن أقرأ رمز أمثلة إعادة الإطار ، وسوف أتعلم Clojure يومًا ما. ربما فعلت ، ومن ثم تعرف المزيد. شكرا لجهودك.

1.

في Redux ، تبدأ طلب الخادم في منشئ العمل (راجع gaearon الرد على السؤال الأصلي لهذه المشكلة) ؛ يجب أن تكون مخفضاتك نقية
في إعادة الإطار تفعل ذلك في معالج الحدث (المخفض) ؛ قد يكون لمعالجي الأحداث (مخفضات) آثار جانبية

يقول Doc I الذي أوضحه بوضوح أنه يجب أن أقوم ببدء مكالمة webapi في معالج الحدث ثم إرسال الحدث في حالة نجاحه. لا أفهم كيف يمكنني التعامل مع هذا الحدث المرسل في نفس معالج الحدث. يتم إرسال حدث النجاح هذا إلى جهاز التوجيه (مكافئ للمرسل على ما أعتقد) وأحتاج إلى معالج حدث آخر للتعامل معه. معالج الحدث الثاني هذا نقي ومكافئ لمخفض Redux ، الأول يعادل ActionCreator. بالنسبة لي هو نفس الشيء أو من الواضح أنني أفتقد بعض البصيرة المهمة.

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

نحن لا نتفق على هذا. لا تهم الأشياء غير المتزامنة في الواقع ، لا يمكنك إعادة تشغيلها في Redux وبالتالي ليس لديك خبرة مطورة هنا. يظهر الاختلاف عندما يكون لديك العديد من المخفضات النقية المعقدة. في Redux ، يمكنك أن تأخذ الحالة A ، بعض تسلسل الإجراءات ، المخفض ، إعادة تشغيله والحصول على الحالة B. تحتفظ بكل شيء كما هو لكنك تقوم بتغيير الكود إلى المخفض وإعادة تحميله ، وإعادة تشغيل نفس تسلسل الإجراء من الحالة A وأنت الحصول على الحالة C ، ورؤية تأثير تغيير التعليمات البرمجية على الفور. لا يمكنك فعل ذلك باستخدام نهج @ tomkis1 .

يمكنك حتى بناء سيناريوهات اختبار فوقها. حفظ بعض الحالة الأولية ، وحفظ بعض تسلسل الإجراءات ، وتقليل تسلسل الإجراء إلى الحالة A ، والحصول على الحالة B وتأكيدها. ثم قم بإجراء تغييرات على الكود الذي تتوقع أن ينتج عنه نفس الحالة B ، وأعد سيناريو الاختبار لتأكيد أنه صحيح وأن تغييراتك لم تفسد تطبيقك. لا أقول أنه أفضل نهج اختبار ولكن يمكن القيام به.

في الواقع ، سأكون مندهشًا تمامًا إذا كان Clojurists الذين أعتبرهم تقريبًا ممارسين شديدين للبرمجة الوظيفية كما أوصى Haskellers بمزج الآثار الجانبية مع الكود الذي يمكن أن يكون نقيًا على الأقل بدون مستوى معين من التكوين.

التقييم / التفاعل الكسول هو الأسلوب الأساسي في كيفية نقل التعليمات البرمجية ذات التأثير الجانبي إلى أقصى حد ممكن من الكود النقي. التقييم الكسول هو كيفية تطبيق Haskell أخيرًا لمفهوم النقاء الوظيفي غير العملي للحصول على برنامج يقوم بشيء عملي ، لأن البرنامج النقي تمامًا لا يمكنه فعل الكثير. باستخدام التركيب الأحادي وغيره ، يتم إنشاء البرنامج بأكمله كتدفق بيانات وللحفاظ على الخطوة الأخيرة في خط الأنابيب نقية وعدم استدعاء الآثار الجانبية في الكود الخاص بك ، يقوم البرنامج بإرجاع وصف ما يجب القيام به. يتم تمريره إلى وقت التشغيل الذي ينفذه بتكاسل - لا يمكنك تشغيل التنفيذ عن طريق استدعاء إلزامي. على الأقل كيف أفهم البرمجة الوظيفية من وجهة نظر مستوى الطيور. ليس لدينا مثل هذا وقت التشغيل ، فنحن نقوم بمحاكاته باستخدام ActionCreators والتفاعلية. إخلاء المسؤولية ، لا تعتبره مفروغًا منه ، هذا ما فهمته من عدم قراءة الكثير عن FP ليكون أي نوع من السلطة هنا. قد أكون خارج المنزل ولكني أعتقد في الواقع أنني فهمت هذه النقطة.

لا أفهم كيف يمكنني التعامل مع هذا الحدث المرسل في نفس معالج الحدث.

أنا لا أقول ذلك. أعني بـ "الآثار الجانبية" بدء طلب HTTP في معالج الحدث (المخفض). يعد إجراء IO أيضًا من الآثار الجانبية ، حتى لو لم يؤثر على عودة وظيفتك. لا أعني "الآثار الجانبية" من حيث تعديل الحالة مباشرة في معالجات النجاح / الخطأ.

معالج الحدث الثاني هذا نقي ومكافئ لمخفض Redux ، الأول يعادل ActionCreator.

لا أعتقد أن الأول يعادل Action Creator. وإلا فلماذا تحتاج إلى Action Creators للقيام بذلك على الإطلاق؟ إذا كنت تستطيع أن تفعل الشيء نفسه في المخفض؟

لا يمكنك فعل ذلك باستخدام نهج @ tomkis1 .

يوافق على. وهذا ما قصدته عندما كتبت "خيارات أخرى (ربما أقل قوة)".

لا أعتقد أن الأول يعادل Action Creator. وإلا فلماذا تحتاج إلى Action Creators للقيام بذلك على الإطلاق؟ إذا كنت تستطيع أن تفعل الشيء نفسه في المخفض؟

تُستخدم برامج ActionCreators لعزل الآثار الجانبية عن التطبيق النقي (الذي يمكن إعادة تشغيله). نظرًا لأن Redux مصمم ، فلا يمكنك (لا ينبغي) إجراء آثار جانبية في المخفضات. أنت بحاجة إلى بناء آخر حيث يمكنك وضع التعليمات البرمجية ذات التأثيرات الجانبية. في Redux ، تكون هذه التركيبات عبارة عن AC أو Middlewares. الاختلاف الوحيد الذي أراه في إعادة الإطار هو ما تفعله البرامج الوسيطة وأن إعادة الإطار لا تحتوي على أدوات وسيطة لتحويل الأحداث (الإجراءات) قبل تشغيل المعالجات.

بدلاً من AC / middlewares ، يمكن استخدام أي شيء في الواقع - استخدام الوحدة ، والخدمات (مثل خدمات Angular) ، يمكنك بناؤها كملف lib منفصل يمكنك بعد ذلك التفاعل عن طريق AC أو الوسيطة وترجمة API إلى إجراءات. إذا سألتني ، فإن AC ليس المكان المناسب لوضع هذا الكود فيه. يجب أن يكون AC مجرد منشئي / مصانع بسيطة تنشئ كائنات فعل من الحجج. إذا كنت أرغب في أن أكون خالصًا ، فسيكون من الأفضل وضع التعليمات البرمجية ذات التأثير الجانبي بشكل صارم على البرامج الوسيطة. ActionCreator هو مصطلح سيء لنوع من مسؤولية وحدة التحكم. ولكن إذا كان هذا الرمز عبارة عن مكالمة webapi أحادية الخط ، فهناك ميل لوضعه ببساطة داخل التيار المتردد وللتطبيق البسيط يمكن أن يكون جيدًا.

AC ، middlewares ، طرف ثالث libs تشكل طبقة أحب أن أسمي طبقة الخدمة. ستكون Facebook WebApiUtils الأصلية جزءًا من هذه الطبقة ، تمامًا كما لو كنت تستخدم جهاز توجيه رد الفعل وتستمع إلى تغييراته وترجمتها إلى إجراءات لتحديث Redux.state ، يمكن استخدام Relay كطبقة خدمة (من استخدام Redux للتطبيق / العرض حالة). نظرًا لأن معظم libs الطرف الثالث مبرمجة حاليًا ، فإنها لا تلعب بشكل جيد مع إعادة التشغيل. من الواضح أنني لا أرغب في إعادة كتابة كل شيء من البداية ، لذا سيفعل ذلك. ما أحب أن أفكر فيه هو أن هذه libs ، والخدمات ، والمرافق ، وما إلى ذلك ، تعمل على توسيع بيئة المتصفح لتشكيل منصة على رأسها يمكنني إنشاء تطبيقي القابل لإعادة التشغيل. هذه المنصة مليئة بالآثار الجانبية ، في الواقع هي المكان الذي أنقل إليه الآثار الجانبية عمدًا. لقد قمت بترجمة واجهة برمجة تطبيقات النظام الأساسي هذه إلى إجراءات ويتصل تطبيقي بشكل غير مباشر بهذه المنصة من خلال الاستماع إلى كائنات الحركة هذه (محاولة محاكاة نهج Haskell الذي حاولت وصفه في منشور أعلاه). هذا نوع من الاختراق في البرمجة الوظيفية ، وهو كيفية تحقيق النقاء (ربما ليس بالمعنى الأكاديمي المطلق) ولكن يؤدي إلى آثار جانبية.

هناك شيء آخر يربكني لكني قد أسأت فهمك. أعتقد أنك قلت إنه لمنطق معقد من المفيد أن يكون كل شيء في مكان واحد. أعتقد العكس تماما.

تتشابه أعمال الإجراءات برمتها في مفهومها مع أحداث DOM. لا أرغب حقًا في مزج كود العمل الخاص بي مع رمز كيف يكتشف متصفح fe حدث mousemove وأنا متأكد من أنك لن تفعل ذلك أيضًا. لحسن الحظ ، اعتدت العمل على addListener ("mousemove" ، ...) لأن هذه المسؤوليات منفصلة جيدًا. النقطة المهمة هي تحقيق هذا الفصل الجيد بين الاهتمامات لمنطق عملي أيضًا. ActionCreators ، middlewares هي أدوات لذلك.

تخيل أنني سأكتب تطبيقًا مقابل webapi قديمًا لا يتوافق جيدًا مع احتياجات عملي. للحصول على البيانات المطلوبة ، يجب أن أتصل بنقطتي نهاية ، استخدم نتيجتهما لإنشاء مكالمات تالية ثم دمج كل النتائج في نموذج أساسي سأستخدمه في Redux.state. لن يتسرب هذا المنطق إلى مخفضاتي حتى لا أضطر إلى تحويل البيانات في مخفضاتي مرارًا وتكرارًا. سيكون في الوسيط. على نحو فعال ، سأعزل api المتقادم الفوضوي وأطور تطبيق عملي حيث سيتم كتابته مقابل واجهة برمجة تطبيقات لطيفة. بمجرد الانتهاء من ذلك ، ربما سأحصل على تغيير لإعادة بناء webapi الخاص بنا ، وبعد ذلك سأعيد كتابة برمجي الوسيط.

ومن ثم يبدو أن وجود كل شيء في مكان واحد سهل بالنسبة لتطبيق سهل. لكن على عكس الشيء المعقد ، أرى أن الفصل الجيد بين الاهتمامات مفيد. لكن ربما لم أفهم وجهة نظرك أو حالة الاستخدام التي تشير إليها.

في الواقع ، ربما سأقوم بتحويل أكبر قدر من تحويل البيانات إلى شكلها الأساسي إلى مخفضات واستخدام تكوين المخفضات بدلاً من ذلك لأنه سيعطيني إعادة التشغيل وقابلية الاختبار لهذا الكود النقي بشكل عام.

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

اعتقدت أنك تريد مزج مكالمات webapi مع منطق المخفضات للحصول عليها في مكان واحد. ما أقوله أنك لا تفعل ذلك في إطار إعادة الإطار إما لأنهم يقترحون إرسال حدث على النجاح.

يمكن أن يكون هناك موقف عندما يكون webapi -> الكثير من المنطق -> webapi -> الكثير من المنطق -> ... -> نتيجة للمخفض ، يتم تشغيل السلسلة بأكملها بنقرة واحدة. في مثل هذه الحالات ، من المحتمل أن يكون معظم المنطق في AC ، لن أقسمه حتى تنتهي جميع الآثار الجانبية. هل هذا ما تشير إليه؟

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

لا أعتقد أنني أحب إمكانية القيام بذلك كسلسلة من معالجات الأحداث المتصلة بشكل غير مباشر. سيكون سلسلة وعد أو يمكن ملاحظتها.

يمكن أن يكون لدى Reduxers الأكثر خبرة رأيًا مختلفًا حول هذا الفكر ( gaearon ، johanneslumpe ، acdlite ، emmenko ...)

vladap أعتقد أن هذه المحادثة ابتعدت كثيرًا عن موضوع هذه المشكلة. لقد ذكرت بالفعل النقطة الرئيسية لتعليقاتي هنا:

ومن ثم يمكنني أن أتخيل أنه قد يكون من الصعب الحفاظ على المنطق بين التيار المتردد والمخفضات متزامنة في بعض السيناريوهات المعقدة للغاية.

مثال سريع:

الموقف 1: لديك قائمة بأهم 10 مؤلفي مدونة في مكان ما على الصفحة. الآن قمت بحذف فئة من المشاركات في المدونة. عند الحذف الناجح ، تحتاج إلى تحديث قائمة المؤلفين هذه من الخادم لتحديثها.

الموقف 2: أنت في صفحة مختلفة. لا يحتوي على قائمة بالمؤلفين ، ولكنه يحتوي على قائمة من أفضل 10 تعليقات. يمكنك أيضًا حذف بعض فئات منشورات المدونة وسيتعين عليك تحديث قائمة التعليقات عند الحذف الناجح.

فكيف نتعامل مع هذا على مستوى محرك الدولة؟ (يمكننا أيضًا استخدام خطافات React للحصول على بعض المساعدة ، ولكن يجب أن يكون محرك الحالة الجيدة قادرًا على البقاء متسقًا بمفرده)

خيارات:
أ) نضع هذا المنطق في AC. بعد ذلك ، سوف يلمس AC CategoryApi (للحذف) ، وسيقرأ من userList state و commentList state (للتحقق من القوائم الموجودة حاليًا) ، تحدث إلى UserListApi و / أو CommentListApi (لتحديث القوائم) + إرسال TOP_AUTHORS_UPDATED و / أو TOP_COMMENTS_UPDATED . لذلك سوف يمس بشكل أساسي 3 مجالات مختلفة.

ب) نضعها في معالجات الأحداث userList و commentList . سيستمع هذان المعالِجان إلى حدث DELETE_CATEGORY_SUCCESS ، ثم يستدعي خدمة API الخاصة بهم ويقومون بدوره بإرسال حدث TOP_AUTHORS_UPDATED / TOP_COMMENTS_UPDATED . لذلك كل معالج يلامس فقط الخدمات / حالة المجال الخاص به.

ربما يكون هذا المثال بسيطًا للغاية ، ولكن حتى في هذا المستوى تصبح الأشياء أقل جمالًا مع استدعاءات API في AC.

يأتي الاختلاف من حقيقة أنه على عكس معالجات الأحداث في إعادة الإطار ، فإن ActionCreators سباقة في التعامل مع منطق الأعمال. ويمكن تنفيذ تيار متردد واحد فقط بناءً على حدث DOM. يمكن لهذا التيار المتردد عالي المستوى استدعاء مكيفات أخرى. يمكن أن تكون هناك ACs منفصلة لـ UserListApi و CommentListApi بحيث يتم فصل المجالات بشكل أفضل ، ولكن يجب أن يكون هناك دائمًا AC (وحدة تحكم مثل) التي تربطها. هذا الجزء هو رمز حتمي كلاسيكي تمامًا بينما تعتمد إعادة الإطار بالكامل على الأحداث. مع القليل من العمل ، يمكن استبداله بنهج مفضل ، أو قائم على الأحداث ، أو ملاحظات ، أو cps ، إلخ.

vladap سيكون من المثير للاهتمام معرفة ما إذا كان هناك نهج آخر قابل للتطبيق: عندما يمكن أن يكون للمخفضات آثار جانبية ، ولكن يمكن أيضًا عزلها بحيث يمكن تجاهلها عند إعادة التشغيل.

لنفترض أن توقيع المخفضات سيتغير إلى: (state, action) => (state, sideEffects?) حيث يكون sideEffects هو الإغلاق. ثم اعتمادًا على إطار السياق ، يمكن إما تقييم هذه التأثيرات الجانبية أو تجاهلها (في حالة إعادة العرض).

ثم سيبدو المثال من وصف المشكلة الأصلي كما يلي:

export default function user(state = initialState, action) {
  switch (action.type) {
    case LOGIN_ATTEMPT:
      return [state, () => {LoginApiCall.login(action.user)}];
    case LOGGED_FAILED:
      // do something
      return state;
    case LOGGED_SUCCESSFULLY:
      // do something else
     return state;
    default:
      return state;
  }
}

(كل شيء يبقى كما هو إلى حد كبير)

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

يمكنك أيضًا كتابة كود FSM عادي أو كود Statechart (مع تكوين مخفضات / معالجات للأحداث)

كما أنك لست بحاجة إلى إرجاع الإغلاق مع التوقيع المحدد مسبقًا في منشئي الإجراءات.

لست متأكدًا من المشكلات التي يمكن أن يسببها هذا النهج ، ولكن بالنسبة لي ، فإن الأمر يستحق اللعب.

لنفترض أن توقيع المخفضات سيتغير إلى: (الحالة ، الإجراء) => (state ، sideEffects؟) حيث يكون sideEffects عبارة عن إغلاق. ثم اعتمادًا على إطار السياق ، يمكن إما تقييم هذه التأثيرات الجانبية أو تجاهلها (في حالة إعادة العرض).

هذا في الواقع مشابه لما سمعت أن Elm تفعله بـ (State, Action) => (State, Request?) . لم أر أي أمثلة حتى الآن ولكن إذا أراد شخص ما استكشاف هذا ، فلا تتردد في ذلك.

هناك طريقة أخرى للحصول على بعض الإلهام وهي تنفيذ مصادر الأحداث على رأس نموذج الممثل - Akka Persistance ، PersistentFSM . لا أقول إنه أفضل ولكن ليس سيئًا معرفة المحاولات الأخرى. يمكن للممثلين استخدام الآثار الجانبية ولكن إذا كنت أتذكر بشكل صحيح في بعض الأحيان ، يتطلب الأمر كتابة رمز واضح لكيفية إعادة العرض

ربما تكون ميزة إعادة التشغيل تحرف قراراتنا حيث ينتمي الكود. يبدو أننا بدأنا نفكر كما يلي ... "أريد هذا إعادة التشغيل ومن ثم يجب أن يذهب إلى المخفض". يمكن أن يؤدي إلى رمز ذي حدود غير واضحة لأننا لا نقوم بتقسيمه وفقًا لمسؤولياته / أدواره ونميل إلى تقسيم منطق أعمالنا بشكل مصطنع لجعله قابلاً لإعادة التشغيل.

إذا كنت سأضع ميزة إعادة التشغيل جانبًا ، كيف سأكتب رمز عملي؟ أهدف إلى وضع منطق عملي أمام سجل الإجراءات ووضعه في مكان واحد. إن آلات الحالة المنطقية للأعمال ، أو أيًا كان ما سأستخدمه ، ستقوم بكل القرارات الصعبة المعقدة التي تؤدي إلى تدفق FACTS (الإجراءات) الذي تم حله بالفعل. ستكون المخفضات عمومًا محدثين بسيطين وستكون مسؤوليتهم الرئيسية هي كيفية تطبيق حمولة العمل على Redux.state. يمكن أن يكون لديهم بعض المنطق ولكن بمعنى ما يفسرون الحقائق بشكل مختلف للحصول على نوع آخر من وجهات النظر حول تدفق الحقائق (الإجراء) لتقديمه إلى المستخدم.

لتقسيم المسؤوليات ، أود التفكير في الأمر بهذه الطريقة أيضًا. ما الرمز الذي يمكن أن يُطلب إعادة تنفيذه على الخادم؟ Fe لدعم الأجهزة البطيئة بشكل أفضل عن طريق تحريك منطق الأعمال المكلف للحساب ، وربما بعض الأسباب الأمنية ، وإخفاء خاصية الاستخبارات أيا كان. لا يمكنني نقل المنطق من المخفضات إلى الخادم بسهولة لأنه مرتبط بطبقة عرض. إذا تمت كتابة منطق العمل المعقد أمام سجل الإجراءات ، يمكنني استبداله باستدعاءات REST التي تترجم إلى نفس مسار الإجراء ويجب أن تعمل طبقة العرض الخاصة بي (على الأرجح).

الجانب السلبي هو أنه على عكس Akka Persistance ليس لدي أي وسيلة لإعادة تشغيل هذا الرمز.

يجب أن ألقي نظرة أعمق على Elm ذات يوم وأفهم كيف سيعمل اقتراحك.

gaearon شكرا لضمير Elm. وجدت هذه المحادثة - https://gist.github.com/evancz/44c90ac34f46c63a27ae - بأفكار مشابهة (لكنها أكثر تقدمًا).

يقدمون مفهوم المهام (http ، db ، إلخ) والتأثيرات (مجرد قائمة انتظار من المهام). لذلك يمكن لـ "المخفض" الخاص بك أن يعيد بالفعل حالة وتأثيرات جديدة.

الشيء الرائع في الأمر (ولم أفكر في الأمر بهذه الطريقة) - هو أنه إذا كان بإمكانك جمع المهام أثناء تسلسل مخفضاتك - وبعد ذلك قم بتطبيق بعض الوظائف على هذه القائمة. لنفترض أنه يمكنك تجميع طلبات http ، وتغليف استعلامات قاعدة البيانات بالمعاملات ، وما إلى ذلك.

أو يمكنك أن تقول "مدير إعادة التشغيل" وهو مجرد أداة للتأثيرات.

merk أعتقد أنه تمت كتابة معظمها بالفعل. مجرد أن هذا النوع من التعليمات البرمجية ذات التأثيرات الجانبية المستقلة هو على الأرجح الأكثر صعوبة. من المحتمل أن تدمر الفوضى عند إعادة التشغيل لأن الفواصل الزمنية لن تتم مزامنتها مع timetravel على افتراض أن timetravel يعمل بنفس الكود وستبدأ الفواصل الزمنية في وضع إعادة التشغيل أيضًا.

لا يحتاج التطبيق المعتاد إلى رمز مميز وعد تنازلي لانتهاء الصلاحية مقدم للمستخدم ، لذلك من الناحية الفنية لا يجب أن يكون في Redux.state. قد يحتاج تطبيق المسؤول إلى الحصول عليه. ومن ثم يمكنك تنفيذه في كلا الاتجاهين مهما كان ما يناسبك.

أحد الخيارات هو بدء العد التنازلي لانتهاء الصلاحية في AC تسجيل الدخول وأفترض أنه يعمل إلى ما لا نهاية ويموت مع التطبيق أو يجب تسجيل الخروج لتنظيفه. عندما يتم تشغيل الفاصل الزمني ، فإنه يقوم بما هو مطلوب لتأكيد الرمز المميز وإذا انتهت صلاحيته ، فإنه يرسل إجراء LOGIN_EXPIRED ويقوم مخفض الاستماع بمسح جلسة المستخدم وتغيير الموقع الذي يؤدي بدوره إلى انتقال جهاز التوجيه إلى / تسجيل الدخول.

آخر هو استخدام lib الخاص بالطرف الثالث وتفويض المخاوف إليه وترجمة واجهة برمجة التطبيقات الخاصة به إلى إجراء LOGIN_EXPIRED. أو اكتب رمزك وحالة العد التنازلي الخاصة بك هنا إذا لم تكن بحاجة إليها في طبقة العرض.

يمكنك الاحتفاظ بها في Redux.state لكن هذه الحالة متقلبة. مع الحالة الكبيرة يمكننا الحصول على نفس مشاكل البرمجة باستخدام المتغيرات العامة. ومن الصعب ، وربما من المستحيل اختباره. من السهل اختبار المخفضات ولكن بعد ذلك يثبت المرء أنه يعمل عندما يقوم مخفض واحد فقط بتحديث هذا المفتاح المعين في كائن الحالة. يمكن أن يصبح هذا سيئًا في مشروع كبير مع العديد من المطورين من الصفات المختلفة. يمكن لأي شخص أن يقرر كتابة المخفض والاعتقاد بأن بعض أجزاء الحالة جيدة للتحديث ولا يمكنني تخيل كيفية اختبار جميع المخفضات معًا في جميع تسلسلات التحديث الممكنة.

اعتمادًا على حالة الاستخدام الخاصة بك ، قد يكون من المنطقي حماية هذه الحالة ، لأنها تسجيل دخول - وظيفة مهمة جدًا ، وفصلها. إذا كانت هذه الحالة مطلوبة في طبقة العرض ، فقم بنسخها إلى Redux.state بطريقة مماثلة مثل حالة موقع جهاز التوجيه المتفاعل والتي يتم نسخها إلى Redux.state في بعض الأمثلة حولها. إذا كانت دولتك وفريقك صغيرًا وحسن التصرف ، فلا بأس من وجوده في مكان واحد.

تضمين التغريدة

واو ، إن جوهر Elm هذا رائع! كما أنه يذكرني بهندسة "السائقين"

merk في الواقع ، لن يؤثر LOGIN_EXPIRED المستقل على إعادة التشغيل كثيرًا لأنه سيصل إلى نهاية سجل الإجراءات ولن تتم معالجته على الفور من خلال إعادة التشغيل وربما لن يصل إلى سجل الإجراءات على الإطلاق - لا أعرف كيف يتم تنفيذ الإعادة بالضبط.

gaeron يبدو أنه على مستوى عالٍ ، ينفذ Elm and Cycle النمط الذي كنت أحاول وصفه إذا فهمته بشكل صحيح. لدي متصفح يمنحني نظامًا أساسيًا أساسيًا ، وأقوم بتوسيع هذه المنصة بطبقة الخدمة الخاصة بي (http ، db ...) لإنشاء نظام أساسي لتطبيقي. ثم أحتاج إلى نوع من الغراء الذي يتفاعل مع طبقة الخدمة ويسمح لتطبيقي الخالص بالتواصل معها بشكل غير مباشر حتى يتمكن تطبيقي من وصف ما يريد فعله ولكن لا ينفذه بنفسه (الرسائل المرسلة إلى برامج تشغيل الدورة ، Elm Effects الحصول على قائمة المهام). يمكنني القول أن تطبيقي يقوم ببناء "برنامج" باستخدام هياكل البيانات (يذكرني بالرمز باعتباره تعويذة بيانات) والذي يتم تمريره بعد ذلك إلى طبقة الخدمة للتنفيذ ويهتم تطبيقي فقط بنتيجة هذا "البرنامج" الذي يتم تطبيقه بعد ذلك لحالتها.

merk : كذلك. إذا تم وضع الرمز المميز وانتهاء صلاحيته في Redux.state / أو في أي مكان في أي خدمة js أخرى ، فلن يتم نقله عندما يفتح المستخدم التطبيق في علامة تبويب جديدة. أود أن أقول أن المستخدم يتوقع أن يظل مسجلاً. لذلك أفترض أن هذه الحالة يجب أن تكون بالفعل في SessionStore.

حتى بدون ذلك ، يمكن فصل الحالات عندما لا يعني انتهاء صلاحية الرمز المميز التنظيف الفوري لمعلومات المستخدم والانتقال إلى / تسجيل الدخول. حتى لا يلمس المستخدم الخادم بتفاعلاته (لديه ما يكفي من البيانات المخزنة مؤقتًا) ، يمكنه الاستمرار في العمل ويتم تشغيل الإجراء LOGIN_EXPIRED فقط بمجرد قيامه بشيء يتطلب خادمًا. لا تعني حالة isLogged المستندة إلى Redux.state وجود رمز مميز صالح. تُستخدم الحالة isLogged في Redux.state لتحديد ما سيتم عرضه ، ويمكن إخفاء الحالة حول الرمز المميز في طبقة العرض والحفاظ عليها فقط على مستوى منشئي الإجراء دون الحاجة إلى كتابة الإجراءات والمخفضات لها ، باستثناء تلك التي تؤثر على طبقة العرض.

vladar أعتقد أنني قد أحصل على وجهة نظرك العامة. أعتقد أنني لم أدرك ذلك من قبل.

لا توجد طريقة سهلة لكيفية إعادة التحكم من المخفض إلى منشئ الإجراء وتمرير بعض القيم. لنفترض أن هذه القيم ليست ضرورية للتصيير ، فهي حتى مؤقتة ولكنها مطلوبة لبدء عملية غير متزامنة.

actionCreator() {
  ... getState
  ... some logic L1
  ... dispatch({ACTION1})
}

reducer() {
  case ACTION1
    ... some logic L2
    ... x, y result from L2
    ... some logic L3
    ... resulting in new state
    ... return new state
}

هناك 3 خيارات حول كيفية بدء عملية غير متزامنة باستخدام x ، y المحسوبة في المخفض. لا أحد منهم مثالي.

1) نقل المنطق من المخفض إلى منشئ الإجراء وتقليل قوة إعادة التحميل السريع. المخفض هو فقط محدث حالة غبية أو له منطق L3 فصاعدًا.

2) احفظ x ، y في Redux.state ، ملوثًا بقيم مؤقتة / عابرة واستخدمه أساسًا كقناة اتصال عالمية بين المخفضات ومنشئي الإجراءات التي لست مقتنعًا أنني أحبها. أعتقد أنه من الجيد أن تكون حالة فعلية ولكن ليس لهذا النوع من القيم. يتغير منشئ الإجراء إلى:

actionCreator() {
  ... getState
  ... some logic L1
  ... dispatch({ACTION1})
  // I assume I get updated state if previous dispatch is sync.
  // If previous dispatch is async it has to be chained properly.
  // If updated state can't be received here after a dispatch
  // then I would think there is a flaw.
  ... getState
  ... asyncOperation(state.x, state.y)
}

3) احفظ x ، y للحالة ، استخدمها كدعامات في بعض المكونات وقم بنفقها عبر حلقة طبقة العرض بالكامل باستخدام componentWillReceiveProps مما يؤدي إلى إنشاء منشئ الإجراء الجديد والعملية غير المتزامنة. أسوأ خيار إذا سألتني ، نشر منطق الأعمال في كل مكان.

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

قد يتطلب الأمر أن يكون Redux قادرًا على تحديد وتجاهل هذه المكالمات غير المتزامنة عند إعادة التشغيل وهو ما تقترحه بالضبط.

لا توجد طريقة سهلة لكيفية إعادة التحكم من المخفض إلى منشئ الإجراء وتمرير بعض القيم.

أود أن أقول أن هذا هو أحد الأعراض. يوضح المثال الثاني وجهة نظري جيدًا (انظر أدناه).

ضع في اعتبارك التدفق العام لانتقال الحالة (في عالم ولايات ميكرونيزيا الموحدة):

  1. يصل الحدث
  2. نظرًا للحالة الحالية ، تحسب FSM الحالة المستهدفة الجديدة
  3. تنفذ ولايات ميكرونيزيا الموحدة عمليات للانتقال من الحالة الحالية إلى الحالة الجديدة

لاحظ أن الوضع الحالي مهم! يمكن أن يؤدي نفس الحدث إلى نتائج مختلفة اعتمادًا على الحالة الحالية. وكنتيجة لتسلسلات مختلفة من العمليات (أو الوسائط) في الخطوة 3.

هذا هو إلى حد كبير كيف يعمل انتقال الحالة في Flux عندما تتم مزامنة الإجراء الخاص بك. المخفضات / المخازن الخاصة بك هي بالفعل ولايات ميكرونيزيا الموحدة.

ولكن ماذا عن الإجراءات غير المتزامنة؟ تجبرك معظم أمثلة Flux ذات الإجراءات غير المتزامنة على الاعتقاد بأنها معكوسة:

  1. يقوم التيار المتردد بعملية غير متزامنة (بغض النظر عن الحالة الحالية)
  2. AC يرسل العمل نتيجة لهذه العملية
  3. المخفض / المتجر يعالجها ويغير الحالة

لذلك يتم تجاهل الحالة الحالية ، ويفترض أن تكون الحالة المستهدفة هي نفسها دائمًا. بينما في الواقع ، لا يزال تدفق انتقال الحالة المتسق كما هو. ويجب أن تبدو هكذا (مع أجهزة التكييف):

  1. AC يحصل على الحالة الحالية قبل إرسال الإجراء
  2. يرسل AC العمل
  3. يقرأ AC حالة جديدة بعد العمل
  4. الحالة المعطاة قبل الإجراء وبعده - تقرر أي العمليات يجب تنفيذها (أو القيم التي يجب تمريرها كوسائط)

بالضبط المثال الثاني الخاص بك. لا أقول إن كل هذه الخطوات مطلوبة في كل وقت. في كثير من الأحيان يمكنك حذف بعضها. لكن في الحالات المعقدة - هذه هي الطريقة التي يجب أن تتصرف بها. إنه تدفق معمم لأي انتقال للدولة. وهذا يعني أيضًا أن حدود FSM الخاصة بك قد انتقلت إلى AC مقابل المخفض / المخزن.

لكن الحدود المتحركة تسبب مشاكل أخرى:

  1. إذا كانت العمليات غير المتزامنة في المتجر / المخفض - يمكن أن يكون لديك اثنين من FSM مستقلين يتفاعلان مع نفس الحدث. لذلك لإضافة ميزة جديدة - يمكنك فقط إضافة مخزن / مخفض يتفاعل مع بعض الأحداث الحالية وهذا كل شيء.
    ولكن مع العمليات في AC - سيتعين عليك إضافة مخزن / مخفض وأيضًا الانتقال وتحرير التيار المتردد الحالي عن طريق إضافة جزء غير متزامن من المنطق هناك أيضًا. إذا كان يحتوي بالفعل على منطق غير متزامن لبعض المخفضات الأخرى ... فلن يصبح رائعًا.
    من الواضح أن الحفاظ على مثل هذا الرمز أكثر صعوبة.
  2. يختلف التحكم في تدفق تطبيقك في حالة المزامنة مقابل الإجراءات غير المتزامنة. بالنسبة لإجراءات المزامنة ، يتحكم مخفض الإجراءات في الانتقال ، في حالة عدم التزامن - يتحكم التيار المتردد بشكل فعال في جميع التحولات التي قد تكون ناجمة عن هذا الحدث / الإجراء.

لاحظ أن معظم المشكلات مخفية بمتطلبات الحالة البسيطة لمعظم تطبيقات الويب. ولكن إذا كان لديك تطبيق معقد ذي الحالة - فستواجه بالتأكيد هذه المواقف.

في معظم الحالات ، من المحتمل أن تتوصل إلى جميع أنواع الحلول البديلة. لكن يمكنك تجنبها في المقام الأول إذا كان منطق انتقال الحالة الخاص بك مغلفًا بشكل أفضل ولم يتم فصل FSM بين أجهزة التكييف والمخفضات.

لسوء الحظ ، لا يزال جزء "التأثير الجانبي" من انتقال الدولة جزءًا من انتقال الدولة ، وليس جزءًا منفصلًا من المنطق المستقل. لهذا السبب يبدو لي (state, action) => (state, sideEffects) أكثر طبيعية. نعم ، هذا ليس توقيع "تقليل" بعد الآن٪) لكن مجال إطار العمل ليس تحويلات البيانات ، ولكن انتقالات الحالة.

إنها نفس المناقشة كما هو الحال مع تدفق الفانيليا إذا كان يجب بدء غير المتزامن في المبدعين أو في المتاجر.

نعم ، لكن Flux لا يمنعك من الحصول على أشياء غير متزامنة في المتاجر طالما أنك ترسل () في عمليات الاسترجاعات غير المتزامنة مقابل الحالة المتغيرة مباشرةً. إنها نوع من عدم إبداء الرأي حتى لو تم إعادة صياغة معظم التطبيقات باستخدام أجهزة التكييف غير المتزامنة. أنا شخصياً أعتقد أنه من ذكريات MVC ، لأنه من الملائم عقلياً التعامل مع ACs على أنها وحدات تحكم مقابل إجراء تحول عقلي إلى FSM.

تضمين التغريدة

(state, action) => (state, sideEffects)

فقط أفكر. هذا يعني أنه سيتعين علينا تغيير نوع الإرجاع الخاص بوظيفة CombinedReducers إلى [state، SideEffects] أو [state، [SideEffects]] وتغيير كودها لدمج SideEffects من المخفضات. Original (state، action) => نوع مخفض الحالة يمكن أن يظل مدعومًا لأن combeducers ستجبر (الحالة) على إرجاع النوع إلى [state] أو [state، []].

ثم نحتاج إلى تنفيذ SideEffects وإرساله إلى مكان ما في Redux. يمكن أن يتم ذلك في Store.dispatch () والذي من شأنه تطبيق حالة جديدة وتنفيذ قائمة SideEffects وإرسالها. أنا أفكر إذا كان بإمكاننا الدخول في بعض التكرار اللانهائي اللطيف.

بدلاً من ذلك ، يمكن تنفيذه في مكان ما على مستوى البرامج الوسيطة ولكن بعد ذلك أعتقد أن Store.dispatch سيحتاج إلى إرجاع قائمة SideEffects مع إجراء ما.

تضمين التغريدة

نعم ، لكن Flux لا يمنعك من الحصول على أشياء غير متزامنة في المتاجر طالما أنك ترسل () في عمليات الاسترجاعات غير المتزامنة مقابل الحالة المتغيرة مباشرةً. إنها نوع من عدم إبداء الرأي حتى لو تم إعادة صياغة معظم التطبيقات باستخدام أجهزة التكييف غير المتزامنة. أنا شخصياً أعتقد أنه من ذكريات MVC ، لأنه من الملائم عقلياً التعامل مع ACs على أنها وحدات تحكم مقابل إجراء تحول عقلي إلى FSM.

أوافق على أن معاملة ActionCreators على أنها متحكمات ليست طريقة تفكير جيدة. هذا هو السبب الذي يجعلني أفكر في النهاية أنه لمنطق معقد أحتاج إلى إعادة التحكم من المخفض إلى التيار المتردد. لا يجب أن أحتاج ذلك. إذا كان AC يتعامل مع غير متزامن ، فيجب أن يكونوا مثل معالجات الطلبات - يتم التنفيذ بناءً على القرار الذي تم اتخاذه في مكان آخر ولا ينبغي لهذا الطلب أن يتقاطع مع المجالات.

لتجنب دورهم كمتحكمين ، فإن الخيار الوحيد الآن هو توجيه المنطق من خلال المكونات الذكية واستخدام ActionCreators فقط لتنفيذ الخطوة الأولية للمنطق في استجابة مباشرة لواجهة المستخدم. يتم اتخاذ القرار الأول الفعلي في مكون ذكي يعتمد على Redux.state و / أو الحالة المحلية للمكون.

هذا النهج في المثال السابق لحذف الفئة وتحديث / حذف شيء آخر ردًا على استخدام webapi لن يكون جيدًا. أفترض أنه سيؤدي إلى مزيج من التقنيات المختلفة بناءً على تفضيلات مطوري البرامج - هل يجب أن يكون في التيار المتردد ، في المخفض ، في المكون الذكي؟ دعونا نرى كيف ستتطور الأنماط.

كنت أشتري النقطة لإبقاء المكالمات غير المتزامنة بعيدة عن المتاجر. لكنني لا أعتقد أنه صالح لأنه ، imho ، يزيد التعقيد بشكل كبير في مكان آخر. من ناحية أخرى ، لا أرى الكثير من التعقيد في وجود وظيفة غير متزامنة في مخزن / مخفض بقدر ما يتم تنفيذه هناك وإرساله فقط - أو نهج خالص والتعامل مع هذه الوظائف كبيانات وتشغيلها في مكان ما بعيدًا.

يقوم الممثلون بالمثل ، يمكنهم تنفيذ غير متزامن وإرسال رسالة (كائن عمل) إلى ممثل آخر ، أو إرسالها إلى نفسك للاستمرار في ولايات ميكرونيزيا الموحدة.

أنا أغلق لأنه لا يبدو أن هناك أي شيء قابل للتنفيذ هنا. يجب أن يجيب مستند الإجراءات غير المتزامن على معظم الأسئلة. المزيد من المناقشات النظرية لا تبدو ذات قيمة بالنسبة لي بدون الكود الفعلي الذي يمكننا تشغيله. ؛-)

gaearon هل يمكنك إضافة كود فعلي ينفذ الطريقة الصحيحة للتعامل مع استدعاءات API إلى أحد الأمثلة مع redux (خارج مثال "العالم الحقيقي" الذي يستخدم البرمجيات الوسيطة)؟

لا يزال لدي أسئلة حول التنسيب الفعلي بعد قراءة جميع المستندات وهذه المشكلة عدة مرات أواجه مشكلة في فهم الملفات التي يجب وضع هذه الآثار الجانبية فيها https://github.com/rackt/redux/issues/291#issuecomment -123010379 . أعتقد أن هذا هو المثال "الصحيح" من موضوع هذه المشكلة.

شكرا مقدما على أي توضيح في هذا المجال.

أود أن أشير إلى فكرة مهمة عن صانعي الأفعال النجسة:

إن وضع استدعاءات غير متزامنة في صانعي الإجراءات ، أو وجود منشئين غير نقيين بشكل عام ، له عيب كبير بالنسبة لي: إنه يجزئ منطق التحكم بطريقة تجعل تبديل منطق التحكم في تطبيقك بالكامل ليس بهذه البساطة مثل مجرد تبديل متجرك المنشئ. أعتزم دائمًا استخدام البرامج الوسيطة للعمليات غير المتزامنة وتجنب منشئي الإجراءات غير النقية تمامًا.

سيحتوي التطبيق الذي أقوم بتطويره على نسختين على الأقل: أحدهما يعمل في Raspberry Pi في الموقع ، والآخر يدير بوابة. قد تكون هناك إصدارات منفصلة لبوابة / بوابة عامة يديرها العميل. ما إذا كان يتعين علي استخدام واجهات برمجة تطبيقات مختلفة غير مؤكد ، لكنني أريد الاستعداد لهذا الاحتمال بأفضل ما يمكنني ، وتتيح لي البرامج الوسيطة القيام بذلك.

وبالنسبة لي ، فإن مفهوم توزيع استدعاءات API بين صانعي الإجراءات يتعارض تمامًا مع مفهوم Redux للتحكم المركزي في تحديثات الحالة. بالتأكيد ، مجرد حقيقة أن طلب واجهة برمجة التطبيقات يعمل في الخلفية ليس جزءًا من حالة متجر Redux - لكنه لا يزال حالة التطبيق مع ذلك ، وهو شيء تريد التحكم فيه بدقة. وأجد أنه بإمكاني الحصول على تحكم أكثر دقة فيه ، كما هو الحال مع مخازن Redux / مخفضات السرعة ، يكون هذا التحكم مركزيًا وليس موزعًا.

@ jedwards1211 قد تكون مهتمًا بـ https://github.com/redux-effects/redux-effects في حالة عدم قيامك بفحصها بعد ، وكذلك في المناقشة في # 569.

gaearon رائع ، شكرًا على الإشارة إلى ذلك! على أي حال ، لم يكن استخدام البرامج الوسيطة الخاصة بي صعبًا للغاية حتى الآن :)

لقد ابتكرت نهجًا شبيهًا بالدردار : إعادة README النهج ويقارنه بالبدائل.

gregwebs أحب redux-side-effect ، على الرغم من أنه سيكون محرجًا مع منطق التحكم القابل للتبديل أيضًا ، لأن هناك سؤالًا حول كيفية الحصول على وظيفة sideEffect في وحدات المخفض ، عندما يكون هناك العديد من مُكوِّن متجر مختلف وحدات لاستخدامها في بنيات مختلفة.

يمكنني استخدام اختراق مضحك على الرغم من ذلك: فقط قم بتخزين sideEffect في state نفسه ، لذا فهو متاح تلقائيًا للمخفض! :اللسان الى الخارج وعين تغمز:

شيء مثل https://github.com/rackt/redux/pull/569/ قريب من الوضع المثالي بالنسبة لكيفية العمل ، على الرغم من أنني بالطبع لا أريد استخدامه في مشروع إنتاج ما لم يصبح جزءًا قياسيًا من API.

إليكم فكرة: اجعل البرنامج الوسيط عصا وظيفة sideEffect في الإجراء. اختراق بسيط ، ولكن أصغر تغيير ممكن ضروري لجعل أجهزة التخفيض قادرة على بدء رمز غير متزامن:

sideEffectMiddleware.js :

export default store => next => action => {
  let sideEffects = [];
  action.sideEffect = callback => sideEffects.push(callback);
  let result = next(action);
  sideEffects.forEach(sideEffect => sideEffect(store));
  return result;
};

هناك عدة طرق لمقاربة sideEffect البسيطة. يرجى تجربة المتغيرات الخاصة بك والإبلاغ عنها مرة أخرى

لقد أعدت بناء الكود الخاص بي لاستخدام الأسلوب sideEffectMiddleware أعلاه ، وأحب حقًا منظمة الكود الناتجة. من الجيد ألا تضطر إلى استيراد وظيفة sideEffect من أي مكان إلى مخفضاتي ، فهي تأتي فقط في action .

@ jedwards1211 لقد قمت بنشر الرمز الخاص بك كـ actionSideEffectMiddleware في الإصدار 2.1.0 من الحزمة.

تضمين التغريدة هل تمانع في إدراجي كمتعاون؟

@ jedwards1211 تمت إضافتك. ربما ستضيف دعم إعادة التشغيل المناسب :) لا يمكنني الاستفادة من ذلك حتى الآن حيث أستخدم هذا.

gaearon أعتبر أن الإجراءات المسجلة

إنها تفعل ذلك ، إنها مجرد كائنات عادية بالفعل بحلول وقت تسجيلها ، لذلك لا تتدخل البرامج الوسيطة عادةً.

تضمين التغريدة لذلك لم أجرب هذه الميزة بالفعل ، لكن ... أعتقد أن بعض البرامج الوسيطة ذات الآثار الجانبية ستكسر الرقم القياسي / إعادة التشغيل أيضًا؟ خذ redux-effects-fetch على سبيل المثال ، سيستمر تنفيذ طلب ajax على كائن عادي إجراء FETCH:

function fetchMiddleware ({dispatch, getState}) {
  return next => action =>
    action.type === 'FETCH'
      ? fetch(action.payload.url, action.payload.params).then(checkStatus).then(deserialize, deserialize)
      : next(action)
}

إذا قام المُسجل بتنظيف عمليات الاسترجاعات [success, failure] من إجراء FETCH steps ، فإن البرنامج redux-effects لن يرسل أي إجراءات لتغيير الحالة ، ولكن لا يزال المرء لا يريد إعادة التشغيل لتحريك مجموعة من طلبات أياكس.

هل هناك طريقة ما لفصل البرمجيات الوسيطة "الخالصة" (التي لا ترسل أبدًا إجراءات إضافية) مثل redux-logger عن البرامج الوسيطة "غير النقية" (التي قد ترسل إجراءات)؟

لسوء الحظ ، لم أدقق في redux-effects لذلك لا يمكنني الإجابة على سؤالك.

لا مشكلة ، على أي حال ، هل يضيف المُسجل أي إشارات إلى الإجراءات التي يمكن أن تستخدمها البرامج الوسيطة لتقرير ما إذا كان سيتم تنفيذ الآثار الجانبية أم لا؟ أنا أحاول فقط اكتشاف طريقة في البرنامج الوسيط الخاص بي لأتمكن من دعم أدوات التطوير بشكل صحيح

لا. من المتوقع أن يتم وضع devTools() _after_ applyMiddleware في سلسلة تكوين المُحسِّن ، لذلك بحلول وقت الوصول إلى الإجراء ، يكون الإجراء "النهائي" الذي لا يحتاج إلى مزيد من التفسير.

حسنًا ، بعد أن تؤدي البرامج الوسيطة تأثيرًا جانبيًا ، يمكنها فقط إزالة أي حقول من الإجراء / تعديلها بحيث لا تؤدي إلى التأثير الجانبي عندما تعود من خلال البرامج الوسيطة عند إعادة التشغيل ، ومن ثم يجب أن العمل مع أدوات التطوير ، أليس كذلك؟

نعم ، هذا يبدو وكأنه طريقة جيدة.

عظيم ، شكرا لتنويرني!

gaearonvladar @ jedwards1211 قد تكون مهتمة في https://github.com/salsita/redux-side-effects. إنه في الأساس تطبيق elmish (state, action) => (state, sideEffects) ولكن بدلاً من المخفض الذي يُعيد tuple (الذي لاحظته بالفعل غير ممكن) فإنه ينتج عنه تأثيرات.
لقد لعبت معها قريبًا ، يبدو أن إعادة التحميل والإعادة الساخنة على ما يرام. لا أرى أي ميزة إعادة مكسورة ، حتى الآن ، ولكن ربما قد يجد بعض كبار المراجعين شيئًا ما. :)
بالنسبة لي ، يبدو هذا بمثابة إضافة مهمة حقًا إلى مكدس إعادة الإرسال لأنه يسمح للمنطق أن يكون بشكل أساسي في المخفضات مما يجعلها آلات حالة فعالة وقابلة للاختبار ، والتي تفوض التأثيرات (إجراءات الانتقال التي لا تعتمد نتيجتها على الحالة الحالية فقط) إلى الخدمات الخارجية ( إلى حد كبير مثل Elm architecure والتأثيرات والخدمات).

طريقة أخرى هي redux-saga التي تستخدم أيضًا مولدات ولكنها تحافظ على الآثار الجانبية منفصلة عن المخفضات.

gaearon أعتقد minedeljkovic يعني

بالنسبة لي ، يبدو هذا بمثابة إضافة مهمة حقًا إلى مكدس إعادة الإرسال لأنه يسمح للمنطق أن يكون بشكل أساسي في المخفضات مما يجعلها آلات حالة فعالة وقابلة للاختبار ، والتي تفوض التأثيرات (إجراءات الانتقال التي لا تعتمد نتيجتها على الحالة الحالية فقط) إلى الخدمات الخارجية ( إلى حد كبير مثل Elm architecure والتأثيرات والخدمات).

كميزة رئيسية على النهج التقليدي ، لأن https://github.com/yelouafi/redux-saga/ مشابه جدًا لكيفية عمل redux-thunk ، بدلاً من بعض السكر النحوي فوقه مما يجعل المعاملات طويلة الأمد أسهل .

من ناحية أخرى ، يشبه https://github.com/salsita/redux-side-effects هندسة Elm.

نعم ، لا تستخدم التأثيرات الجانبية redux-saga و redux-redux المولدات فقط للإعلان عن الآثار الجانبية ، والحفاظ على الملاحم والمخفضات نقية ، على التوالي. هذا مشابه.

سببان لماذا أفضل ذلك في علبة التروس:

  1. لديك وصول صريح إلى الحالة الحالية والتي قد تؤثر على كيفية إعلان التأثير (أعتقد أن هذه كانت إحدى نقاط vladar أثناء هذه المناقشة)
  2. لا يوجد مفهوم جديد تم تقديمه (Saga in redux-saga)

تعليقي في https://github.com/rackt/redux/issues/1139#issuecomment -165419770 لا يزال صالحًا لأن redux-saga لن يحل هذا ولا توجد طريقة أخرى لحل هذا باستثناء قبول النموذج المستخدمة في هندسة Elm.

أضع فكرة بسيطة تحاول التأكيد على نقاطي حول الآثار الجانبية للإحياء: https://gist.github.com/minedeljkovic/7347c0b528110889aa50

حاولت تحديد أبسط سيناريو ممكن أعتقد أنه مجرد "عالم حقيقي" بما فيه الكفاية ولكن ما زلت أؤكد بعض النقاط من هذه المناقشة.

السيناريو هو:
يحتوي التطبيق على جزء "تسجيل المستخدم" حيث يجب إدخال بيانات المستخدم الشخصية. من بين البيانات الشخصية الأخرى ، يتم اختيار بلد الميلاد من قائمة البلدان. يتم إجراء التحديد في مربع حوار "تحديد البلد" ، حيث يقوم المستخدم بتحديد بلد ولديه خيار تأكيد أو إلغاء التحديد. إذا كان المستخدم يحاول تأكيد الاختيار ، ولكن لم يتم تحديد أي بلد ، فيجب تحذيره.

قيود العمارة:

  1. يجب أن تكون وظيفة اختيار البلد معيارية (بروح البط المعاد) ، بحيث يمكن إعادة استخدامها في أجزاء متعددة من التطبيق (على سبيل المثال أيضًا في جزء "إدارة المنتج" من التطبيق ، حيث يجب إدخال بلد الإنتاج)
  2. لا ينبغي اعتبار شريحة الدولة الخاصة باختيار الدولة عالمية (لتكون في جذر حالة إعادة الإرسال) ، ولكن يجب أن تكون محلية بالنسبة للوحدة النمطية (وتتحكم فيها تلك الوحدة) التي تستدعيها.
  3. يجب أن يتضمن المنطق الأساسي لهذه الوظيفة أقل قدر ممكن من الأجزاء المتحركة (من وجهة نظري ، يتم تنفيذ هذا المنطق الأساسي في المخفضات فقط (والجزء الأكثر أهمية فقط من المنطق). يجب أن تكون المكونات تافهة ، كما يجب أن تكون جميع المكونات المدفوعة بالإعادة كن :). ستكون مسؤوليتهم الوحيدة هي تقديم الوضع الحالي ، وإرسال الإجراءات).

أهم جزء في الجوهر ، فيما يتعلق بهذه المحادثة ، هو الطريقة التي يتعامل بها مخفض التحديد القطري مع إجراء CONFIRM_SELECTION.

gaearon ، أود في الغالب أن أقدر رأيك في بياني بأن الآثار الجانبية للإحياء بالإضافة إلى الإعادة القياسية توفر سطحًا لأبسط حل مع الأخذ في الاعتبار التراكيب.

فكرة بديلة محتملة لتنفيذ هذا السيناريو (بدون استخدام الآثار الجانبية للإعادة ، ولكن باستخدام ملحمة الإعادة ، أو إعادة الثقل أو طريقة أخرى) ، ستكون أيضًا محل تقدير كبير.

@ tomkis1 ، أود أن أسيء استخدامها. :)

(هناك تطبيق مختلف قليلاً في هذا المضمون https://gist.github.com/minedeljkovic/9d4495c7ac5203def688 ، حيث يتم تجنب globalActions. ليس مهمًا لهذا الموضوع ، ولكن ربما يود شخص ما التعليق على هذا النمط)

minedeljkovic شكرا على جوهر. أرى مشكلة مفاهيمية واحدة في مثالك. الغرض من redux-side-effects هو استخدامه للتأثيرات الجانبية فقط. ومع ذلك ، في مثالك ، لا توجد آثار جانبية ، بل هناك معاملة تجارية طويلة الأمد ، وبالتالي فإن redux-saga أكثر ملاءمة. يمكن لـslorber و yelouafi إلقاء المزيد من الضوء على هذا.

بعبارة أخرى ، المشكلة الأكثر إثارة للقلق بالنسبة لي هي التزامن dispatching من الإجراء الجديد داخل المخفض (https://github.com/salsita/redux-side-effects/pull/9#issuecomment-165475991)

yield dispatch => dispatch({type: COUNTRY_SELECTION_SUCCESS, payload: state.selectedCountryId});

أعتقد أن slorber جاء بمصطلح يسمى "التأثير الجانبي للأعمال" وهذه هي حالتك بالضبط. يتألق redux-saga في حل هذه المشكلة بالذات.

mindjuice لست متأكدًا تمامًا من فهم https://github.com/paldepind/functional-frontend-architecture/issues/20#issuecomment -162822909

يسمح نمط الملحمة أيضًا بتوضيح بعض الأشياء الضمنية.
على سبيل المثال ، أنت تصف فقط ما حدث بإطلاق الإجراءات (أفضل الأحداث لأنها بالنسبة لي يجب أن تكون في زمن الماضي) ، ثم تبدأ بعض قواعد العمل وتحدث واجهة المستخدم. في حالتك ، يكون عرض الخطأ ضمنيًا. مع القصة ، عند التأكيد ، إذا لم يتم تحديد أي بلد ، فمن المحتمل أن ترسل إجراء "NO_COUNTRY_SELECTED_ERROR_DISPLAYED" أو شيء من هذا القبيل. أفضل أن يكون ذلك واضحًا تمامًا.

كما يمكنك رؤية الملحمة كنقطة اقتران بين البط.
على سبيل المثال لديك duck1 و duck2 ، مع إجراءات محلية على كل منهما. إذا كنت لا تحب فكرة الجمع بين البطتين (أعني أن بطة واحدة ستستخدم الإجراءات ، ومنشئو البطة الثانية) ، يمكنك فقط السماح لهم بوصف ما حدث ، ثم إنشاء ملحمة تربط بعض قواعد العمل المعقدة بين 2 بطة.

سووو ، إنه خيط طويل بشكل غير مؤلم وما زال هناك أي حل للمشكلة حتى الآن؟

لنفترض أن لديك action() غير متزامن ويجب أن يشير الرمز الخاص بك إلى خطأ أو إظهار النتيجة.

كان النهج الأول هو جعله مثل

// the action call
action().then(dispatch(SUCCESS)).catch(dispatch(FAILURE))

// the reducer
case SUCCESS:
    state.succeeded = true
    alert('Success')

case FAILURE:
    state.succeeded = false
    alert('Failure')

ولكن اتضح أنها ليست طريقة الإحياء لأن المخفض يحتوي الآن على "آثار جانبية" (لا أعرف ما الذي من المفترض أن يعنيه ذلك).
باختصار ، الطريقة الصحيحة هي نقل هذه alert() من المخفض في مكان ما.

يمكن أن يكون هذا في مكان ما مكون React الذي يستدعي هذا action .
حتى الآن يبدو الرمز الخاص بي كما يلي:

// the reducer
case SUCCESS:
    state.succeeded = true

case FAILURE:
    state.succeeded = false

// the React component
on_click: function()
{
    action().then
    ({
        dispatch(SUCCESS)

        alert('Success')
        // do something else. maybe call another action
    })
    .catch
    ({
        dispatch(FAILURE)

        alert('Failure')
        // do something else. maybe call another action
    })
}

هل هذه هي الطريقة الصحيحة الآن للقيام بذلك؟
أو هل أحتاج إلى مزيد من البحث وتطبيق بعض مكتبات الطرف الثالث؟

الإعادة ليست بسيطة على الإطلاق. ليس من السهل فهمه في حالة تطبيقات العالم الحقيقي. هناك حاجة إلى Mabye عينة من ريبو التطبيق في العالم الحقيقي كمثال أساسي يوضح الطريقة الصحيحة.

@ halt-hammerzeit Redux نفسها بسيطة للغاية ؛ يأتي التجزئة المربكة من احتياجات أو آراء مختلفة حول دمج / فصل الآثار الجانبية عن المخفضات عند استخدام Redux.

أيضًا ، يأتي التجزئة من حقيقة أنه ليس من الصعب فعل آثار جانبية كما تريد مع Redux. استغرق الأمر حوالي 10 أسطر من التعليمات البرمجية فقط لتدوير البرمجيات الوسيطة ذات التأثير الجانبي الأساسي. لذا احتضن حريتك ولا تقلق بشأن الطريقة "الصحيحة".

@ jedwards1211 "Redux نفسها" ليس لها أي قيمة أو معنى ولا معنى لها لأن هدفها الرئيسي هو حل المشكلات اليومية لمطوري Flux. وهذا يعني AJAX والرسوم المتحركة الجميلة وجميع الأشياء الأخرى _ العادية والمتوقعة

@ halt-hammerzeit لديك نقطة هناك. يبدو أن Redux يهدف بالتأكيد إلى جعل معظم الناس يوافقون على كيفية إدارة تحديثات الحالة ، لذلك من العار أن معظم الناس لم يوافقوا على كيفية أداء الآثار الجانبية.

هل رأيت مجلد "أمثلة" في الريبو؟

في حين أن هناك طرقًا متعددة لأداء الآثار الجانبية ، فإن التوصية عمومًا هي القيام بذلك خارج Redux أو منشئو الإجراءات الداخلية ، كما نفعل في كل مثال.

نعم ، أعرف ... كل ما أقوله هو أن هذا الخيط قد أوضح عدم وجود إجماع (على الأقل بين الناس هنا) حول أفضل طريقة لأداء الآثار الجانبية.

على الرغم من أن معظم المستخدمين ربما يستخدمون منشئي الإجراءات الخارقة ونحن مجرد قيم متطرفة.

^ ما قاله.

أفضل أن تتفق العقول العظيمة على حل صالح واحد
ونحتها على الحجر حتى لا أضطر لقراءة 9000 شاشة
مسلك

في يوم الخميس الموافق 7 يناير 2016 ، كتب Andy Edwards [email protected] :

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

-
قم بالرد على هذا البريد الإلكتروني مباشرة أو قم بعرضه على GitHub
https://github.com/rackt/redux/issues/291#issuecomment -169751432.

لذا ، أعتقد أن الحل المتفق عليه حاليًا هو القيام بكل شيء في العمل
المبدعين. سأحاول استخدام هذا الأسلوب في حال وجدت أي عيوب فيه
سأقوم بنشره هنا مرة أخرى

في يوم الخميس الموافق 7 يناير 2016 ، كتب Николай Кучумов [email protected] :

^ ما قاله.

أفضل أن تتفق العقول العظيمة على حل صالح واحد
ونحتها على الحجر حتى لا أضطر لقراءة 9000 شاشة
مسلك

في يوم الخميس الموافق 7 يناير 2016 ، أرسل Andy Edwards < [email protected]
<_e i = "16">

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

-
قم بالرد على هذا البريد الإلكتروني مباشرة أو قم بعرضه على GitHub
https://github.com/rackt/redux/issues/291#issuecomment -169751432.

توجد هذه الخيوط وما شابهها لأن 1٪ من مستخدمي Redux يرغبون في البحث عن حلول أكثر قوة / نقية / توضيحية للآثار الجانبية. من فضلك لا تأخذ الانطباع أنه لا يمكن لأحد أن يوافق على ذلك. تستخدم الغالبية الصامتة التفكير والوعود ، وهم في الغالب سعداء بهذا النهج. لكن مثل أي تقنية لها جوانب سلبية ومقايضات. إذا كان لديك منطق غير متزامن معقد ، فقد ترغب في إسقاط Redux واستكشاف Rx بدلاً من ذلك. نمط الإحياء معبر بسهولة في Rx.

حسنا، شكرا

في يوم الخميس الموافق 7 يناير 2016 ، كتب Dan Abramov [email protected] :

توجد هذه المواضيع المشابهة لأن 1٪ من مستخدمي Redux يحبون البحث عنها
حلول أقوى / نقية / تصريحية للآثار الجانبية. من فضلك لا
تأخذ الانطباع أنه لا يمكن لأحد الاتفاق على ذلك. يستخدم الغالبية الصامتة
يفكر ويعود ، وهم في الغالب سعداء بهذا النهج. لكن مثل أي
التكنولوجيا لها سلبيات ومقايضات. إذا كان لديك منطق معقد غير متزامن
قد ترغب في إسقاط Redux واستكشاف Rx بدلاً من ذلك. نمط الإحياء هو
معبرة بسهولة في Rx.

-
قم بالرد على هذا البريد الإلكتروني مباشرة أو قم بعرضه على GitHub
https://github.com/rackt/redux/issues/291#issuecomment -169761410.

gaearon نعم ، أنت على حق. وأنا أقدر أن Redux مرن بما يكفي لاستيعاب جميع مناهجنا المختلفة!

@ halt-hammerzeit ألق نظرة على إجابتي هنا حيث أشرح لماذا يمكن أن تكون ملحمة redux أفضل من redux-thunk: http://stackoverflow.com/questions/34570758/why-do-we-need-middleware-for- تدفق غير متزامن في إعادة / 34599594

gaearon بالمناسبة ، إجابتك على stackoverflow بها نفس الخلل مثل الوثائق - فهي تغطي فقط أبسط الحالات
http://stackoverflow.com/questions/34570758/why-do-we-need-middleware-for-async-flow-in-redux/34599594#34599594
تتجاوز التطبيقات الواقعية بسرعة مجرد جلب البيانات عبر AJAX: يجب عليها أيضًا معالجة الأخطاء وتنفيذ بعض الإجراءات اعتمادًا على ناتج استدعاء الإجراء.
نصيحتك هي فقط dispatch بعض الأحداث الأخرى وهذا كل شيء.
فماذا لو قانون بلدي يريد alert() مستخدم على إنجاز العمل؟
هذا هو المكان الذي لن يساعد فيه هؤلاء thunk s ، لكن الوعود ستفعل.
أكتب حاليًا الكود الخاص بي باستخدام وعود بسيطة ويعمل بهذه الطريقة.

لقد قرأت التعليق المجاور حول redux-saga .
لم أفهم ما يفعله رغم ذلك ، لول.
أنا لست مهتمًا كثيرًا بالشيء monads وهكذا ، وما زلت لا أعرف ما هو thunk ، ولا أحب هذه الكلمة الغريبة للبدء بها.

حسنًا ، أواصل استخدام الوعود في مكونات React بعد ذلك.

نقترح إعادة الوعود من thunks في جميع أنحاء المستندات.

gaearon نعم ، هذا ما أتحدث عنه: on_click() { dispatch(load_stuff()).then(show_modal('done')) }
من شأنها أن تفعل.

أعتقد أنك ما زلت تفتقد شيئًا ما.
يرجى الاطلاع على README of redux-thunk.
يوضح كيفية ربط صانعي الإجراءات الخفية ضمن قسم "التكوين".

gaearon نعم ، هذا بالضبط ما أفعله:

store.dispatch(
  makeASandwichWithSecretSauce('My wife')
).then(() => {
  console.log('Done!');
});

إنه بالضبط ما كتبته أعلاه:

on_click()
{
    dispatch(load_stuff())
        .then(() => show_modal('done'))
        .catch(() => show_error('not done'))
}

هذا القسم README مكتوب بشكل جيد ، THX

لا ، لم تكن هذه وجهة نظري. ألق نظرة على makeSandwichesForEverybody . إنه صانع عمل خارق يستدعي صانعي أعمال thunk الآخرين. هذا هو السبب في أنك لست بحاجة إلى وضع كل شيء في مكونات. راجع أيضًا مثال async في هذا الريبو للمزيد من هذا.

gaearon لكنني أعتقد أنه لن يكون من المناسب أن أضع ، على سبيل المثال ، رمز الرسوم المتحركة
ضع في اعتبارك هذا المثال:

button_on_click()
{
    this.props.dispatch(load_stuff())
        .then(() =>
        {
                const modal = ReactDOM.getDOMNode(this.refs.modal)
                jQuery(modal).fancy_animation(1200 /* ms */)
                setTimeout(() => jQuery.animate(modal, { background: 'red' }), 1500 /* ms */)
        })
        .catch(() =>
        {
                alert('Failed to bypass the root mainframe protocol to override the function call on the hyperthread's secondary firewall')
        })
}

كيف ستعيد كتابته بالطريقة الصحيحة؟

@ halt-hammerzeit يمكنك جعل actionCreator يعيد الوعود بحيث يمكن للمكون إظهار بعض القرص الدوار أو أي شيء باستخدام حالة المكون المحلي (يصعب تجنبها عند استخدام jquery على أي حال)

خلاف ذلك ، يمكنك إدارة المؤقتات المعقدة لتشغيل الرسوم المتحركة باستخدام redux-saga.

ألق نظرة على منشور المدونة هذا: http://jaysoo.ca/2016/01/03/managing-processes-in-redux-using-sagas/

أوه ، في هذه الحالة ، لا بأس من الاحتفاظ بها في المكون.
(ملاحظة: بشكل عام في React ، من الأفضل استخدام نموذج تعريفي لكل شيء بدلاً من إظهار الوسائط بشكل إلزامي مع jQuery. ولكن ليس بالأمر المهم.)

slorber نعم ، أنا بالفعل أعود

gaearon طيب ، لقد حصلت عليك. في عالم الماكينة المثالي ، سيكون من الممكن بالطبع البقاء داخل نموذج البرمجة التصريحي ، لكن للواقع مطالبه الخاصة ، غير المنطقية ، على سبيل المثال ، من وجهة نظر الآلة. الناس كائنات غير عقلانية لا تعمل فقط مع الأصفار والآحاد البحتة وتتطلب المساومة على جمال الكود ونقاوته لصالح القدرة على القيام ببعض الأشياء غير المنطقية.

أنا راض عن الدعم في هذا الموضوع. يبدو أن شكوكي قد تم حلها الآن.

مناقشة جديدة ذات صلة: https://github.com/reactjs/redux/issues/1528

gearon شرح رائع جدا! : كأس: شكر: +1:

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات

القضايا ذات الصلة

captbaritone picture captbaritone  ·  3تعليقات

elado picture elado  ·  3تعليقات

mickeyreiss-visor picture mickeyreiss-visor  ·  3تعليقات

rui-ktei picture rui-ktei  ·  3تعليقات

ms88privat picture ms88privat  ·  3تعليقات