ํธ์ง: ์ฌ๊ธฐ์ ๋ด๊ฐ ์ดํดํ๋ ค๊ณ ํ๋ ๊ฒ์ ์์์ด ์๋ ํ์ด์ง๊ฐ ์์ ๋ ๋ฐ์ดํฐ ํ๋ฆ์ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋๋ค. ์์์ ์ ์ถํ ํ ๋ค๋ฅธ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์ ํ๊ฑฐ๋ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ํ์ํฉ๋๋ค .
๋ฌธ์ ๋ ์ด ์ ๋ณด๊ฐ ๋ฐ์ดํฐ ํ๋ฆ์์ _์์ ์ ์ฅ_๋์ด์ผ ํ๋ ์์น์ ๋๋ค. ๋ฉ์์ง๋ฅผ ๋ฆฌ๋๋ ์ ํ๊ฑฐ๋ ๋ ๋๋งํ ํ์๋ ์ํ(๋๋ ์ ์ฅํ ์ ๋ณด)๊ฐ ๋ ์ด์ ๊ด๋ จ์ด ์์ผ๋ฏ๋ก ์ฌ์ค์ ํ๊ฑฐ๋ ์ ๋ฆฌํด์ผ ํฉ๋๋ค.
๋ฐฑ์๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์๋ ์ผ๋ฐ์ ์ผ๋ก ๋ก๊ทธ์ธ, ๊ฐ์ , ๋น๋ฐ๋ฒํธ ์ฌ์ค์ ๋ฑ๊ณผ ๊ฐ์ ํ์ด์ง๊ฐ ์์ต๋๋ค.
์ฌ์ฉ์๊ฐ ์ฑ์ผ๋ก ์ด๋ํ๊ณ ๋ก๊ทธ์ธํ์ง ์์ ๊ฒฝ์ฐ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์
๋ฉ๋๋ค. ์ด๊ฒ์ 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(...)
}
}
๋๊ตฌ๋ ์ง์ด ๋ฌธ์ ๊ฐ ์์ต๋๊น? ๋ด๊ฐ ๋ญ๊ฐ๋ฅผ ๋์น๊ณ ์๊ฑฐ๋ ๊ทธ๊ฒ์ด "์ฌ๋ฐ๋ฅธ"๋ฐฉ๋ฒ์ ๋๊น?
๋ค๋ฅธ ๋์์ด ์์ต๋๊น?
์ด๋ค ํผ๋๋ฐฑ์ด๋ ํ์ํฉ๋๋ค! :)
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
๋ฉ์๋๋ฅผ ์ถ๊ฐ๋ก ํธ์ถํฉ๋๋ค.
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
์ง๋ฌธ์ด '์์ ์ ์ถ'์ ์ผ๋ฐํํ๋ ๊ฒ๋ณด๋ค ์กฐ๊ธ ๋ ๊น๋ค๊ณ ์๊ฐํฉ๋๋ค. ์ค์ ๋ก ์ํ๊ฐ ๋ฌด์์ด๋ฉฐ ์ด๋ค ๋ฐ์ดํฐ๊ฐ
store
์ ๋ํ๋์ผ ํ๊ณ ์ด๋ค ๋ฐ์ดํฐ๊ฐ ํ์๋์ง ์์์ผ ํ๋์ง์ ๋ํ ๊ฒ์ ๋๋ค.๋๋ ํ๋ญ์ค ๋ฐ์ดํฐ ํ๋ฆ์
View
๋ถ๋ถ์ ์์ ํจ์render(state): DOM
์๊ฐํ๊ณ ์ถ์ง๋ง ์์ ํ ์ฌ์ค์ ์๋๋๋ค.๋ ๊ฐ์ ์ ๋ ฅ ํ๋๋ฅผ ๋ ๋๋งํ๋ ๊ฐ๋จํ ์์ ๊ตฌ์ฑ ์์์ ์๋ฅผ ๋ค์ด๋ณด๊ฒ ์ต๋๋ค.
์ด ํ๋์ ์ ๋ ฅํ๋ ๋์ ์ผ๋ถ ๋ด๋ถ ์ํ ์์ ์ด ์ํ๋์ด ๋ง์ง๋ง์ ๋ค์๊ณผ ๊ฐ์ด ๋ฉ๋๋ค.
๊ทธ๋์ ์ฐ๋ฆฌ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ํ์ ์ฐ๊ฒฐ๋์ง ์์ ์ํ๋ฅผ ์ป์๊ณ ์๋ฌด๋ ๊ทธ๊ฒ์ ๋ํด ์ ๊ฒฝ ์ฐ์ง ์์์ต๋๋ค. ๋ค์์ ์ด ์์์ ๋ค์ ๋ฐฉ๋ฌธํ๋ฉด ์ง์์ง ๊ฒ์ผ๋ก ํ์๋๊ณ 'John Snow'๋ ์์ํ ์ฌ๋ผ์ง ๊ฒ์ ๋๋ค.
ํ๋ญ์ค ๋ฐ์ดํฐ ํ๋ฆ์ ๊ฑด๋๋ฆฌ์ง ์๊ณ DOM์ ์์ ํ ๊ฒ ๊ฐ์ต๋๋ค.
์ด๊ฒ์ด ์ด๋ค ํต์ง์์ ๋ํ ๊ตฌ๋ ์์์ด๋ผ๊ณ ๊ฐ์ ํฉ์๋ค. ์ด์ ์ฐ๋ฆฌ๋ ์ธ๋ถ ์ธ๊ณ์ ์ํธ ์์ฉํด์ผ ํ๋ฉฐ ํน์ ์์ ์ ์ฌ์ฉํ์ฌ ์ด๋ฅผ ์ํํฉ๋๋ค.
์ฌ์ฉ์๊ฐ ์ ์ถ ๋ฒํผ์ ๋๋ฅด๋ฉด UI์ ์๋ต์ด ์ฑ๊ณตํ์ง ๋ชปํ๊ณ ๋ค์์ ๋ฌด์์ ํด์ผ ํ๋์ง ์์ํฉ๋๋ค. ๋ฐ๋ผ์ ๋ก๋ฉ ํ๋ฉด์ ๋ ๋๋งํ๊ฑฐ๋ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋ฆฌ๋๋ ์ ํ๊ฑฐ๋ ํ์ํ๊ธฐ ์ํด ์์ฒญ ์ํ๋ฅผ ์ถ์ ํ๊ณ ์ถ์ต๋๋ค.
์ฒซ ๋ฒ์งธ ์์ด๋์ด๋
loading
,submitted
,failed
์ ๊ฐ์ ์ผ๋ถ ๋ด๋ถ ์์ ์ ์ ๋ฌํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ํ๊ฐ ์ด์ ๋ํ ์ฑ ์์ ์ง๋๋ก ํ๋ ๊ฒ์ ๋๋ค. ์ฐ๋ฆฌ๋ ์ํ๋ฅผ ๋ ๋๋งํ๊ณ ์ธ๋ถ ์ฌํญ์ ๋ํด ์๊ฐํ์ง ์๊ธฐ ๋๋ฌธ์ ๋๋ค.๊ทธ๋ฌ๋ ์ฌ๊ธฐ์ ์ ๋ง๊ฐ ๋ํ๋ฉ๋๋ค. ์ด์ ์ด ํ์ด์ง ์ํ๊ฐ ์ ์ง๋๊ณ ๋ค์
render(state)
ํธ์ถ์ ๋ํฐ ์ํ๋ก ํผ์ ํ์ํ๋ฏ๋ก ํผ์ ๋ง์ดํธํ ๋๋ง๋ค ํธ์ถํ๊ธฐ ์ํดcleanup
์ก์ ์ ๋ง๋ค์ด์ผ ํฉ๋๋ค.๊ทธ๋ฆฌ๊ณ ๊ฐ ์์์ ๋ํด
loading
,submitted
,failed
๋ฐcleanup
์์ ๊ณผ store/reducersโฆ ์ฐ๋ฆฌ๊ฐ ์ค์ ๋ก ๋ ๋๋งํ๋ ค๋ ์ํ์ ๋ํด.submitted
๋ ์ ํ๋ฆฌ์ผ์ด์ ์ํ์ ์ํฉ๋๊น ์๋๋ฉดname
ํ๋ ๊ฐ๊ณผ ๊ฐ์ ๊ตฌ์ฑ ์์ ์ํ์ผ ๋ฟ์ ๋๋ค. ์ฌ์ฉ์๊ฐ ์ด ์ํ๋ฅผ ์บ์์ ์ ์งํ๋ ๋ฐ ์ ๋ง๋ก ๊ด์ฌ์ด ์์ต๋๊น? ์๋๋ฉด ๋ค์ ๋ฐฉ๋ฌธํ ๋ ๊นจ๋ํ ํ์์ ๋ณด๊ณ ์ถ์ดํฉ๋๊น? ๋ต: ์์กดํฉ๋๋ค.๊ทธ๋ฌ๋ ์์ํ ์ ์ถ ์์์ ๊ฒฝ์ฐ ์์ ์ํ๊ฐ ์์ฉ ํ๋ก๊ทธ๋จ ์ํ์ ์๋ฌด ๊ด๋ จ์ด ์๋ค๊ณ ๊ฐ์ ํ ์ ์์ต๋๋ค
logged in
๊ทธ๊ฐ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ณ๊ฒฝํ๊ฑฐ๋ ์คํจํ๋์ง์ ๋ํ ์ ๋ณด๋ฅผ ์ป์ผ๋ ค๋ฉด ์์ฒญ์ด ์ฑ๊ณตํ๋ฉด ๊ทธ๋ฅผ ๋ค๋ฅธ ๊ณณ์ผ๋ก ๋ฆฌ๋๋ ์ ํ์ญ์์ค.๊ทธ๋ ๋ค๋ฉด Component ์ด์ธ์ ๋ค๋ฅธ ๊ณณ์์๋ ์ด ์ํ๊ฐ ํ์ํ์ง ์๋ค๋ ์ ์์ ์ด๋ป์ต๋๊น? ์๋ํด ๋ณด๊ฒ ์ต๋๋ค.
๋ ๋ฉ์ง ๊ตฌํ์ ์ ๊ณตํ๊ธฐ ์ํด ์ฐ๋ฆฌ๋ 2015๋ ์ ์ฐ๋ฆฌ๊ฐ ๋น๋๊ธฐ์ ์ธ๊ณ์ ์ด๊ณ ์๊ณ ์ด๋์๋
Promises
๊ฐ ์๋ค๊ณ ๊ฐ์ ํ ์ ์์ผ๋ฏ๋ก ์ฐ๋ฆฌ์ ์์ ์ดPromise
๋ฅผ ๋ฐํํ๋๋ก ํฉ๋๋ค.๋ค์ ๋จ๊ณ์์๋
Component
์์ ์์ ์ ์ถ์ ํ๊ธฐ ์์ํ ์ ์์ต๋๋ค.์ ํ๋ฆฌ์ผ์ด์ ์ํ๋ฅผ ์ค์ผ์ํค์ง ์๊ณ ๊ตฌ์ฑ ์์๋ฅผ ๋ ๋๋งํ๋ ๋ฐ ํ์ํ ๋ชจ๋ ๊ฒ์ ๊ฐ์ถ๊ณ ์์ผ๋ฉฐ ์ฒญ์ํ ํ์๊ฐ ์๋ ๊ฒ ๊ฐ์ต๋๋ค.
๊ฑฐ์ ๋ค ์์ต๋๋ค. ์ด์
Component
ํ๋ฆ์ ์ผ๋ฐํํ ์ ์์ต๋๋ค.์ฐ์ ์์ ๋ฐ ์ํ ์ ๋ฐ์ดํธ ๋ฐ์์ ์ถ์ ํ๋ ๋ฌธ์ ๋ก ๋ถํ ํ ์ ์์ต๋๋ค.
(repo ๋ฒ์์ ๋จธ๋ฌผ๊ธฐ ์ํด
redux
์ ๊ด์ ์์ ๊ทธ๊ฒ์ ํ ์ ์์ต๋๋ค)๊ทธ๋ฆฌ๊ณ
์ด์
decorator
onSubmit
submitted
๋ฐerror
์ํ์ ์ ๊ณตํ๋ decorator์ ๋ค์๊ณผ ๊ฐ์ ๊ฒฝ์ฐ ์ด๋๊ฐ๋ก ๋ฆฌ๋๋ ์ ๋๋decorator
๊ฐ ์์ต๋๋ค.submitted === true
์์ฑ์ ๊ฐ์ ธ์ต๋๋ค. ๊ทธ๊ฒ๋ค์ ์ฐ๋ฆฌ์ ์์์ ์ฒจ๋ถํ๊ณ ์ฐ๋ฆฌ๊ฐ ์ํ๋ ๊ฒ์ ๋ด ์๋ค.routing
๋ผ์ด๋ธ๋ฌ๋ฆฌ,flux
๊ตฌํ, ์ ํ๋ฆฌ์ผ์ด์ state
์ ์ข ์์ฑ์ด ์๋ ์์ ๋ ๋๋ง ํ์, ์ ์ถ ๋ฐ ๋ฆฌ๋๋ ์ ์ ์ํด ์์ฑ๋ ์์ ์ ์ํํฉ๋๋ค.์ฝ์ด์ฃผ์ ์ ๊ฐ์ฌํ๊ณ ์๊ฐ ๋ด์ ์ฃ์กํฉ๋๋ค.