Redux: ๋ฆฌ๋””๋ ‰์…˜์ด ์žˆ๋Š” ๋กœ๊ทธ์ธ/๊ฐ€์ž… ํŽ˜์ด์ง€์˜ ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ ๋ชจ๋ฒ” ์‚ฌ๋ก€

์— ๋งŒ๋“  2015๋…„ 07์›” 22์ผ  ยท  66์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: reduxjs/redux

ํŽธ์ง‘: ์—ฌ๊ธฐ์„œ ๋‚ด๊ฐ€ ์ดํ•ดํ•˜๋ ค๊ณ  ํ•˜๋Š” ๊ฒƒ์€ ์–‘์‹์ด ์žˆ๋Š” ํŽ˜์ด์ง€๊ฐ€ ์žˆ์„ ๋•Œ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ์–‘์‹์„ ์ œ์ถœํ•œ ํ›„ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜ ํ•˜๊ฑฐ๋‚˜ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค .

๋ฌธ์ œ๋Š” ์ด ์ •๋ณด๊ฐ€ ๋ฐ์ดํ„ฐ ํ๋ฆ„์—์„œ _์ž„์‹œ ์ €์žฅ_๋˜์–ด์•ผ ํ•˜๋Š” ์œ„์น˜์ž…๋‹ˆ๋‹ค. ๋ฉ”์‹œ์ง€๋ฅผ ๋ฆฌ๋””๋ ‰์…˜ํ•˜๊ฑฐ๋‚˜ ๋ Œ๋”๋งํ•œ ํ›„์—๋Š” ์ƒํƒœ(๋˜๋Š” ์ €์žฅํ•œ ์ •๋ณด)๊ฐ€ ๋” ์ด์ƒ ๊ด€๋ จ์ด ์—†์œผ๋ฏ€๋กœ ์žฌ์„ค์ •ํ•˜๊ฑฐ๋‚˜ ์ •๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.


๋ฐฑ์—”๋“œ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋กœ๊ทธ์ธ, ๊ฐ€์ž…, ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ๋“ฑ๊ณผ ๊ฐ™์€ ํŽ˜์ด์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž๊ฐ€ ์•ฑ์œผ๋กœ ์ด๋™ํ•˜๊ณ  ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜๋ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ RR onEnter ํ›„ํฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‰ฝ๊ฒŒ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ ๋‹ค์Œ ์‚ฌ์šฉ์ž๊ฐ€ ์–‘์‹์„ ์ž‘์„ฑํ•˜๊ณ  ์ œ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด ์‹œ์ ์—์„œ ์ผ๋ฐ˜์ ์œผ๋กœ ์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ๋ฃจํŠธ ๋˜๋Š” ๋‹ค๋ฅธ "๋ณด์•ˆ" ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์‹คํŒจํ•œ ๊ฒฝ์šฐ ํŽ˜์ด์ง€์— ๋จธ๋ฌผ๋ฉด์„œ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

(์ด๊ฒƒ์€ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. ์ง€๊ธˆ์€ ๋กœ๊ทธ์ธ์—๋งŒ ์ง‘์ค‘ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.)

์ง€๊ธˆ๊นŒ์ง€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ๋ฆ„์ด ์žˆ์Šต๋‹ˆ๋‹ค(์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€).

  • ์‚ฌ์šฉ์ž๊ฐ€ / ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.
  • /login ๋กœ ๋ฆฌ๋””๋ ‰์…˜
  • ์–‘์‹์„ ์ž‘์„ฑํ•˜๊ณ  ์ œ์ถœ
  • ๊ดœ์ฐฎ์œผ๋ฉด / (๋˜๋Š” ๋‹ค๋ฅธ ํŽ˜์ด์ง€)๋กœ ๋ฆฌ๋””๋ ‰์…˜
  • ์‹คํŒจํ•œ ๊ฒฝ์šฐ ๋กœ๊ทธ์ธ ์–‘์‹์— ๋จธ๋ฌผ๊ณ  ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ

์ด ํ๋ฆ„์„ redux๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ๋‹ค์Œ ์„ค์ •์ด ์žˆ๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

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

์ด ํ๋ฆ„์ด ์ •ํ™•ํ•˜๊ธฐ๋ฅผ ๋ฐ”๋ผ๋ฉด์„œ :) - ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ์— shouldRedirect ๋ฐ errorMessage 2๊ฐœ์˜ ํ”Œ๋ž˜๊ทธ๊ฐ€ ์žˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด๋Ÿฌํ•œ ํ”Œ๋ž˜๊ทธ๋Š” ์–‘์‹์„ ์ œ์ถœํ•  ๋•Œ๋งŒ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

์ฒซ ๋ฒˆ์งธ ์งˆ๋ฌธ: ํ•ด๋‹น ํ”Œ๋ž˜๊ทธ๋ฅผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ๋กœ ์œ ์ง€ํ•ด๋„ ๋ฉ๋‹ˆ๊นŒ?

๊ณ ๋ คํ•ด์•ผ ํ•  ๋˜ ๋‹ค๋ฅธ ์‚ฌํ•ญ์€ ๋ฆฌ๋””๋ ‰์…˜ํ•  ๋•Œ ํ•ด๋‹น ํ”Œ๋ž˜๊ทธ๋ฅผ "์žฌ์„ค์ •"ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๋‹ค๋ฅธ ํŽ˜์ด์ง€(์˜ˆ: ๊ฐ€์ž…, ...)๋กœ ์ด๋™ํ•˜๋ ค๋ฉด ๊นจ๋—ํ•œ ์ƒํƒœ( { shouldRedirect: false, errorMessage: null } )์—ฌ์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‹ค๋ฅธ ์ž‘์—…์„ ์ „๋‹ฌํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

๋ˆ„๊ตฌ๋“ ์ง€์ด ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ? ๋‚ด๊ฐ€ ๋ญ”๊ฐ€๋ฅผ ๋†“์น˜๊ณ  ์žˆ๊ฑฐ๋‚˜ ๊ทธ๊ฒƒ์ด "์˜ฌ๋ฐ”๋ฅธ"๋ฐฉ๋ฒ•์ž…๋‹ˆ๊นŒ?

๋‹ค๋ฅธ ๋Œ€์•ˆ์ด ์žˆ์Šต๋‹ˆ๊นŒ?

์–ด๋–ค ํ”ผ๋“œ๋ฐฑ์ด๋“  ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค! :)

discussion docs

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

์งˆ๋ฌธ์ด '์–‘์‹ ์ œ์ถœ'์„ ์ผ๋ฐ˜ํ™”ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ์กฐ๊ธˆ ๋” ๊นŠ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ์ƒํƒœ๊ฐ€ ๋ฌด์—‡์ด๋ฉฐ ์–ด๋–ค ๋ฐ์ดํ„ฐ๊ฐ€ store ์— ๋‚˜ํƒ€๋‚˜์•ผ ํ•˜๊ณ  ์–ด๋–ค ๋ฐ์ดํ„ฐ๊ฐ€ ํ‘œ์‹œ๋˜์ง€ ์•Š์•„์•ผ ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‚˜๋Š” ํ”Œ๋Ÿญ์Šค ๋ฐ์ดํ„ฐ ํ๋ฆ„์˜ View ๋ถ€๋ถ„์„ ์ˆœ์ˆ˜ ํ•จ์ˆ˜ render(state): DOM ์ƒ๊ฐํ•˜๊ณ  ์‹ถ์ง€๋งŒ ์™„์ „ํžˆ ์‚ฌ์‹ค์€ ์•„๋‹™๋‹ˆ๋‹ค.
๋‘ ๊ฐœ์˜ ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์–‘์‹ ๊ตฌ์„ฑ ์š”์†Œ์˜ ์˜ˆ๋ฅผ ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

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

์ด ํ•„๋“œ์— ์ž…๋ ฅํ•˜๋Š” ๋™์•ˆ ์ผ๋ถ€ ๋‚ด๋ถ€ ์ƒํƒœ ์ˆ˜์ •์ด ์ˆ˜ํ–‰๋˜์–ด ๋งˆ์ง€๋ง‰์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฉ๋‹ˆ๋‹ค.

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

๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ์™€ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์€ ์ƒํƒœ๋ฅผ ์–ป์—ˆ๊ณ  ์•„๋ฌด๋„ ๊ทธ๊ฒƒ์— ๋Œ€ํ•ด ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์— ์ด ์–‘์‹์„ ๋‹ค์‹œ ๋ฐฉ๋ฌธํ•˜๋ฉด ์ง€์›Œ์ง„ ๊ฒƒ์œผ๋กœ ํ‘œ์‹œ๋˜๊ณ  'John Snow'๋Š” ์˜์›ํžˆ ์‚ฌ๋ผ์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
ํ”Œ๋Ÿญ์Šค ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๊ณ  DOM์„ ์ˆ˜์ •ํ•œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์ด ์–ด๋–ค ํ†ต์ง€์„œ์— ๋Œ€ํ•œ ๊ตฌ๋… ์–‘์‹์ด๋ผ๊ณ  ๊ฐ€์ •ํ•ฉ์‹œ๋‹ค. ์ด์ œ ์šฐ๋ฆฌ๋Š” ์™ธ๋ถ€ ์„ธ๊ณ„์™€ ์ƒํ˜ธ ์ž‘์šฉํ•ด์•ผ ํ•˜๋ฉฐ ํŠน์ˆ˜ ์ž‘์—…์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

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

์‚ฌ์šฉ์ž๊ฐ€ ์ œ์ถœ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด UI์˜ ์‘๋‹ต์ด ์„ฑ๊ณตํ•˜์ง€ ๋ชปํ–ˆ๊ณ  ๋‹ค์Œ์— ๋ฌด์—‡์„ ํ•ด์•ผ ํ•˜๋Š”์ง€ ์˜ˆ์ƒํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋กœ๋”ฉ ํ™”๋ฉด์„ ๋ Œ๋”๋งํ•˜๊ฑฐ๋‚˜ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฆฌ๋””๋ ‰์…˜ํ•˜๊ฑฐ๋‚˜ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด ์š”์ฒญ ์ƒํƒœ๋ฅผ ์ถ”์ ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.
์ฒซ ๋ฒˆ์งธ ์•„์ด๋””์–ด๋Š” loading , submitted , failed ์™€ ๊ฐ™์€ ์ผ๋ถ€ ๋‚ด๋ถ€ ์ž‘์—…์„ ์ „๋‹ฌํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ๊ฐ€ ์ด์— ๋Œ€ํ•œ ์ฑ…์ž„์„ ์ง€๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์ƒํƒœ๋ฅผ ๋ Œ๋”๋งํ•˜๊ณ  ์„ธ๋ถ€ ์‚ฌํ•ญ์— ๋Œ€ํ•ด ์ƒ๊ฐํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

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

๊ทธ๋Ÿฌ๋‚˜ ์—ฌ๊ธฐ์— ์•…๋งˆ๊ฐ€ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. ์ด์ œ ์ด ํŽ˜์ด์ง€ ์ƒํƒœ๊ฐ€ ์œ ์ง€๋˜๊ณ  ๋‹ค์Œ render(state) ํ˜ธ์ถœ์€ ๋”ํ‹ฐ ์ƒํƒœ๋กœ ํผ์„ ํ‘œ์‹œํ•˜๋ฏ€๋กœ ํผ์„ ๋งˆ์šดํŠธํ•  ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•ด cleanup ์•ก์…˜์„ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ๊ฐ ์–‘์‹์— ๋Œ€ํ•ด loading , submitted , failed ๋ฐ cleanup ์ž‘์—…๊ณผ store/reducersโ€ฆ ์šฐ๋ฆฌ๊ฐ€ ์‹ค์ œ๋กœ ๋ Œ๋”๋งํ•˜๋ ค๋Š” ์ƒํƒœ์— ๋Œ€ํ•ด.

submitted ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ์— ์†ํ•ฉ๋‹ˆ๊นŒ ์•„๋‹ˆ๋ฉด name ํ•„๋“œ ๊ฐ’๊ณผ ๊ฐ™์€ ๊ตฌ์„ฑ ์š”์†Œ ์ƒํƒœ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์ด ์ƒํƒœ๋ฅผ ์บ์‹œ์— ์œ ์ง€ํ•˜๋Š” ๋ฐ ์ •๋ง๋กœ ๊ด€์‹ฌ์ด ์žˆ์Šต๋‹ˆ๊นŒ? ์•„๋‹ˆ๋ฉด ๋‹ค์‹œ ๋ฐฉ๋ฌธํ•  ๋•Œ ๊นจ๋—ํ•œ ํ˜•์‹์„ ๋ณด๊ณ  ์‹ถ์–ดํ•ฉ๋‹ˆ๊นŒ? ๋‹ต: ์˜์กดํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์ˆœ์ˆ˜ํ•œ ์ œ์ถœ ์–‘์‹์˜ ๊ฒฝ์šฐ ์–‘์‹ ์ƒํƒœ๊ฐ€ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ์ƒํƒœ์™€ ์•„๋ฌด ๊ด€๋ จ์ด ์—†๋‹ค๊ณ  ๊ฐ€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค logged in ๊ทธ๊ฐ€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ณ€๊ฒฝํ–ˆ๊ฑฐ๋‚˜ ์‹คํŒจํ–ˆ๋Š”์ง€์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์–ป์œผ๋ ค๋ฉด ์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด ๊ทธ๋ฅผ ๋‹ค๋ฅธ ๊ณณ์œผ๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•˜์‹ญ์‹œ์˜ค.

๊ทธ๋ ‡๋‹ค๋ฉด Component ์ด์™ธ์˜ ๋‹ค๋ฅธ ๊ณณ์—์„œ๋Š” ์ด ์ƒํƒœ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค๋Š” ์ œ์•ˆ์€ ์–ด๋–ป์Šต๋‹ˆ๊นŒ? ์‹œ๋„ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
๋” ๋ฉ‹์ง„ ๊ตฌํ˜„์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด ์šฐ๋ฆฌ๋Š” 2015๋…„์— ์šฐ๋ฆฌ๊ฐ€ ๋น„๋™๊ธฐ์‹ ์„ธ๊ณ„์— ์‚ด๊ณ  ์žˆ๊ณ  ์–ด๋””์—๋‚˜ Promises ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์šฐ๋ฆฌ์˜ ์ž‘์—…์ด Promise ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

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

๋‹ค์Œ ๋‹จ๊ณ„์—์„œ๋Š” Component ์—์„œ ์ž‘์—…์„ ์ถ”์ ํ•˜๊ธฐ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ๋ฅผ ์˜ค์—ผ์‹œํ‚ค์ง€ ์•Š๊ณ  ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๋ชจ๋“  ๊ฒƒ์„ ๊ฐ–์ถ”๊ณ  ์žˆ์œผ๋ฉฐ ์ฒญ์†Œํ•  ํ•„์š”๊ฐ€ ์—†๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.
๊ฑฐ์˜ ๋‹ค ์™”์Šต๋‹ˆ๋‹ค. ์ด์ œ Component ํ๋ฆ„์„ ์ผ๋ฐ˜ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์šฐ์„  ์ž‘์—… ๋ฐ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ๋ฐ˜์‘์„ ์ถ”์ ํ•˜๋Š” ๋ฌธ์ œ๋กœ ๋ถ„ํ• ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
(repo ๋ฒ”์œ„์— ๋จธ๋ฌผ๊ธฐ ์œ„ํ•ด redux ์˜ ๊ด€์ ์—์„œ ๊ทธ๊ฒƒ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค)

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

๊ทธ๋ฆฌ๊ณ 

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

์ด์ œ decorator onSubmit submitted ๋ฐ error ์†Œํ’ˆ์„ ์ œ๊ณตํ•˜๋Š” decorator์™€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ ์–ด๋”˜๊ฐ€๋กœ ๋ฆฌ๋””๋ ‰์…˜๋˜๋Š” decorator ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. submitted === true ์†์„ฑ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ๋“ค์„ ์šฐ๋ฆฌ์˜ ์–‘์‹์— ์ฒจ๋ถ€ํ•˜๊ณ  ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๊ฒƒ์„ ๋ด…์‹œ๋‹ค.

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

routing ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, flux ๊ตฌํ˜„, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ state ์˜ ์ข…์†์„ฑ์ด ์—†๋Š” ์ˆœ์ˆ˜ ๋ Œ๋”๋ง ํ˜•์‹, ์ œ์ถœ ๋ฐ ๋ฆฌ๋””๋ ‰์…˜์„ ์œ„ํ•ด ์ƒ์„ฑ๋œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•˜๊ณ  ์‹œ๊ฐ„ ๋‚ด์„œ ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋“  66 ๋Œ“๊ธ€

shouldRedirect ๋Œ€์‹  isLoggedIn ๋งŒ ์ถ”์ ํ•˜๋Š” ์„ธ์…˜ ๊ฐ์†๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์–ด๋–ป์Šต๋‹ˆ๊นŒ? ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด shouldRedirect isLoggedIn ๋ฅผ ์„ ํƒํ•˜์—ฌ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์—์„œ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ์ƒํƒœ ์žฌ์„ค์ •์€ ๊ฐ์†๊ธฐ์—์„œ LOGGED_IN ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜์—ฌ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ์ž‘์—…์ด ๋ฐœ์ƒํ•˜๋ฉด ์˜ค๋ฅ˜ ์ƒํƒœ๋ฅผ null ๊ฐ’์œผ๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ์žฌ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๊ฒŒ ๋ง์ด ๋˜์š”? :๋””

๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋งŒ ์žˆ์œผ๋ฉด ์ดํ•ด๊ฐ€ ๋˜์ง€๋งŒ ์ข€ ๋” ์ผ๋ฐ˜์ ์ธ ์ ‘๊ทผ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๊ฐ€์ž… ํŽ˜์ด์ง€ ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค. ๋กœ๊ทธ์ธํ•œ ๊ฒฝ์šฐ์—๋„ ํ•ด๋‹น ํŽ˜์ด์ง€๋ฅผ ๊ณ„์† ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น ํ”Œ๋ž˜๊ทธ๋Š” ๋ชจ๋‘ ๋™์ผํ•œ ํ๋ฆ„์„ ๊ฐ€์ง€๋ฏ€๋กœ ๋ชจ๋“  ํŽ˜์ด์ง€์—์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด์ œ ๋” ๋ช…ํ™•ํ•ด์ง€๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค. :)

์˜ˆ, ๋‚ด ๊ด€์ ์—์„œ "shouldRedirect"๋Š” ํŠน์ • ์ž‘์—…๊ณผ ์—ฐ๊ฒฐํ•˜๊ธฐ์—๋Š” ๋„ˆ๋ฌด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค. ์–ด๋–ป๊ฒŒ๋“  ํ•ด๋‹น ํ”Œ๋ž˜๊ทธ๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์„ ์žŠ์€ ๋‹ค์Œ ํŽ˜์ด์ง€๊ฐ€ ์‹ค์ œ๋กœ ๋ฆฌ๋””๋ ‰์…˜๋˜์–ด์„œ๋Š” ์•ˆ ๋˜๋Š” ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜๋˜๋Š” ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ํ™•์‹คํžˆ, ์ƒํƒœ๋ฅผ ์ •๋ฆฌํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์•ก์…˜์œผ๋กœ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜๋Š” ๋ฐ˜์‘ ์™ธ๋ถ€์˜ ๊ธฐ๋ก ๋ณ€๊ฒฝ์„ ์ˆ˜์‹ ํ•œ ๋‹ค์Œ ์ƒํƒœ ์ง€์šฐ๊ธฐ ์ž‘์—…์„ ํŠธ๋ฆฌ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ตฌ์„ฑ ์š”์†Œ ์ „์ฒด์— ํ•ด๋‹น ๋…ผ๋ฆฌ๋ฅผ ๋ถ„์‚ฐํ•  ํ•„์š”๊ฐ€ ์—†์ง€๋งŒ ํ•œ ๊ณณ์—์„œ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ”๋žŒ์งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ๊ทธ๊ฒƒ์€ ๊ตญ๊ฐ€ ์ฒญ์‚ฐ์„ ์œ„ํ•œ ๊ฐ€๋Šฅํ•œ ํ•ด๊ฒฐ์ฑ…์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ์ข‹์€ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฆฌ๋””๋ ‰์…˜์„ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•„์ง ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋ฌธ์ œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ํ”Œ๋ž˜๊ทธ๋ฅผ ํ•ด๋‹น ์ฃผ์— ๋ณด์œ ํ•˜๋Š” ๊ฒƒ์ด ์˜๋ฏธ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ? ๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด ์ด ํ๋ฆ„์„ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๊นŒ?

@gaearon ์‹œ๊ฐ„๋˜์‹œ๋ฉด ํ”ผ๋“œ๋ฐฑ๋„ ๋“ฃ๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. ๊ฐ์‚ฌ ํ•ด์š”! :+1:

๋‚ด ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์—์„œ ๋ฏธ๋“ค์›จ์–ด๋กœ ๋ฆฌ๋””๋ ‰์…˜ ์ค‘์ด๋ฉฐ ํ”Œ๋ž˜๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋‚˜๋Š” 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);
    };
}

๋ฆฌํŒฉํ† ๋ง๋˜์ง€ ์•Š์•˜์œผ๋ฉฐ ๋‚ด ๋ผ์šฐํ„ฐ ์†”๋ฃจ์…˜์˜ ํ…Œ์ŠคํŠธ ๋ชฉ์ ์œผ๋กœ๋งŒ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. :)

์ด๊ฒƒ์€ ๋‚ด ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์—์„œ ๋ณต์‚ฌํ•˜์—ฌ ๋ถ™์—ฌ๋„ฃ๊ณ  ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ ๋กœ๋“œ ๋ฐ ๋กœ๊ทธ์ธ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ๊ฐ€๋Šฅํ•œ ํ•œ ์ตœ์„ ์„ ๋‹คํ•ด ์ฝ”๋“œ์— ์ฃผ์„์„ ๋‹ฌ๊ณ  ์งˆ๋ฌธ์— ๋Œ€ํ•ด reactiflux์— ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

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)

๊ท€ํ•˜์˜ ์งˆ๋ฌธ @emmenko ์— ๋Œ€ํ•œ ๋‹ต๋ณ€๊ณผ ๊ด€๋ จํ•˜์—ฌ ๊ท€ํ•˜์˜ ์ฃผ์— shouldRedirect ๊ฐ€ ์žˆ๋Š” ๊ฒƒ์€ ํ—ˆ์šฉ๋˜์ง€ ์•Š์œผ๋ฉฐ ๋ณด๋‹ค ๊ธฐ๋Šฅ์ ์ธ ์ ‘๊ทผ ๋ฐฉ์‹์œผ๋กœ ์ด๋ฃจ์–ด์ ธ์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. errorMessage ๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ์— ์žˆ์œผ๋ฉด ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. ์ตœ์ƒ์œ„ errorMessage ๊ฐ€ ์•„๋‹Œ state.auth.errorMessage ์™€ ๊ฐ™์€ ๋กœ๊ทธ์ธ ์ƒํƒœ์ธ์ง€ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค.

๊ท€ํ•˜์˜ ์ƒํƒœ์—์„œ shouldRedirect๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ํ—ˆ์šฉ๋˜์ง€ ์•Š์œผ๋ฉฐ ๋ณด๋‹ค ๊ธฐ๋Šฅ์ ์ธ ์ ‘๊ทผ ๋ฐฉ์‹์œผ๋กœ ์ˆ˜ํ–‰๋˜์–ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

๋™์˜ํ•œ๋‹ค. ํ•œ ๋ฒˆ๋งŒ ์œ ์šฉํ•œ ๊ฒƒ์€ IMO ์ƒํƒœ์— ์žˆ์–ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค.

๋˜ํ•œ slack์— ๋”ฐ๋ผ isLoggedIn๋ฅผ const isUserLoggedIn = state => state.user.id !== null ์™€ ๊ฐ™์€ ๊ฐ„๋‹จํ•œ ์„ ํƒ๊ธฐ๋กœ ๊ณ„์‚ฐํ•  ์ˆ˜ ์žˆ๋Š” ๊ณ„์‚ฐ๋œ ์†์„ฑ์— ๋” ๊ฐ€๊น๊ธฐ ๋•Œ๋ฌธ์— isLoggedIn ๋ฅผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ์— ์ €์žฅํ•ด์„œ๋Š” ์•ˆ ๋œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

์„ ํƒ๊ธฐ ์‚ฌ์šฉ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ https://github.com/faassen/reselect ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

@frederickfogerty ์˜ˆ๋ฅผ ๋“ค์–ด ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์งˆ๋ฌธ: ์ด๊ฒƒ์€ ์ผ๋ฐ˜์ ์œผ๋กœ state ์— loggedIn ํ”Œ๋ž˜๊ทธ๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋กœ๊ทธ์ธ์— ์ž˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๊ฐ€์ž…, ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ๋“ฑ๊ณผ ๊ฐ™์€ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋Š” ์–ด๋–ป์Šต๋‹ˆ๊นŒ?
ํ•ด๋‹น ํŽ˜์ด์ง€๋Š” ๋กœ๊ทธ์ธ๊ณผ ๋™์ผํ•˜๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์ •์ƒ์ด๋ฉด ๋ฆฌ๋””๋ ‰์…˜ํ•˜๊ณ , ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋ ‡๋‹ค๋ฉด ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ๋ฅผ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๊นŒ? ์ €๋Š” ๊ทธ๋Ÿฌํ•œ ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ์˜ฌ๋ฐ”๋ฅธ ํ๋ฆ„์„ ์ฐพ๊ธฐ ์œ„ํ•ด ๊ณ ๊ตฐ๋ถ„ํˆฌํ•˜๊ณ  ์žˆ์œผ๋ฉฐ ๊ตญ๊ฐ€๊ฐ€ ์–ด๋””์— ์žˆ์–ด์•ผ ํ•˜๋Š”์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค.

ํ•œ ๋ฒˆ๋งŒ ์œ ์šฉํ•œ ๊ฒƒ์€ IMO ์ƒํƒœ์— ์žˆ์–ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค.

@gaearon ์ €๋„ ๋™์˜ํ•˜์ง€๋งŒ, ์ด์— ๋Œ€ํ•œ ์˜ฌ๋ฐ”๋ฅธ ์ ‘๊ทผ ๋ฐฉ์‹์ด ๋ฌด์—‡์ธ์ง€ ์•„์ง ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋‹น์‹ ์€ ์—ฌ์ „ํžˆ โ€‹โ€‹์–ด๋”˜๊ฐ€์— ์ƒํƒœ๊ฐ€ ์žˆ์–ด์•ผํ•ฉ๋‹ˆ๋‹ค ...

์ด๊ฒƒ์ด ๋‚ด๊ฐ€ ์ƒํƒœ์— ์•„๋ฌด๊ฒƒ๋„ ์ €์žฅํ•˜์ง€ ์•Š๊ณ  atm์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

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

isLoggedIn์„ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ์ƒํƒœ์— ์ €์žฅํ•˜๋ฉด ์•ˆ ๋œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ๋Š” ๊ณ„์‚ฐ๋œ ์†์„ฑ์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๋„ค, ์ดํ•ด๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

@gaearon ์ด ์ผ๋ฐ˜์ ์ธ ์ž‘์—… ํ๋ฆ„(๋ฆฌ๋””๋ ‰์…˜ ๋˜๋Š” ์˜ค๋ฅ˜ ํ‘œ์‹œ)์— ๋Œ€ํ•œ ์งˆ๋ฌธ์€ "redux"๋กœ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•˜๋Š”์ง€ ์•„๋‹ˆ๋ฉด ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•˜๋Š”์ง€์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ๋Œ€์•ˆ์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

๊ด€๋ จ์ด ์žˆ๊ณ  ์—ฌ๊ธฐ์— ๊ณ ์ •ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค... ์‹ค์ œ๋กœ ์ €๋„ ์ด ๋ฌธ์ œ๋ฅผ ๋‹ค๋ฃจ๊ณ  ์žˆ์—ˆ๊ณ  ํ˜„์žฌ ๋‹ค์Œ ์˜ˆ์ œ๋ฅผ Redux์— ๋‹ค์‹œ ๊ตฌํ˜„ํ•˜๋ ค๊ณ  ํ–ˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ฐ€ ์‹œ๊ฐ„์— ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์•„์ง ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ๋Š” ํ•ญ๋ชฉ์ด ์—†์Šต๋‹ˆ๋‹ค.
https://auth0.com/blog/2015/04/09/adding-authentication-to-your-react-flux-app/

์ˆœ์ˆ˜ํ•œ Flux์ž…๋‹ˆ๋‹ค.
https://github.com/auth0/react-flux-jwt-authentication-sample

๋ชจ๋ฒ” ์‚ฌ๋ก€์— ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

@emmenko

๊ทธ๋ ‡๋‹ค๋ฉด ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ๋ฅผ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๊นŒ?

์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•˜๋Š” ๋™์•ˆ isLoggedIn (์•ฑ ์ƒํƒœ๋Š” ์•„๋‹ˆ์ง€๋งŒ ์‚ฌ์†Œํ•œ)๊ฐ€ ์—ฌ์ „ํžˆ ๋ณ€๊ฒฝ๋˜๊ณ  ๋ฆฌ๋””๋ ‰์…˜๋ฉ๋‹ˆ๋‹ค. ๊ทธ ์ƒํƒœ ๋ณ€๊ฒฝ์ด ์–ด๋””์—์„œ ์™”๋Š”์ง€ ์ƒ๊ด€ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. :)

๋‚ด๊ฐ€ ์ทจํ•œ ๊ฐ€์žฅ ์ข‹์€ ์ ‘๊ทผ ๋ฐฉ์‹์€ ๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ์œ„ํ•ด ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•œ ์ปค๋„ฅํ„ฐ์— ์ด ๋…ผ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด AppProtected ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. , ๊ทธ๋Ÿฐ ๋‹ค์Œ ์ด ๋…ผ๋ฆฌ๋ฅผ AppProtected ๊ตฌ์„ฑ ์š”์†Œ์˜ ์ปค๋„ฅํ„ฐ์— ๋„ฃ์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์ด ๋‚ด๊ฐ€ ์ƒํƒœ์— ์•„๋ฌด๊ฒƒ๋„ ์ €์žฅํ•˜์ง€ ์•Š๊ณ  atm์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

๋‚˜๋Š” UI์™€ AC ์‚ฌ์ด์— ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ํ๋ฆ„์ด ์žˆ๋Š” ๊ทธ ์ ‘๊ทผ ๋ฐฉ์‹์ด ๋งˆ์Œ์— ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ช…๋ น์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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')
  }
}
  • ์‚ฌ์šฉ์ž์˜ ๋กœ๊ทธ์ธ์ด ์„ฑ๊ณตํ•˜๋ฉด ๊ด€๋ จ ํŽ˜์ด์ง€์—์„œ ๋ฆฌ๋””๋ ‰์…˜๋ฉ๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž์˜ ๋กœ๊ทธ์ธ์ด ์‹คํŒจํ•˜๋ฉด ๋กœ๊ทธ์ธ๋˜์ง€ ์•Š๊ณ  ๊ฐ™์€ ํŽ˜์ด์ง€์— ์œ ์ง€๋ฉ๋‹ˆ๋‹ค(์•„๋ฌด ์ผ๋„ ์ผ์–ด๋‚˜์ง€ ์•Š์Œ)

์ด๊ฒƒ์€ ๋‹น์‹ ์ด ์–ธ๊ธ‰ํ•œ ๋ชจ๋“  ํŽ˜์ด์ง€์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

์ฃ„์†กํ•˜์ง€๋งŒ ์—ฌ์ „ํžˆ ๋ฆฌ๋””๋ ‰์…˜/์˜ค๋ฅ˜ ๋ Œ๋”๋ง์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค.

ํ•œ ๊ฑธ์Œ ๋’ค๋กœ ๋ฌผ๋Ÿฌ๋‚˜์„œ ๋‚ด๊ฐ€ reset-password ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๋˜ํ•œ ์ผ์ข…์˜ ์›์ƒท ํ”Œ๋ž˜๊ทธ๋ฅผ ์ €์žฅํ•˜์—ฌ ์ƒํƒœ๋ฅผ ๊ฑด๋“œ๋ ค์„œ๋Š” ์•ˆ ๋œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค(์•ž์„œ ์–ธ๊ธ‰ํ•œ ๋Œ€๋กœ).
์–‘์‹ ์ œ์ถœ์„ ํด๋ฆญํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ฉ๋‹ˆ๊นŒ?

์ด๊ฒƒ์ด ๋‚ด๊ฐ€ ์•Œ๊ณ  ์‹ถ์€ ๊ฒƒ, ํ๋ฆ„์ด ์–ด๋–ป๊ฒŒ ๋˜์–ด์•ผ ํ•˜๊ณ  ์–ด๋””์— ์žˆ์–ด์•ผ ํ•˜๋Š”์ง€์ž…๋‹ˆ๋‹ค.

PS: ์ง€๊ธˆ๊นŒ์ง€ ํ”ผ๋“œ๋ฐฑ ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

[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]

์ด๊ฒƒ์€ ๋งค์šฐ ๊ฒฐํ•ฉ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋กœ๊ทธ์ธ ๋ฐ ๊ฐ€์ž… ํŽ˜์ด์ง€๋งŒํผ ์šฐ์•„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋‚˜ ์ž์•ผ๊ฒ ์–ด, ๋‚ด์ผ ์งˆ๋ฌธ ๋” ๋Œ€๋‹ตํ• ๊ฒŒ

๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ํ”Œ๋ž˜๊ทธ๋ฅผ ์ƒํƒœ์— ๋‹ค์‹œ ์ €์žฅํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค ;)

์ถ”์‹ : ์ข‹์€ ๋ฐค!

์งˆ๋ฌธ์ด '์–‘์‹ ์ œ์ถœ'์„ ์ผ๋ฐ˜ํ™”ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ์กฐ๊ธˆ ๋” ๊นŠ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ์ƒํƒœ๊ฐ€ ๋ฌด์—‡์ด๋ฉฐ ์–ด๋–ค ๋ฐ์ดํ„ฐ๊ฐ€ store ์— ๋‚˜ํƒ€๋‚˜์•ผ ํ•˜๊ณ  ์–ด๋–ค ๋ฐ์ดํ„ฐ๊ฐ€ ํ‘œ์‹œ๋˜์ง€ ์•Š์•„์•ผ ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‚˜๋Š” ํ”Œ๋Ÿญ์Šค ๋ฐ์ดํ„ฐ ํ๋ฆ„์˜ View ๋ถ€๋ถ„์„ ์ˆœ์ˆ˜ ํ•จ์ˆ˜ render(state): DOM ์ƒ๊ฐํ•˜๊ณ  ์‹ถ์ง€๋งŒ ์™„์ „ํžˆ ์‚ฌ์‹ค์€ ์•„๋‹™๋‹ˆ๋‹ค.
๋‘ ๊ฐœ์˜ ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์–‘์‹ ๊ตฌ์„ฑ ์š”์†Œ์˜ ์˜ˆ๋ฅผ ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

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

์ด ํ•„๋“œ์— ์ž…๋ ฅํ•˜๋Š” ๋™์•ˆ ์ผ๋ถ€ ๋‚ด๋ถ€ ์ƒํƒœ ์ˆ˜์ •์ด ์ˆ˜ํ–‰๋˜์–ด ๋งˆ์ง€๋ง‰์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฉ๋‹ˆ๋‹ค.

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

๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ์™€ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์€ ์ƒํƒœ๋ฅผ ์–ป์—ˆ๊ณ  ์•„๋ฌด๋„ ๊ทธ๊ฒƒ์— ๋Œ€ํ•ด ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์— ์ด ์–‘์‹์„ ๋‹ค์‹œ ๋ฐฉ๋ฌธํ•˜๋ฉด ์ง€์›Œ์ง„ ๊ฒƒ์œผ๋กœ ํ‘œ์‹œ๋˜๊ณ  'John Snow'๋Š” ์˜์›ํžˆ ์‚ฌ๋ผ์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
ํ”Œ๋Ÿญ์Šค ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๊ณ  DOM์„ ์ˆ˜์ •ํ•œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์ด ์–ด๋–ค ํ†ต์ง€์„œ์— ๋Œ€ํ•œ ๊ตฌ๋… ์–‘์‹์ด๋ผ๊ณ  ๊ฐ€์ •ํ•ฉ์‹œ๋‹ค. ์ด์ œ ์šฐ๋ฆฌ๋Š” ์™ธ๋ถ€ ์„ธ๊ณ„์™€ ์ƒํ˜ธ ์ž‘์šฉํ•ด์•ผ ํ•˜๋ฉฐ ํŠน์ˆ˜ ์ž‘์—…์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

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

์‚ฌ์šฉ์ž๊ฐ€ ์ œ์ถœ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด UI์˜ ์‘๋‹ต์ด ์„ฑ๊ณตํ•˜์ง€ ๋ชปํ–ˆ๊ณ  ๋‹ค์Œ์— ๋ฌด์—‡์„ ํ•ด์•ผ ํ•˜๋Š”์ง€ ์˜ˆ์ƒํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋กœ๋”ฉ ํ™”๋ฉด์„ ๋ Œ๋”๋งํ•˜๊ฑฐ๋‚˜ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฆฌ๋””๋ ‰์…˜ํ•˜๊ฑฐ๋‚˜ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด ์š”์ฒญ ์ƒํƒœ๋ฅผ ์ถ”์ ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.
์ฒซ ๋ฒˆ์งธ ์•„์ด๋””์–ด๋Š” loading , submitted , failed ์™€ ๊ฐ™์€ ์ผ๋ถ€ ๋‚ด๋ถ€ ์ž‘์—…์„ ์ „๋‹ฌํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ๊ฐ€ ์ด์— ๋Œ€ํ•œ ์ฑ…์ž„์„ ์ง€๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์ƒํƒœ๋ฅผ ๋ Œ๋”๋งํ•˜๊ณ  ์„ธ๋ถ€ ์‚ฌํ•ญ์— ๋Œ€ํ•ด ์ƒ๊ฐํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

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

๊ทธ๋Ÿฌ๋‚˜ ์—ฌ๊ธฐ์— ์•…๋งˆ๊ฐ€ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. ์ด์ œ ์ด ํŽ˜์ด์ง€ ์ƒํƒœ๊ฐ€ ์œ ์ง€๋˜๊ณ  ๋‹ค์Œ render(state) ํ˜ธ์ถœ์€ ๋”ํ‹ฐ ์ƒํƒœ๋กœ ํผ์„ ํ‘œ์‹œํ•˜๋ฏ€๋กœ ํผ์„ ๋งˆ์šดํŠธํ•  ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•ด cleanup ์•ก์…˜์„ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ๊ฐ ์–‘์‹์— ๋Œ€ํ•ด loading , submitted , failed ๋ฐ cleanup ์ž‘์—…๊ณผ store/reducersโ€ฆ ์šฐ๋ฆฌ๊ฐ€ ์‹ค์ œ๋กœ ๋ Œ๋”๋งํ•˜๋ ค๋Š” ์ƒํƒœ์— ๋Œ€ํ•ด.

submitted ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ์— ์†ํ•ฉ๋‹ˆ๊นŒ ์•„๋‹ˆ๋ฉด name ํ•„๋“œ ๊ฐ’๊ณผ ๊ฐ™์€ ๊ตฌ์„ฑ ์š”์†Œ ์ƒํƒœ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์ด ์ƒํƒœ๋ฅผ ์บ์‹œ์— ์œ ์ง€ํ•˜๋Š” ๋ฐ ์ •๋ง๋กœ ๊ด€์‹ฌ์ด ์žˆ์Šต๋‹ˆ๊นŒ? ์•„๋‹ˆ๋ฉด ๋‹ค์‹œ ๋ฐฉ๋ฌธํ•  ๋•Œ ๊นจ๋—ํ•œ ํ˜•์‹์„ ๋ณด๊ณ  ์‹ถ์–ดํ•ฉ๋‹ˆ๊นŒ? ๋‹ต: ์˜์กดํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์ˆœ์ˆ˜ํ•œ ์ œ์ถœ ์–‘์‹์˜ ๊ฒฝ์šฐ ์–‘์‹ ์ƒํƒœ๊ฐ€ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ์ƒํƒœ์™€ ์•„๋ฌด ๊ด€๋ จ์ด ์—†๋‹ค๊ณ  ๊ฐ€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค logged in ๊ทธ๊ฐ€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ณ€๊ฒฝํ–ˆ๊ฑฐ๋‚˜ ์‹คํŒจํ–ˆ๋Š”์ง€์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์–ป์œผ๋ ค๋ฉด ์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด ๊ทธ๋ฅผ ๋‹ค๋ฅธ ๊ณณ์œผ๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•˜์‹ญ์‹œ์˜ค.

๊ทธ๋ ‡๋‹ค๋ฉด Component ์ด์™ธ์˜ ๋‹ค๋ฅธ ๊ณณ์—์„œ๋Š” ์ด ์ƒํƒœ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค๋Š” ์ œ์•ˆ์€ ์–ด๋–ป์Šต๋‹ˆ๊นŒ? ์‹œ๋„ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
๋” ๋ฉ‹์ง„ ๊ตฌํ˜„์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด ์šฐ๋ฆฌ๋Š” 2015๋…„์— ์šฐ๋ฆฌ๊ฐ€ ๋น„๋™๊ธฐ์‹ ์„ธ๊ณ„์— ์‚ด๊ณ  ์žˆ๊ณ  ์–ด๋””์—๋‚˜ Promises ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์šฐ๋ฆฌ์˜ ์ž‘์—…์ด Promise ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

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

๋‹ค์Œ ๋‹จ๊ณ„์—์„œ๋Š” Component ์—์„œ ์ž‘์—…์„ ์ถ”์ ํ•˜๊ธฐ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ๋ฅผ ์˜ค์—ผ์‹œํ‚ค์ง€ ์•Š๊ณ  ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๋ชจ๋“  ๊ฒƒ์„ ๊ฐ–์ถ”๊ณ  ์žˆ์œผ๋ฉฐ ์ฒญ์†Œํ•  ํ•„์š”๊ฐ€ ์—†๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.
๊ฑฐ์˜ ๋‹ค ์™”์Šต๋‹ˆ๋‹ค. ์ด์ œ Component ํ๋ฆ„์„ ์ผ๋ฐ˜ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์šฐ์„  ์ž‘์—… ๋ฐ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ๋ฐ˜์‘์„ ์ถ”์ ํ•˜๋Š” ๋ฌธ์ œ๋กœ ๋ถ„ํ• ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
(repo ๋ฒ”์œ„์— ๋จธ๋ฌผ๊ธฐ ์œ„ํ•ด redux ์˜ ๊ด€์ ์—์„œ ๊ทธ๊ฒƒ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค)

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

๊ทธ๋ฆฌ๊ณ 

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

์ด์ œ decorator onSubmit submitted ๋ฐ error ์†Œํ’ˆ์„ ์ œ๊ณตํ•˜๋Š” decorator์™€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ ์–ด๋”˜๊ฐ€๋กœ ๋ฆฌ๋””๋ ‰์…˜๋˜๋Š” decorator ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. submitted === true ์†์„ฑ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ๋“ค์„ ์šฐ๋ฆฌ์˜ ์–‘์‹์— ์ฒจ๋ถ€ํ•˜๊ณ  ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๊ฒƒ์„ ๋ด…์‹œ๋‹ค.

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

routing ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, flux ๊ตฌํ˜„, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ state ์˜ ์ข…์†์„ฑ์ด ์—†๋Š” ์ˆœ์ˆ˜ ๋ Œ๋”๋ง ํ˜•์‹, ์ œ์ถœ ๋ฐ ๋ฆฌ๋””๋ ‰์…˜์„ ์œ„ํ•ด ์ƒ์„ฑ๋œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•˜๊ณ  ์‹œ๊ฐ„ ๋‚ด์„œ ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿด ์ˆ˜ ์žˆ์ง€! ์†”์งํžˆ ๋งํ•ด์„œ ์ €๋Š” ์—ฌ์ „ํžˆ Redux: #159์— ๋กœ์ปฌ ๊ตฌ์„ฑ ์š”์†Œ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐ ๊ด€์‹ฌ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

@stremlenye ๊ฝค ๋งค๋„๋Ÿฝ์Šต๋‹ˆ๋‹ค. ๊ณต์œ ํ•ด ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

@iclanzan thx ์ฝ๊ธฐ์šฉ :)
์‚ฌ์‹ค ์ง€๊ธˆ์€ ์ด์ „ ๋Œ“๊ธ€์—์„œ ์–ธ๊ธ‰ํ•œ @gaearon ์ œ์•ˆ์— ๋Œ€ํ•ด ์ƒ๊ฐํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ช‡ ๊ฐ€์ง€ ์ค‘์š”ํ•œ ์•„์ด๋””์–ด๋ฅผ ์–ป์œผ๋ ค๋ฉด #159 ํ† ๋ก ์„ ์‚ดํŽด๋ณด์‹ญ์‹œ์˜ค.

๋งˆ๋ฌด๋ฆฌ - RR 1.0์ด ๋‚˜์˜ค๋ฉด "Usage with React Router" ๋ ˆ์‹œํ”ผ์—์„œ ๋‹ค๋ฃฐ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ด๋Š” https://github.com/rackt/redux/issues/637์—์„œ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@stremlenye ๋‹น์‹ ์˜ ์ ‘๊ทผ ๋ฐฉ์‹์€ ๊ฝค ๊ต‰์žฅํ•ฉ๋‹ˆ๋‹ค! ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹  ๋ชจ๋“  _unathorized_ ํ•ญ๋ชฉ์ด ์ค‘์ฒฉ๋˜๊ณ  ์ธ์ฆ ์ €์žฅ์†Œ์— ์‚ฌ์šฉ์ž๊ฐ€ ์žˆ์œผ๋ฉด ๋ฆฌ๋””๋ ‰์…˜์„ ๋‹ด๋‹นํ•˜๋Š” react-router ๊ณ ์ฐจ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์˜๋ฏธ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

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

Auth ๊ฐ€ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ–ˆ๋Š”์ง€ ๊ฐ์ง€ํ•˜๋Š” ๊ฒƒ๋งŒ ์ฒ˜๋ฆฌํ•˜๋Š” ์ด์™€ ๊ฐ™์€ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฐ ์‹์œผ๋กœ Login ๊ตฌ์„ฑ ์š”์†Œ์—์„œ ์ž‘์—…์„ ์ „๋‹ฌํ•˜์—ฌ ์—ฌ์ „ํžˆ ๋‚ด ์Šคํ† ์–ด๋ฅผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ์ˆœ์ˆ˜ํ•˜์ง€๋Š” ์•Š์ง€๋งŒ ์ฝ”๋“œ๊ฐ€ ์ ๊ณ  ๋จธ๋ฆฌ๋ฅผ ๋Œ๋ฆฌ๊ธฐ๊ฐ€ ๋” ์‰ฝ์Šต๋‹ˆ๋‹ค. :)

@tomazzaman ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค :)
๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๋Œ€๋ถ€๋ถ„์˜ React ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋Š” ์ƒˆ๋กœ์šด Route ๊ตฌํ˜„ ์—†์ด ๋™์ผํ•œ ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ApplicationContainer ์™€ ๊ฐ™์€ ๊ฒƒ์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
RR-v1.0.0-rc1 ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ onEnter hook ์œผ๋กœ ๋™์ผํ•œ ๋ชฉํ‘œ์— ๋„๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ์ƒํƒœ๊ฐ€ ์ธ์ฆ๋จ๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๋‚˜๋Š” ์ด๊ฒƒ์— ๋Œ€ํ•ด์„œ๋„ ๋จธ๋ฆฌ๋ฅผ ์‹ธ๋งค๊ณ  ์žˆ์—ˆ๊ณ  @stremlenye ๊ฐ€ ์–ด๋–ป๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๋ณด์—ฌ์ฃผ๋Š” ํ›Œ๋ฅญํ•œ ์ผ์„ ํ•œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ๋Š” "๋ชจ๋“  ๊ฒƒ์€ ์กฐ์น˜๋ฅผ ๊ฑฐ์ณ์•ผ ํ•˜๋Š” ์ž‘์—… - ๊ฐ์†๊ธฐ - ์—…๋ฐ์ดํŠธ ๊ตฌ์„ฑ ์š”์†Œ์™€ ์ €์žฅ์†Œ ์ƒํƒœ ๋ฃจํ”„"(์ €๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค)์— ๊ฐ‡ํžˆ๋Š” ๊ฒฝํ–ฅ์ด ์žˆ๊ณ  ๋ชจ๋“  ์ข…๋ฅ˜์˜ ์•ฑ์˜ ๋งค์šฐ ํŠน์ •ํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ๋งŒ ์‚ฌ์šฉ๋˜๋Š” ์ƒํƒœ(1 ํ™”๋ฉด์—์„œ "๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ •์„ ์„ฑ๊ณต์ ์œผ๋กœ ์‹คํ–‰ํ–ˆ์Šต๋‹ˆ๊นŒ?"์™€ ๊ฐ™์€ ํ•ญ๋ชฉ) ๋ฐ˜๋ฉด ๊ตฌ์„ฑ ์š”์†Œ ๋‚ด๋ถ€์—์„œ ๋น„๋™๊ธฐ ํ˜ธ์ถœ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ตฌ์„ฑ ์š”์†Œ ์ž์ฒด๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋ฉด ์‰ฝ๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ „์—ญ ์ƒํƒœ๋ฅผ ์˜ค์—ผ์‹œํ‚ค๋Š” ๋Œ€์‹  this.setState ์‚ฌ์šฉ + ๋‚˜์ค‘์— ํ•ด๋‹น ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋‘ ๋ฒˆ์งธ๋กœ ์—ด ๊ฒฝ์šฐ์— ๋Œ€๋น„ํ•˜์—ฌ ๋ชจ๋“  ์ƒํƒœ ๋ณ€์ˆ˜๋ฅผ ์žฌ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ์‹œ์ž‘/์„ฑ๊ณต/์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ์ฝ”๋“œ๋กœ ์ƒํƒœ ๊ฐ์†Œ๊ธฐ๋ฅผ ์˜ค์—ผ์‹œํ‚ค๋Š” ๊ฒƒ์€ ์–ธ๊ธ‰์กฐ์ฐจ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์—์„œ๋„ ๋‚ด ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์—ฌ ์ •๋ฆฌ๋˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

@ir-fuel ์‹œ๊ฐ„์ด ์žˆ๋‹ค๋ฉด github์—์„œ ์–ป์„ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์˜ ์ž‘์€ ์˜ˆ๋ฅผ ๋“ค์–ด ์ฃผ์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

๊ทธ๋ ‡๊ฒŒ ํ•˜๊ธฐ๋Š” ์–ด๋ ต์ง€๋งŒ ์ œ๊ฐ€ ํ•œ ์ผ์˜ ์ž‘์€ ์˜ˆ๋ฅผ ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ •์„ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹ค์ œ ์žฌ์„ค์ • ์ž‘์—…์€ ์ด์ œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

  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 ๋Š” ๋‚ด ์„œ๋ฒ„์— ๋Œ€ํ•œ ajax ํ˜ธ์ถœ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์Šคํ† ์–ด/๋ฆฌ๋“€์„œ๋กœ์˜ ์™•๋ณต์ด ์—†๊ณ , ํ•˜๋‚˜์˜ ๊ตฌ์„ฑ ์š”์†Œ ๋‚ด๋ถ€์—์„œ๋งŒ ๊ด€์‹ฌ์ด ์žˆ๋Š” ํ•ญ๋ชฉ์œผ๋กœ ์ „์—ญ ์ƒํƒœ๋ฅผ ์˜ค์—ผ์‹œํ‚ค์ง€ ์•Š์œผ๋ฉฐ ์ˆœ์ „ํžˆ ํ•ด๋‹น ๊ตฌ์„ฑ ์š”์†Œ์˜ ์ƒํƒœ๋ฅผ ์ผ์‹œ์ ์œผ๋กœ ํ‘œ์‹œํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋ฉฐ, ๊ฐ์†๊ธฐ ๋‚ด๋ถ€์˜ ์—ฌ๋Ÿฌ ์ƒํƒœ ๋ณ€์ˆ˜๋ฅผ ์žฌ์„ค์ •ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

๋ Œ๋”๋ง์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

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

์ด๊ฒƒ์ด ๋ถ„๋ช…ํ•˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

์ข‹์•„ ๋ณด์ธ๋‹ค :+1:
(๋‚ด ๊ด€์ ์—์„œ) ๋‹ค๋ฃจ์–ด์•ผ ํ•˜๋Š” ์œ ์ผํ•œ ํŠธ๋ฆญ์€ ํ•ด์ œ๋œ ์ž‘์—…์—์„œ ์•ฝ์†์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์กฐ์ •ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค(์•„๋งˆ๋„ ์ž‘์—…์˜ ๋ฐ˜ํ™˜ ๊ฐ’์„ ๋Œ€์‹ ํ•  ๋ฟ์ž…๋‹ˆ๋‹ค). ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ตฌ์„ฑ ์š”์†Œ ๋ฒ”์œ„์˜ Promise ํ•ญ๋ชฉ์— ์—ฐ๊ฒฐํ•  ๊ธฐํšŒ๋ฅผ ์œ ์ง€ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ์ผ๋ฐ˜ ํ๋ฆ„์—์„œ ์ƒํƒœ๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ์ž‘์—…์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.

์•ฑ ์ƒํƒœ๋ฅผ ์ด๋ฏธ ๋กœ๊ทธ์ธํ•œ ์ƒํƒœ๋กœ ์ƒ๊ฐํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ์—†์œผ๋ฉด ์•ฑ ์ƒํƒœ๊ฐ€ ํ•„์š”ํ•œ ์ด์œ ๋Š” ๋ฌด์—‡์ž…๋‹ˆ๊นŒ? ๋ฌผ๋ก  ๋กœ๊ทธ์ธ ๊ตฌ์„ฑ ์š”์†Œ ๋‚ด๋ถ€์— ํ•˜๋‚˜๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์˜ ์•ฑ ๋กœ์ง์€ ์•ฑ ์ƒ๋‹จ ์ƒํƒœ๋ฅผ ์†Œ์œ ํ•˜๊ธฐ ์œ„ํ•ด ๊ทธ๋ ‡๊ฒŒ ๋ณต์žกํ•˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋งž๋‚˜์š”?

@SkateFreak ๋กœ๊ทธ์ธ ํ๋ฆ„์€ ์•ฑ์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค. React๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์€ ๋กœ๊ทธ์ธ ํ๋ฆ„ ์ƒํƒœ(์ง„ํ–‰๋ฅ , ์˜ค๋ฅ˜), ๊ฐ€์ž… ํ๋ฆ„ ์ƒํƒœ ๋“ฑ์„ ์ƒ๊ฐํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๊นŒ(๋‹ค๋ฅธ ์•ฑ ๊ตฌ์„ฑ ์š”์†Œ์— ์ด๋ฏธ Redux๋ฅผ ์‚ฌ์šฉ ์ค‘์ธ ๊ฒฝ์šฐ Redux์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ).

@sompylasar ์ €๋Š” ์•ฑ ์ƒํƒœ์— ์ฐฌ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋‚ด ์•ฑ ์™ธ๋ถ€์— ์žˆ๋Š” ํ•œ redux ์Šคํ† ์–ด ์—†์ด ๋” ๊ฐ„๋‹จํ•œ ์•ฑ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๋‚ด ์•ฑ์„ ์ž…๋ ฅํ•˜์ž๋งˆ์ž ๋‚ด redux ์Šคํ† ์–ด๊ฐ€ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค.

์ด ์ ‘๊ทผ ๋ฐฉ์‹์€ ์ง€๊ธˆ ์ด ์ˆœ๊ฐ„๊นŒ์ง€ ์ €์—๊ฒŒ ํšจ๊ณผ๊ฐ€ ์žˆ์—ˆ์ง€๋งŒ ์•„๋งˆ๋„ ํ–ฅํ›„ ํ”„๋กœ์ ํŠธ์—์„œ ๋กœ๊ทธ์ธ ๋‹จ๊ณ„ ์•ฑ ์ƒํƒœ์˜ ํ•„์š”์„ฑ์„ ์ดํ•ดํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์Šคํ† ์–ด์˜ ๋ฃจํŠธ ์ˆ˜์ค€์—์„œ ์Šคํ† ์–ด๋ฅผ 'app' ๋ถ€๋ถ„๊ณผ 'user/login' ๋ถ€๋ถ„์œผ๋กœ ๋ถ„ํ• ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ฐ๊ฐ์€ ์ž์ฒด ๋ฆฌ๋“€์„œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด ๊ฒฝ์šฐ ๋กœ๊ทธ์ธ ๊ฐ์†๊ธฐ ์ฝ”๋“œ๋กœ ์•ฑ ๊ฐ์†๊ธฐ๋ฅผ ์˜ค์—ผ์‹œํ‚ค์ง€ ์•Š์œผ๋ฉฐ ๊ทธ ๋ฐ˜๋Œ€์˜ ๊ฒฝ์šฐ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.

๋ณด๋‚ธ ์‚ฌ๋žŒ: SkateFreak < ์•Œ๋ฆผ @github.com ์•Œ๋ฆผ @github.com >
๋‹ต์žฅ: rackt/redux < [email protected] [email protected] >
๋‚ ์งœ: 2015๋…„ 12์›” 13์ผ ์ผ์š”์ผ 13:48
๋ฐ›๋Š” ์‚ฌ๋žŒ: rackt/redux < [email protected] [email protected] >
์ฐธ์กฐ: Joris Mans < [email protected] [email protected] >
์ œ๋ชฉ: Re: [redux] ๋ฆฌ๋””๋ ‰์…˜์ด ์žˆ๋Š” ๋กœ๊ทธ์ธ/๊ฐ€์ž… ํŽ˜์ด์ง€์˜ ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ ๋ชจ๋ฒ” ์‚ฌ๋ก€(#297)

@sompyl asarhttps://github.com/sompylasar ์ €๋Š” ์•ฑ ์ƒํƒœ์— ์ฐฌ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ œ ์•ฑ ์™ธ๋ถ€์—์„œ ์‚ฌ์šฉํ•˜๋Š” ํ•œ redux ์Šคํ† ์–ด ์—†์ด ๋” ๊ฐ„๋‹จํ•œ ์•ฑ์„ ๊ทธ์—๊ฒŒ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๋‚ด ์•ฑ์„ ์ž…๋ ฅํ•˜์ž๋งˆ์ž ๋‚ด redux ์Šคํ† ์–ด๊ฐ€ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค.

์ด ์ ‘๊ทผ ๋ฐฉ์‹์€ ์ง€๊ธˆ ์ด ์ˆœ๊ฐ„๊นŒ์ง€ ์ €์—๊ฒŒ ํšจ๊ณผ๊ฐ€ ์žˆ์—ˆ์ง€๋งŒ ์•„๋งˆ๋„ ํ–ฅํ›„ ํ”„๋กœ์ ํŠธ์—์„œ ๋กœ๊ทธ์ธ ๋‹จ๊ณ„ ์•ฑ ์ƒํƒœ์˜ ํ•„์š”์„ฑ์„ ์ดํ•ดํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด ์ด๋ฉ”์ผ์— ์ง์ ‘ ๋‹ต์žฅํ•˜๊ฑฐ๋‚˜ Gi tHubhttps://github.com/rackt/redux/issues/297#issuecomment -164287194์—์„œ ํ™•์ธํ•˜์„ธ์š”.

์ž˜๋ชป๋œ ๊ฒฝ์šฐ ๋‚˜๋ฅผ ์ˆ˜์ •ํ•˜์ง€๋งŒ ๊ฒฝ๋กœ์™€ ๊ตฌ์„ฑ ์š”์†Œ ๊ฐ„์— ๋ฐ์ดํ„ฐ์™€ ์•ฑ ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•˜๋Š” ํ”Œ๋Ÿญ์Šค ์ €์žฅ์†Œ์˜ ๋ชฉ์ ์ด ์•„๋‹Œ๊ฐ€์š”? ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•  ๋•Œ ๋ชจ๋“  ๊ฒƒ์ด ์ค‘์š”ํ•˜์ง€ ์•Š๋‹ค๋ฉด ์™œ ๋‚ด ์ƒํƒœ๋ฅผ "์ถ•์†Œ"ํ•˜๊ณ  ์ตœ์ƒ์œ„ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด "์ž‘์—…"์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๊นŒ?

๋‚˜๋Š” ๊ทธ๊ฒƒ์ด ์ผ์„ํ•˜๋Š” ๋งค์šฐ ์กฐ์ง์ ์ด๊ณ  ๊นจ๋—ํ•œ ๋ฐฉ๋ฒ•์ด์ง€๋งŒ ์ด์ ์„ ๋ณด์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. SignIn, SignUp ๋ฐ ForgotPassword๊ฐ€ ํฌํ•จ๋œ <Sign /> ๊ตฌ์„ฑ ์š”์†Œ์—์„œ ์‚ฌ์ „ ๋กœ๊ทธ์ธ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๊ฐ€์ž…์€ ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฌป๋Š” ๊ฒƒ๋ณด๋‹ค ๋” ๋ณต์žกํ•œ ํ”„๋กœ์„ธ์Šค์ผ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
๊ทธ๊ฒƒ์€ ๋ชจ๋‘ ํ๋ฆ„์˜ ๋ณต์žก์„ฑ์— ๋‹ฌ๋ ค ์žˆ์Šต๋‹ˆ๋‹ค.

๋ณด๋‚ธ ์‚ฌ๋žŒ: SkateFreak < ์•Œ๋ฆผ @github.com ์•Œ๋ฆผ @github.com >
๋‹ต์žฅ: rackt/redux < [email protected] [email protected] >
๋‚ ์งœ: 2015๋…„ 12์›” 13์ผ ์ผ์š”์ผ 14:00
๋ฐ›๋Š” ์‚ฌ๋žŒ: rackt/redux < [email protected] [email protected] >
์ฐธ์กฐ: Joris Mans < [email protected] [email protected] >
์ œ๋ชฉ: Re: [redux] ๋ฆฌ๋””๋ ‰์…˜์ด ์žˆ๋Š” ๋กœ๊ทธ์ธ/๊ฐ€์ž… ํŽ˜์ด์ง€์˜ ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ ๋ชจ๋ฒ” ์‚ฌ๋ก€(#297)

์ž˜๋ชป๋œ ๊ฒฝ์šฐ ๋‚˜๋ฅผ ์ˆ˜์ •ํ•˜์ง€๋งŒ ๊ฒฝ๋กœ์™€ ๊ตฌ์„ฑ ์š”์†Œ ๊ฐ„์— ๋ฐ์ดํ„ฐ์™€ ์•ฑ ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•˜๋Š” ํ”Œ๋Ÿญ์Šค ์ €์žฅ์†Œ์˜ ๋ชฉ์ ์ด ์•„๋‹Œ๊ฐ€์š”? ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•  ๋•Œ ๋ชจ๋“  ๊ฒƒ์ด ์ค‘์š”ํ•˜์ง€ ์•Š๋‹ค๋ฉด ์™œ ๋‚ด ์ƒํƒœ๋ฅผ "์ถ•์†Œ"ํ•˜๊ณ  ์ตœ์ƒ์œ„ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด "์ž‘์—…"์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๊นŒ?

๋‚˜๋Š” ๊ทธ๊ฒƒ์ด ์ผ์„ํ•˜๋Š” ๋งค์šฐ ์กฐ์ง์ ์ด๊ณ  ๊นจ๋—ํ•œ ๋ฐฉ๋ฒ•์ด์ง€๋งŒ ์ด์ ์„ ๋ณด์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. SignIn, SignUp ๋ฐ ForgotPassword๊ฐ€ ํฌํ•จ๋œ ๊ตฌ์„ฑ ์š”์†Œ์—์„œ ์‚ฌ์ „ ๋กœ๊ทธ์ธ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์ด ์ด๋ฉ”์ผ์— ์ง์ ‘ ๋‹ต์žฅํ•˜๊ฑฐ๋‚˜ Gi tHubhttps://github.com/rackt/redux/issues/297#issuecomment -164287893์—์„œ ํ™•์ธํ•˜์„ธ์š”.

๋™์˜ํ•œ๋‹ค.
ํ•˜์ง€๋งŒ SignUp์„ ์ƒํƒœ๊ฐ€ ์žˆ๋Š” ์–‘์‹์œผ๋กœ ๋ด…๋‹ˆ๋‹ค. ์•„๋ฌด๋ฆฌ ๋ณต์žกํ•ด๋„ ์ถฉ๋ถ„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค...

๋ฐ˜๋ฉด์— ๊ฐ€์ž…์— ๋Œ€ํ•ด ๋ฌด์—‡์„ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ?
์‚ฌ๋žŒ๋“ค์ด ๋ฏธ์ณค๋‹ค

ํ˜„์žฌ ๋‚˜๋Š” ๋น„์Šทํ•˜์ง€๋งŒ ๋‹ค๋ฅธ ๋ฌธ์ œ๋ฅผ ๋งŒ๋‚ฌ์Šต๋‹ˆ๋‹ค.

์ €๋Š” ๋งค์šฐ ๋ณต์žกํ•œ ๊ฐ€์ž…๊ณผ ๊ฐ™์€ ํ๋ฆ„์„ ์ž‘์—… ์ค‘์ด๋ฉฐ ์‚ฌ์šฉ์ž๊ฐ€ ํ•„์ˆ˜ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๋ช‡ ๊ฐ€์ง€ ๊ณ„์† ํŽ˜์ด์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ํŽ˜์ด์ง€๋Š” ์–‘์‹์ž…๋‹ˆ๋‹ค. ํ๋ฆ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

FormPage_A -> FormPage_B -> FormPage_C -> SuccessPage

๋ฌธ์ œ๋Š” FormPage_C ์ „์— API ์š”์ฒญ์„ ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” FormPage_A/B์˜ ๋ธŒ๋ผ์šฐ์ €์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ FormPage_C์˜ API๋กœ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ FormPage_A ๋ฐ FormPage_B์˜ ์–‘์‹ ๋ฐ์ดํ„ฐ๋ฅผ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ์ƒํƒœ์— ๋„ฃ์–ด์•ผ ํ•ฉ๋‹ˆ๊นŒ?

๊ทธ๋ ‡๋‹ค๋ฉด UI์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋Š” ์ž„์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ๋กœ ์ €์žฅํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  redux์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ์— ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ์ž๊ฐ€ FormPage_B์—์„œ ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ ๊ณ ์น˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”?

์•„๋‹ˆ์˜ค๋ผ๋ฉด, ๊ทธ๊ฒƒ์„ ๋‘๋Š” ๊ฒƒ์ด ์˜ฌ๋ฐ”๋ฅธ ์žฅ์†Œ์˜€์Šต๋‹ˆ๊นŒ? redux ํ๋ฆ„์—์„œ fake api ๊ฐ™์€ ๋‹ค๋ฅธ ๋ ˆ์ด์–ด๋ฅผ ๋นŒ๋“œํ•˜์—ฌ ์ €์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๊นŒ?

@frederickfogerty @gaearon @ir-fuel @stremlenye

์ œ์ถœ๊ณผ ๊ด€๋ จ๋œ this.state ์˜ ์†์„ฑ์„ ๋‹ค์Œ ๋‹จ๊ณ„์˜ ์†Œํ’ˆ์œผ๋กœ ์ „๋‹ฌํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ FormPage_A.state ๋Š” FormPage_B.props FormPage_C ์—์„œ ์ œ์ถœํ•ฉ๋‹ˆ๋‹ค.

๋˜๋Š” ์ด๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ƒ์œ„ ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ์ƒ์œ„ ์ปจํŠธ๋กค์˜ state ์†์„ฑ์— ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋งˆ์ง€๋ง‰ ๋‹จ๊ณ„ ํ›„์— ๋ชจ๋‘ ์ œ์ถœ

์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ๋‹ค์‹œ ๋กœ๋“œํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜๋ฉด ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

@kpaxqin ์ด ๊ฒฝ์šฐ Flow ์™€ ๊ฐ™์€ ๊ด€๋ฆฌ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ตฌ์„ฑ ์š”์†Œ๋Š” ๊ฐ ๋‹จ๊ณ„์˜ ์ƒํƒœ์™€ ์ผ๋ฐ˜ back-forward ๋…ผ๋ฆฌ๋กœ ๋‹จ๊ณ„์˜ ํ•˜์œ„ ์ง‘ํ•ฉ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ ๋‹จ๊ณ„๋ฅผ ๊ฐ€๋Šฅํ•œ ํ•œ ์ƒํƒœ ๋น„์ €์žฅ ์ƒํƒœ๋กœ ๋งŒ๋“ค๊ณ  ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋‘˜๋Ÿฌ์‹ธ๋Š” ์ „์ฒด ํ”„๋กœ์„ธ์Šค์˜ ์ƒํƒœ๋ฅผ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒƒ:

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

@ir-fuel @stremlenye

์ œ์•ˆํ•ด ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.
์šฐ๋ฆฌ ๋ชจ๋‘๋Š” ์ด๋Ÿฌํ•œ ์ผ์‹œ์ ์ธ ๊ฒƒ๋“ค์„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ๋กœ ๋งŒ๋“ค๊ณ  ์‹ถ์ง€ ์•Š์€ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์‚ฌ์šฉ์ž๊ฐ€ ํ๋ฆ„์˜ ์ ˆ๋ฐ˜์—์„œ ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ ๊ณ ์น  ๋•Œ

์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ๋‹ค์‹œ ๋กœ๋“œํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜๋ฉด ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๊ฒƒ์€ ์ข‹์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ์•„๋‹Œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๋ชจ๋ฐ”์ผ ์‚ฌ์ดํŠธ์ž…๋‹ˆ๋‹ค(ํฐ ์–‘์‹์„ ์ž‘์€ ํŽ˜์ด์ง€๋กœ ๋‚˜๋ˆ„๋Š” ์ด์œ  ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค). ๋ชจ๋ฐ”์ผ์—์„œ ์ž…๋ ฅํ•˜๋Š” ๊ฒƒ์ด ๊ณ ํ†ต์Šค๋Ÿฝ๊ณ  ๋ชจ๋“  ๊ฒƒ์„ ์žƒ์œผ๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ํ™”๋ฅผ ๋‚ผ ๊ฒƒ์ด๋ผ๊ณ  ๋ฏฟ์Šต๋‹ˆ๋‹ค. ์ƒˆ๋กœ ๊ณ ์นจ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅธ ํ›„์—๋Š” ๊ทธ๋ƒฅ ํฌ๊ธฐํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

redux ํ๋ฆ„์—์„œ fake api ๋ฅผ ๋นŒ๋“œํ•˜๋Š” ๊ฒƒ์ด ํ—ˆ์šฉ ๊ฐ€๋Šฅํ•œ ์„ ํƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ๋‹จ๊ณ„์—์„œ fake api ์— ํ•ญ๋ชฉ์„ ์ œ์ถœํ•˜๊ณ  ํ•„์š”ํ•œ ๊ฒฝ์šฐ API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ํ•„์š”ํ•  ๋•Œ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. api์ฒ˜๋Ÿผ ์—ฐ๊ฒฐ๋˜๋Š” ์œ ์ผํ•œ ๊ฒƒ์€ ์ž‘์—… ์ƒ์„ฑ์ž์ด๋ฉฐ ํŽ˜์ด์ง€ ๋ฐ ๊ตฌ์„ฑ ์š”์†Œ๋Š” ๊นจ๋—ํ•˜๊ณ  ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๋„ ์‰ฝ์Šต๋‹ˆ๋‹ค(API ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๊ณตํ†ต ์ฝ”๋“œ ์‚ฌ์šฉ).

ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ์ง€์†ํ•˜๋ ค๋ฉด ๊ตฌ์„ฑ ์š”์†Œ ์ƒํƒœ๋‚˜ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ์–ด์•ผ ํ•˜๊ณ  ์ž‘์—… ์ƒ์„ฑ๊ธฐ์—์„œ ๋ถ€์ž‘์šฉ์„ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ €์—๊ฒŒ ์˜๋ฏธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

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

๊ทธ๊ฒƒ์€ ๋‚˜์—๊ฒŒ ๋ช…ํ™•ํ•ด์กŒ์ง€๋งŒ ์šฐ๋ฆฌ ์•ฑ์— redux ํ๋ฆ„์˜ ๋ฌด์–ธ๊ฐ€๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์ด์ƒํ•ด ๋ณด์ž…๋‹ˆ๋‹ค.

๋‚˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๊ท€ํ•˜์˜ ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•ด ์ •๋ง๋กœ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
๋‹ค๋ฅธ ํ•œํŽธ์œผ๋กœ๋Š” ์—ฌ๋Ÿฌ ์–‘์‹์ด ์žˆ๋Š” ๋ณต์žกํ•œ ๊ฐ€์ž… ์ ˆ์ฐจ๊ฐ€ ์žˆ๋‹ค๊ณ  ๋งํ•˜๋ฉด์„œ ๋ชจ๋ฐ”์ผ ์‚ฌ์ดํŠธ๋ผ๊ณ  ๋งํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์‚ฌ์šฉ์ž๋ฅผ ํ™”๋‚˜๊ฒŒ ๋งŒ๋“ค๊ณ  ๋ชจ๋ฐ”์ผ ์žฅ์น˜์—์„œ ๋„ˆ๋ฌด ๋งŽ์€ ํ•ญ๋ชฉ์„ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ํฌ๊ธฐํ•˜๊ฒŒ ๋งŒ๋“ค ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜์ง€ ์•Š์Šต๋‹ˆ๊นŒ? ๋ฆฌ๋กœ๋“œ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ๋ณด๋‹ค ๋” ๋‚˜์˜๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์–ด์จŒ๋“  ๋งŽ์€ ์‚ฌ์ดํŠธ๊ฐ€ ๊ฐ€์ž… ๊ณผ์ • ์ค‘์— ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

๋‚˜๋Š” ๊ทธ๊ฒƒ์ด ์‹ค์ œ๋กœ ๋งŽ์ด ์ผ์–ด๋‚˜์ง€ ์•Š๋Š” ์ผ์„ ํ›จ์”ฌ ๋” ๋ณต์žกํ•˜๊ฒŒ ๋งŒ๋“ค๊ณ  ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์ €๋Š” ๊ฐ€์ž… ์ ˆ์ฐจ๋ฅผ ๋‹จ์ˆœํ™”ํ•˜๊ฑฐ๋‚˜ ์›น์‚ฌ์ดํŠธ์—์„œ ์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋กœ ๋ถ„ํ• ํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ๋งˆ์นจ๋‚ด ๊ณ„์ •์„ ํ™œ์„ฑํ™”ํ•˜๊ธฐ ์ „์— X ํŽ˜์ด์ง€์˜ ์–‘์‹์„ ์ž‘์„ฑํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์ธ์ƒ์„ ์ฃผ์ง€ ์•Š๋„๋ก ํ•˜๋Š” ๋ฐ ๋” ์ง‘์ค‘ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ๋กœ ๊ฐ€์ž… ์ค‘๊ฐ„ ์žฌ๋กœ๋“œ๋ฅผ ์ง€์›ํ•˜๋ ค๋ฉด ์ด๋Ÿฌํ•œ ๋ชจ๋“  ๋‹จ๊ณ„๋ฅผ ์ œ์–ดํ•˜๋Š” โ€‹โ€‹์ƒ์œ„ ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์ƒํƒœ๋ฅผ localStorage ์— ์ €์žฅํ•˜๋„๋ก ํ•˜์„ธ์š”.

@kpaxqin "๊ฐ€์งœ API" ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์™œ ๊ฐ€์งœ BTW์ธ์ง€, ๋‹ค๋‹จ๊ณ„ ํ”„๋กœ์„ธ์Šค์— ํ•„์š”ํ•œ ์ค‘๊ฐ„ ์ €์žฅ์†Œ API์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  API๊ฐ€ ๋ฐ˜๋“œ์‹œ ์„œ๋ฒ„์— ์žˆ์–ด์•ผ ํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋ฉฐ HTTP ๋˜๋Š” ๊ธฐํƒ€ ๋„คํŠธ์›Œํ‚น ํ”„๋กœํ† ์ฝœ์„ ํ†ตํ•ด ์•ก์„ธ์Šคํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

@ir-fuel์— ๋™์˜ํ•˜๋Š” ๊ฒƒ์€ ํ˜„์žฌ ๋‹จ๊ณ„๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋ชจ๋“  ๋‹ค๋‹จ๊ณ„ ํ”„๋กœ์„ธ์Šค ์ƒํƒœ๊ฐ€ ์ €์žฅ๋˜์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

@ir-fuel Reload๋Š” ๋ช…์‹œ์ ์ธ ์‚ฌ์šฉ์ž ์˜๋„ ์—†์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์‚ฌ์šฉ์ž๊ฐ€ ๋…ธํŠธ๋ถ์ด๋‚˜ ์ „ํ™” ํ†ตํ™”์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ๋ชจ๋ฐ”์ผ ์•ฑ์œผ๋กœ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ธฐ๊ธฐ์˜ ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ์œผ๋กœ ์ธํ•ด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ƒํƒœ๋ฅผ ์žƒ์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ๋Œ์•„์˜ฌ ๋•Œ ๋‹ค์‹œ ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค.

๋ฌธ์ œ๋Š” ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ช‡ ํ™”๋ฉด์—์„œ๋งŒ ํ•„์š”ํ•œ ๊ฒƒ์œผ๋กœ ๊ธ€๋กœ๋ฒŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ๋ฅผ ์˜ค์—ผ์‹œํ‚ค๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

@sompylasar @kpaxqin ์ €๋Š” @ir-fuel์ด ์˜ฌ๋ฐ”๋ฅธ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์ œ์•ˆํ–ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๊ฒฐ๊ตญ ์ง€์†๋˜์–ด์•ผ ํ•˜๋Š” ์ค‘๊ฐ„์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์œ ์ง€ํ•˜์‹ญ์‹œ์˜ค. ๊ตฌํ˜„ํ•˜๊ธฐ๊ฐ€ ํ›จ์”ฌ ์‰ฝ๊ณ  ์„œ๋ฒ„๊ฐ€ ์ผ๊ด€๋˜์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ๋ฅผ ์œ ์ง€ํ•˜๋Š” ๋ฐ ์‹ค์ œ๋กœ ๊ด€์—ฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. t ๋ชจ๋“  http ์š”์ฒญ(๋ชจ๋ฐ”์ผ ์žฅ์น˜์˜ ์ฃผ์š” ๋ฌธ์ œ์ด๊ธฐ๋„ ํ•จ)์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ํ–ฅํ›„ ์ ‘๊ทผ ๋ฐฉ์‹์„ ๋งค์šฐ ์‰ฝ๊ฒŒ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋”˜๊ฐ€์— ์œ ์ง€ํ•˜๋Š” ๊ฒƒ(ํ”„๋กœ์„ธ์Šค๊ฐ€ ์‹คํŒจํ–ˆ๊ฑฐ๋‚˜ ์ด๋ฏธ ์™„๋ฃŒ๋œ ํ›„ ์–ด๋–ป๊ฒŒ ์ •๋ฆฌํ•  ๊ฒƒ์ธ์ง€ ์ƒ๊ฐํ•˜์‹ญ์‹œ์˜ค)์ด ์ฃผ์ œ์˜ ์ดˆ๊ธฐ ์ฃผ์ œ์˜€์Šต๋‹ˆ๋‹ค.
์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋˜ ๋‹ค๋ฅธ ์˜ต์…˜์€ URL์— ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์–‘์‹์ด ์ข‹์€ ์ ˆ์ถฉ์•ˆ์ด ๋  ์ˆ˜ ์žˆ๋Š” ๋ช‡ ๊ฐœ์˜ ํ…์ŠคํŠธ ํ•„๋“œ๋กœ ๊ตฌ์„ฑ๋œ ๊ฒฝ์šฐ.

๊ทธ๊ฒƒ์€ "ํด๋ผ์ด์–ธํŠธ ๋˜๋Š” ์„œ๋ฒ„ ์ธก์— ์ €์žฅ"ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ "๋ถ€๋ชจ ๊ตฌ์„ฑ ์š”์†Œ ํด๋ž˜์Šค์—์„œ ์ด '์ž„์‹œ'๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๊นŒ?" ๋Œ€ "์ „์ฒด ์ž‘์—…/๋ฆฌ๋“€์„œ/์ƒํƒœ ๋Œ„์Šค๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๊นŒ?"์˜ ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค.
์ž„์‹œ ์ƒํƒœ์ธ ๊ฒฝ์šฐ ์•ก์…˜/๋ฆฌ๋“€์„œ๋ฅผ ํ†ต๊ณผํ•˜๋Š” ๊ฒƒ์„ ์ข‹์•„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ์— ๋Œ€ํ•ด์„œ๋งŒ ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

@ir-์—ฐ๋ฃŒ :+1:

@tremlenye ๋‚ด ๋‹ต๋ณ€์„ ๋‹ค์‹œ ์ฝ์œผ์‹ญ์‹œ์˜ค. ์ง€์†์„ฑ API ๊ณ„์ธต์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์„œ๋ฒ„ ์ธก API๋ฅผ ๋งŒ๋“ค ํ•„์š”๊ฐ€ ์—†๋‹ค๊ณ  ๋ช…์‹œ์ ์œผ๋กœ ๋งํ•ฉ๋‹ˆ๋‹ค.

@ir-fuel action/reducer๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š์œผ๋ฉด ์ƒํƒœ ์ „ํ™˜์˜ ์˜ˆ์ธก ๊ฐ€๋Šฅ์„ฑ๊ณผ ์žฌ์ƒ ๊ฐ€๋Šฅ์„ฑ์ด ์ทจ์†Œ๋ฉ๋‹ˆ๋‹ค.

@ir-fuel ์–‘์‹์ด ์ œ์ถœ๋œ ํ›„ ์•ฑ ์ƒํƒœ๋ฅผ ์‰ฝ๊ฒŒ ์ง€์šธ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ "์˜ค์—ผ"๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

RE: ํด๋ฆฌ์–ด. ์ž‘์—…์„ ์ทจ์†Œํ•˜๊ณ  UI์— ๋ฒ„ํŠผ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ€์ž… ํ๋ฆ„์„ ์žฌ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๋ผ์šฐํ„ฐ๋ฅผ ํ†ตํ•ด์„œ๋„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์ดˆ๊ธฐ ๊ฐ€์ž… URL์„ ๋ฐฉ๋ฌธํ•  ๋•Œ ํ•ด๋‹น ์ž‘์—…์„ ์ „๋‹ฌํ•˜๊ณ  ๊ฐ€์ž…์ด ์‹œ์ž‘๋œ ๊ฒฝ์šฐ URL์„ ๋‹ค๋ฅธ ๊ฒƒ์œผ๋กœ ๋ณ€๊ฒฝ).

@sompylasar ๋ช…์‹œ์  ๋ฒ„ํŠผ์œผ๋กœ ์ƒํƒœ๋ฅผ ์žฌ์„ค์ •ํ•˜๋Š” ๋ฐ ๋ฌธ์ œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์˜ค๋ฅ˜ ํ›„์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ƒํƒœ๊ฐ€ ์†์ƒ๋  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์˜ˆ์ธกํ•˜๊ณ  ๊ฐ๊ฐ์˜ ํŠน์ • ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ๋ณต์› ์ „๋žต์„ ์ •์˜ํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

@stremlenye ์˜ˆ, ์†Œํ”„ํŠธ์›จ์–ด ์—”์ง€๋‹ˆ์–ด๋ง์€ ๋•Œ๋•Œ๋กœ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ์ƒํƒœ์™€ ์ „ํ™˜์€ ์ด๋ฅผ ์™„ํ™”ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ƒํƒœ๊ฐ€ json ๊ฐ์ฒด์˜ ๋ฌด๋ฆฌ ์ด์ƒ์ด๊ณ (๋ชจ๋“  ๊ฒƒ์ด ๊ด€๋ก€์— ๋”ฐ๋ผ ์ ์šฉ๋˜๊ณ  ์•„๋ฌด ๊ฒƒ๋„ ์ ์šฉ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์‹ซ์–ดํ•ฉ๋‹ˆ๋‹ค) Immutable.js์˜ Record ๋ผ์ธ์—์„œ ๋ฌด์–ธ๊ฐ€๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ์ฒด์˜ ์†์„ฑ์„ ๋ฏธ๋ฆฌ ์„ ์–ธํ•ด์•ผ ํ•˜๋ฉฐ ์—ฌ๊ธฐ์—๋Š” ์›ํ•˜๋Š” ๋ชจ๋“  ์ž„์‹œ ํ•ญ๋ชฉ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

์ €์—๊ฒŒ Immutable.js๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  redux๋กœ ๋ฌด์–ธ๊ฐ€๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒƒ์€ ๋‹จ์ง€ ์‚ฌ๊ณ ๊ฐ€ ์ผ์–ด๋‚˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Signup ๋ ˆ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค๊ณ  ๋“ฑ๋กํ•  ๋•Œ๋งŒ null์ด ์•„๋‹Œ ์ƒํƒœ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚˜.

@ir-์—ฐ๋ฃŒ

๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ƒํƒœ๊ฐ€ json ๊ฐ์ฒด์˜ ๋ฌด๋ฆฌ ์ด์ƒ์ด๊ณ (๋ชจ๋“  ๊ฒƒ์ด ๊ด€๋ก€์— ๋”ฐ๋ผ ์ ์šฉ๋˜๊ณ  ์•„๋ฌด ๊ฒƒ๋„ ์ ์šฉ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์‹ซ์–ดํ•ฉ๋‹ˆ๋‹ค) Immutable.js์˜ ๋ ˆ์ฝ”๋“œ ๋ผ์ธ์—์„œ ๋ฌด์–ธ๊ฐ€๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์†์„ฑ์„ ์„ ์–ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹น์‹ ์ด ์›ํ•˜๋Š” ๋ชจ๋“  ์ž„์‹œ ๋ฌผ๊ฑด์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ €์—๊ฒŒ Immutable.js๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  redux๋กœ ๋ฌด์–ธ๊ฐ€๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒƒ์€ ๋‹จ์ง€ ์‚ฌ๊ณ ๊ฐ€ ์ผ์–ด๋‚˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํ˜„์žฌ๋กœ์„œ๋Š” Redux์™€ ํ•จ๊ป˜ Immutable.js๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•ด ํ™•์‹ ์ด ์„œ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ†ตํ•ฉํ•˜๊ธฐ์—๋Š” ๋„ˆ๋ฌด ๋ณต์žกํ•ด ๋ณด์˜€๊ณ  ์„ฑ๋Šฅ๋„ ์ข‹์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ๋ฐ ํ…Œ์ŠคํŠธ ์ค‘ ๋”ฅ ํ”„๋ฆฌ์ฆˆ(deep-freeze)์™€ ํ…Œ์ŠคํŠธ ์„ธํŠธ๋Š” ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ์— ์ถฉ๋ถ„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ฆฌ๋“€์„œ๋‚˜ ์ฐธ์กฐ๊ฐ€ ์žˆ๋Š” ๋‹ค๋ฅธ ์žฅ์†Œ์—์„œ ์ƒํƒœ ๋ณ€๊ฒฝ์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•œ ์—„๊ฒฉํ•œ ๊ทœ์น™์ž…๋‹ˆ๋‹ค.

๊ฐ€์ž… ๊ธฐ๋ก์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ณ  ๊ฐ€์ž…ํ•  ๋•Œ๋งŒ null์ด ์•„๋‹Œ ๊ฒƒ์œผ๋กœ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋„ค, ๊ทธ๋ ‡๊ฒŒ ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋ฌธ์ œ๋Š” ๋” ๊ทผ๋ณธ์ ์ž…๋‹ˆ๋‹ค. ์›น ์•ฑ์— ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์ด ์žˆ์„ ์ˆ˜ ์žˆ๊ณ  ๊ทธ๊ฒŒ ์ €๋ฅผ ๊ดด๋กญํžˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์ด๊ฒƒ์ด ์šฐ๋ฆฌ๊ฐ€ ๋ช…์‹œ์  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ๋ผ๊ณ  ๋ถ€๋ฅด๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋งค ์ˆœ๊ฐ„ ๋งŽ์€ ๊ตฌ์„ฑ์š”์†Œ๋กœ ๊ตฌ์„ฑ๋œ ์•ฑ์˜ ์ƒํƒœ๋ฅผ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ƒํƒœ ๊ตฌ์กฐ๋ฅผ ๋œ ๋ช…์‹œ์ ์ด๊ณ  ๋” ๋™์ ์œผ๋กœ ๋งŒ๋“ค๊ณ  ์‹ถ๋‹ค๋ฉด https://github.com/tonyhb/redux-ui ๋ฅผ ๋”ฐ๋ฅด์‹ญ์‹œ์˜ค.

์ด๊ฒƒ์ด JS ๊ฐœ๋ฐœ์˜ ์ „์ฒด ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค. "๊ด€๋ก€๋ฅผ ์ง€ํ‚ค์ž". ์ด๊ฒƒ์ด ์‚ฌ๋žŒ๋“ค์ด ๋ณต์žกํ•œ ํ™˜๊ฒฝ์—์„œ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์„ ๊นจ๋‹ซ๊ธฐ ์‹œ์ž‘ํ•˜๋Š” ์ด์œ ์ž…๋‹ˆ๋‹ค. ์‚ฌ๋žŒ๋“ค์€ ์‹ค์ˆ˜๋ฅผ ํ•˜๊ณ  TypeScript ๋ฐ Immutable.js์™€ ๊ฐ™์€ ์†”๋ฃจ์…˜์„ ์ œ๊ณตํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

ํ†ตํ•ฉํ•˜๋Š” ๊ฒƒ์ด ์™œ ๋„ˆ๋ฌด ๋ณต์žกํ• ๊นŒ์š”? '๊ธฐ๋ก'์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ๋ชจ๋“  ๊ฐ์ฒด(์ฝ๊ธฐ๋งŒ ํ•˜๋Š” ํ•œ)๋ฅผ ์ผ๋ฐ˜ JS ๊ฐ์ฒด๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์†์„ฑ์— ์•ก์„ธ์Šคํ•˜๊ธฐ ์œ„ํ•ด ์  ํ‘œ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•˜์—ฌ ์–ด๋–ค ์†์„ฑ์ด ๊ฐ์ฒด์˜ ์ผ๋ถ€์ธ์ง€ ๋ฏธ๋ฆฌ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ด์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. .

Immutable.js๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  redux ๊ฐœ๋ฐœ์„ ์•„๋ฌด๋Ÿฐ ๋ฌธ์ œ ์—†์ด ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹จ์ˆœํžˆ ์šฐ์—ฐํžˆ ๊ฐ์ฒด๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์‚ฌ์‹ค์€ redux์˜ ํฐ ์žฅ์ ์ด๋ฉฐ ๊ฐ์ฒด ํ”„๋กœํ† ํƒ€์ž…์„ ์ •์˜ํ•˜๋Š” '์œ ํ˜• ์•ˆ์ „์„ฑ'๋„ ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ •๋ง๋กœ ๊ฐ€์ž… ์ค‘๊ฐ„ ์žฌ๋กœ๋“œ๋ฅผ ์ง€์›ํ•˜๋ ค๋ฉด ์ด๋Ÿฌํ•œ ๋ชจ๋“  ๋‹จ๊ณ„๋ฅผ ์ œ์–ดํ•˜๋Š” โ€‹โ€‹์ƒ์œ„ ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ํ•ด๋‹น ์ƒํƒœ๋ฅผ localStorage์— ์ €์žฅํ•˜๋„๋ก ํ•˜์„ธ์š”.

@ir-์—ฐ๋ฃŒ
๋‚˜์—๊ฒŒ localStorage์— ๋ฌผ๊ฑด์„ ๋„ฃ๋Š” ๊ฒƒ์€ ๋ถ€์ž‘์šฉ์ฒ˜๋Ÿผ ๋ณด์ด๋ฉฐ ๊ตฌ์„ฑ ์š”์†Œ์—์„œ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์€ ์ข‹์€ ๋ฐฉ๋ฒ•์ด ์•„๋‹Œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋ฌธ์ œ๋Š” ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ช‡ ํ™”๋ฉด์—์„œ๋งŒ ํ•„์š”ํ•œ ๊ฒƒ์œผ๋กœ ๊ธ€๋กœ๋ฒŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ๋ฅผ ์˜ค์—ผ์‹œํ‚ค๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์•„๋งˆ๋„ ๋‚ด๊ฐ€ ๊ฐ€์งœ API๋ฅผ ์ถฉ๋ถ„ํžˆ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…ํ•˜์ง€ ๋ชปํ–ˆ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์„ ๋นŒ๋“œํ•˜๋Š” ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ด์œ ๋Š” only need in a few screens ๋ฅผ redux์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ์— ๋„ฃ๊ณ  ์‹ถ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. FormPage_A/B์—์„œ๋Š” API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์–‘์‹ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.

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

FormPage_C(์ตœ์ข… ํ˜•์‹)์—์„œ:

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

์ด๋Ÿฌํ•œ ๋ชจ๋“  ์ž„์‹œ ๋ฐ์ดํ„ฐ๋Š” ๊ฐ€์งœ API์— ์žˆ์œผ๋ฉฐ ๊ธ€๋กœ๋ฒŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ๋Š” ์˜ค์—ผ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ๋ฆ„์„ ๋‹ค์‹œ ์‹œ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ€์งœ API์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ง€์šฐ๋ ค๋ฉด ์ž‘์—…์„ ๋ณด๋‚ด๋ฉด ๋ฉ๋‹ˆ๋‹ค.

@sompylasar ๋ช…์‹œ์  ๋ฒ„ํŠผ์œผ๋กœ ์ƒํƒœ๋ฅผ ์žฌ์„ค์ •ํ•˜๋Š” ๋ฐ ๋ฌธ์ œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์˜ค๋ฅ˜ ํ›„์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ƒํƒœ๊ฐ€ ์†์ƒ๋  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์˜ˆ์ธกํ•˜๊ณ  ๊ฐ๊ฐ์˜ ํŠน์ • ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ๋ณต์› ์ „๋žต์„ ์ •์˜ํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

@tremlenye ์ €๋„ ๊ทธ ์ ์— ๋™์˜ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Ÿฌํ•œ ํŠน์ • ์‹คํŒจ ์‚ฌ๋ก€๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋ฌธ์ œ์ผ ๊ฐ€๋Šฅ์„ฑ์ด ๋” ๋†’๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์ƒํƒœ๋ฅผ ์–ด๋””์— ๋‘๋“  ์ƒ๊ด€์—†์ด ํ•ญ์ƒ ์—ฌ๊ธฐ์— ์žˆ์œผ๋ฉฐ ์ ์ ˆํ•œ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์–ด์•ผ ํ•˜์ง€๋งŒ ์ถฉ๋ถ„ํžˆ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…๋˜์ง€๋Š” ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ˆœ๊ฐ„. ๊ฐœ๋ฐœ์ž๊ฐ€ ํ•  ์ˆ˜ ์žˆ๋Š” ์œ ์ผํ•œ ์ผ์€ ์ถ”์ ์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค์–ด ์šฐ๋ฆฌ๊ฐ€ ๋œ ๊ณ ํ†ต์Šค๋Ÿฝ๊ฒŒ ๊ณ ์น  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋ถ€๋ชจ ๊ตฌ์„ฑ ์š”์†Œ์— ์ƒํƒœ๋ฅผ ๋„ฃ์œผ๋ฉด ์ƒํ™ฉ์ด ๋ณต์žกํ•ด์งˆ ๋•Œ ์ถ”๋ก ํ•˜๊ธฐ๊ฐ€ ๋” ์–ด๋ ค์›Œ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€์งœ API๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•ด๋‹น ์ƒํƒœ๋ฅผ ์ž‘์—…์œผ๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  ๋” ์˜ˆ์ธก ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋ณด์ž…๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ๋‚˜๋Š” ๊ฐ€์งœ API๊ฐ€ ๋œ ๋ณต์žกํ•˜๊ณ  ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ์— ๋„ˆ๋ฌด ๋ฌด๊ฑฐ์›Œ ๋ณด์ธ๋‹ค๋Š” ๋ฐ ๋™์˜ํ•ฉ๋‹ˆ๋‹ค.

์œ„์˜ ๋ชจ๋“  ๋ฉ”์‹œ์ง€๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” componentWillReceiveProps์—์„œ ์†Œํ’ˆ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค.

์ƒํƒœ ํ˜•ํƒœ๊ฐ€ { loginPending, loginError }๋ผ๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ๋กœ๊ทธ์ธ์„ ์‹œ์ž‘ํ•  ๋•Œ loginPending = true๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์„ฑ๊ณต ๋˜๋Š” ์‹คํŒจ ์‹œ { loginPending: false, loginError: 'some error.'๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. }.

๊ทธ๋Ÿฐ ๋‹ค์Œ ๊ตฌ์„ฑ ์š”์†Œ์—์„œ ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์ด๋ฒคํŠธ๊ฐ€ ์žˆ๋Š”์ง€ ๊ฐ์ง€ํ•˜๊ณ  ํŽ˜์ด์ง€๋ฅผ ๋ฆฌ๋””๋ ‰์…˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

์•„๋ฆ„๋‹ต์ง€๋Š” ์•Š์ง€๋งŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

react-router-redux ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌ๋””๋ ‰์…˜ํ•  ๋•Œ ์–‘์‹์„ ์ง€์šฐ๋Š” ๊ฒƒ๊ณผ ๊ด€๋ จ๋œ ๊ฒธ์†ํ•œ ๊ตฌํ˜„์ž…๋‹ˆ๋‹ค. ๊ฐ์†๊ธฐ:

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

์ด ์ ‘๊ทผ ๋ฐฉ์‹์ด ๊ดœ์ฐฎ๋‹ค๊ณ  ์ƒ๊ฐํ•˜์‹ญ๋‹ˆ๊นŒ? ํšจ๊ณผ๊ฐ€์žˆ๋‹ค.

๋‚˜๋Š” ์ด๊ฒƒ์œผ๋กœ ๋๋‚ฌ๋‹ค. ๋‹น์‹ ์˜ ์ƒ๊ฐ์„ ๋งํ•ด ๋ณด์„ธ์š”:

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

๋“ฑ๋ก ์–‘์‹ ์ปจํ…Œ์ด๋„ˆ:

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

๊ทธ๋ฆฌ๊ณ  ๋„์šฐ๋ฏธ:

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

์ค‘์š”ํ•œ ๊ฒƒ - ์„ฑ๊ณต์ ์ธ ๋“ฑ๋ก๋งŒ์ด Redux ์ €์žฅ์†Œ์— ์ž‘์—…์œผ๋กœ ๋“ค์–ด๊ฐ€๊ณ , ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์˜ค๋ฅ˜๊ฐ€ ์ปจํ…Œ์ด๋„ˆ ๊ตฌ์„ฑ ์š”์†Œ ์ƒํƒœ๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์—๊ฒŒ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ๋‹ค์Œ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•œ ๋‹ค์Œ ๋‹ค์‹œ ์–‘์‹ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. ์–‘์‹์ด ๋น„์–ด ์žˆ๊ณ  ์˜ค๋ฅ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. "USER_REGISTRATION_REQUEST" ๋˜๋Š” "USER_REGISTRATION_FAILURE" ์ž‘์—…์€ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ผ ๊ตฌ์„ฑ ์š”์†Œ ์ƒํƒœ๋ฅผ ์„ค๋ช…ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์—†์Šต๋‹ˆ๋‹ค.

@sunstorymvp

async / await ๋กœ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ–ˆ์œผ๋ฉฐ ์ด ์„คํƒ•์€ ์š”์ฒญ์˜ ์„ธ ๊ฐ€์ง€ ์ƒํƒœ(before, in progress, after)๊ฐ€ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์ˆจ๊น๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ƒํƒœ๋Š” ๋ณด๊ธฐ ๊ตฌ์„ฑ ์š”์†Œ์—์„œ ์ˆจ๊ฒจ์ง‘๋‹ˆ๋‹ค. ์š”์ฒญ ์‹œ์ž‘์€ ๋ฌด์‹œ๋˜๊ณ  ์š”์ฒญ ์˜ค๋ฅ˜๋Š” ๋ณด๊ธฐ์—์„œ ์ฒ˜๋ฆฌ๋˜๊ณ  redux๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์—ฌ๊ธฐ์—์„œ ์•ฑ ์ƒํƒœ๋Š” ๋น„์ฆˆ๋‹ˆ์Šค๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ์–ด๋–ค ์ด์œ ๋กœ ์ด ์ƒํƒœ๋ฅผ ํฌํ•จํ•˜๋Š” ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ๋งˆ์šดํŠธ ํ•ด์ œ๋˜์—ˆ๋‹ค๊ฐ€ ๋‹ค์‹œ ๋งˆ์šดํŠธ๋˜๋ฉด ๋“ฑ๋ก ์š”์ฒญ ์ƒํƒœ๊ฐ€ ์†์‹ค๋˜๊ณ  ๊ทธ ๊ฒฐ๊ณผ๊ฐ€ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์•„ ํ›„์† ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์˜ˆ: ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฏธ ๋“ฑ๋ก๋จ).

์‚ฌ์šฉ์ž์—๊ฒŒ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ๋‹ค์Œ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•œ ๋‹ค์Œ ๋‹ค์‹œ ์–‘์‹ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. ์–‘์‹์ด ๋น„์–ด ์žˆ๊ณ  ์˜ค๋ฅ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ๋‹ฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ์ €์žฅ์†Œ์—์„œ ์˜ค๋ฅ˜๋ฅผ ์žฌ์„ค์ •ํ•˜๋Š” USER_REGISTRATION_RESET ์™€ ๊ฐ™์€ ์ž‘์—…์„ ๊ตฌ์„ฑ ์š”์†Œ ๋งˆ์šดํŠธ์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ž‘์—…์€ ๋ณด๋ฅ˜ ์ค‘์ธ ๋“ฑ๋ก ์š”์ฒญ์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ฒ˜๋ฆฌ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋ณด๋ฅ˜ ์ค‘์ธ ์š”์ฒญ ๊ฒฐ๊ณผ๊ฐ€ ์•ฑ์—์„œ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

ํ”ผ๋“œ๋ฐฑ์— ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

async / await๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋“œ์— ์ถ”๊ฐ€ํ–ˆ์œผ๋ฉฐ ์ด ์„คํƒ•์€ ์š”์ฒญ์˜ ์„ธ ๊ฐ€์ง€ ์ƒํƒœ(before, in progress, after)๊ฐ€ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์ˆจ๊น๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์กฐ๊ฑด์€ ๋‚ด๊ฐ€ async/await๋ฅผ ์‚ฌ์šฉํ•˜๋Š”์ง€ ์—ฌ๋ถ€์— ๊ด€๊ณ„์—†์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์ƒํƒœ๋Š” ๋ณด๊ธฐ ๊ตฌ์„ฑ ์š”์†Œ์—์„œ ์ˆจ๊ฒจ์ง‘๋‹ˆ๋‹ค.

์˜๋„ํ•œ ๋Œ€๋กœ ๊ตฌ์„ฑ ์š”์†Œ ์ƒํƒœ์ž…๋‹ˆ๋‹ค. ์™œ ์ด๊ฒƒ์ด ์•ฑ ์ƒํƒœ์—ฌ์•ผ ํ•ฉ๋‹ˆ๊นŒ?

์š”์ฒญ ์‹œ์ž‘์ด ๋ฌด์‹œ๋ฉ๋‹ˆ๋‹ค.

์ง„ํ–‰ ์ค‘...

์š”์ฒญ ์˜ค๋ฅ˜๋Š” ๋ณด๊ธฐ์—์„œ ์ฒ˜๋ฆฌ๋˜๊ณ  redux๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์˜๋„ ํ•œ๋Œ€๋กœ

์–ด๋–ค ์ด์œ ๋กœ ์ด ์ƒํƒœ๋ฅผ ํฌํ•จํ•˜๋Š” ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ๋งˆ์šดํŠธ ํ•ด์ œ๋˜์—ˆ๋‹ค๊ฐ€ ๋‹ค์‹œ ๋งˆ์šดํŠธ๋˜๋ฉด ๋“ฑ๋ก ์š”์ฒญ ์ƒํƒœ๊ฐ€ ์†์‹ค๋˜๊ณ  ๊ทธ ๊ฒฐ๊ณผ๊ฐ€ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๊ทธ๊ฒƒ์€ ์‚ฌ์‹ค์ด ์•„๋‹™๋‹ˆ๋‹ค. ๊ตฌ์„ฑ ์š”์†Œ ์ƒํƒœ ๋˜๋Š” ์กด์žฌ ์—ฌ๋ถ€์— ๊ด€๊ณ„์—†์ด ์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ๋“ฑ๋ก๋˜๊ณ  ๋ฆฌ๋””๋ ‰์…˜๋ฉ๋‹ˆ๋‹ค. register ์•ก์…˜ ์ƒ์„ฑ๊ธฐ(handleData)๋ฅผ ๋ณด์‹ญ์‹œ์˜ค.

์ด๋ฅผ ๋‹ฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ์ž‘์—…์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค ...

๊ทธ๊ฒƒ์ด ํ•ต์‹ฌ์ด์•ผ. ๋˜ ๋‹ค๋ฅธ ํ–‰๋™? ์™œ ๊ทธ๋ ‡๊ฒŒ ๋งŽ์€ ์ƒ์šฉ๊ตฌ? ์ œ์ถœ ๋ฒ„ํŠผ์„ ๋น„ํ™œ์„ฑํ™” ๋˜๋Š” ํ™œ์„ฑํ™”ํ•˜๊ณ , ์–‘์‹ ์˜ค๋ฅ˜ ๋˜๋Š” ์‹ค์‹œ๊ฐ„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ‘œ์‹œํ•˜๊ณ , ์š”์ฒญ์ด ์—ด๋ ค ์žˆ๋Š” ๋™์•ˆ ์Šคํ”ผ๋„ˆ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ž‘์—…์€ ๊ธฐ๋ณธ ์ƒํƒœ๋ฅผ ๋ณต์›ํ•˜๋Š” ์ƒˆ๋กœ์šด ์ž‘์—…์œผ๋กœ ์ด์–ด์ง‘๋‹ˆ๋‹ค. ์™œ ์ด๊ฒƒ์ด ์•ฑ ์ƒํƒœ์—ฌ์•ผ ํ•ฉ๋‹ˆ๊นŒ?

๊ทธ๊ฒƒ์€ ์‚ฌ์‹ค์ด ์•„๋‹™๋‹ˆ๋‹ค. ๊ตฌ์„ฑ ์š”์†Œ ์ƒํƒœ ๋˜๋Š” ์กด์žฌ ์—ฌ๋ถ€์— ๊ด€๊ณ„์—†์ด ์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ๋“ฑ๋ก๋˜๊ณ  ๋ฆฌ๋””๋ ‰์…˜๋ฉ๋‹ˆ๋‹ค. ๋ ˆ์ง€์Šคํ„ฐ ์•ก์…˜ ์ƒ์„ฑ์ž(handleData)๋ฅผ ์‚ดํŽด๋ณด์„ธ์š”.

์˜ˆ, ํ•˜์ง€๋งŒ ์š”์ฒญ ์ƒํƒœ๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ์—์„œ ์ถ”์ ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋‘ ๊ฐœ์˜ ํ›„์† register ์š”์ฒญ ๊ฐ€๋Šฅ์„ฑ์ด ๋‚จ์Šต๋‹ˆ๋‹ค.

์ œ์ถœ ๋ฒ„ํŠผ์„ ๋น„ํ™œ์„ฑํ™” ๋˜๋Š” ํ™œ์„ฑํ™”ํ•˜๊ณ , ์–‘์‹ ์˜ค๋ฅ˜ ๋˜๋Š” ์‹ค์‹œ๊ฐ„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ‘œ์‹œํ•˜๊ณ , ์š”์ฒญ์ด ์—ด๋ ค ์žˆ๋Š” ๋™์•ˆ ์Šคํ”ผ๋„ˆ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ž‘์—…์€ ๊ธฐ๋ณธ ์ƒํƒœ๋ฅผ ๋ณต์›ํ•˜๋Š” ์ƒˆ๋กœ์šด ์ž‘์—…์œผ๋กœ ์ด์–ด์ง‘๋‹ˆ๋‹ค. ์™œ ์ด๊ฒƒ์ด ์•ฑ ์ƒํƒœ์—ฌ์•ผ ํ•ฉ๋‹ˆ๊นŒ?

์˜ˆ์ธก ๊ฐ€๋Šฅ์„ฑ๊ณผ ์žฌ์ƒ ๊ฐ€๋Šฅ์„ฑ์„ ์œ„ํ•ด ์ด๊ฒƒ์ด Redux์˜ ๋ชฉ์ ์ž…๋‹ˆ๋‹ค. ์•ก์…˜๊ณผ ์ดˆ๊ธฐ ์ƒํƒœ๋งŒ ์žˆ์œผ๋ฉด ์•ก์…˜๋งŒ ๋””์ŠคํŒจ์น˜ํ•˜์—ฌ ์•ฑ์„ ํŠน์ • ์ƒํƒœ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ตฌ์„ฑ ์š”์†Œ์— ์ผ๋ถ€ ์ƒํƒœ๊ฐ€ ํฌํ•จ๋˜๊ธฐ ์‹œ์ž‘ํ•˜๋ฉด ์•ฑ ์ƒํƒœ๊ฐ€ ๋ถ„๋ฆฌ๋˜๊ธฐ ์‹œ์ž‘ํ•˜๊ณ  ์•ฑ ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๋‹จ์ผ ์ƒํƒœ ํŠธ๋ฆฌ๊ฐ€ ์—†๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ๊ทธ๊ฒƒ์— ๋Œ€ํ•ด ์ง€๋‚˜์น˜๊ฒŒ ๋…๋‹จ์ ์ด์ง€ ๋งˆ์‹ญ์‹œ์˜ค. ์‹œ๋‚˜๋ฆฌ์˜ค์— ๋”ฐ๋ผ Redux ์ƒํƒœ๋กœ ํ•ญ๋ชฉ์„ ๋„ฃ์–ด์•ผ ํ•˜๋Š” ์ด์œ ์™€ ๋กœ์ปฌ ๊ตฌ์„ฑ ์š”์†Œ ์ƒํƒœ๋กœ ๋‘๋Š” ์ด์œ ๊ฐ€ ๋งŽ์ด ์žˆ์Šต๋‹ˆ๋‹ค. http://redux.js.org/docs/FAQ.html#organizing -state-only-redux-state ๋ฐ https://news.ycombinator.com/item?id=11890229๋ฅผ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค.

์ข‹์•„, ์ด์ œ ๋‚˜์—๊ฒŒ ๋ถ„๋ช…ํ•ด. ๊ทธ๊ฒƒ์€ ๋‹ฌ๋ ค์žˆ๋‹ค. ๊ฐ์‚ฌ ํ•ด์š”!

componentWillReceiveProps ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ๊ตฌ์„ฑ ์š”์†Œ์— auth ๊ฐœ์ฒด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ์ „ํ˜€ ๋„์›€์ด ๋˜๋Š”์ง€ ํ™•์‹คํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

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

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฌธ์ œ๊ฐ€ ์—†์„๊นŒ์š”?

๊ทธ๋Ÿฌํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์€ ์–ด๋–ป์Šต๋‹ˆ๊นŒ http://codereview.stackexchange.com/questions/138296/implementing-redirect-in-redux-middleware?
๋ฏธ๋“ค์›จ์–ด์˜ react-router-redux push ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌ๋””๋ ‰์…˜ URL์„ ์ž‘์—…์œผ๋กœ ์ „๋‹ฌํ•˜๊ณ  next ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€๋กœ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰