Redux: Praktik terbaik dalam menangani aliran data untuk halaman login/pendaftaran dengan pengalihan

Dibuat pada 22 Jul 2015  ·  66Komentar  ·  Sumber: reduxjs/redux

EDIT: apa yang saya coba pahami di sini adalah bagaimana menangani aliran data ketika Anda memiliki halaman dengan formulir. Setelah Anda mengirimkan formulir, Anda mengarahkan ulang ke halaman lain atau membuat pesan kesalahan .

Tantangannya adalah di mana informasi ini harus _disimpan sementara_ dalam aliran data. Karena setelah Anda mengarahkan atau merender pesan, status (atau informasi yang Anda simpan) tidak relevan lagi dan harus diatur ulang atau dibersihkan.


Dalam aplikasi web backend Anda biasanya memiliki halaman seperti login, signup, reset password, dll.

Saat pengguna menavigasi ke aplikasi dan tidak masuk, dia akan diarahkan ke halaman masuk. Ini dapat dengan mudah dilakukan dengan kait RR onEnter .

Kemudian pengguna akan mengisi formulir dan mengirimkannya. Pada titik ini biasanya jika permintaan berhasil, Anda ingin mengarahkan ulang ke root aplikasi atau ke halaman "aman" lainnya. Jika gagal, Anda ingin tetap berada di halaman dan menampilkan pesan kesalahan.

(Ini berlaku tidak hanya untuk halaman login, tetapi halaman lain serta yang disebutkan sebelumnya. Saya hanya akan fokus pada login untuk saat ini)

Sejauh ini Anda memiliki aliran ini (yang merupakan kasus penggunaan umum):

  • pengguna pergi ke /
  • redirect ke /login
  • isi formulir dan kirim
  • jika ok redirect ke / (atau halaman lain)
  • jika gagal tetap pada formulir login dan tampilkan pesan kesalahan

Jika kami menerapkan aliran ini dengan redux, saya kira saya memiliki pengaturan berikut:

// actions
function login (form) {
  return dispatch => doLogin(form)
    .then(() => dispatch({ type: 'LOGGED_IN' }))
    .catch(() => dispatch({ type: 'LOGIN_FAILED' }))
}

// reducer
const initialState = {
  // some fields...,
  loggedIn: false,
  shouldRedirect: false,
  errorMessage: null
}
function application (state = initialState, action) {
  switch action.type {
    case 'LOGGED_IN':
      return Object.assign({}, state, { loggedIn: true, shouldRedirect: true })
    case 'LOGIN_FAILED':
      return Object.assign({}, state, { loggedIn: false, shouldRedirect: false, errorMessage: action.error.message })
  }
  return state
}

// component
@connect(state => { application: state.application })
class Login extends React.Component {
  componentWillUpdate () {
    const { router } = this.context
    const { application } = this.props
    if (application.shouldRedirect)
      router.transition(...)  
  }

  onSubmit () {
    const actions = bindActionCreators(applicationActions, dispatch)
    actions.login({...})
  }

  render () {
    const { errorMessage } = this.props
    return (
      <div>
        {errorMessage ? <p>{errorMessage}</p> : null}
        <Form {...props} onSubmit={onSubmit}>
          {...}
        </Form>
      </div>
    )
  }
}

Mengingat aliran ini benar - semoga :) - Anda dapat melihat bahwa saya memiliki 2 flag dalam status aplikasi: shouldRedirect dan errorMessage .
Bendera tersebut hanya digunakan saat mengirimkan formulir.

Pertanyaan pertama: apakah boleh memiliki flag-flag itu dalam status aplikasi?

Hal lain yang perlu dipertimbangkan adalah saya harus "mengatur ulang" flag-flag tersebut ketika saya mengarahkan ulang karena jika saya ingin pergi ke halaman lain (misalnya mendaftar, ...) Saya harus memiliki status bersih ( { shouldRedirect: false, errorMessage: null } ).

Jadi saya mungkin ingin mengirim tindakan lain seperti

// actions
function resetSubmitState () {
  return { type: 'RESET_SUBMIT' }
}

// component
componentWillUpdate () {
  const { store, router } = this.context
  const { application } = this.props
  if (application.shouldRedirect) {
    store.dispatch(applicationActions.resetSubmitState())
    router.transition(...)  
  }
}

Apakah ada yang punya masalah ini? Apakah saya melewatkan sesuatu atau apakah itu cara yang "benar" untuk melakukannya?

Apakah ada alternatif lain?

Setiap umpan balik sangat disambut! :)

discussion docs

Komentar yang paling membantu

Saya pikir pertanyaannya sedikit lebih dalam daripada hanya menggeneralisasi 'kirim formulir' – ini lebih tentang status sebenarnya dan data mana yang akan muncul di store dan mana yang tidak.

Saya suka berpikir tentang View bagian dari aliran data fluks sebagai fungsi murni render(state): DOM tetapi itu tidak sepenuhnya benar.
Mari kita ambil contoh komponen formulir sederhana yang membuat dua bidang input:

class Form extends React.Component {

  onFieldChanged (event) {
    this.setState({[event.target.name]: event.target.value})
  }

  render () {
    return (
      <form onChange={::this.onFieldChanged}>
        <input type="text" name="name" />
        <input type="text" name="surname" />
        <input type="email" name="email" />
      </form>
    )
  }
}

Selama mengetik ke dalam bidang ini beberapa modifikasi keadaan internal dilakukan sehingga kita akan memiliki sesuatu seperti ini di akhir:

{
  name: 'John'
  surname: 'Snow'
}

Jadi kami mendapatkan beberapa status yang tidak terkait dengan status aplikasi dan tidak ada yang peduli tentang itu. Ketika kami akan mengunjungi kembali formulir ini di lain waktu, formulir itu akan ditampilkan terhapus dan 'John Snow' akan hilang selamanya.
Sepertinya kami memodifikasi DOM tanpa menyentuh aliran data fluks.

Mari kita asumsikan bahwa ini adalah formulir berlangganan untuk beberapa surat pemberitahuan. Sekarang kita harus berinteraksi dengan dunia luar dan kita akan melakukannya menggunakan tindakan khusus kita.

import { subscribe } from 'actions'

class Form extends React.Component {

  onFieldChanged (event) {
    this.setState({[event.target.name]: event.target.value})
  }

  onSubmit (event) {
    event.preventDefault()
    const { name, surname, email } = this.state
    subscribe(name, surname, email)
  }

  render () {
    return (
      <form onChange={::this.onFieldChanged} onSubmit={::this.onSubmit}>
        <input type="text" name="name" />
        <input type="text" name="surname" />
        <input type="email" name="email" />
        <button type="submit">Subscribe</button>
      </form>
    )
  }
}

Ketika pengguna menekan tombol kirim, dia mengharapkan respons apa pun dari UI jika tidak berhasil dan apa yang harus dilakukan selanjutnya. Jadi kami ingin melacak status permintaan untuk merender layar pemuatan, mengarahkan ulang, atau menampilkan pesan kesalahan.
Ide pertama adalah mengirimkan beberapa tindakan internal seperti loading , submitted , failed untuk membuat status aplikasi bertanggung jawab untuk itu. Karena kami ingin membuat status dan tidak memikirkan detailnya.

  render () {
    //provided by connector of whatever
    const { props: { isLoading, error } } = this
    return (
      <form onChange={::this.onFieldChanged} onSubmit={::this.onSubmit}
        disabled={isLoading}>
        <input type="text" name="name" />
        <input type="text" name="surname" />
        <input type="email" name="email" />
        <button type="submit">Subscribe</button>
        {error ? <p>{ error }</p> : null}
      </form>
    )
  }

Tapi di sini iblis muncul: sekarang kita memiliki status halaman ini bertahan dan panggilan render(state) berikutnya akan menampilkan formulir dalam keadaan kotor, jadi kita perlu membuat tindakan cleanup untuk memanggilnya setiap kali kita memasang formulir.
Dan untuk setiap formulir yang kita miliki, kita akan menambahkan boilerplate yang sama dari tindakan loading , submitted , failed dan cleanup dengan store/reducers… sampai kita berhenti dan berpikir tentang keadaan yang sebenarnya ingin kita render.

Apakah submitted termasuk dalam status aplikasi atau hanya status komponen, seperti nilai bidang name . Apakah pengguna benar-benar peduli dengan status ini untuk dipertahankan dalam cache atau dia ingin melihat formulir bersih ketika dia mengunjunginya kembali? Jawabannya: tergantung.

Tetapi dalam kasus pengiriman formulir murni, kami dapat berasumsi bahwa status formulir tidak ada hubungannya dengan status aplikasi: kami ingin tahu faktanya apakah pengguna logged in atau tidak dalam hal halaman login, atau kami tidak ingin untuk mengetahui apa pun tentang apakah dia mengubah kata sandi atau gagal: jika permintaan berhasil, arahkan saja dia ke tempat lain.

Jadi bagaimana dengan proposisi kita tidak membutuhkan keadaan ini di tempat lain kecuali Komponen? Mari kita coba.
Untuk memberikan implementasi yang lebih mewah, kita dapat mengasumsikan bahwa pada tahun 2015 kita hidup di dunia yang tidak sinkron dan kita memiliki Promises di mana-mana, jadi biarkan tindakan kita mengembalikan Promise :

function subscribe (name, surname, email) {
  return api.request('/subscribtions', 'create', { name, surname, email })
}

Pada langkah berikutnya kita bisa mulai melacak tindakan di Component kita :

onSubmit (event) {
  event.preventDefault()
  const { name, surname, email } = this.state
  subscribe(name, surname, email)
    .then(() => { this.setState({ submitted: true }) })
    .catch(error => { this.setState({ error }) })
}

componentWillUpdate (object nextProps, object nextState) {
  if(nextState.submitted)
    redirect('somewhere')
}

Sepertinya kami memiliki semua yang kami butuhkan untuk merender komponen tanpa mencemari status aplikasi dan menghilangkan kebutuhan untuk membersihkannya.
Jadi kita hampir sampai. Sekarang kita bisa mulai menggeneralisasi aliran Component .
Pertama-tama mari kita berpisah menjadi masalah: melacak tindakan dan reaksi pembaruan status:
(Mari kita lakukan dalam hal redux untuk tetap dalam batas repo)

import React, { PropTypes } from 'react'
import { bindActionCreators } from 'redux'

// Keeps track of action
export default function connectSubmitForm (Form, submitAction) {
  return React.createClass({
    contextTypes: {
      //redux Store
      store: PropTypes.object.isRequired
    },

    getInitialState () {
      return {}
    },

    onSubmit (...args) {
      const { context: { store: { dispatch } } } = this
      const { submitAction: submit }
        = bindActionCreators({ submitAction }, dispatch)
      submit(...args)
        .then(() => this.setState({ submitted: true }))
        .catch(error => this.setState({ error }))
    },

    render () {
      const {
        onSubmit,
        props,
        state: { submitted, error }
      } = this
      return (<Form {...props} onSubmit={onSubmit} submitted={submitted}
        error={error} />)
    }
  })
}

dan

// redirect to path if predicate returns true
export default function redirect (path, predicate) {
  return Component =>
    class Composed extends React.Component {

      componentWillMount () {
        if (predicate(props))
          redirectTo(path)
      }

      componentWillReceiveProps (nextProps) {
        if (predicate(nextProps))
          redirectTo(path)
      }

      render () {
        return <Component {...this.props} />
      }
    }
}

//redirect to path if submitted
export default function redirectSubmitted (path) {
  return redirect(path, ({ submitted }) => submitted)
}

Sekarang di satu sisi kita memiliki decorator yang menyediakan submitted dan error props dengan onSubmit callback dan di sisi lain decorator yang mengarahkan ke suatu tempat jika mendapat properti submitted === true . Mari kita lampirkan mereka ke formulir kita dan lihat apa yang kita inginkan.

@redirectSubmitted('/')
class Form extends React.Component {

  onFieldChanged (event) {
    this.setState({[event.target.name]: event.target.value})
  }

  onSubmit (event) {
    event.preventDefault()
    const { name, surname, email } = this.state
    this.props.onSubmit(name, surname, email)
  }

  render () {
    return (
      <form onChange={::this.onFieldChanged} onSubmit={::this.onSubmit}>
        <input type="text" name="name" />
        <input type="text" name="surname" />
        <input type="email" name="email" />
        <button type="submit">Subscribe</button>
        {this.props.error ? <p>{ this.props.error }</p>: null}
      </form>
    )
  }
}

export default submitForm(Form, subscribe)

Dan ini dia: bentuk render murni tanpa ketergantungan routing library, implementasi flux , application state , melakukan apa yang dibuat untuk: submit dan redirect.

Terima kasih telah membaca dan maaf telah meluangkan waktu Anda.

Semua 66 komentar

Alih-alih memiliki shouldRedirect di sana, bagaimana dengan memiliki peredam sesi, yang hanya melacak isLoggedIn ? Dengan begitu Anda dapat mengalihkan dari halaman login Anda ke halaman lain mana pun dengan mencentang isLoggedIn alih-alih shouldRedirect .

Juga, mengatur ulang status dapat dilakukan hanya dengan memproses tindakan LOGGED_IN di peredam Anda. Jika tindakan itu terjadi, Anda tahu bahwa Anda dapat mengatur ulang status kesalahan dengan aman ke nilai null .

Apakah ini masuk akal? :D

Masuk akal jika hanya ada halaman login, tetapi pendekatannya sedikit lebih umum. Misalnya untuk halaman pendaftaran, atau halaman reset password. Bahkan jika Anda masuk, Anda masih dapat melihat halaman-halaman itu.

Dan flag-flag itu harus digunakan oleh semua halaman itu, karena semuanya memiliki alur yang sama.

Semoga lebih jelas sekarang :)

Ya, dari sudut pandang saya "shouldRedirect" terlalu umum untuk dikaitkan dengan tindakan tertentu. Ini mungkin membuka bug di mana Anda entah bagaimana lupa menyetel flag itu dan kemudian halaman dialihkan ke halaman lain yang sebenarnya tidak seharusnya dialihkan.

Tapi tentu, jika Anda ingin membersihkan negara Anda, Anda bisa melakukannya dengan tindakan. Atau Anda dapat mendengarkan perubahan riwayat di luar reaksi dan kemudian memicu tindakan pembersihan status. Itu mungkin lebih baik karena Anda tidak harus menyebarkan logika itu ke seluruh komponen Anda, tetapi Anda dapat menyimpannya di satu tempat.

Tapi itu hanya solusi yang mungkin untuk kliring negara. Saya belum yakin bagaimana menyelesaikan pengalihan dengan cara yang baik.

Pertanyaannya juga: apakah masuk akal untuk memiliki bendera-bendera itu di negara bagian sama sekali? Jika tidak, bagaimana aliran ini harus diterapkan?

@gaearon jika Anda punya waktu, saya juga ingin mendengar tanggapan Anda. Terima kasih! :+1:

Dalam aplikasi saya, saya mengarahkan di middleware dan saya tidak menggunakan flag apa pun. Tapi saya tidak menggunakan react-router.

// action

/**
 * Creates login action
 *
 * <strong i="6">@param</strong> {string} email
 * <strong i="7">@param</strong> {string} password
 *
 * <strong i="8">@returns</strong> {{types: *[], promise: *}}
 */
export function login(email, password) {
    return {
        types: [LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_FAILURE],
        promise: post('/login').send({ email: email, password: password }).promise()
    };
}

// auth middleware 
/**
 * Intercepts LOGIN action and redirects to login screen
 * Otherwise just sends action to next middleware
 *
 * <strong i="9">@returns</strong> {Function}
 */
function authMiddleware({getState, dispatch}) {
    return (next) => (action) => {
        if (typeof action === 'object' && action.hasOwnProperty('type')) {
            if (action.type === LOGIN_SUCCESS) {
                next(action); // send it to next so identity will be set

                // get current route
                const state = getState();
                let path = '/dashboard';

                if (typeof state['router'] === 'object' && typeof state['router']['route'] === 'object' && null !== state['router']['route']) {
                    if (state.router.route.name === 'login' && typeof state.router.route.query['to'] === 'string') {
                        path = state.router.route.query.to;
                    }
                }

                return next(actions.transitionTo(path));
            }
        }

        return next(action);
    };
}

Itu tidak di-refactored, itu dimaksudkan hanya untuk tujuan pengujian solusi router saya :).

Ini adalah salinan dan tempel dari aplikasi saya dan bagaimana saya menangani pemuatan data pengguna dan masuk. Saya akan mencoba mengomentari kode sebaik mungkin, dan saya akan berada di reactiflux untuk pertanyaan.

const AppConnector = createConnector((props$, state$, dispatch$, context$) => {
  // true when the user is logged in - drawn from Store state
  const loggedIn$ = state$
    .map(s => s.apiKey !== null)
    .distinctUntilChanged()

  // this returns an observable of a path to redirect to
  // in my app, I have a url like `/login?nextPath=dashboard`, but if that
  // isn't set, just go home
  const redirectTo$ = routeState$
    .map(s => s.router.query.nextPath || '/home')

  const redirect$ = loggedIn$
    .withLatestFrom(
      context$,
      redirectTo$,
      (loggedIn, context, path) => () => context.router.transitionTo(loggedIn ? path : '/login') // when the loggedIn state changes, i.e. user has logged in or out, respond to that change
    )
    .do(go => go())

  // It's awesome to have this here, because it means that no matter 
  // where the user loads in to the app (all child views of this view), 
  // the app will realise it needs to load data, and loads the according data 
  const userData$ = state$
    .map(getUser)

  const userDataIsEmpty$ = userData$
    .map(user => user.id === null || typeof user.id === 'undefined')
    .filter(s => s === true)

  const loadUserData$ = Rx.Observable.combineLatest(loggedIn$, userDataIsEmpty$, (logged, userDataIsEmpty) => loggedIn && userDataIsEmpty)
    // only when user is logged in but has no user data
    .filter(s => s === true)
    // fetch data with an action creator
    .withLatestFrom(dispatch$, (userData, dispatch) => () => dispatch(UserActions.loadUserData()))
    .forEach(go => go())


  return Rx.Observable.combineLatest(props$, state$, context$, redirect$, (props, state, context) => ({...props, ...state, ...context}))
}, render)

Dalam hal menjawab pertanyaan Anda @emmenko , saya pikir memiliki shouldRedirect di negara Anda tidak dapat diterima, dan harus dilakukan dengan pendekatan yang lebih fungsional. Memiliki errorMessage dalam status aplikasi baik-baik saja, pastikan itu dalam status masuk, seperti state.auth.errorMessage , daripada errorMessage tingkat atas

Saya pikir memiliki shouldRedirect di negara Anda tidak dapat diterima, dan harus dilakukan dengan pendekatan yang lebih fungsional

Saya setuju. Hal-hal yang hanya berguna sekali tidak boleh dalam keadaan IMO.

Juga, sesuai slack, saya tidak berpikir Anda harus menyimpan isLoggedIn dalam status aplikasi, karena ini lebih merupakan properti yang dihitung, yang dapat dihitung dengan pemilih sederhana seperti const isUserLoggedIn = state => state.user.id !== null

Lihat https://github.com/faassen/reselect untuk penggunaan pemilih lainnya

@frederickfogerty terima kasih untuk contohnya.

Pertanyaan: ini berfungsi dengan baik untuk login karena Anda biasanya memiliki flag loggedIn di state . Tetapi bagaimana dengan halaman lain seperti pendaftaran, pengaturan ulang kata sandi, dll?
Halaman-halaman itu berperilaku sama seperti login: redirect jika ok, jika tidak tampilkan pesan kesalahan.
Jadi bagaimana Anda menangani kasus-kasus itu? Saya berjuang untuk menemukan aliran yang benar untuk kasus-kasus itu, dan saya tidak yakin di mana negara bagian harus tinggal.

Hal-hal yang hanya berguna sekali tidak boleh dalam keadaan IMO.

@gaearon saya setuju, tapi saya masih tidak yakin apa pendekatan yang benar untuk itu. Anda masih perlu memiliki status di suatu tempat...

Ini adalah bagaimana saya melakukannya atm tanpa menyimpan apa pun di negara bagian.

// actions
function login () {
  return { type: 'LOGGED_IN' }  
}

function submitLogin (form, dispatch) {
  return api.doLogin(form)
  .then(() => {
    dispatch(login()) // can dispatch something at this point
    return Promise.resolve()
  })
}

// in the component (login, signup, ...)
onSubmit () {
  actions.submitLogin(form, dispatch) // this returns a promise
  .then(() => this.setState({ shouldRedirect: true }))
  .catch(error => this.setState({ shouldRedirect: false, errorMessage: error }))
}

Saya tidak berpikir Anda harus menyimpan isLoggedIn dalam status aplikasi, karena ini lebih merupakan properti yang dihitung

Ya, masuk akal.

@gaearon Saya kira pertanyaan untuk alur kerja umum ini (redirect atau show error) adalah apakah itu harus dilakukan dengan "redux" atau dengan cara yang berbeda. Dan apa yang akan menjadi alternatif ini?

terkait dan saya akan menyematkannya di sini ... sebenarnya saya juga berurusan dengan masalah ini dan saat ini mencoba mengimplementasikan kembali contoh berikut ke dalam Redux. Belum ada yang bisa ditampilkan karena saya melakukan ini di waktu luang saya:
https://auth0.com/blog/2015/04/09/adding-authentication-to-your-react-flux-app/

Ini adalah Fluks murni:
https://github.com/auth0/react-flux-jwt-authentication-sample

Mungkin ini berguna untuk praktik terbaik?!

@emmenko

Jadi bagaimana Anda menangani kasus-kasus itu?

Selama penggunaan Anda masuk, maka isLoggedIn (yang tidak dalam status aplikasi, tetapi minor) masih diubah, dan ini akan mengarahkan mereka. Tidak peduli dari mana perubahan status itu berasal :)

Pendekatan terbaik yang saya ambil adalah menambahkan logika ini ke konektor di mana fungsionalitas diperlukan untuk anak-anaknya misalnya untuk otorisasi, katakanlah Anda memiliki komponen AppProtected , di mana segala sesuatu yang merupakan anak dari komponen tersebut memerlukan otorisasi , lalu Anda memasukkan logika ini ke konektor komponen AppProtected.

Ini adalah bagaimana saya melakukannya atm tanpa menyimpan apa pun di negara bagian.

Saya jelas tidak menyukai pendekatan itu, yang memiliki aliran data dua arah antara UI dan AC, yang bertentangan dengan fluks. Jika Anda terikat untuk melakukannya secara imperatif, Anda dapat melakukan sesuatu seperti

componentWillReceiveProps(nextProps) {
  if (['/login', '/sign-up'].indexOf(this.props.router.path) !== -1 && this.props.isLoggedIn) {
    this.context.router.transitionTo(this.props.router.query.nextPath || '/home')
  }
}
  • Jika login pengguna berhasil, dia dialihkan dari halaman yang relevan.
  • Jika login pengguna gagal, dia tidak login, dan tetap berada di halaman yang sama (tidak terjadi apa-apa)

Ini berfungsi untuk semua halaman yang Anda sebutkan

Maaf, tapi saya masih tidak mengerti bagaimana saya menangani pengalihan/render kesalahan.

Mari kita mundur selangkah dan katakan saya memiliki halaman reset-password . Kami juga berasumsi bahwa kami tidak boleh menyentuh status apa pun dengan menyimpan semacam flag one-shot (seperti yang disebutkan sebelumnya).
Ketika saya mengklik untuk mengirimkan formulir, apa yang harus terjadi?

Inilah yang ingin saya ketahui, bagaimana seharusnya aliran itu dan di mana seharusnya ia hidup.

PS: terima kasih atas tanggapan Anda sejauh ini!

[navigates to reset-password page] -> 
[types in email and hits submit] -> 
[onSubmit fires bound AC] ->
on successful reset this continues with:
[RESET_PASSWORD_SUCCESS] -> 
[resetPassword reducer returns state {passwordReset: true}] ->
[resetPasswordComponent.componentWillReceiveProps will check if state.passwordReset is true, and then will transition to its route]
on a failed reset this continues with:
[RESET_PASSWORD_FAILURE] -> 
[resetPassword reducer returns state {passwordResetError}] -> 
[resetPasswordComponent.componentWillReceiveProps() will check if state.passwordReset is true, and since is it not, nothing will happen - in render(), the error will be displayed]

Ini sama sekali tidak seanggun untuk halaman login dan pendaftaran, karena sangat berpasangan

Saya harus tidur, saya akan menjawab pertanyaan lagi besok

Jadi kami menyimpan bendera di negara bagian lagi ;)

ps: selamat malam!

Saya pikir pertanyaannya sedikit lebih dalam daripada hanya menggeneralisasi 'kirim formulir' – ini lebih tentang status sebenarnya dan data mana yang akan muncul di store dan mana yang tidak.

Saya suka berpikir tentang View bagian dari aliran data fluks sebagai fungsi murni render(state): DOM tetapi itu tidak sepenuhnya benar.
Mari kita ambil contoh komponen formulir sederhana yang membuat dua bidang input:

class Form extends React.Component {

  onFieldChanged (event) {
    this.setState({[event.target.name]: event.target.value})
  }

  render () {
    return (
      <form onChange={::this.onFieldChanged}>
        <input type="text" name="name" />
        <input type="text" name="surname" />
        <input type="email" name="email" />
      </form>
    )
  }
}

Selama mengetik ke dalam bidang ini beberapa modifikasi keadaan internal dilakukan sehingga kita akan memiliki sesuatu seperti ini di akhir:

{
  name: 'John'
  surname: 'Snow'
}

Jadi kami mendapatkan beberapa status yang tidak terkait dengan status aplikasi dan tidak ada yang peduli tentang itu. Ketika kami akan mengunjungi kembali formulir ini di lain waktu, formulir itu akan ditampilkan terhapus dan 'John Snow' akan hilang selamanya.
Sepertinya kami memodifikasi DOM tanpa menyentuh aliran data fluks.

Mari kita asumsikan bahwa ini adalah formulir berlangganan untuk beberapa surat pemberitahuan. Sekarang kita harus berinteraksi dengan dunia luar dan kita akan melakukannya menggunakan tindakan khusus kita.

import { subscribe } from 'actions'

class Form extends React.Component {

  onFieldChanged (event) {
    this.setState({[event.target.name]: event.target.value})
  }

  onSubmit (event) {
    event.preventDefault()
    const { name, surname, email } = this.state
    subscribe(name, surname, email)
  }

  render () {
    return (
      <form onChange={::this.onFieldChanged} onSubmit={::this.onSubmit}>
        <input type="text" name="name" />
        <input type="text" name="surname" />
        <input type="email" name="email" />
        <button type="submit">Subscribe</button>
      </form>
    )
  }
}

Ketika pengguna menekan tombol kirim, dia mengharapkan respons apa pun dari UI jika tidak berhasil dan apa yang harus dilakukan selanjutnya. Jadi kami ingin melacak status permintaan untuk merender layar pemuatan, mengarahkan ulang, atau menampilkan pesan kesalahan.
Ide pertama adalah mengirimkan beberapa tindakan internal seperti loading , submitted , failed untuk membuat status aplikasi bertanggung jawab untuk itu. Karena kami ingin membuat status dan tidak memikirkan detailnya.

  render () {
    //provided by connector of whatever
    const { props: { isLoading, error } } = this
    return (
      <form onChange={::this.onFieldChanged} onSubmit={::this.onSubmit}
        disabled={isLoading}>
        <input type="text" name="name" />
        <input type="text" name="surname" />
        <input type="email" name="email" />
        <button type="submit">Subscribe</button>
        {error ? <p>{ error }</p> : null}
      </form>
    )
  }

Tapi di sini iblis muncul: sekarang kita memiliki status halaman ini bertahan dan panggilan render(state) berikutnya akan menampilkan formulir dalam keadaan kotor, jadi kita perlu membuat tindakan cleanup untuk memanggilnya setiap kali kita memasang formulir.
Dan untuk setiap formulir yang kita miliki, kita akan menambahkan boilerplate yang sama dari tindakan loading , submitted , failed dan cleanup dengan store/reducers… sampai kita berhenti dan berpikir tentang keadaan yang sebenarnya ingin kita render.

Apakah submitted termasuk dalam status aplikasi atau hanya status komponen, seperti nilai bidang name . Apakah pengguna benar-benar peduli dengan status ini untuk dipertahankan dalam cache atau dia ingin melihat formulir bersih ketika dia mengunjunginya kembali? Jawabannya: tergantung.

Tetapi dalam kasus pengiriman formulir murni, kami dapat berasumsi bahwa status formulir tidak ada hubungannya dengan status aplikasi: kami ingin tahu faktanya apakah pengguna logged in atau tidak dalam hal halaman login, atau kami tidak ingin untuk mengetahui apa pun tentang apakah dia mengubah kata sandi atau gagal: jika permintaan berhasil, arahkan saja dia ke tempat lain.

Jadi bagaimana dengan proposisi kita tidak membutuhkan keadaan ini di tempat lain kecuali Komponen? Mari kita coba.
Untuk memberikan implementasi yang lebih mewah, kita dapat mengasumsikan bahwa pada tahun 2015 kita hidup di dunia yang tidak sinkron dan kita memiliki Promises di mana-mana, jadi biarkan tindakan kita mengembalikan Promise :

function subscribe (name, surname, email) {
  return api.request('/subscribtions', 'create', { name, surname, email })
}

Pada langkah berikutnya kita bisa mulai melacak tindakan di Component kita :

onSubmit (event) {
  event.preventDefault()
  const { name, surname, email } = this.state
  subscribe(name, surname, email)
    .then(() => { this.setState({ submitted: true }) })
    .catch(error => { this.setState({ error }) })
}

componentWillUpdate (object nextProps, object nextState) {
  if(nextState.submitted)
    redirect('somewhere')
}

Sepertinya kami memiliki semua yang kami butuhkan untuk merender komponen tanpa mencemari status aplikasi dan menghilangkan kebutuhan untuk membersihkannya.
Jadi kita hampir sampai. Sekarang kita bisa mulai menggeneralisasi aliran Component .
Pertama-tama mari kita berpisah menjadi masalah: melacak tindakan dan reaksi pembaruan status:
(Mari kita lakukan dalam hal redux untuk tetap dalam batas repo)

import React, { PropTypes } from 'react'
import { bindActionCreators } from 'redux'

// Keeps track of action
export default function connectSubmitForm (Form, submitAction) {
  return React.createClass({
    contextTypes: {
      //redux Store
      store: PropTypes.object.isRequired
    },

    getInitialState () {
      return {}
    },

    onSubmit (...args) {
      const { context: { store: { dispatch } } } = this
      const { submitAction: submit }
        = bindActionCreators({ submitAction }, dispatch)
      submit(...args)
        .then(() => this.setState({ submitted: true }))
        .catch(error => this.setState({ error }))
    },

    render () {
      const {
        onSubmit,
        props,
        state: { submitted, error }
      } = this
      return (<Form {...props} onSubmit={onSubmit} submitted={submitted}
        error={error} />)
    }
  })
}

dan

// redirect to path if predicate returns true
export default function redirect (path, predicate) {
  return Component =>
    class Composed extends React.Component {

      componentWillMount () {
        if (predicate(props))
          redirectTo(path)
      }

      componentWillReceiveProps (nextProps) {
        if (predicate(nextProps))
          redirectTo(path)
      }

      render () {
        return <Component {...this.props} />
      }
    }
}

//redirect to path if submitted
export default function redirectSubmitted (path) {
  return redirect(path, ({ submitted }) => submitted)
}

Sekarang di satu sisi kita memiliki decorator yang menyediakan submitted dan error props dengan onSubmit callback dan di sisi lain decorator yang mengarahkan ke suatu tempat jika mendapat properti submitted === true . Mari kita lampirkan mereka ke formulir kita dan lihat apa yang kita inginkan.

@redirectSubmitted('/')
class Form extends React.Component {

  onFieldChanged (event) {
    this.setState({[event.target.name]: event.target.value})
  }

  onSubmit (event) {
    event.preventDefault()
    const { name, surname, email } = this.state
    this.props.onSubmit(name, surname, email)
  }

  render () {
    return (
      <form onChange={::this.onFieldChanged} onSubmit={::this.onSubmit}>
        <input type="text" name="name" />
        <input type="text" name="surname" />
        <input type="email" name="email" />
        <button type="submit">Subscribe</button>
        {this.props.error ? <p>{ this.props.error }</p>: null}
      </form>
    )
  }
}

export default submitForm(Form, subscribe)

Dan ini dia: bentuk render murni tanpa ketergantungan routing library, implementasi flux , application state , melakukan apa yang dibuat untuk: submit dan redirect.

Terima kasih telah membaca dan maaf telah meluangkan waktu Anda.

Cukup adil! Sejujurnya saya masih tertarik untuk melihat penyimpanan status komponen lokal di Redux: #159.

@stremlenye Itu cukup licin. Terima kasih sudah berbagi!

@iclanzan thx udah baca :)
Sebenarnya sekarang saya sedang memikirkan proposal @gaearon yang dia sebutkan di komentar sebelumnya. Lihatlah diskusi #159 untuk mendapatkan beberapa ide penting.

Penutup—ketika RR 1.0 keluar, kita akan membahasnya dalam resep “Penggunaan dengan React Router”.
Ini dapat dilacak di https://github.com/rackt/redux/issues/637.

@stremlenye pendekatan Anda cukup mengagumkan! Apakah masuk akal untuk - alih-alih menggunakan dekorator - menggunakan komponen reaksi-router tingkat tinggi di mana semua yang _unathorized_ akan bersarang dan itu akan bertanggung jawab untuk mengarahkan kembali setelah toko auth memiliki pengguna?

<ReduxRouter>
    <Route component={ Auth }>
      <Route path="/" component={ Login } />
      <Route path="/reset-password" component={ ResetPassword } />
    </Route>
  </ReduxRouter>

Sesuatu seperti ini, di mana Auth hanya mendeteksi jika pengguna login. Dengan cara ini, saya masih dapat memanipulasi toko saya dengan mengirimkan tindakan dari komponen Login . Tidak begitu murni, tetapi lebih sedikit kode dan lebih mudah untuk memahami :)

@tomazzaman Terima kasih :)
Sebagian besar aplikasi React yang saya tulis menyertakan sesuatu seperti ApplicationContainer yang dapat digunakan untuk tujuan yang sama tanpa implementasi Route yang baru.
Jika Anda menggunakan RR-v1.0.0-rc1 Anda dapat mencapai tujuan yang sama dengan onEnter hook : cukup periksa apakah status saat ini cocok dengan yang diautentikasi atau tidak.

Saya telah membungkus kepala saya di sekitar ini juga, dan tampaknya @stremlenye melakukan pekerjaan yang bagus untuk menunjukkan bagaimana hal itu dapat dilakukan. Masalahnya adalah bahwa seseorang cenderung terjebak dalam "semuanya harus melalui tindakan - peredam - perbarui-komponen-dengan-keadaan toko" (saya juga) dan bahwa kita kemudian cenderung mencemari toko dengan segala macam menyatakan itu hanya digunakan dalam skenario yang sangat spesifik di aplikasi (pada 1 layar, hal-hal seperti "apakah saya berhasil menjalankan kata sandi reset"), sementara ini dapat dengan mudah ditangani hanya dengan melakukan panggilan async di dalam komponen dan memperbarui komponen itu sendiri menggunakan this.setState alih-alih mencemari keadaan global + harus mengatur ulang semua vars keadaan itu jika kita akan membuka komponen itu untuk kedua kalinya nanti. Bahkan tidak menyebutkan mencemari reduksi negara dengan semua kode penanganan awal/sukses/kesalahan.

Akan mencoba mengadaptasi kode saya di sini juga untuk melihat apakah itu membersihkan semuanya.

@ir-fuel jika ada waktu, bisakah Anda memberi contoh kecil tentang apa yang akan Anda dapatkan di github?

Yah akan sulit untuk melakukan itu, tapi saya bisa memberikan contoh kecil dari apa yang saya lakukan. Saya memiliki komponen di mana pengguna dapat meminta untuk mengatur ulang kata sandinya. Operasi reset yang sebenarnya terjadi seperti ini sekarang:

  submitForgotPassword = (username) => {
    this.setState({isLoading:true})
    doForgotPassword(username).then((result) => {
      this.setState({passwordIsReset:true,isLoading:false})
    }).catch((result) => {
      this.setState({passwordIsReset:false,isLoading:false,errorMessage:result.error})
    })
  }

doForgotPassword adalah fungsi yang melakukan panggilan ajax ke server saya.

Jadi tidak ada bolak-balik ke toko/pereduksi, tidak mencemari keadaan global dengan hal-hal yang hanya Anda pedulikan di dalam satu komponen dan murni digunakan untuk sementara menunjukkan keadaan dalam komponen itu, dan tidak perlu mengatur ulang sekelompok var keadaan di dalam reduksi Anda.

dan render terlihat seperti ini

  render() {

    let divClassName = ''

    if(this.state.isLoading) {
      divClassName = 'disable-controls'
    }

    let child = null

    if(this.state.passwordIsReset)
      child = (<ForgotPasswordSuccess/>)
    else
      child = (<ForgotPasswordForm submit={this.submitForgotPassword} errorMessage={this.state.errorMessage}/>)

    return (
      <div className={divClassName}>
        {child}
      </div>
    )
  }

Saya harap ini jelas.

Terlihat bagus :+1:
Satu-satunya trik yang harus dicakup (dari sudut pandang saya) adalah menyesuaikan middleware untuk mengembalikan janji dari tindakan yang dicabut (mungkin hanya proxy nilai pengembalian tindakan mungkin). Melakukan ini, kami akan mengizinkan tindakan untuk mengubah status dalam aliran normal jika diperlukan menjaga kesempatan untuk terhubung ke barang Promise dalam lingkup komponen.

saya pikir tidak apa-apa untuk memikirkan status aplikasi Anda sebagai status yang sudah masuk. jika tidak ada pengguna yang login, mengapa Anda memerlukan status aplikasi? tentu, Anda bisa memilikinya di dalam komponen Login.

logika aplikasi di halaman Login seharusnya tidak terlalu rumit untuk memiliki status teratas aplikasi, bukan?

@SkateFreak Alur masuk adalah bagian dari aplikasi. Tidakkah Anda memikirkan status alur masuk (kemajuan, kesalahan), status alur pendaftaran, dll. yang sebaiknya Anda gunakan React juga (kemungkinan dengan Redux, jika Anda sudah menggunakannya untuk komponen aplikasi lain).

@sompylasar saya mendukung status aplikasi, saya hanya berpikir bahwa selama pengguna berada di luar aplikasi saya, saya dapat menyajikan aplikasi yang lebih sederhana tanpa toko redux. toko redux saya akan muncul segera setelah Anda MASUKKAN aplikasi saya.

pendekatan ini berhasil untuk saya sampai saat ini, tetapi mungkin saya akan memahami perlunya status aplikasi fase masuk dalam proyek masa depan saya

Anda dapat di tingkat akar toko Anda membagi toko Anda di bagian 'aplikasi' dan bagian 'pengguna/login', masing-masing dengan peredamnya sendiri. Jadi dalam hal ini Anda tidak akan pernah mencemari reduksi aplikasi Anda dengan kode peredam login dan sebaliknya.

Dari: SkateFreak < notification @github.comnotification@ github.com >
Balas-Ke: rackt/redux < [email protected] [email protected] >
Tanggal: Minggu 13 Desember 2015 13:48
Kepada: rackt/redux < [email protected] [email protected] >
Cc: Joris Mans < [email protected] [email protected] >
Subjek: Re: [redux] Praktik terbaik dalam menangani aliran data untuk halaman login/pendaftaran dengan pengalihan (#297)

@sompyl asarhttps://github.com/sompylasar saya mendukung keadaan aplikasi, saya hanya berpikir bahwa selama penggunaannya LUAR dari aplikasi saya, saya dapat melayani dia aplikasi yang lebih sederhana tanpa toko redux. toko redux saya akan muncul segera setelah Anda MASUKKAN aplikasi saya.

pendekatan ini berhasil untuk saya sampai saat ini, tetapi mungkin saya akan memahami perlunya status aplikasi fase masuk dalam proyek masa depan saya

Balas email ini secara langsung atau lihat di Gi tHubhttps://github.com/rackt/redux/issues/297#issuecomment -164287194.

perbaiki saya jika saya salah tetapi bukankah tujuan penyimpanan fluks untuk berbagi data dan status aplikasi antara rute dan komponen? mengapa saya perlu "mengurangi" status saya dan menggunakan "tindakan" saya untuk mempertahankan status teratas jika semuanya tidak masalah saat pengguna masuk?

saya tidak melihat keuntungannya, meskipun ini adalah cara yang sangat terorganisir dan bersih untuk melakukan sesuatu. saya mengelola status pra-login saya di komponen <Sign /> saya, yang berisi SignIn, SignUp, dan ForgotPassword.

Karena pendaftaran mungkin merupakan proses yang lebih rumit daripada hanya meminta nama pengguna dan kata sandi.
Itu semua tergantung pada kompleksitas aliran Anda.

Dari: SkateFreak < notification @github.comnotification@ github.com >
Balas-Ke: rackt/redux < [email protected] [email protected] >
Tanggal : Minggu 13 Desember 2015 14:00
Kepada: rackt/redux < [email protected] [email protected] >
Cc: Joris Mans < [email protected] [email protected] >
Subjek: Re: [redux] Praktik terbaik dalam menangani aliran data untuk halaman login/pendaftaran dengan pengalihan (#297)

perbaiki saya jika saya salah tetapi bukankah tujuan penyimpanan fluks untuk berbagi data dan status aplikasi antara rute dan komponen? mengapa saya perlu "mengurangi" status saya dan menggunakan "tindakan" saya untuk mempertahankan status teratas jika semuanya tidak masalah saat pengguna masuk?

saya tidak melihat keuntungannya, meskipun ini adalah cara yang sangat terorganisir dan bersih untuk melakukan sesuatu. saya mengelola status pra-login saya di komponen saya, yang berisi SignIn, SignUp, dan ForgotPassword.

Balas email ini secara langsung atau lihat di Gi tHubhttps://github.com/rackt/redux/issues/297#issuecomment -164287893.

saya setuju.
tetapi saya melihat SignUp sebagai formulir, dengan status - yang seharusnya cukup tidak peduli seberapa rumit ...

di sisi lain, apa yang saya ketahui tentang pendaftaran...
orang-orang tidak waras

Saat ini saya telah menemui beberapa masalah yang serupa tetapi berbeda.

Saya sedang mengerjakan alur seperti pendaftaran yang sangat rumit, dan kami memiliki beberapa halaman lanjutan untuk memungkinkan pengguna memasukkan info yang diperlukan, setiap halaman adalah formulir. Alirannya terlihat seperti:

FormPage_A -> FormPage_B -> FormPage_C -> SuccessPage

Masalahnya adalah kami tidak membuat permintaan API apa pun sebelum FormPage_C. Kami hanya menyimpan data di browser di FormPage_A/B dan mengirim semuanya ke API di FormPage_C.

Jadi, pertanyaannya adalah haruskah kita memasukkan data formulir FormPage_A dan FormPage_B ke dalam status aplikasi?

Jika ya, sepertinya kami menyimpan beberapa data sementara yang tidak berpengaruh pada UI ke status aplikasi. Dan karena status aplikasi redux tinggal di memori, apa yang akan terjadi ketika pengguna me-refresh halaman di FormPage_B?

Jika tidak, apakah tempat yang tepat untuk meletakkannya? Haruskah kita membangun lapisan lain seperti fake api dari aliran redux untuk menyimpannya ?

@frederickfogerty @gaearon @ir-fuel @stremlenye

Saya hanya akan meneruskan properti di this.state yang relevan dengan kiriman Anda sebagai alat peraga ke langkah berikutnya.
jadi FormPage_A.state akan diteruskan di FormPage_B.props dll dan lakukan pengiriman di FormPage_C

atau jika Anda memiliki komponen induk yang mengelola ini, simpan status di properti state kontrol induk tersebut dan setelah langkah terakhir kirimkan semua

Pastikan saja jika pengguna memutuskan untuk memuat ulang halaman yang Anda mulai dari awal lagi.

@kpaxqin Saya pikir dalam hal ini Anda dapat mendeklarasikan komponen pengelola seperti Flow yang akan menangani subset langkah dengan statusnya dan logika back-forward generik untuk masing-masing.
Jadi Anda bisa membuat langkah Anda se-stateless mungkin dan melacak status keseluruhan proses dalam melampirkan komponen.
Sesuatu seperti:

<Flow onForward={…} onBackward={}>
  <Step1 />
  <Step2 />
  <Step3 />
  …
  <Finish onSubmit={this.props.submit(this.state.model) />
</Flow>

@ir-fuel @stremlenye

Terima kasih atas saran Anda.
Tampaknya kita semua tidak ingin menempatkan hal-hal sementara itu ke dalam status aplikasi.

Tetapi ketika pengguna menyegarkan halaman di setengah alur

Pastikan saja jika pengguna memutuskan untuk memuat ulang halaman yang Anda mulai dari awal lagi.

Tampaknya bukan pengalaman pengguna yang baik, kami adalah situs seluler (itulah salah satu alasan mengapa kami membagi formulir besar menjadi halaman kecil), sulit untuk memasukkan sesuatu di seluler, dan saya yakin pengguna akan menjadi gila jika mereka kehilangan semua hal-hal setelah menekan tombol refresh, mungkin mereka akan menyerah begitu saja.

Saya pikir mungkin membangun fake api dari aliran redux adalah pilihan yang dapat diterima, mengirimkan sesuatu ke fake api pada setiap langkah dan mengambilnya saat diperlukan seperti kami memiliki api untuk itu, dan karena kami menggunakan itu seperti api, satu-satunya yang terhubung dengannya adalah pembuat tindakan, halaman dan komponen akan bersih dan juga mudah untuk menangani kesalahan (gunakan kode umum untuk menangani kesalahan api).

Masuk akal bagi saya karena begitu kami ingin mempertahankan data tersebut, seharusnya tidak dalam status komponen atau status aplikasi, efek sampingnya harus ditangani di pembuat tindakan

//fake api
const fakeApi = {
  getStepA: ()=>{
    /* get things from LS */
    return Promise
  },
  setStepA: ()=>{
    /* put things into LS */
    return Promise
  }
}

// action creator
submitA(dispatch, getState) { // thunk function
  fakeApi
    .setStepA()
    .then(dispatchSuccess)
    .then(transitionToStepB)
    .catch(dispatchError);
}

Itu membuat segalanya menjadi jelas bagi saya tetapi tampaknya aneh untuk menambahkan sesuatu dari aliran redux kami ke dalam aplikasi kami.

Saya benar-benar tidak akan peduli tentang pengguna yang memuat ulang halaman Anda.
Di sisi lain, Anda memberi tahu kami bahwa Anda memiliki proses pendaftaran yang rumit dengan beberapa formulir, dan kemudian Anda mengatakan itu adalah situs seluler. Tidakkah menurut Anda itu juga akan membuat pengguna marah dan membuat mereka menyerah jika mereka harus mengisi terlalu banyak hal di perangkat seluler? Saya pikir itu bahkan lebih buruk daripada tidak mendukung reload. Saya rasa tidak banyak situs yang mendukung pemuatan ulang halaman di tengah proses pendaftaran.

Saya pikir itu hanya membuat banyak hal menjadi lebih rumit untuk sesuatu yang sebenarnya tidak sering terjadi. Saya akan lebih fokus untuk mencoba menyederhanakan proses pendaftaran, atau membaginya dalam beberapa langkah di situs web Anda sehingga pengguna tidak memiliki kesan bahwa dia sedang mengisi X halaman formulir sebelum akhirnya mengaktifkan akunnya.

Dan jika Anda benar-benar ingin mendukung pemuatan ulang pendaftaran pertengahan, minta saja komponen induk yang mengontrol semua langkah ini menyimpan statusnya di localStorage .

@kpaxqin saya akan menggunakan pendekatan "API palsu". Mengapa BTW palsu, hanya API penyimpanan perantara yang Anda butuhkan untuk proses multi-langkah Anda. Tidak setiap API harus berada di server dan diakses melalui HTTP atau protokol jaringan lainnya.

Apa yang saya setujui dengan @ir-fuel adalah bahwa semua status proses multi-langkah harus disimpan, bukan hanya langkah saat ini.

@ir-fuel Reload dapat terjadi tanpa maksud pengguna yang eksplisit, misalnya, pengguna dapat beralih ke aplikasi seluler lain, seperti notebook, atau panggilan telepon, dan browser dapat kehilangan statusnya karena memori perangkat tidak mencukupi, sehingga akan memuat ulang ketika pengguna kembali.

Masalahnya lagi adalah Anda mencemari status aplikasi global dengan sesuatu yang hanya Anda perlukan di beberapa layar di aplikasi web Anda.

@sompylasar @kpaxqin Saya pikir @ir-fuel menyarankan pendekatan yang tepat – jika Anda memerlukan sesuatu perantara untuk akhirnya bertahan, pertahankan di sisi klien: jauh lebih mudah untuk diterapkan, tidak benar-benar melibatkan server ke dalam mempertahankan data yang tidak konsisten, bukan' t melakukan permintaan http apa pun (yang juga merupakan masalah utama pada perangkat seluler) dan sangat mudah untuk mengubah pendekatan di masa mendatang.

Dan tentang menyimpan data apa pun di suatu tempat – pikirkan bagaimana Anda akan membersihkannya setelah proses gagal atau sudah selesai – itulah tema awal sebuah topik.
Opsi lain yang dapat Anda gunakan – menyimpan data pengguna di url. Jika formulir Anda terdiri dari beberapa bidang teks yang bisa menjadi solusi tradeoff yang baik.

Ini bukan "menyimpan sisi klien atau server", ini lebih merupakan masalah "apakah saya menangani 'ad hoc' ini di kelas komponen induk saya" vs "apakah saya melakukan seluruh tindakan/peredam/tarian negara".
Saya tidak suka melalui tindakan/pereduksi jika itu untuk keadaan sementara. Saya hanya melakukan ini untuk status aplikasi nyata.

@ir-fuel :+1:

@stremlenye Silakan baca jawaban saya lagi. Saya katakan secara eksplisit bahwa Anda tidak perlu membuat API sisi server untuk membuat lapisan API persistensi.

@ir-fuel Tidak melalui tindakan/peredam membatalkan prediktabilitas dan replayability transisi status.

@ir-fuel Anda dapat dengan mudah menghapus status aplikasi Anda setelah formulir dikirimkan, sehingga tidak "tercemar".

RE: membersihkan. Buat tindakan CLEAR dan memiliki tombol di UI Anda untuk mengatur ulang alur pendaftaran. Anda juga dapat melakukannya melalui router (mengirimkan tindakan itu saat mengunjungi URL pendaftaran awal, dan jika pendaftaran telah dimulai, ubah URL ke yang lain).

@sompylasar tidak ada masalah untuk mengatur ulang status Anda dengan tombol eksplisit - masalah terjadi setelah kegagalan yang tidak ditangani: sulit untuk memprediksi cara status dapat rusak dan menentukan strategi pemulihan untuk setiap kasus tertentu.

@stremlenye ya, rekayasa perangkat lunak terkadang sulit. Status dan transisi yang dapat diprediksi membantu mengurangi hal ini.

Tidak hanya itu, tetapi jika status Anda lebih dari sekelompok objek json (yang saya benci karena semuanya berdasarkan konvensi dan tidak ada yang dipaksakan) dan Anda menggunakan sesuatu di baris Record Immutable.js maka Anda perlu mendeklarasikan properti objek Anda di muka dan itu mencakup semua barang sementara yang Anda inginkan di sana.

Bagi saya membangun sesuatu dengan redux tanpa menggunakan Immutable.js hanyalah sebuah kecelakaan yang menunggu untuk terjadi.

Saya tahu bahwa Anda dapat membuat Catatan Signup dan hanya membuatnya tidak nol saat mendaftar, tetapi masalahnya lebih mendasar, karena Anda dapat memiliki banyak kasus di aplikasi web Anda, dan itulah yang mengganggu Aku.

@ir-fuel

Tidak hanya itu, tetapi jika status Anda lebih dari sekumpulan objek json (yang saya benci karena semuanya berdasarkan konvensi dan tidak ada yang dipaksakan) dan Anda menggunakan sesuatu di baris Immutable.js' Record maka Anda perlu mendeklarasikan properti objek Anda di muka dan itu termasuk semua barang sementara yang Anda inginkan di sana.

Bagi saya membangun sesuatu dengan redux tanpa menggunakan Immutable.js hanyalah sebuah kecelakaan yang menunggu untuk terjadi.

Untuk saat ini, saya tidak yakin menggunakan Immutable.js dengan redux, tampaknya terlalu rumit untuk diintegrasikan (dan tidak berkinerja). Pembekuan dalam selama pengembangan dan pengujian, dan serangkaian pengujian harus cukup untuk mencakup sebagian besar kasus. Dan konvensi ketat untuk menghindari keadaan bermutasi di reduksi atau tempat lain di mana Anda memiliki referensi.

Saya tahu bahwa Anda dapat membuat Catatan Pendaftaran dan hanya membuatnya tidak nol saat mendaftar

Ya, itulah yang ada dalam pikiran saya.

tetapi masalahnya lebih mendasar, karena Anda bisa memiliki banyak kasus di aplikasi web Anda, dan itulah yang mengganggu saya.

Itulah yang kami sebut status aplikasi eksplisit -- setiap saat kami mengetahui status aplikasi yang terdiri dari banyak komponen. Jika Anda ingin membuat struktur status kurang eksplisit dan lebih dinamis, ikuti https://github.com/tonyhb/redux-ui

Itulah seluruh masalah dengan pengembangan JS. "Mari kita tetap berpegang pada konvensi". Dan itulah mengapa orang mulai menyadari bahwa itu tidak bekerja di lingkungan yang kompleks, karena orang membuat kesalahan, dan datang dengan solusi seperti TypeScript dan Immutable.js.

Mengapa terlalu rumit untuk diintegrasikan? Jika Anda menggunakan 'Rekam', Anda menggunakan semua objek itu (selama Anda hanya membacanya) sebagai objek JS biasa, dengan notasi titik untuk mengakses properti, dengan keuntungan Anda mendeklarasikan di depan properti mana yang merupakan bagian dari objek Anda .

Saya melakukan semua pengembangan redux saya dengan Immutable.js, tanpa masalah apa pun. Fakta bahwa tidak dapat secara tidak sengaja memodifikasi suatu objek adalah keuntungan besar dalam redux dan 'keamanan tipe' dalam mendefinisikan prototipe objek Anda juga nyaman.

Dan jika Anda benar-benar ingin mendukung pemuatan ulang pendaftaran pertengahan, cukup minta komponen induk yang mengontrol semua langkah ini menyimpan statusnya di penyimpanan lokal.

@ir-fuel
Bagi saya memasukkan sesuatu ke localStorage sepertinya efek samping dan sepertinya bukan praktik yang baik untuk melakukannya di component.

Masalahnya lagi adalah Anda mencemari status aplikasi global dengan sesuatu yang hanya Anda perlukan di beberapa layar di aplikasi web Anda.

Mungkin saya belum menjelaskan API palsu dengan cukup jelas, alasan terpenting untuk membuatnya adalah saya tidak ingin memasukkan only need in a few screens ke status aplikasi redux. Di FormPage_A/B mereka hanya memanggil API untuk menyimpan data formulir lalu lanjutkan langkah berikutnya:

//action
submitFormA (formA) {
  fakeApi
    .saveFormA(formA)
    .then(()=>{ transitionTo(nextPage) })
    .catch()
}

dan di FormPage_C ( bentuk akhir ):

submitFormC (formC) {
  fakeApi
    .getFormAnB()
    .then(mergeFormData(formC)) //mergeFormData is a curry function
    .then(realApi.submitSignUp)
    .catch()
}

Semua data sementara itu hidup dalam api palsu dan status aplikasi global belum tercemar. Jika kami ingin menghapus data dalam api palsu untuk memulai kembali aliran, cukup kirim tindakan untuk melakukannya.

@sompylasar tidak ada masalah untuk mengatur ulang status Anda dengan tombol eksplisit - masalah terjadi setelah kegagalan yang tidak ditangani: sulit untuk memprediksi cara status dapat rusak dan menentukan strategi pemulihan untuk setiap kasus tertentu.

@stremlenye Saya setuju dengan itu, Tapi saya pikir kasus kegagalan khusus itu lebih cenderung menjadi masalah bisnis, mereka selalu ada di sini di mana pun kita menempatkan negara bagian, dan mereka harus ditangani dengan cara yang benar tetapi tidak dijelaskan dengan cukup jelas di saat ini. Satu-satunya hal yang dapat dilakukan pengembang adalah membuatnya mudah dilacak sehingga kami dapat memperbaikinya dengan lebih sedikit rasa sakit.

Dan menempatkan state di komponen induk mungkin membuat lebih sulit untuk memikirkan kapan semuanya menjadi rumit. Dengan api palsu, kami mengubah status itu dengan tindakan dan tampaknya lebih dapat diprediksi.

Tetapi saya setuju bahwa API palsu terlihat terlalu berat dalam banyak kasus dengan lebih sedikit kerumitan.

Tidak melalui semua pesan di atas. Di sini saya hanya memberikan pendekatan saya: mendeteksi perubahan prop di componentWillReceiveProps.

Katakan bahwa bentuk statusnya adalah: { loginPending, loginError }, saat memulai login, setel loginPending = true. Saat berhasil atau gagal, setel { loginPending: false, loginError: 'some error.' }.

Kemudian saya dapat mendeteksi apakah acara berhasil masuk dalam suatu komponen dan mengarahkan ulang halaman:

componentWillReceiveProps(nextProps) {
  if (this.props.loginPending && !nextProps.loginPending && !nextProps.loginError) {
    // Login success, redirect the page here.
  }
}

Itu hanya bekerja meskipun tidak indah.

Implementasi sederhana saya tentang membersihkan formulir pada pengalihan menggunakan react-router-redux . Peredam:

const initialState = {
    login: initialLoginState,
    signup: initialSignupState,
    // ...
};


export default function(state = initialState, action) {
  switch (action.type) {
    case '@@router/LOCATION_CHANGE':
      return {
        ...state,
        login: initialLoginState,
        signup: initialSignupState
      };
      // ...

Apakah menurut Anda pendekatan ini baik-baik saja? Berhasil.

Aku berakhir dengan ini. Ceritakan apa yang Anda pikirkan:

mendaftar tindakan redux:

import { checkStatus } from 'helpers/fetch_helpers';
import { AUTH } from 'config/constants.endpoints';
import { USER_LOGGED_IN, USER_LOGGED_OUT, USER_REGISTERED } from 'config/constants.actions';
import { replace } from 'react-router-redux';

// other actions...

const userRegistered = (user) => ({
  type: USER_REGISTERED,
  user
});

export const register = (formData, redirectTo = '/') => {
  const fetchParams = {
    method: 'post',
    body: formData
  };

  const handleData = (dispatch) => (response) => response.json().then( (data) => {
    dispatch(userRegistered(data));
    dispatch(replace(redirectTo));
  });

  return (dispatch) => fetch(AUTH, fetchParams)
    .then(checkStatus)
    .then(handleData(dispatch));
};

wadah formulir pendaftaran:

import React, { Component } from 'react';
import RegisterForm from './register_form';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as userActions from 'store/actions/user';
import { parseFormErrors } from 'helpers/fetch_helpers';

class RegisterFormContainer extends Component {
  state = {
    errors: {}
  }

  async handleSubmit(e) {
    e.preventDefault();

    const form = e.currentTarget;
    const formData = new FormData(form);
    const { register } = this.props.actions;

    try {
      await register(formData);
    } catch (error) {
      const errors = await parseFormErrors(error);

      this.setState({ errors });
    }
  }

  render() {
    return (
      <RegisterForm handleSubmit={ ::this.handleSubmit } errors={ this.state.errors } />
    );
  }
}

const mapDispatchToProps = (dispatch, ownProps) => ({
  actions: bindActionCreators({ register: userActions.register }, dispatch)
});

export default connect(
  null,
  mapDispatchToProps
)(RegisterFormContainer);

dan pembantu:

export const checkStatus = (response) => {
  if (response.ok) {
    return response;
  } else {
    const error = new Error(response.statusText);

    error.response = response;
    throw error;
  }
};

export const parseFormErrors = (error) => error.response.json().then( (data) => {
  if (data.errors) {
    const joinedErrors = _.mapValues(data.errors, (errors) => errors.join(' ') );

    return { ...joinedErrors };
  } else {
    console.error('request failed:', error);

    return {};
  }
});

Yang penting - hanya pendaftaran yang berhasil yang masuk ke toko Redux sebagai tindakan, jika tidak, kesalahan diteruskan ke status komponen penampung. Pengguna mendapatkan kesalahan, lalu menavigasi ke halaman lain, lalu kembali ke halaman formulir - formulir kosong dan tidak ada kesalahan. Tidak ada tindakan "USER_REGISTRATION_REQUEST" atau "USER_REGISTRATION_FAILURE" karena tindakan tersebut menggambarkan status komponen, bukan status aplikasi.

@sunstorymvp

Anda telah mempermanis kode Anda dengan async / await , dan gula ini menyembunyikan fakta bahwa ada tiga status permintaan (sebelum, sedang berlangsung, setelah). Status ini disembunyikan di komponen tampilan. Permintaan mulai diabaikan, dan kesalahan permintaan ditangani dalam tampilan dan tidak dikirim melalui redux, jadi status aplikasi tidak berfungsi di sini. Jika karena alasan tertentu komponen yang berisi status ini di-unmount dan kemudian di-remount, status permintaan register akan hilang dan hasilnya tidak akan tertangani, dan ini dapat menyebabkan kesalahan berikutnya (misalnya pengguna telah terdaftar).

Pengguna mendapatkan kesalahan, lalu menavigasi ke halaman lain, lalu kembali ke halaman formulir - formulir kosong dan tidak ada kesalahan.

Untuk mencapai itu, Anda dapat mengirimkan tindakan seperti USER_REGISTRATION_RESET pada pemasangan komponen yang akan mengatur ulang kesalahan di toko. Tindakan ini hanya boleh ditangani jika tidak ada permintaan pendaftaran yang tertunda, jika tidak, hasil permintaan yang tertunda tidak akan ditangani oleh aplikasi.

terima kasih untuk umpan balik.

Anda telah mempermanis kode Anda dengan async/menunggu, dan gula ini menyembunyikan fakta bahwa ada tiga status permintaan (sebelum, sedang berlangsung, setelah)

kondisi ini ada terlepas dari apakah saya menggunakan async/menunggu atau tidak.

Status ini disembunyikan di komponen tampilan

sebagaimana dimaksud, itu adalah status komponen.. mengapa ini harus dalam status aplikasi?

Permintaan mulai diabaikan

sedang berlangsung...

dan kesalahan permintaan ditangani dalam tampilan dan tidak dikirim melalui redux

seperti yang dimaksudkan

Jika karena alasan tertentu komponen yang berisi status ini di-unmount dan kemudian di-remount, status permintaan register akan hilang dan hasilnya tidak akan tertangani

itu tidak benar. pengguna akan didaftarkan dan dialihkan jika permintaan berhasil, terlepas dari status komponen atau bahkan keberadaan. lihat pembuat tindakan register (handleData).

Untuk mencapai itu, Anda dapat mengirimkan tindakan ...

Itulah intinya. Tindakan lain? Mengapa begitu banyak boilerplate? Nonaktifkan atau aktifkan tombol kirim, tampilkan kesalahan formulir atau validasi langsung, tampilkan pemintal saat permintaan terbuka.. dan tindakan ini mengarah ke tindakan baru yang memulihkan status default.. mengapa ini harus dalam status aplikasi?

itu tidak benar. pengguna akan didaftarkan dan dialihkan jika permintaan berhasil, terlepas dari status komponen atau bahkan keberadaan. lihat pembuat tindakan daftar (handleData).

Ya, tetapi ini menyisakan kemungkinan dua permintaan register berikutnya karena status permintaan tidak dilacak dalam status aplikasi.

Nonaktifkan atau aktifkan tombol kirim, tampilkan kesalahan formulir atau validasi langsung, tampilkan pemintal saat permintaan terbuka.. dan tindakan ini mengarah ke tindakan baru yang memulihkan status default.. mengapa ini harus dalam status aplikasi?

Untuk prediktabilitas dan replayability, ini adalah tujuan dari Redux. Hanya memiliki tindakan dan status awal, Anda bisa mendapatkan aplikasi ke status tertentu hanya dengan mengirimkan tindakan. Jika komponen mulai berisi beberapa status, status aplikasi mulai pecah, tidak ada pohon status tunggal yang mewakili status aplikasi.

Namun, jangan terlalu dogmatis tentang hal itu. Ada banyak alasan untuk memasukkan barang ke status Redux, dan banyak alasan untuk membiarkan sesuatu dalam status komponen lokal, tergantung pada skenario Anda. Lihat http://redux.js.org/docs/FAQ.html#organizing -state-only-redux-state dan https://news.ycombinator.com/item?id=11890229.

Ok, sekarang jelas bagi saya: itu tergantung. Terima kasih!

Saya menggunakan componentWillReceiveProps dan memiliki objek auth pada komponen saya. Tidak yakin apakah ini membantu sama sekali.

// using reduxform
class LoginForm extends Component {
  static propTypes = {
    // this holds info on the current user.
    auth: PropTypes.shape({
      userId: PropTypes.number,
      token: PropTypes.string
    }),
     // react-router-redux.
    history: PropTypes.object.isRequired,
    submitting: PropTypes.bool.isRequired,
    fields: PropTypes.object.isRequired
  };
  componentWillReceiveProps(newProps) {
    const { auth } = newProps;
    auth && auth.token && this.props.history.go('#/');
  }
  render() {
    return (/**...*/) // form logic here.
  }
}

Apakah ada yang salah dengan melakukannya dengan cara ini?

Bagaimana dengan pendekatan seperti itu http://codereview.stackexchange.com/questions/138296/implementing-redirect-in-redux-middleware?
Meneruskan url redirect ke tindakan dan memanggil metode next ekstra dengan push dari react-router-redux di middleware.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat