Redux: Mencoba melakukan panggilan API di tempat yang benar

Dibuat pada 20 Jul 2015  ·  115Komentar  ·  Sumber: reduxjs/redux

Saya mencoba membuat aliran sukses / kesalahan masuk tetapi perhatian utama saya adalah di mana saya bisa meletakkan logika ini.

Saat ini saya menggunakan actions -> reducer (switch case with action calling API) -> success/error on response triggering another action .

Masalah dengan pendekatan ini adalah peredam tidak berfungsi saat saya memanggil tindakan dari panggilan API.

apakah saya melewatkan sesuatu?

Peredam

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

tindakan

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

Panggilan 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

Komentar yang paling membantu

Terakhir, di mana Anda meletakkan panggilan API login?

Inilah tepatnya pembuat tindakan dispatch => {} . Efek samping!

Itu hanyalah pencipta aksi. Gabungkan dengan tindakan lain:

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

Di komponen Anda, panggil saja

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

// --or--

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

Semua 115 komentar

Ini hampir benar, tetapi masalahnya _Anda tidak bisa begitu saja memanggil pembuat tindakan murni dan mengharapkan sesuatu terjadi_. Jangan lupa pembuat tindakan Anda hanyalah fungsi yang menentukan _what_ perlu dikirim.

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

Sekarang, mari kita lihat contoh Anda. Hal pertama yang ingin saya klarifikasi adalah Anda tidak memerlukan formulir dispatch => {} asinkron jika Anda hanya mengirimkan satu tindakan secara sinkron dan tidak memiliki efek samping (berlaku untuk loginError dan loginRequest ).

Ini:

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

dapat disederhanakan sebagai

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

Kedua, reduksi Anda seharusnya fungsi _pure yang tidak memiliki efek samping_. Jangan mencoba memanggil API Anda dari peredam.

Ini:

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

mungkin akan terlihat lebih mirip

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

Terakhir, di mana Anda meletakkan panggilan API login?

Inilah tepatnya pembuat tindakan dispatch => {} . Efek samping!

Itu hanyalah pencipta aksi. Gabungkan dengan tindakan lain:

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

Di komponen Anda, panggil saja

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

// --or--

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

Terakhir, jika Anda sering menulis pembuat tindakan besar seperti ini, sebaiknya tulis middleware khusus untuk panggilan asinkron yang kompatibel dengan janji apa pun yang Anda gunakan untuk asinkron.

Teknik yang saya jelaskan di atas (pembuat tindakan dengan tanda tangan dispatch => {} ) sekarang disertakan di Redux, tetapi di 1.0 hanya akan tersedia sebagai paket terpisah yang disebut redux-thunk . Saat Anda melakukannya, Anda mungkin juga memeriksa redux-promise-middleware atau redux-promise .

@gaearon : tepuk: itu penjelasan yang luar biasa! ;) Itu pasti harus membuatnya menjadi dokumen.

@gaearon penjelasan yang luar biasa! : piala:

@gaearon Bagaimana jika Anda perlu melakukan beberapa logika untuk menentukan panggilan API mana yang harus dipanggil? Bukankah logika domain yang seharusnya ada di satu tempat (reduksi)? Mempertahankannya dalam Tindakan, bagi saya, para pembuat konten seperti melanggar satu sumber kebenaran. Selain itu, sebagian besar Anda perlu meningkatkan panggilan API dengan beberapa nilai dari status aplikasi. Anda kemungkinan besar juga ingin menguji logika tersebut. Kami menemukan bahwa membuat panggilan API di Reducers (fluks atom) sangat membantu dan dapat diuji dalam proyek skala besar.

Kami menemukan bahwa membuat panggilan API di Pengurang (fluks atom) sangat membantu dan dapat diuji dalam proyek skala besar.

Ini memecahkan rekor / pemutaran ulang. Fungsi dengan efek samping lebih sulit untuk diuji daripada fungsi murni menurut definisi. Anda dapat menggunakan Redux seperti ini tetapi itu sepenuhnya bertentangan dengan desainnya. :-)

Mempertahankannya dalam Tindakan, bagi saya, para pembuat konten seperti melanggar satu sumber kebenaran.

“Sumber kebenaran tunggal” berarti data berada di satu tempat, dan tidak memiliki salinan independen. Ini tidak berarti "semua logika domain harus berada di satu tempat".

Bagaimana jika Anda perlu melakukan beberapa logika untuk menentukan panggilan API mana yang harus dipanggil? Bukankah logika domain yang seharusnya ada di satu tempat (reduksi)?

Pengecil menentukan bagaimana keadaan diubah oleh tindakan. Mereka tidak perlu khawatir tentang dari mana tindakan ini berasal. Bisa dari komponen, pembuat aksi, rekaman sesi berseri, dll. Inilah keindahan konsep menggunakan aksi.

Setiap panggilan API (atau logika yang menentukan API mana yang dipanggil) terjadi sebelum reduksi. Inilah mengapa Redux mendukung middleware. Middleware Thunk yang dijelaskan di atas memungkinkan Anda menggunakan kondisional dan bahkan membaca dari status:

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

Pembuat tindakan dan middleware _designed_ untuk melakukan efek samping dan saling melengkapi.
Pengecil hanyalah mesin negara dan tidak ada hubungannya dengan asinkron.

Pengecil menentukan bagaimana keadaan diubah oleh tindakan.

Ini memang poin yang sangat valid, terima kasih.

Saya akan buka kembali untuk anak cucu sampai versi ini ada di dokumen.

: +1:

Saya berpendapat bahwa sementara reduksi adalah mesin negara, begitu pula pembuat tindakan secara umum (tetapi secara implisit).

Katakanlah pengguna mengklik tombol "kirim" dua kali. Pertama kali Anda akan mengirim permintaan HTTP ke server (di pembuat tindakan) dan mengubah status menjadi "mengirimkan" (di peredam). Kedua kalinya Anda tidak ingin melakukan itu.

Dalam model saat ini, pembuat tindakan Anda harus memilih tindakan apa yang akan dikirim tergantung pada keadaan saat ini. Jika saat ini tidak "mengirimkan" - kemudian kirim permintaan HTTP dan kirim satu tindakan, jika tidak - jangan lakukan apa pun atau bahkan kirim tindakan yang berbeda (seperti peringatan).

Jadi aliran kontrol Anda secara efektif dibagi menjadi dua mesin status dan salah satunya implisit dan penuh dengan efek samping.

Saya akan mengatakan bahwa pendekatan Flux ini menangani sebagian besar kasus penggunaan aplikasi web biasa dengan sangat baik. Tetapi ketika Anda memiliki logika kontrol yang kompleks, ini mungkin menjadi masalah. Semua contoh yang ada tidak memiliki logika kontrol yang rumit, jadi masalah ini agak tersembunyi.

https://github.com/Day8/re-frame adalah contoh pendekatan berbeda di mana mereka memiliki pengendali peristiwa tunggal yang bertindak sebagai satu-satunya FSM.

Tapi kemudian pengurang memiliki efek samping. Jadi, sangat menarik bagaimana mereka menangani fitur "replay" dalam kasus seperti itu - tanya mereka di https://github.com/Day8/re-frame/issues/86

Secara umum, menurut saya ini adalah masalah nyata dan tidak mengherankan jika muncul berulang kali. Sangat menarik untuk melihat solusi apa yang akan muncul pada akhirnya.

Beri tahu kami tentang bingkai ulang dan efek samping!

Di buku saya, ada 3 jenis status di aplikasi web Redux yang layak.

1) Lihat komponen (React this.state). Ini harus dihindari tetapi terkadang saya hanya ingin menerapkan beberapa perilaku yang dapat digunakan kembali hanya dengan menggunakan kembali komponen secara independen dari Redux.

2) Status aplikasi bersama, yaitu Redux.state. Ini adalah status yang digunakan lapisan tampilan untuk menyajikan aplikasi kepada pengguna dan komponen tampilan menggunakannya untuk "berkomunikasi" satu sama lain. Dan status ini dapat digunakan untuk "berkomunikasi" antara ActionCreators, Middlewares, pandangan karena keputusan mereka dapat bergantung pada status ini. Karenanya "berbagi" itu penting. Itu tidak harus menjadi status aplikasi yang lengkap. Atau saya pikir tidak praktis untuk menerapkan segala sesuatu sebagai jenis keadaan ini (dalam hal tindakan).

3) Status yang dipegang oleh modul / libs / layanan kompleks lain yang berdampak samping. Saya akan menulis ini untuk menangani skenario yang dijelaskan vladar. Atau react-router adalah contoh lain. Anda dapat memperlakukannya sebagai kotak hitam yang memiliki statusnya sendiri atau Anda dapat memutuskan untuk mengangkat bagian dari status react-router menjadi status bersama aplikasi (Redux.state). Jika saya akan menulis modul HTTP kompleks yang dengan cerdik akan menangani semua permintaan saya dan pengaturan waktunya, saya biasanya tidak tertarik dengan detail waktu permintaan di Redux.state. Saya hanya akan menggunakan modul ini dari ActionCreators / Middlewares, mungkin bersama dengan Redux.state untuk mendapatkan Redux.state baru.

Jika saya ingin menulis komponen tampilan yang menunjukkan waktu permintaan saya, saya harus memindahkan status ini ke Redux.state.

@vladar Apakah yang Anda maksud bahwa AC / MW adalah mesin negara implisit? Itu karena mereka tidak memegang negara dengan sendirinya tetapi mereka masih bergantung pada negara yang diadakan di tempat lain dan bahwa mereka dapat mendefinisikan logika kontrol bagaimana keadaan ini berkembang dalam waktu? Untuk beberapa kasus, saya pikir mereka masih dapat diimplementasikan sebagai penutupan dan mempertahankan statusnya sendiri, menjadi mesin negara eksplisit?

Alternatifnya, saya dapat menyebut Redux.state sebagai "negara bagian publik", sementara negara bagian lain bersifat pribadi. Cara mendesain aplikasi saya adalah tindakan untuk memutuskan apa yang akan disimpan sebagai negara pribadi dan apa sebagai negara publik. Bagi saya sepertinya kesempatan bagus untuk enkapsulasi, oleh karena itu saya tidak melihat masalah untuk memiliki negara yang dibagi menjadi tempat-tempat yang berbeda sejauh tidak menjadi neraka bagaimana mereka mempengaruhi satu sama lain.

@vladar

Tapi kemudian pengurang memiliki efek samping. Jadi sangat menarik bagaimana mereka menangani fitur "replay" dalam kasus seperti itu

Untuk mencapai replay yang mudah, kode tersebut harus deterministik. Inilah yang dicapai Redux dengan membutuhkan reduksi murni. Akibatnya, Redux.state membagi aplikasi menjadi bagian non-deterministik (asinkron) dan deterministik. Anda dapat memutar ulang bahavior di atas Redux.state dengan asumsi Anda tidak melakukan hal gila dalam melihat komponen. Sasaran penting pertama untuk mencapai kode deterministik adalah memindahkan kode asinkron dan menerjemahkannya menjadi kode sinkron melalui log tindakan. Inilah yang dilakukan arsitektur Flux secara umum. Tetapi secara umum itu tidak cukup dan kode yang mempengaruhi atau bermutasi lainnya masih dapat merusak pemrosesan deterministik.

Mencapai kemampuan pemutaran ulang dengan pengurang efek samping sangat sulit dilakukan, bahkan tidak mungkin, atau hanya akan berfungsi sebagian dengan banyak casing sudut dengan kemungkinan sedikit efek praktis.

Untuk mencapai jadwal perjalanan yang mudah, kami menyimpan snapshot dari status aplikasi alih-alih memutar ulang tindakan (yang selalu sulit) seperti yang dilakukan di Redux.

Untuk mencapai jadwal perjalanan yang mudah, kami menyimpan snapshot dari status aplikasi alih-alih memutar ulang tindakan (yang selalu sulit) seperti yang dilakukan di Redux.

Redux DevTools menyimpan snapshot dan tindakan. Jika Anda tidak memiliki tindakan, Anda tidak dapat memuat ulang reduksi. Ini adalah fitur paling kuat dari alur kerja yang diaktifkan DevTools.

Perjalanan waktu bekerja dengan baik dengan pembuat tindakan tidak murni karena _itu terjadi pada tingkat tindakan mentah (akhir) yang dikirim_. Ini adalah benda biasa jadi mereka sepenuhnya deterministik. Ya, Anda tidak bisa "membatalkan" panggilan API, tapi saya tidak melihat ada masalah dengan itu. Anda dapat mengembalikan tindakan mentah yang dipancarkan olehnya.

Itu karena mereka tidak memegang negara dengan sendirinya tetapi mereka masih bergantung pada negara yang diadakan di tempat lain dan bahwa mereka dapat mendefinisikan logika kontrol bagaimana keadaan ini berkembang dalam waktu?

Ya, itulah yang saya maksud. Tetapi Anda mungkin juga berakhir dengan duplikasi logika kontrol Anda. Beberapa kode untuk menunjukkan masalah (dari contoh saya di atas dengan klik kirim ganda).

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

Dan kemudian Anda memiliki peredam untuk memeriksa status "isSubmitting" lagi

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

Jadi, Anda akhirnya mendapatkan pemeriksaan logika yang sama di dua tempat. Jelas duplikasi dalam contoh ini minimal, tetapi untuk skenario yang lebih kompleks mungkin tidak bagus.

Saya pikir dalam contoh terakhir, pemeriksaan harus _not_ berada di dalam peredam. Jika tidak, tindakan tersebut dapat dengan cepat berubah menjadi inkonsistensi (misalnya tindakan dikirim karena kesalahan, tetapi diabaikan, tetapi kemudian tindakan "berhasil" juga dikirim tetapi tidak terduga, dan status mungkin digabungkan secara tidak benar).

(Maaf jika itu yang Anda maksud ;-)

Saya setuju dengan gaeron karena! IsSubmitting sudah dikodekan dengan fakta bahwa tindakan SUBMIT_STARTED telah dikirim. Ketika suatu tindakan adalah hasil dari beberapa logika daripada logika itu tidak boleh direplikasi di peredam. Maka tanggung jawab peredam hanyalah ketika ia menerima SUBMIT_STARTED, ia tidak berpikir dan hanya memperbarui status dengan muatan tindakan karena orang lain telah mengambil tanggung jawab untuk memutuskan SUBMIT_STARTED itu.

Seseorang harus selalu bertujuan bahwa ada satu hal yang bertanggung jawab atas satu keputusan.

Peredam selanjutnya dapat membangun fakta SUBMIT_STARTED dan memperluasnya dengan logika tambahan tetapi logika ini harus berbeda. Fe:

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

Saya dapat membayangkan bahwa terkadang sulit untuk memutuskan siapa yang harus bertanggung jawab untuk apa. ActionCreator, Middleware, modul kompleks mandiri, peredam?

Saya akan bertujuan untuk memiliki keputusan sebanyak mungkin dalam pereduksi sambil menjaganya tetap murni, karena mereka dapat dimuat ulang. Jika keputusan diperlukan untuk efek samping / async maka mereka pergi ke AC / MW /ewhere_else. Ini juga dapat bergantung pada bagaimana praktik terbaik berkembang terkait penggunaan kembali kode, yaitu reduksi vs. middleware.

AC tidak dapat mengubah status.isSubmitting secara langsung. Oleh karena itu, jika logikanya bergantung padanya, AC perlu menjamin bahwa status.isSubmitting akan sinkron dengan logikanya. Jika logika AC ingin menyetel state.isSubmitted ke true dengan data baru dan membaca kembali, ia mengharapkannya disetel seperti itu. Seharusnya tidak ada logika lain yang berpotensi mengubah premis ini. ACTION adalah primitif sinkronisasi itu sendiri. Pada dasarnya tindakan mendefinisikan protokol dan protokol harus didefinisikan dengan baik dan jelas.

Tapi saya rasa saya tahu apa yang ingin Anda katakan. Redux.state adalah status bersama. Status bersama selalu rumit. Sangat mudah untuk menulis kode dalam reduksi yang mengubah status dengan cara yang tidak terduga oleh logika AC. Oleh karena itu saya dapat membayangkan sulit untuk menjaga logika antara AC dan reduksi sinkron dalam beberapa skenario yang sangat kompleks. Hal ini dapat menyebabkan bug yang mungkin sulit diikuti dan di-debug. Saya percaya bahwa untuk skenario seperti itu selalu mungkin untuk menutup logika ke AC / Middleware dan membuat reduksi hanya bertindak bodoh berdasarkan tindakan yang ditentukan dengan baik tanpa atau sedikit logika.

Seseorang selalu dapat menambahkan peredam baru yang akan merusak beberapa AC lama yang tergantung. Seseorang harus berpikir dua kali sebelum membuat AC bergantung pada Redux.state.

Saya memiliki pembuat tindakan yang memerlukan token otentikasi yang dikembalikan oleh permintaan API dengan kredensial pengguna. Ada batasan waktu pada token ini dan perlu disegarkan serta dikelola oleh sesuatu. Apa yang Anda usulkan tampaknya menunjukkan bahwa status ini tidak termasuk dalam Redux.state?

Kemana harus pergi? Secara konseptual apakah itu diteruskan ke konstruktor penyimpanan Redux sebagai kondisi luar yang tidak berubah?

Oleh karena itu saya dapat membayangkan sulit untuk menjaga logika antara AC dan reduksi sinkron dalam beberapa skenario yang sangat kompleks.

Ya. Saya tidak mengatakan bahwa ini adalah penghenti pertunjukan, tetapi mungkin menjadi tidak konsisten. Inti dari mesin negara adalah untuk bereaksi terhadap peristiwa - terlepas dari urutan kemunculannya - dan tetap konsisten.

Jika Anda mengandalkan pengurutan peristiwa yang tepat dari AC - Anda hanya secara implisit memindahkan bagian-bagian mesin negara Anda ke AC dan memasangkannya dengan peredam.

Mungkin ini hanya masalah preferensi, tetapi saya merasa jauh lebih baik ketika store / reducer selalu bertindak secara konsisten dan tidak bergantung pada sesuatu di luar untuk menjaga konsistensi di tempat lain.

Seseorang harus berpikir dua kali sebelum membuat AC bergantung pada Redux.state.

Saya pikir Anda tidak dapat menghindarinya dalam kasus yang kompleks (tanpa memindahkan beberapa efek samping ke reduksi / penanganan acara).

Sepertinya ini adalah kompromi: "menghapus fitur hot-reload atau replay" vs "mengatur status di satu tempat". Dalam kasus selanjutnya Anda dapat menulis FSM atau Statechart nyata seperti proposal kerangka ulang, dalam kasus sebelumnya itu lebih banyak logika kontrol ad-hoc seperti yang kita miliki di Flux sekarang.

Saya telah membaca (terlalu cepat) melalui Re-frame dan itu adalah hal yang sama seperti Redux dengan beberapa perbedaan detail implementasi. Komponen tampilan bingkai ulang didasarkan pada yang dapat diamati, Peristiwa adalah Tindakan, Bingkai ulang mengirimkan peristiwa (tindakan) secara langsung sebagai objek (mereka dibuat dalam komponen tampilan) tanpa menggunakan pembuat, Penangan Peristiwa murni dan sama dengan Redux pereduksi.

Menangani efek samping tidak banyak dibahas atau saya melewatkannya, tetapi apa yang saya asumsikan adalah bahwa efek samping tersebut ditangani di Middlewares. Dan ada perbedaan utamanya. Middlewares membungkus Penangan acara (reduksi), buat dengan mereka dari luar, sementara Redux Middlewares tidak membungkus reduksi. Dengan kata lain Re-frame menyusun efek samping langsung ke reduksi murni sementara Redux memisahkannya.

Ini berarti bahwa seseorang tidak dapat merekam acara Bingkai Ulang dan memutarnya ulang dengan mudah.

Ketika saya berbicara tentang kemungkinan inkonsistensi, saya menganggap status bersama sebagai akar penyebab dan saya percaya Re-frame memiliki risiko yang sama.

Perbedaannya terletak pada menggabungkan Middlewares dengan reduksi. Ketika Middlewares selesai dalam Re-frame, mereka langsung memberikan hasil ke event handler (reduksi), hasil ini tidak dicatat sebagai event / aksi, oleh karena itu tidak dapat diputar ulang. Saya akan mengatakan Redux hanya menempatkan kait di antara alih-alih dalam bentuk tindakan, oleh karena itu saya percaya saya secara teknis dapat mencapai hal yang sama seperti yang dilakukan Re-frame, dengan lebih banyak fleksibilitas dengan mengorbankan lebih banyak pengetikan (membuat tindakan) dan mungkin lebih banyak ruang untuk melakukannya salah.

Pada akhirnya tidak ada Redux yang menghentikan saya untuk menerapkan pendekatan yang persis sama seperti di Re-frame. Saya dapat membuat fungsi apa pun yang mengambil peredam sebagai parameter, melakukan beberapa pemrosesan di dalamnya kemudian langsung memanggil fungsi peredam dengan hasilnya dan menyebutnya Peredam Middlewares atau apa pun. Ini hanya tentang jika saya ingin membuatnya sedikit lebih mudah dengan mengorbankan DevTools dan lebih banyak kopling.

Saya masih harus berpikir lebih banyak jika ada keuntungan signifikan dalam pendekatan Re-frame di atas beberapa penyederhanaan (mengurangi jumlah tindakan). Saya terbuka untuk membuktikan bahwa saya salah, seperti yang telah saya katakan, saya membaca dengan cepat.

Sebenarnya, saya sedikit salah. Perbedaannya bukan pada implementasi Redux vs Re-frame, tetapi sebenarnya pada "di mana Anda meletakkan efek samping".

Jika Anda melakukan ini di event handler (reduksi) dan melarang pembuat tindakan memengaruhi aliran kontrol Anda, maka secara teknis tidak ada perbedaan - Anda juga dapat menggunakan fsm / statechart di Redux dan mengontrol aliran Anda di satu tempat.

Namun dalam kenyataannya - ada perbedaan. @gaearon menjelaskannya dengan sangat baik: Redux mengharapkan reduksi Anda menjadi murni, jika tidak,

Bingkai ulang juga menyatakan bahwa penangan peristiwa itu murni, tetapi bahkan di Readme mereka menyebutkan bahwa permintaan HTTP dimulai di penangan peristiwa. Jadi ini agak tidak jelas bagi saya, tetapi sepertinya harapan mereka berbeda: Anda dapat menempatkan efek samping Anda pada penanganan acara.

Maka menarik bagaimana mereka mendekati replay (dan itulah yang saya tanyakan di https://github.com/Day8/re-frame/issues/86).

Saya berasumsi satu-satunya cara untuk melakukannya ketika penangan acara Anda dapat memiliki efek samping adalah apa yang disebutkan @ tomkis1 - status perekaman dikembalikan oleh setiap penangan acara (bukan

Pemuatan ulang panas masih dapat berfungsi, kecuali bahwa hal itu tidak dapat memengaruhi peristiwa "masa lalu" - hanya menghasilkan beberapa cabang status baru pada waktunya.

Jadi perbedaan dalam desain mungkin tidak terlalu kentara, tetapi bagi saya itu penting.

Saya pikir mereka melakukan permintaan HTTP dengan membuat penangan acara murni dengan middlewares yang berpengaruh samping. Komposisi ini mengembalikan event handler baru yang tidak murni lagi tetapi bagian dalam tetap murni. Saya tidak berpikir mereka menyarankan untuk membangun penangan acara yang menggabungkan mutasi app-db (status) dan efek samping. Itu membuat kode lebih bisa diuji.

Jika Anda merekam hasil dari sebuah event handler, Anda tidak dapat membuatnya menjadi hot-reloadable yang berarti Anda dapat mengubah kodenya dengan cepat. Anda harus merekam hasil middleware dan itu akan persis sama dengan yang dilakukan Redux - rekam hasil ini sebagai tindakan dan berikan ke reducer (dengarkan).

Saya rasa saya dapat mengatakan bahwa middlewares Re-frame proaktif sementara Redux memungkinkan reaktivitas (peredam ad-hoc apa pun dapat mendengarkan hasil middleware / ac). Satu hal yang mulai saya sadari adalah bahwa AC yang banyak digunakan di Flux dan middlewares sama dengan pengendali.

Seperti yang saya pahami, @ tomkis1 menyarankan

Oke, menyadari bahwa reducer / event handler mengembalikan status baru sepenuhnya, maka Anda mengatakan hal yang sama.

@merk Saya akan mencoba untuk menulis pemikiran saya tentang hal itu mungkin pada hari Senin karena saya mungkin tidak sampai di sini selama akhir pekan.

Dokumen bingkai ulang merekomendasikan untuk berbicara dengan server di penangan acara: https://github.com/Day8/re-frame#talking -to-a-server

Jadi di sinilah perbedaannya dari Redux. Dan perbedaan ini lebih dalam dari yang terlihat pada pandangan pertama.

Hm, saya gagal melihat perbedaan dari Redux:

Penangan peristiwa yang memulai harus mengatur bahwa penangan saat berhasil atau saat gagal untuk permintaan HTTP ini sendiri hanya mengirimkan peristiwa baru . Mereka tidak boleh mencoba mengubah sendiri app-db. Itu selalu dilakukan di pawang.

Ganti acara dengan Action. Redux hanya memiliki nama khusus untuk event handler murni - peredam. Saya pikir saya melewatkan sesuatu atau otak saya sedang down akhir pekan ini.

Saya bisa mencium perbedaan yang lebih signifikan bagaimana peristiwa dijalankan - antri dan asinkron. Saya pikir Redux menjalankan sinkronisasi tindakan dan mengunci langkah demi langkah tindakan untuk menjamin konsistensi negara. Ini selanjutnya dapat memengaruhi replayability karena saya tidak yakin apakah hanya menyimpan acara dan mengulanginya lagi dan lagi akan menghasilkan status akhir yang sama. Mengabaikan itu saya tidak bisa begitu saja karena saya tidak tahu kejadian mana yang memicu efek samping tidak seperti di Redux di mana tindakan selalu memicu kode murni.

@vladap Untuk meringkas perbedaan yang saya lihat:

1.

  • di Redux Anda memulai permintaan server dalam tindakan pembuat (lihat @gaearon membalas pertanyaan asli tentang masalah ini); pengurang Anda harus murni
  • di Re-frame Anda melakukannya di event handler (peredam); event-handler Anda (reduksi) mungkin memiliki efek samping

2.

  • Dalam kasus pertama aliran kontrol Anda dibagi antara AC dan peredam.
  • Dalam kasus kedua, semua aliran kontrol Anda berada di satu tempat - pengendali kejadian (peredam).

3.

  • Jika aliran kontrol Anda terbagi - Anda memerlukan koordinasi antara AC dan peredam (meskipun implisit): AC membaca status yang dihasilkan oleh peredam; peredam mengasumsikan urutan kejadian yang tepat dari AC. Secara teknis Anda memperkenalkan kopling AC dan peredam, karena perubahan pada peredam juga dapat mempengaruhi AC dan sebaliknya.
  • Jika aliran kontrol Anda ada di satu tempat - Anda tidak memerlukan koordinasi tambahan + Anda dapat menggunakan alat seperti FSM atau Statechart di event-handler / reduksi Anda


    1. Cara redux untuk menerapkan pengurang murni dan memindahkan efek samping ke AC memungkinkan pemuatan ulang dan pemutaran ulang panas

  • Cara kerangka ulang untuk memiliki efek samping di event-handler (reduksi) menutup pintu untuk memutar ulang seperti yang dilakukan di Redux (dengan mengevaluasi ulang reduksi), tetapi opsi lain (mungkin kurang kuat) masih memungkinkan - seperti yang disebutkan @ tomkis1

Adapun perbedaan dalam pengalaman pengembang - ini hanya muncul ketika Anda memiliki banyak hal asinkron (pengatur waktu, permintaan http paralel, soket web, dll). Untuk sinkronisasi semuanya sama (karena tidak ada aliran kontrol di AC).

Saya rasa saya perlu menyiapkan beberapa contoh dunia nyata dari skenario kompleks semacam itu untuk memperjelas maksud saya.

Saya mungkin membuat Anda marah, saya minta maaf untuk itu tetapi kami tidak setuju pada satu hal dasar. Mungkin saya harus membaca kode contoh bingkai ulang, saya akan belajar Clojure suatu hari nanti. Mungkin ya, maka Anda tahu lebih banyak. Terima kasih atas usahanya.

1.

di Redux Anda memulai permintaan server dalam tindakan pembuat (lihat @gaearon membalas pertanyaan asli tentang masalah ini); pengurang Anda harus murni
di Re-frame Anda melakukannya di event handler (peredam); event-handler Anda (reduksi) mungkin memiliki efek samping

Doc yang saya buat berani secara eksplisit mengatakan bahwa saya harus memulai panggilan webapi di event handler dan kemudian mengirimkan acara dalam kesuksesannya. Saya tidak mengerti bagaimana saya bisa menangani acara yang dikirim ini di penangan kejadian yang sama. Acara yang berhasil ini dikirim ke router (saya kira seperti dispatcher) dan saya perlu lagi event handler untuk menanganinya. Penangan kejadian kedua ini murni dan setara dengan peredam Redux, yang pertama setara dengan ActionCreator. Bagi saya itu adalah hal yang sama atau saya jelas kehilangan beberapa wawasan penting.

Adapun perbedaan dalam pengalaman pengembang - ini hanya muncul ketika Anda memiliki banyak hal asinkron (pengatur waktu, permintaan http paralel, soket web, dll). Untuk sinkronisasi semuanya sama (karena tidak ada aliran kontrol di AC).

Kami tidak setuju yang satu ini. Hal-hal async sebenarnya tidak masalah, Anda tidak dapat memutar ulang di Redux sehingga Anda tidak memiliki pengalaman pengembang di sini. Perbedaannya muncul ketika Anda memiliki banyak pereduksi murni yang kompleks. Di Redux Anda dapat mengambil status A, beberapa urutan tindakan, reducer, memutar ulang dan mendapatkan status B. Anda tetap mempertahankan semuanya tetapi Anda membuat perubahan kode ke reducer dan mem-reloadnya, memutar ulang urutan tindakan yang sama dari status A dan Anda dapatkan status C, segera lihat dampak dari perubahan kode Anda. Anda tidak dapat melakukannya dengan pendekatan @ tomkis1 .

Anda bahkan dapat membuat skenario pengujian di atasnya. Simpan beberapa status awal, simpan beberapa urutan tindakan, kurangi urutan tindakan ke status A, dapatkan status B, dan tegaskan. Kemudian buat perubahan pada kode yang Anda harapkan akan menghasilkan status B yang sama, putar ulang skenario pengujian untuk menegaskan bahwa itu benar dan perubahan Anda tidak merusak aplikasi Anda. Saya tidak mengatakan itu adalah pendekatan pengujian terbaik tetapi itu bisa dilakukan.

Sebenarnya saya akan sangat terkejut jika Clojurist yang saya anggap hampir sama kerasnya dengan praktisi pemrograman fungsional seperti Haskeller akan merekomendasikan untuk mencampur efek samping dengan kode yang bisa murni setidaknya tanpa beberapa tingkat komposisi.

Evaluasi malas / reaktivitas adalah teknik dasar bagaimana memindahkan kode yang berpengaruh samping sejauh mungkin dari kode yang sebaliknya murni. Evaluasi malas adalah bagaimana Haskell akhirnya menerapkan konsep kemurnian fungsional yang tidak praktis untuk mendapatkan program yang melakukan sesuatu yang praktis, karena program yang sepenuhnya murni tidak dapat berbuat banyak. Menggunakan komposisi monadik dan lainnya, seluruh program dibuat sebagai aliran data dan untuk menjaga langkah terakhir dalam pipeline tetap murni dan tidak pernah benar-benar memanggil efek samping dalam kode Anda, program mengembalikan deskripsi apa yang harus dilakukan. Ini diteruskan ke runtime yang mengeksekusinya dengan malas - Anda tidak memicu eksekusi dengan panggilan imperatif. Setidaknya begitulah cara saya memahami pemrograman fungsional dari pandangan level burung. Kami tidak memiliki runtime seperti yang kami simulasikan dengan ActionCreators dan reaktivitas. Penafian, jangan anggap remeh, itulah yang saya pahami dari tidak begitu banyak membaca tentang FP menjadi otoritas apa pun di sini. Saya mungkin pergi tetapi saya benar-benar percaya bahwa saya mengerti maksudnya.

Saya tidak mengerti bagaimana saya bisa menangani acara yang dikirim ini di penangan kejadian yang sama.

Saya tidak mengatakan itu. Yang saya maksud dengan "efek samping" adalah meminta HTTP dimulai di event handler (reducer). Melakukan IO juga merupakan efek samping, meskipun tidak memengaruhi pengembalian fungsi Anda. Saya tidak bermaksud "efek samping" dalam istilah mengubah status secara langsung dalam penangan keberhasilan / kesalahan.

Penangan kejadian kedua ini murni dan setara dengan peredam Redux, yang pertama setara dengan ActionCreator.

Saya rasa yang pertama tidak setara dengan Action Creator. Jika tidak, mengapa Anda membutuhkan Pembuat Tindakan untuk melakukan ini? Jika Anda dapat melakukan hal yang sama pada peredam?

Anda tidak dapat melakukannya dengan pendekatan @ tomkis1 .

Setuju. Dan itulah yang saya maksud ketika saya menulis "opsi lain (mungkin kurang kuat)".

Saya rasa yang pertama tidak setara dengan Action Creator. Jika tidak, mengapa Anda membutuhkan Pembuat Tindakan untuk melakukan ini? Jika Anda dapat melakukan hal yang sama pada peredam?

ActionCreators digunakan untuk mengisolasi efek samping dari aplikasi murni (yang dapat diputar ulang). Karena Redux dirancang, Anda tidak dapat (tidak boleh) melakukan efek samping di reduksi. Anda memerlukan konstruksi lain di mana Anda dapat menempatkan kode yang berdampak samping. Di Redux, konstruksi ini adalah AC atau middlewares. Satu-satunya perbedaan yang saya lihat di re-frame adalah apa yang dilakukan middlewares dan re-frame itu tidak memiliki middlewares untuk mengubah peristiwa (tindakan) sebelum penangan dipicu.

Alih-alih AC / middleware sebenarnya apa pun dapat digunakan - modul util, layanan (seperti layanan Angular), Anda dapat membangunnya sebagai lib terpisah yang kemudian Anda tautkan dengan AC atau middleware dan menerjemahkan API-nya ke tindakan. Jika Anda bertanya kepada saya AC bukan tempat yang tepat untuk menempatkan kode ini masuk AC seharusnya hanya pembuat / pabrik sederhana yang membangun objek tindakan dari argumen. Jika saya ingin menjadi seorang purist, akan lebih baik untuk menempatkan kode efek samping secara ketat ke middlewares. ActionCreator adalah istilah yang buruk untuk jenis tanggung jawab pengontrol. Tetapi jika kode ini hanya panggilan webapi satu baris maka ada kecenderungan untuk hanya memasukkannya ke dalam AC dan untuk aplikasi sederhana itu bisa baik-baik saja.

AC, middlewares, libs pihak ke-3 membentuk lapisan yang saya suka sebut lapisan layanan. Facebook WebApiUtils asli akan menjadi bagian dari lapisan ini, sama seperti jika Anda akan menggunakan react-router dan mendengarkan perubahannya dan menerjemahkannya ke tindakan untuk memperbarui Redux.state, Relay dapat digunakan sebagai lapisan layanan (daripada menggunakan Redux untuk aplikasi / tampilan negara). Karena sebagian besar lib pihak ke-3 saat ini diprogram, libs tidak berfungsi dengan baik dengan replay. Jelas saya tidak ingin menulis ulang semuanya dari awal jadi itu akan terjadi. Bagaimana saya suka memikirkannya adalah bahwa libs, layanan, utilitas, dll. Ini memperluas lingkungan browser yang membentuk platform bersama di mana saya dapat membangun aplikasi yang dapat diputar ulang. Platform ini penuh dengan efek samping, sebenarnya itu adalah tempat di mana saya sengaja memindahkan efek samping. Saya menerjemahkan api platform ini ke tindakan dan aplikasi saya secara tidak langsung terhubung ke platform ini dengan mendengarkan objek tindakan ini (mencoba mensimulasikan pendekatan Haskell yang saya coba gambarkan dalam posting di atas). Ini adalah semacam peretasan dalam pemrograman fungsional bagaimana tetap mencapai kemurnian (mungkin tidak dalam arti akademis absolut) tetapi memicu efek samping.

Ada hal lain yang membuatku bingung tapi aku bisa salah paham padamu. Saya pikir Anda mengatakan bahwa untuk logika yang kompleks, memiliki segalanya di satu tempat adalah hal yang menguntungkan. Saya pikir cukup bertentangan.

Keseluruhan bisnis tindakan ini memiliki konsep yang serupa dengan peristiwa DOM. Saya benar-benar tidak ingin mencampurkan kode bisnis saya dengan kode bagaimana browser mendeteksi peristiwa mousemove dan saya yakin Anda juga tidak akan melakukannya. Untungnya, saya terbiasa bekerja di atas addListener ('mousemove', ...) karena tanggung jawab ini dipisahkan dengan baik. Intinya adalah untuk mencapai pemisahan perhatian yang baik ini untuk logika bisnis saya juga. ActionCreators, middlewares adalah alat untuk itu.

Bayangkan bahwa saya akan menulis aplikasi melawan webapi usang yang tidak memetakan dengan baik untuk kebutuhan bisnis saya. Untuk mendapatkan data yang diperlukan, saya harus memanggil beberapa titik akhir, menggunakan hasilnya untuk membuat panggilan berikutnya, lalu menggabungkan semua hasil ke dalam bentuk kanonik yang akan saya gunakan di Redux.state. Logika ini tidak akan bocor ke reduksi saya sehingga saya tidak perlu mengubah data di reduksi saya lagi dan lagi. Itu akan berada di middlewares. Secara efektif saya akan mengisolasi api usang yang berantakan dan mengembangkan aplikasi bisnis saya karena akan ditulis dengan api yang bagus. Setelah selesai, mungkin saya akan mendapatkan perubahan untuk membangun kembali webapi kami dan kemudian saya akan menulis ulang middleware saya.

Karenanya, memiliki semuanya di satu tempat tampaknya mudah untuk aplikasi yang mudah. Tetapi cukup bertentangan untuk hal yang kompleks saya melihat pemisahan yang baik dari kepentingan manfaat. Tapi mungkin saya belum mengerti maksud atau kasus penggunaan yang Anda maksud.

Sebenarnya, mungkin saya akan memindahkan sebanyak mungkin transformasi data ke bentuk kanoniknya ke reduksi dan menggunakan komposisi reduksi sebagai gantinya karena itu akan memberi saya replay dan testability untuk kode ini yang umumnya murni.

Saya tidak mengerti bagaimana saya bisa menangani acara yang dikirim ini di penangan kejadian yang sama.
Saya tidak mengatakan itu.

Saya pikir Anda ingin mencampur panggilan webapi dengan logika reduksi untuk memilikinya di satu tempat. Apa yang saya katakan bahwa Anda tidak melakukannya dalam bingkai ulang karena mereka menyarankan untuk menyebarkan acara pada kesuksesan.

Ada situasi ketika webapi -> banyak logika -> webapi -> banyak logika -> ... -> hasil untuk reducer, seluruh rantai dipicu oleh satu klik. Dalam kasus seperti itu sebagian besar logika mungkin ada di AC, saya tidak akan membaginya sampai semua efek samping selesai. Apakah ini yang Anda maksud?

Hal terbaik yang dapat saya lakukan di sini adalah memindahkan sebanyak mungkin logika ke fungsi murni untuk dapat diuji, tetapi mereka akan dipanggil dalam lingkup AC.

Saya rasa saya tidak menyukai kemungkinan untuk melakukannya sebagai rangkaian penanganan acara yang terhubung secara tidak langsung. Itu akan menjadi rantai janji atau yang dapat diamati.

Reduxer yang lebih berpengalaman dapat memiliki pendapat berbeda tentang pemikiran itu ( @gaearon , @johanneslumpe , @acdlite , @emmenko ...)

@vladap Saya pikir percakapan ini bergerak terlalu jauh dari topik masalah ini. Anda telah menyebutkan poin utama dari komentar saya di sini:

Oleh karena itu saya dapat membayangkan sulit untuk menjaga logika antara AC dan reduksi sinkron dalam beberapa skenario yang sangat kompleks.

Contoh cepat:

Situasi 1: Anda memiliki daftar 10 penulis blog teratas di suatu tempat di halaman. Sekarang Anda menghapus kategori posting di blog. Jika berhasil menghapus, Anda perlu memperbarui daftar penulis ini dari server untuk memperbaruinya.

Situasi 2: Anda berada di halaman berbeda. Itu tidak memiliki daftar penulis, tetapi memiliki daftar 10 komentar teratas. Anda juga dapat menghapus beberapa kategori posting blog dan harus memperbarui daftar komentar jika berhasil dihapus.

Jadi bagaimana kita menangani ini pada level mesin negara? (kami juga dapat menggunakan kait React untuk mendapatkan bantuan, tetapi mesin keadaan yang baik harus dapat tetap konsisten dengan sendirinya)

Pilihan:
A) kami menempatkan logika ini di AC. Kemudian AC akan menyentuh CategoryApi (untuk menghapus), akan membaca dari userList negara dan commentList negara (untuk memeriksa daftar mana yang ada di negara bagian sekarang), bicara ke UserListApi dan / atau CommentListApi (untuk menyegarkan daftar) + mengirimkan TOP_AUTHORS_UPDATED dan / atau TOP_COMMENTS_UPDATED . Jadi pada dasarnya akan menyentuh 3 domain berbeda.

B) kita taruh di event handler userList dan commentList . Penangan ini akan mendengarkan peristiwa DELETE_CATEGORY_SUCCESS , lalu memanggil layanan API mereka dan pada gilirannya mengirimkan peristiwa TOP_AUTHORS_UPDATED / TOP_COMMENTS_UPDATED . Jadi setiap penangan hanya menyentuh layanan / status domainnya sendiri.

Contoh ini mungkin terlalu sederhana, tetapi bahkan pada level ini segalanya menjadi kurang cantik dengan panggilan API di AC.

Perbedaannya berasal dari fakta bahwa tidak seperti penangan peristiwa dalam bingkai ulang, ActionCreators proaktif dalam menangani logika bisnis. Dan hanya satu AC yang bisa dijalankan berdasarkan event DOM. AC tingkat atas ini dapat memanggil AC lain. Ada AC terpisah untuk UserListApi dan CommentListApi sehingga domain lebih baik dipisahkan, tetapi selalu harus ada AC (pengontrol seperti) yang menghubungkannya. Bagian ini adalah kode imperatif yang cukup klasik sementara bingkai ulang sepenuhnya berbasis peristiwa. Dengan sedikit kerja, itu dapat diganti dengan pendekatan yang disukai, berbasis peristiwa, dapat diamati, cps dll.

@vladap Akan menarik untuk melihat apakah pendekatan lain dapat dijalankan: ketika pereduksi dapat memiliki efek samping, tetapi juga dapat mengisolasi sehingga dapat diabaikan saat pemutaran ulang.

Katakanlah tanda pengurang akan berubah menjadi: (state, action) => (state, sideEffects?) mana sideEffects adalah penutupan. Kemudian bergantung pada kerangka konteks dapat mengevaluasi efek samping ini atau mengabaikannya (dalam kasus replay).

Kemudian contoh dari deskripsi masalah asli akan terlihat seperti ini:

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

(semuanya tetap kurang lebih sama)

Setidaknya aliran kendali Anda ada di satu tempat, efek samping Anda selalu sederhana (karena mereka hanya membuat tindakan / peristiwa lain di penangan asinkron, dan logika tetap di pereduksi - jadi lebih banyak kode Anda akan diputar ulang).

Anda juga dapat menulis kode FSM atau Statechart biasa (dengan komposisi reduksi / event-handler)

Anda juga tidak perlu mengembalikan penutupan dengan tanda tangan yang telah ditentukan sebelumnya dalam tindakan pembuat.

Tidak yakin masalah apa yang berpotensi ditimbulkan oleh pendekatan ini, tetapi bagi saya itu layak untuk dimainkan.

Katakanlah tanda pengurang akan berubah menjadi: (state, action) => (state, sideEffects?) Di mana sideEffects adalah closure. Kemudian bergantung pada kerangka konteks dapat mengevaluasi efek samping ini atau mengabaikannya (dalam kasus replay).

Ini sebenarnya mirip dengan yang saya dengar Elm lakukan dengan (State, Action) => (State, Request?) . Saya belum melihat contoh apa pun tetapi jika seseorang ingin menjelajahi ini, silakan.

Namun pendekatan lain untuk mungkin mendapatkan inspirasi dari adalah implementasi eventourcing di atas model Aktor - Akka Persistance , PersistentFSM . Saya tidak mengatakan itu lebih baik tetapi tidak buruk untuk mengetahui upaya lain. Aktor dapat menggunakan efek samping tetapi jika saya ingat dengan benar terkadang perlu menulis kode eksplisit bagaimana cara memutar ulang.

Mungkin fitur replay mungkin membelokkan keputusan kita tentang tempat kode berada. Sepertinya kita mulai berpikir sebagai ... "Saya ingin ini dapat diputar ulang sehingga harus pergi ke peredam". Ini dapat menyebabkan kode dengan batasan yang tidak jelas karena kita tidak memotongnya berdasarkan tanggung jawab / perannya dan kita cenderung memotong logika bisnis kita secara artifisial agar dapat diputar ulang.

Jika saya mengesampingkan fitur replay, bagaimana saya akan menulis kode bisnis saya? Saya ingin menempatkan logika bisnis saya di depan log tindakan dan menyimpannya di satu tempat. Mesin status logika bisnis, atau apa pun yang akan saya gunakan, akan melakukan semua keputusan rumit yang sulit yang menghasilkan aliran FACTS (tindakan) yang sudah terselesaikan. Pengurang umumnya akan menjadi pembaru sederhana dan tanggung jawab utama mereka adalah bagaimana menerapkan muatan tindakan ke Redux.state. Mereka dapat memiliki beberapa logika tetapi dalam arti untuk menafsirkan fakta secara berbeda untuk mendapatkan jenis pandangan lain tentang aliran fakta (tindakan) untuk disajikan kepada pengguna.

Untuk mengurangi tanggung jawab, saya suka memikirkannya seperti ini juga. Kode apa yang berpotensi diminta untuk diterapkan kembali di server? Fe untuk mendukung perangkat lambat lebih baik dengan memindahkan komputasi logika bisnis yang mahal, mungkin beberapa alasan keamanan, properti intelijen menyembunyikan apa pun. Saya tidak dapat memindahkan logika dari reduksi ke server dengan mudah karena terikat ke lapisan tampilan. Jika logika bisnis yang kompleks ditulis di depan log tindakan, saya dapat menggantinya dengan panggilan REST yang diterjemahkan ke aliran tindakan yang sama dan lapisan tampilan saya akan berfungsi (mungkin).

Kelemahannya adalah tidak seperti di Akka Persistance, saya tidak memiliki fasilitas cara memutar ulang kode ini.

Saya harus melihat lebih dalam tentang Elm suatu hari nanti dan memahami bagaimana proposal Anda akan bekerja.

@gaearon Terima kasih telah menyebut Elm. Menemukan percakapan ini - https://gist.github.com/evancz/44c90ac34f46c63a27ae - dengan ide serupa (tetapi jauh lebih maju).

Mereka memperkenalkan konsep Tasks (http, db, dll) dan Efek (hanya antrian tugas). Jadi "peredam" Anda memang bisa mengembalikan keadaan dan efek baru.

Hal yang keren tentang itu (dan saya tidak memikirkannya seperti ini) - adalah jika Anda dapat mengumpulkan tugas sambil merangkai reduksi Anda - dan kemudian menerapkan beberapa fungsi ke daftar ini. Katakanlah Anda dapat mengumpulkan permintaan http, membungkus kueri DB dengan transaksi, dll.

Atau Anda bisa mengatakan "manajer replay" yang hanya noop untuk efek.

@merk Saya pikir sebagian besar telah ditulis. Hanya saja, kode yang memiliki efek samping otonom semacam ini mungkin yang paling rumit. Ini mungkin akan merusak malapetaka pada replay karena interval tidak akan disinkronkan dengan scheduleravel dengan asumsi bahwa scheduleravel berjalan pada kode yang sama dan interval akan dimulai dalam mode replay juga.

Aplikasi biasa tidak memerlukan token dan hitungan mundur kedaluwarsa yang disajikan kepada pengguna sehingga secara teknis tidak harus ada di Redux.state. Aplikasi Admin mungkin perlu memilikinya. Karenanya Anda dapat menerapkannya dengan kedua cara apa pun yang sesuai dengan Anda.

Salah satu opsinya adalah memulai hitung mundur kedaluwarsa di login AC dan saya menganggapnya berjalan tanpa henti dan mati dengan aplikasi atau logoff harus membersihkannya. Ketika interval memicu, ia melakukan apa yang diperlukan untuk mengonfirmasi token dan jika kadaluwarsa ia mengirimkan tindakan LOGIN_EXPIRED dan mendengarkan reducer menghapus sesi pengguna dan mengubah lokasi yang pada gilirannya memicu transisi router ke / login.

Cara lainnya adalah menggunakan lib pihak ke-3 dan mendelegasikan masalah ke dalamnya dan menerjemahkan apinya ke tindakan LOGIN_EXPIRED. Atau tulis milik Anda sendiri dan simpan token dan status hitung mundur di sini jika Anda tidak membutuhkannya dalam lapisan tampilan.

Anda dapat menyimpannya di Redux.state tetapi status ini tidak stabil. Dengan status yang besar kita bisa mendapatkan masalah yang sama seperti pemrograman dengan variabel global. Dan itu sulit, mungkin mustahil untuk mengujinya. Sangat mudah untuk menguji reducer tetapi kemudian membuktikannya bekerja ketika hanya satu reducer yang memperbarui kunci tertentu dalam objek state. Ini bisa menjadi buruk dalam proyek besar dengan banyak pengembang dengan kualitas berbeda. Siapa pun dapat memutuskan untuk menulis peredam dan berpikir bahwa beberapa bagian status baik untuk diperbarui dan saya tidak dapat membayangkan cara menguji semua peredam bersama-sama dalam semua kemungkinan urutan pembaruan.

Bergantung pada kasus penggunaan Anda, mungkin masuk akal untuk melindungi status ini, karena ini adalah login - fungsionalitas yang cukup penting, dan telah dipisahkan. Jika status ini diperlukan dalam lapisan tampilan, gandakan ke Redux.state dengan cara yang sama seperti status lokasi react-router direplikasi ke Redux.state dalam beberapa contoh. Jika negara bagian dan tim Anda kecil dan berperilaku baik, Anda dapat melakukannya di satu tempat.

@vladar @vladap

Wow, inti Elm itu benar-benar keren! Ini juga mengingatkan saya pada arsitektur "driver"

@merk Sebenarnya, LOGIN_EXPIRED otonom tidak akan banyak memengaruhi pemutaran ulang karena akan masuk ke akhir log tindakan dan tidak akan segera diproses oleh pemutaran ulang dan mungkin tidak akan sampai ke log tindakan sama sekali - Saya tidak tahu bagaimana tepatnya pemutaran ulang diterapkan.

@gaeron Tampaknya pada tingkat tinggi Elm dan Siklus menerapkan pola yang saya coba gambarkan jika saya memahaminya dengan benar. Saya memiliki browser yang memberi saya platform dasar, saya memperluas platform ini dengan lapisan layanan saya (http, db ...) untuk membangun platform untuk aplikasi saya. Kemudian saya membutuhkan semacam perekat yang berinteraksi dengan lapisan layanan dan memungkinkan aplikasi murni saya untuk berkomunikasi secara tidak langsung dengannya sehingga aplikasi saya dapat menjelaskan apa yang ingin dilakukannya tetapi tidak benar-benar menjalankannya sendiri (pesan dikirim ke driver Cycle, Elm Effects memiliki daftar tugas). Bisa dibilang aplikasi saya sedang membuat "program" menggunakan struktur data (mengingatkan saya tentang kode sebagai mantra data) yang kemudian diteruskan ke lapisan layanan untuk dieksekusi dan aplikasi saya hanya tertarik pada hasil "program" ini yang kemudian diterapkan untuk keadaannya.

@merk : Lebih lanjut. Jika token dan kedaluwarsanya akan ditempatkan di Redux.state / atau di mana pun di layanan js lainnya, itu tidak ditransfer ketika pengguna membuka aplikasi di tab baru. Saya akan mengatakan pengguna mengharapkan dia tetap login. Jadi saya berasumsi bahwa status ini seharusnya ada di SessionStore.

Bahkan tanpa itu, status dapat menjadi terpisah saat token kedaluwarsa tidak berarti segera membersihkan info pengguna dan transisi ke / login. Hingga pengguna tidak menyentuh server dengan interaksinya (dia memiliki cukup data yang disimpan dalam cache), dia dapat terus bekerja dan tindakan LOGIN_EXPIRED hanya dipicu setelah dia melakukan sesuatu yang memerlukan server. Status isLogged berdasarkan Redux.state tidak harus berarti bahwa token yang valid ada. Status isLogged di Redux.state digunakan untuk memutuskan apa yang akan dirender, status di sekitar token dapat disembunyikan ke lapisan tampilan dan dipertahankan hanya pada tingkat pembuat tindakan tanpa harus menulis tindakan dan reduksi untuk itu, kecuali yang mempengaruhi lapisan tampilan.

@vladar Saya pikir saya mungkin mendapatkan poin umum Anda. Saya pikir saya belum menyadarinya sebelumnya.

Tidak ada cara mudah untuk mengembalikan kontrol dari reducer kembali ke pembuat tindakan dan meneruskan beberapa nilai. Anggaplah nilai-nilai ini tidak diperlukan untuk rendering, bahkan bersifat sementara tetapi diperlukan untuk memulai operasi asinkron.

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
}

Ada 3 opsi bagaimana memulai operasi asinkron menggunakan x, y dihitung dalam peredam. Tak satu pun dari mereka yang sempurna.

1) Pindahkan logika dari peredam ke pembuat tindakan dan kurangi daya hot-reload. Peredam hanya pembaru keadaan bodoh atau memiliki logika L3 dan seterusnya.

2) Simpan x, y ke Redux.state, mencemari dengan nilai sementara / sementara dan pada dasarnya menggunakannya sebagai saluran komunikasi global antara pereduksi dan pembuat tindakan yang saya tidak yakin saya suka. Saya pikir tidak apa-apa jika itu keadaan sebenarnya tetapi tidak untuk nilai-nilai semacam ini. Pembuat tindakan berubah menjadi:

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) Simpan x, y untuk menyatakan, gunakan sebagai props di beberapa komponen dan lakukan tunnel melalui seluruh loop view layer menggunakan componentWillReceiveProps yang memicu pembuat tindakan baru dan operasi async. Pilihan terburuk jika Anda bertanya kepada saya, menyebarkan logika bisnis ke mana-mana.

@vladar
Umumnya tidak masalah di mana asinkron dimulai sejauh hasil operasi ini dikemas sebagai tindakan, dikirim dan diterapkan oleh peredam. Ini akan bekerja sama. Ini adalah diskusi yang sama seperti dengan vanilla flux jika async harus dimulai di pembuat tindakan atau di toko.

Ini akan mengharuskan Redux untuk dapat mengidentifikasi dan mengabaikan panggilan asinkron ini saat pemutaran ulang yang persis seperti yang Anda sarankan.

Tidak ada cara mudah untuk mengembalikan kontrol dari reducer kembali ke pembuat tindakan dan meneruskan beberapa nilai.

Menurut saya ini adalah salah satu gejalanya. Contoh kedua Anda menggambarkan sudut pandang saya dengan sangat baik (lihat di bawah).

Pertimbangkan aliran transisi keadaan umum (di dunia FSM):

  1. Acara tiba
  2. Mengingat keadaan saat ini, FSM menghitung keadaan target baru
  3. FSM melakukan operasi untuk transisi dari kondisi saat ini ke kondisi baru

Perhatikan bahwa status saat ini penting! Peristiwa yang sama dapat memberikan hasil yang berbeda bergantung pada keadaan saat ini. Dan sebagai hasil dari urutan operasi (atau argumen) yang berbeda pada langkah 3.

Jadi begitulah transisi keadaan di Flux bekerja saat tindakan Anda disinkronkan. Pengurang / penyimpanan Anda memang FSM.

Tapi bagaimana dengan tindakan async? Sebagian besar contoh Flux dengan aksi asinkron memaksa Anda untuk berpikir bahwa itu terbalik:

  1. AC melakukan operasi asinkron (terlepas dari keadaan saat ini)
  2. AC mengirimkan tindakan sebagai akibat dari operasi ini
  3. Reducer / Store menanganinya dan mengubah status

Jadi status saat ini diabaikan, status target dianggap selalu sama. Padahal pada kenyataannya aliran transisi keadaan yang konsisten masih tetap sama. Dan itu harus terlihat seperti ini (dengan AC):

  1. AC mendapatkan status saat ini sebelum tindakan dikirim
  2. AC mengirimkan tindakan
  3. AC membaca keadaan baru setelah tindakan
  4. Status yang diberikan sebelum tindakan dan setelah - itu memutuskan operasi mana yang akan dilakukan (atau nilai mana yang akan diteruskan sebagai argumen)

Persis contoh kedua Anda. Saya tidak mengatakan, bahwa semua langkah ini diperlukan setiap saat. Seringkali Anda dapat menghilangkan beberapa di antaranya. Tetapi dalam kasus yang kompleks - begitulah cara Anda harus bertindak. Ini adalah aliran umum dari setiap transisi keadaan. Dan itu juga berarti bahwa batas FSM Anda telah berpindah ke AC vs peredam / penyimpanan.

Tapi perpindahan batas menyebabkan masalah lain:

  1. Jika operasi asinkron ada di penyimpanan / peredam - Anda dapat memiliki dua FSM independen yang bereaksi terhadap peristiwa yang sama. Jadi untuk menambahkan fitur baru - Anda bisa menambahkan store / reducer yang bereaksi terhadap beberapa event yang ada dan itu saja.
    Tetapi dengan operasi di AC - Anda harus menambahkan store / reducer dan juga pergi dan mengedit AC yang ada dengan menambahkan bagian logika async di sana juga. Jika sudah berisi logika async untuk beberapa peredam lain ... itu tidak keren.
    Mempertahankan kode seperti itu jelas lebih sulit.
  2. Alur kontrol aplikasi Anda berbeda dalam hal tindakan sinkronisasi vs asinkron. Untuk tindakan sinkronisasi, peredam mengendalikan transisi, dalam kasus asinkron - AC secara efektif mengontrol semua transisi yang berpotensi disebabkan oleh peristiwa / tindakan ini.

Perhatikan, bahwa sebagian besar masalah tersembunyi oleh persyaratan status sederhana di sebagian besar aplikasi web. Tetapi jika Anda memiliki aplikasi stateful yang kompleks - Anda pasti akan menghadapi situasi ini.

Dalam kebanyakan kasus, Anda mungkin akan menemukan segala macam solusi. Tetapi Anda dapat menghindarinya sejak awal jika logika transisi status Anda lebih baik dikemas dan FSM Anda tidak dipisahkan antara AC dan reduksi.

Sayangnya, bagian transisi negara yang "mempengaruhi" masih merupakan bagian dari transisi negara, dan bukan bagian logika independen yang terpisah. Itulah mengapa bagi saya (state, action) => (state, sideEffects) terlihat lebih natural. Ya, itu bukan tanda tangan "kurangi" lagi%) Tetapi domain kerangka bukanlah transformasi data, tetapi transisi status.

Ini adalah diskusi yang sama seperti dengan vanilla flux jika async harus dimulai di pembuat tindakan atau di toko.

Ya, tetapi Flux tidak melarang Anda untuk menyimpan barang asinkron selama Anda mengirimkan () dalam panggilan balik asinkron vs status mutasi secara langsung. Mereka agak tidak beropini meskipun sebagian besar implementasi merekomendasikan penggunaan AC untuk async. Secara pribadi saya pikir ini adalah kenang-kenangan MVC, karena nyaman secara mental untuk memperlakukan AC sebagai pengontrol vs membuat pergeseran mental ke FSM.

@vladar

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

Hanya berfikir. Ini berarti kita harus mengubah jenis kembalian dari fungsi gabunganReduser menjadi [status, SideEffects] atau [status, [SideEffects]] dan mengubah kodenya untuk menggabungkan SideEffects dari reduksi. Original (state, action) => state reducer type bisa tetap didukung karena CombineReducers akan memaksa (state) return type ke [state] atau [state, []].

Kemudian kita perlu menjalankan dan mengirimkan SideEffects di suatu tempat di Redux. Itu bisa dilakukan di Store.dispatch () yang akan menerapkan status baru dan menjalankan daftar SideEffects dan mengirimkannya. Aku sedang berpikir jika kita bisa melakukan rekursi tak berujung yang bagus.

Atau bisa juga dieksekusi di suatu tempat di tingkat middleware tapi kemudian saya pikir Store.dispatch perlu mengembalikan daftar SideEffects bersama dengan sebuah tindakan.

@vladar

Ya, tetapi Flux tidak melarang Anda untuk menyimpan barang asinkron selama Anda mengirimkan () dalam panggilan balik asinkron vs status mutasi secara langsung. Mereka agak tidak beropini meskipun sebagian besar implementasi merekomendasikan penggunaan AC untuk async. Secara pribadi saya pikir ini adalah kenang-kenangan MVC, karena nyaman secara mental untuk memperlakukan AC sebagai pengontrol vs membuat pergeseran mental ke FSM.

Saya setuju bahwa memperlakukan ActionCreators sebagai pengontrol bukanlah pola pikir yang baik. Ini adalah penyebab rute mengapa saya akhirnya berpikir bahwa untuk logika yang kompleks saya perlu mengembalikan kontrol dari peredam kembali ke AC. Saya tidak perlu itu. Jika AC menangani async, mereka paling banyak seperti penangan permintaan - dijalankan berdasarkan keputusan yang dibuat di tempat lain dan permintaan ini tidak boleh memotong domain.

Untuk menghindari perannya sebagai pengontrol, satu-satunya pilihan sekarang adalah merutekan logika melalui komponen pintar dan hanya menggunakan ActionCreators untuk menjalankan langkah awal logika dalam respons langsung ke UI. Keputusan pertama yang sebenarnya dibuat dalam komponen pintar berdasarkan Redux.state dan / atau status lokal komponen.

Pendekatan ini dalam contoh Anda sebelumnya tentang menghapus kategori dan menyegarkan / menghapus sesuatu yang lain sebagai tanggapan menggunakan webapi tidak akan baik. Saya berasumsi itu akan mengarah pada campuran teknik yang berbeda berdasarkan preferensi dev - haruskah itu di AC, di peredam, di komponen pintar? Mari kita lihat bagaimana pola akan berkembang.

Saya membeli poin untuk menjauhkan panggilan asinkron dari toko. Tapi menurut saya itu tidak valid karena, imho, itu secara signifikan meningkatkan kompleksitas di tempat lain. Di sisi lain, saya tidak melihat banyak kerumitan yang memiliki fungsi asinkron di penyimpanan / peredam sejauh hanya dijalankan di sana dan dikirim - atau pendekatan murni dan menangani fungsi ini sebagai data dan memicu mereka di suatu tempat yang jauh.

Aktor melakukan hal serupa, mereka dapat mengeksekusi asinkron dan mengirim pesan (objek tindakan) ke aktor lain, atau mengirimkannya ke diri Anda sendiri untuk melanjutkan di FSM mereka.

Saya menutup karena sepertinya tidak ada yang bisa ditindaklanjuti di sini. Dokumen Tindakan Async akan menjawab sebagian besar pertanyaan. Diskusi teoritis lebih lanjut tampaknya tidak berharga bagi saya tanpa kode aktual yang dapat kami jalankan. ;-)

@ Gaearon dapatkah Anda menambahkan kode aktual yang menerapkan cara yang benar menangani panggilan API ke salah satu contoh dengan redux (di luar contoh "dunia nyata" yang menggunakan middleware)?

Saya masih memiliki pertanyaan tentang penempatan sebenarnya setelah membaca semua dokumen dan masalah ini beberapa kali saya kesulitan memahami file mana yang menempatkan efek samping ini https://github.com/rackt/redux/issues/291#issuecomment -123010379 . Saya rasa ini adalah contoh yang "benar" dari utas masalah ini.

Terima kasih sebelumnya atas klarifikasi apa pun di bidang ini.

Saya ingin menunjukkan pemikiran penting tentang pembuat tindakan tidak murni:

Menempatkan panggilan asinkron di pembuat tindakan, atau memiliki pembuat tindakan tidak murni secara umum, memiliki satu kerugian besar bagi saya: itu memecah logika kontrol sedemikian rupa sehingga sepenuhnya menukar logika kontrol aplikasi Anda tidak sesederhana hanya menukar toko Anda pencipta. Saya bermaksud untuk selalu menggunakan middleware untuk proses async dan menghindari pembuat tindakan tidak murni sepenuhnya.

Aplikasi yang saya kembangkan akan memiliki setidaknya dua versi: satu yang berjalan di Raspberry Pi di tempat, dan satu lagi yang menjalankan portal. Bahkan mungkin ada versi terpisah untuk portal / portal publik yang dioperasikan oleh pelanggan. Apakah saya harus menggunakan API yang berbeda tidak pasti, tetapi saya ingin mempersiapkan kemungkinan itu sebaik mungkin, dan middleware memungkinkan saya melakukannya.

Dan bagi saya konsep memiliki panggilan API yang didistribusikan di antara pembuat tindakan benar-benar bertentangan dengan konsep Redux tentang kontrol terpusat atas pembaruan status. Tentu, fakta bahwa permintaan API yang berjalan di latar belakang bukanlah bagian dari status toko Redux - tetapi tetap saja status aplikasi, sesuatu yang ingin Anda kendalikan dengan tepat. Dan saya menemukan bahwa saya dapat memiliki kontrol paling tepat atas hal itu ketika, seperti dengan toko / reduksi Redux, kontrol itu terpusat daripada didistribusikan.

@ jedwards1211 Anda mungkin tertarik dengan https://github.com/redux-effects/redux-effects jika Anda belum memeriksanya, serta dalam diskusi di # 569.

@gaearon keren, terima kasih telah menunjukkannya! Bagaimanapun, menggunakan middleware saya sendiri sejauh ini tidak terlalu sulit :)

Saya membuat pendekatan mirip Elm: efek samping redux . README menjelaskan pendekatan tersebut dan membandingkannya dengan alternatif.

@gregwebs Saya suka redux-side-effect , meskipun itu akan menjadi canggung dengan logika kontrol swappable juga, karena ada pertanyaan tentang bagaimana saya mendapatkan fungsi sideEffect ke dalam modul peredam saya, ketika ada beberapa konfigurasi toko yang berbeda modul untuk digunakan dalam build yang berbeda.

Saya dapat menggunakan peretasan lucu: cukup simpan sideEffect di state itu sendiri, jadi secara otomatis tersedia untuk peredam! :menjulurkan lidah mengedip mata:

Sesuatu seperti https://github.com/rackt/redux/pull/569/ hampir ideal untuk bagaimana saya ingin bekerja, meskipun tentu saja saya tidak ingin menggunakannya dalam proyek produksi kecuali itu menjadi bagian standar dari API.

Inilah idenya: minta middleware menempelkan fungsi sideEffect pada tindakan. Sedikit hacky, tetapi perubahan sekecil mungkin yang diperlukan agar pereduksi dapat memulai kode asinkron:

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

Ada beberapa cara untuk pendekatan sideEffect sederhana. Silakan coba varian Anda dan laporkan kembali

Saya merefaktor kode saya untuk menggunakan pendekatan sideEffectMiddleware atas, dan saya sangat menyukai organisasi kode yang dihasilkan. Sangat menyenangkan tidak perlu mengimpor fungsi sideEffect dari mana saja ke reduksi saya, itu hanya muncul di action .

@ jedwards1211 Saya menerbitkan kode Anda sebagai actionSideEffectMiddleware dalam versi 2.1.0 paket.

@gregweb keren! Bolehkah mendaftarkan saya sebagai kolaborator?

@ jedwards1211 Anda telah ditambahkan. Mungkin Anda akan menambahkan dukungan replay yang tepat :) Saya belum bisa memanfaatkannya di mana saya menggunakan ini.

@ Gaearon Saya menganggap tindakan yang direkam dan diputar ulang tidak melalui middleware sama sekali, bukan?

Ya, hanya saja mereka sudah menjadi objek biasa pada saat direkam, jadi middleware biasanya tidak ikut campur.

@sukasukaa_sukabumi . Jadi saya belum benar-benar mencoba fitur itu, tapi ... Saya rasa beberapa perantara efek samping akan memecahkan rekor / pemutaran ulang juga? Ambil redux-effects-fetch sebagai contoh, itu masih akan melakukan permintaan ajax pada aksi FETCH objek biasa:

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

Jika perekam membersihkan callback [success, failure] dari tindakan FETCH steps , maka middleware redux-effects tidak akan mengirimkan tindakan apa pun yang mengubah status, tetapi tetap saja, orang tidak mau memutar ulang untuk memicu banyak permintaan ajax.

Adakah cara untuk memisahkan middleware "murni" (yang tidak pernah mengirimkan tindakan tambahan) seperti redux-logger dari middleware "tidak murni" (yang mungkin mengirimkan tindakan)?

Sayangnya, saya belum memeriksa redux-effects cermat sehingga saya tidak bisa menjawab pertanyaan Anda.

Tidak masalah, dalam hal apa pun perekam menambahkan flag apa pun ke tindakan yang dapat digunakan middleware untuk memutuskan apakah akan melakukan efek samping atau tidak? Saya hanya mencoba mencari cara di middleware saya sendiri agar dapat mendukung devtools dengan benar

Tidak. Diharapkan bahwa devTools() diletakkan _setelah_ applyMiddleware dalam rantai komposisi penambah, sehingga pada saat tindakan tercapai, itu adalah tindakan "akhir" yang tidak memerlukan interpretasi lebih lanjut.

Oh, begitu, jadi setelah middleware melakukan efek samping, itu bisa saja menghapus bidang apa pun dari tindakan / tweak sehingga tidak akan memicu efek samping ketika kembali melalui middleware saat diputar ulang, dan kemudian seharusnya bekerja dengan alat pengembang, bukan?

Ya, sepertinya itu cara yang bagus.

Bagus, terima kasih telah mencerahkan saya!

@gaearon @vladar @ jedwards1211 Anda mungkin tertarik dengan https://github.com/salsita/redux-side-effects. Pada dasarnya ini adalah implementasi dari elmish (state, action) => (state, sideEffects) tetapi bukannya reducer mengembalikan tuple (yang sudah Anda perhatikan tidak mungkin) itu menghasilkan efek.
Saya bermain dengannya sebentar, hot reload dan replay sepertinya baik-baik saja. Saya tidak melihat fitur redux rusak, sejauh ini, tapi mungkin beberapa redux senior mungkin menemukan sesuatu. :)
Bagi saya, ini terlihat sebagai tambahan yang sangat penting untuk tumpukan redux karena memungkinkan logika menjadi terutama dalam reduksi menjadikannya mesin status yang efektif dan dapat diuji, yang mendelegasikan efek (tindakan transisi yang hasilnya tidak hanya bergantung pada status saat ini) ke layanan eksternal ( cukup mirip dengan arsitektur Elm, efek dan layanan).

Pendekatan lain adalah redux-saga yang juga menggunakan generator tetapi menjaga efek samping tetap terpisah dari reduksi.

@gaearon Saya percaya @minedeljkovic berarti

Bagi saya, ini terlihat sebagai tambahan yang sangat penting untuk tumpukan redux karena memungkinkan logika menjadi terutama dalam reduksi menjadikannya mesin status yang efektif dan dapat diuji, yang mendelegasikan efek (tindakan transisi yang hasilnya tidak hanya bergantung pada status saat ini) ke layanan eksternal ( cukup mirip dengan arsitektur Elm, efek dan layanan).

sebagai keuntungan utama dibandingkan pendekatan tradisional, karena https://github.com/yelouafi/redux-saga/ sangat mirip dengan cara kerja redux-thunk , alih-alih beberapa gula sintaks di atasnya yang membuat transaksi berjalan lama lebih mudah .

https://github.com/salsita/redux-side-effects di sisi lain lebih seperti arsitektur Elm.

Ya, redux-saga dan redux-efek samping menggunakan generator hanya untuk menyatakan efek samping, masing-masing menjaga saga dan reduksi murni. Itu serupa.

Dua alasan mengapa saya lebih suka reduksi:

  1. Anda memiliki akses eksplisit ke status saat ini yang dapat memengaruhi bagaimana efek harus dinyatakan (saya pikir itu adalah salah satu poin @vladar selama diskusi ini)
  2. Tidak ada konsep baru yang diperkenalkan (Saga di redux-saga)

Komentar saya di https://github.com/rackt/redux/issues/1139#issuecomment -165419770 masih berlaku karena redux-saga tidak akan menyelesaikan masalah ini dan tidak ada cara lain untuk menyelesaikan ini kecuali menerima model digunakan dalam arsitektur Elm.

Saya memberikan inti sederhana yang mencoba menekankan poin saya tentang efek samping redux: https://gist.github.com/minedeljkovic/7347c0b528110889aa50

Saya mencoba mendefinisikan skenario sesederhana mungkin yang menurut saya cukup "dunia nyata" tetapi tetap menekankan beberapa poin dari diskusi ini.

Skenario adalah:
Aplikasi memiliki bagian "Pendaftaran pengguna" di mana data pengguna pribadi harus dimasukkan. Di antara data pribadi lainnya, negara kelahiran dipilih dari daftar negara. Pemilihan dilakukan dalam dialog "Pemilihan negara", di mana pengguna memilih negara dan memiliki pilihan untuk mengonfirmasi atau membatalkan pilihan. Jika pengguna mencoba untuk mengkonfirmasi pilihan, tetapi tidak ada negara yang dipilih, dia harus diperingatkan.

Batasan arsitektur:

  1. Fungsionalitas CountrySelection harus modular (dalam semangat redux ducks ), sehingga dapat digunakan kembali di beberapa bagian aplikasi (misalnya juga di bagian "Administrasi produk" dari aplikasi, di mana negara produksi harus dimasukkan)
  2. Potongan status CountrySelection tidak boleh dianggap global (berada di root status redux), tetapi harus lokal ke modul (dan dikontrol oleh modul itu) yang memanggilnya.
  3. Logika inti dari fungsi ini perlu menyertakan sesedikit mungkin bagian yang bergerak (Dalam intinya, logika inti ini diimplementasikan hanya dalam reduksi (dan hanya bagian terpenting dari logika). Komponen harus sepele, karena semua komponen yang digerakkan redux harus jadilah :). Satu-satunya tanggung jawab mereka adalah memberikan keadaan saat ini, dan mengirimkan tindakan.)

Bagian terpenting dari inti, mengenai percakapan ini, adalah cara peredam countrySelection menangani tindakan CONFIRM_SELECTION.

@ Gaearon , saya sebagian besar akan menghargai pendapat Anda tentang pernyataan saya bahwa efek samping redux sebagai tambahan redux standar memberikan permukaan untuk solusi paling sederhana mempertimbangkan konstratint.

Ide alternatif yang mungkin untuk implementasi skenario ini (tanpa menggunakan efek samping redux, tetapi menggunakan redux-saga, redux-thunk atau metode lain), juga akan sangat sesuai.

@ tomkis1 , Saya ingin sekali

(Ada penerapan yang sedikit berbeda dalam inti ini https://gist.github.com/minedeljkovic/9d4495c7ac5203def688, di mana globalActions dihindari. Tidak penting untuk topik ini, tetapi mungkin seseorang ingin mengomentari pola ini)

@minedeljkovic Terima kasih atas intinya. Saya melihat satu masalah konseptual dengan contoh Anda. redux-side-effects dimaksudkan untuk digunakan hanya untuk efek samping. Dalam contoh Anda bagaimanapun tidak ada efek samping tetapi transaksi bisnis yang berumur panjang dan karena itu redux-saga jauh lebih cocok. @slorber dan @yelouafi dapat menjelaskan lebih lanjut tentang ini.

Dengan kata lain, masalah yang paling mengkhawatirkan bagi saya adalah sinkronisasi dispatching tindakan baru dalam peredam (https://github.com/salsita/redux-side-effects/pull/9#issuecomment-165475991):

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

Saya yakin @slorber datang dengan istilah yang disebut "efek samping bisnis" dan inilah kasus Anda. redux-saga bersinar dalam memecahkan masalah khusus ini.

@mindjuice saya tidak begitu yakin untuk memahami contoh Anda, tetapi saya suka contoh orientasi yang saya berikan di sini: https://github.com/paldepind/functional-frontend-architecture/issues/20#issuecomment -162822909

Pola saga juga memungkinkan untuk membuat beberapa hal implisit menjadi eksplisit.
Misalnya Anda hanya menjelaskan apa yang terjadi dengan tindakan penembakan (saya lebih suka peristiwa karena bagi saya itu harus dalam bentuk lampau), dan kemudian beberapa aturan bisnis berlaku dan memperbarui UI. Dalam kasus Anda, tampilan kesalahan bersifat implisit. Dengan saga, saat dikonfirmasi, jika tidak ada negara yang dipilih, Anda mungkin akan mengirimkan tindakan "NO_COUNTRY_SELECTED_ERROR_DISPLAYED" atau sesuatu seperti itu. Saya lebih suka itu menjadi sangat eksplisit.

Anda juga bisa melihat Saga sebagai titik penghubung antara bebek.
Misalnya, Anda memiliki duck1 dan duck2, dengan masing-masing tindakan lokal. Jika Anda tidak menyukai ide untuk memasangkan 2 bebek (maksud saya satu bebek akan menggunakan tindakan Pencipta bebek kedua) Anda bisa membiarkan mereka menjelaskan apa yang terjadi, dan kemudian membuat Saga yang menghubungkan beberapa aturan bisnis yang rumit antara 2 bebek.

Sooooo, ini adalah utas yang sangat panjang dan masih adakah solusi untuk masalah ini?

Misalkan, Anda memiliki action() asinkron dan kode Anda harus menunjukkan kesalahan atau menunjukkan hasilnya.

Pendekatan pertama adalah membuatnya seperti

// 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')

Tetapi ternyata itu bukan cara Redux karena sekarang peredamnya mengandung "efek samping" (saya tidak tahu apa artinya itu).
Singkat cerita, cara yang benar adalah memindahkan alert() s ini keluar dari peredam di suatu tempat.

Itu di suatu tempat bisa menjadi komponen React yang menyebut ini action .
Jadi sekarang kode saya terlihat seperti:

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

Apakah sekarang ini cara yang benar untuk melakukannya?
Atau apakah saya perlu menyelidiki lebih lanjut dan menerapkan beberapa perpustakaan pihak ketiga?

Redux sama sekali tidak sederhana. Tidak mudah untuk memahami dalam kasus aplikasi dunia nyata. Mungkin diperlukan sampel repo aplikasi dunia nyata sebagai contoh kanonik yang menggambarkan cara yang Benar.

@ halt-hammerzeit Redux sendiri sangat sederhana; fragmentasi yang membingungkan berasal dari kebutuhan atau pendapat yang berbeda tentang mengintegrasikan / memisahkan efek samping dari reduksi saat menggunakan Redux.

Juga, fragmentasi berasal dari fakta bahwa sebenarnya tidak sulit untuk melakukan efek samping seperti yang Anda inginkan dengan Redux. Hanya butuh sekitar 10 baris kode untuk menggulung middleware efek samping dasar saya sendiri. Jadi rangkul kebebasan Anda dan jangan khawatir tentang cara yang "benar".

@ jedwards1211 "Redux itu sendiri" tidak memiliki nilai atau arti dan tidak masuk akal karena tujuan utamanya adalah untuk memecahkan masalah sehari-hari para pengembang Flux. Itu menyiratkan AJAX, animasi yang indah, dan semua hal yang biasa dan diharapkan_ lainnya

@ halt-hammerzeit Anda ada benarnya di sana. Redux tampaknya dimaksudkan untuk membuat kebanyakan orang setuju tentang cara mengelola pembaruan status, jadi sayang sekali tidak membuat kebanyakan orang setuju tentang cara melakukan efek samping.

Pernahkah Anda melihat folder "contoh" di repo?

Meskipun ada banyak cara untuk melakukan efek samping, umumnya rekomendasinya adalah melakukannya di luar Redux atau pembuat tindakan dalam, seperti yang kami lakukan di setiap contoh.

Ya, saya tahu ... yang saya katakan adalah, utas ini telah menggambarkan kurangnya konsensus (setidaknya di antara orang-orang di sini) tentang cara terbaik untuk melakukan efek samping.

Meskipun mungkin sebagian besar pengguna memang menggunakan pembuat tindakan thunk dan kami hanya outlier.

^ apa yang dia katakan.

Saya lebih suka semua pemikir terhebat untuk menyetujui satu solusi Benar
dan mengukirnya di batu sehingga saya tidak perlu membaca 9000 layar
benang

Pada hari Kamis, 7 Januari 2016, Andy Edwards [email protected] menulis:

Ya, saya tahu ... yang saya katakan adalah, utas ini menggambarkan kekurangan
konsensus (setidaknya di antara orang-orang di sini) tentang cara terbaik untuk melakukan pekerjaan sampingan
efek.

-
Balas email ini secara langsung atau lihat di GitHub
https://github.com/rackt/redux/issues/291#issuecomment -169751432.

Jadi, saya kira, pengunduran diri yang disepakati saat ini adalah melakukan segala sesuatu dalam tindakan
pencipta. Saya, saya akan mencoba menggunakan pendekatan ini dan memetikan saya menemukan kekurangan
itu saya akan posting kembali di sini

Pada hari Kamis, 7 Januari 2016, Николай Кучумов [email protected] menulis:

^ apa yang dia katakan.

Saya lebih suka semua pemikir terhebat untuk menyetujui satu solusi Benar
dan mengukirnya di batu sehingga saya tidak perlu membaca 9000 layar
benang

Pada hari Kamis, 7 Januari 2016, Andy Edwards < [email protected]
<_e i = "16">

Ya, saya tahu ... yang saya katakan adalah, utas ini menggambarkan kekurangan
konsensus (setidaknya di antara orang-orang di sini) tentang cara terbaik untuk melakukan pekerjaan sampingan
efek.

-
Balas email ini secara langsung atau lihat di GitHub
https://github.com/rackt/redux/issues/291#issuecomment -169751432.

Utas ini dan yang serupa ada karena 1% pengguna Redux suka mencari solusi yang lebih kuat / murni / deklaratif untuk efek samping. Tolong jangan mengambil kesan bahwa tidak ada yang bisa menyetujui itu. Mayoritas pendiam menggunakan pikiran dan janji, dan sebagian besar senang dengan pendekatan ini. Tetapi seperti teknologi apa pun, ia memiliki kelemahan dan pengorbanan. Jika Anda memiliki logika asinkron yang kompleks, Anda mungkin ingin melepaskan Redux dan menjelajahi Rx sebagai gantinya. Pola redux mudah ekspresif di Rx.

Ok terima kasih

Pada hari Kamis, 7 Januari 2016, Dan Abramov [email protected] menulis:

Utas ini dan yang serupa ada karena 1% pengguna Redux suka mencari
solusi yang lebih kuat / murni / deklaratif untuk efek samping. Tolong jangan
mengambil kesan bahwa tidak ada yang bisa menyetujui itu. Mayoritas diam menggunakan
berpikir dan berjanji, dan sebagian besar senang dengan pendekatan ini. Tapi seperti apapun
teknologi itu memiliki kelemahan dan pengorbanan. Jika Anda memiliki logika asinkron yang kompleks
Anda mungkin ingin menjatuhkan Redux dan menjelajahi Rx sebagai gantinya. Pola reduks adalah
mudah ekspresif di Rx.

-
Balas email ini secara langsung atau lihat di GitHub
https://github.com/rackt/redux/issues/291#issuecomment -169761410.

@ Gaearon ya, kamu benar. Dan saya menghargai bahwa Redux cukup fleksibel untuk mengakomodasi semua pendekatan kami yang berbeda!

@ halt-hammerzeit lihat jawaban saya di sini di mana saya menjelaskan mengapa redux-saga bisa lebih baik daripada redux-thunk: http://stackoverflow.com/questions/34570758/why-do-we-need-middleware-for- async-flow-in-redux / 34599594

@gaearon Ngomong-ngomong, jawaban Anda di stackoverflow memiliki kelemahan yang sama dengan dokumentasinya - hanya mencakup kasus yang paling sederhana
http://stackoverflow.com/questions/34570758/why-do-we-need-middleware-for-async-flow-in-redux/34599594#34599594
Aplikasi dunia nyata dengan cepat melampaui hanya mengambil data melalui AJAX: mereka juga harus menangani kesalahan dan melakukan beberapa tindakan bergantung pada keluaran panggilan tindakan.
Saran Anda adalah hanya dengan dispatch acara lain dan hanya itu.
Jadi bagaimana jika kode saya ingin alert() pengguna setelah tindakan selesai?
Di situlah thunk s tidak akan membantu, tetapi janji akan membantu.
Saat ini saya menulis kode saya menggunakan promise sederhana dan berfungsi seperti itu.

Saya telah membaca komentar yang berdekatan tentang redux-saga .
Tidak mengerti apa yang dilakukannya, lol.
Saya tidak terlalu menyukai hal monads dan semacamnya, dan saya masih tidak tahu apa itu thunk , dan saya tidak suka kata aneh itu sejak awal.

Ok, jadi saya tetap menggunakan Promises di komponen React.

Kami menyarankan untuk mengembalikan janji dari basa-basi di seluruh dokumen.

@gaearon Ya, itulah yang saya bicarakan: on_click() { dispatch(load_stuff()).then(show_modal('done')) }
Itu akan berhasil.

Saya pikir Anda masih melewatkan sesuatu.
Silakan lihat README of redux-thunk.
Ini menunjukkan cara merangkai pembuat aksi thunk di bawah bagian "Komposisi".

@gaearon Ya, itulah yang saya lakukan:

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

Persis seperti yang saya tulis di atas:

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

Bagian README ditulis dengan baik, thx

Tidak, ini bukan maksud saya. Lihatlah makeSandwichesForEverybody . Ini adalah pencipta aksi thunk yang memanggil pencipta aksi thunk lainnya. Inilah mengapa Anda tidak perlu memasukkan semuanya ke dalam komponen. Lihat juga async contoh di repo ini untuk lebih dari ini.

@gaearon Tapi menurut saya tidak pantas untuk memasukkan, katakanlah, kode animasi mewah saya ke pembuat tindakan, bukan?
Pertimbangkan contoh ini:

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

Bagaimana Anda akan menulis ulang dengan cara yang benar?

@ halt-hammerzeit Anda dapat membuat actionCreator mengembalikan promise sehingga komponen dapat menampilkan spinner atau apa pun dengan menggunakan status komponen lokal (tetap sulit dihindari saat menggunakan jquery)

Jika tidak, Anda dapat mengelola pengatur waktu yang kompleks untuk menggerakkan animasi dengan redux-saga.

Lihatlah entri blog ini: http://jaysoo.ca/2016/01/03/managing-processes-in-redux-using-sagas/

Oh, dalam hal ini tidak masalah untuk menyimpannya dalam komponen.
(Catatan: umumnya di React, yang terbaik adalah menggunakan model deklaratif untuk segala hal daripada menampilkan modals secara imperatif dengan jQuery. Tapi bukan masalah besar.)

@slorber Ya, saya sudah mengembalikan Janji dan hal-hal dari "terima kasih" saya (atau apa pun

@gaearon Ok, saya mengerti. Dalam dunia permesinan yang ideal, tentu saja dimungkinkan untuk tetap berada di dalam model pemrograman deklaratif, tetapi realitas memiliki tuntutannya sendiri, irasional, katakanlah, dari sudut pandang mesin. Orang-orang adalah makhluk irasional yang tidak hanya beroperasi dengan nol dan satu murni dan itu membutuhkan kompromi terhadap keindahan dan kemurnian kode demi kemampuan untuk melakukan beberapa hal yang irasional.

Saya puas dengan dukungan di utas ini. Keraguan saya sepertinya terpecahkan sekarang.

@gaearon penjelasan yang sangat luar biasa! : trophy: Terima kasih: +1:

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

dmitry-zaets picture dmitry-zaets  ·  3Komentar

vslinko picture vslinko  ·  3Komentar

jimbolla picture jimbolla  ·  3Komentar

vraa picture vraa  ·  3Komentar

CellOcean picture CellOcean  ·  3Komentar