РЕДАКТИРОВАТЬ: я пытаюсь понять, как обрабатывать поток данных, когда у вас есть страница с формой. После отправки формы вы либо перенаправляете на другую страницу, либо выдаете сообщение об ошибке .
Проблема заключается в том, где эта информация должна быть _временно сохранена_ в потоке данных. Потому что после того, как вы перенаправите или отобразите сообщение, состояние (или информация, которую вы сохранили) больше не имеет значения и должно быть сброшено или очищено.
В серверном веб-приложении у вас обычно есть такие страницы, как вход в систему, регистрация, сброс пароля и т. д.
Когда пользователь переходит в приложение и не вошел в систему, он перенаправляется на страницу входа. Это легко сделать с помощью хука RR onEnter
.
Затем пользователь заполнит форму и отправит ее. На этом этапе обычно, если запрос был успешным, вы хотите перенаправить в корень приложения или на другую «защищенную» страницу. Если это не удалось, вы хотите остаться на странице и показать сообщение об ошибке.
(Это относится не только к странице входа, но и к другим страницам, а также упомянутым выше. Сейчас я сосредоточусь на входе в систему)
Пока у вас есть этот поток (который является распространенным вариантом использования):
/
/login
/
(или на другую страницу)Если мы реализуем этот поток с избыточностью, я думаю, у меня есть следующая настройка:
// 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>
)
}
}
Учитывая, что этот поток правильный - надеюсь :) - вы можете видеть, что у меня есть 2 флага в состоянии приложения: shouldRedirect
и errorMessage
.
Эти флаги используются только при отправке формы.
Первый вопрос: нормально ли иметь эти флаги в состоянии приложения?
Еще одна вещь, которую следует учитывать, это то, что я должен «сбросить» эти флаги при перенаправлении, потому что, если я хочу перейти на другую страницу (например, зарегистрироваться, ...), у меня должно быть чистое состояние ( { 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
? Таким образом, вы можете перенаправить со своей страницы входа на любую другую страницу, отметив isLoggedIn
вместо shouldRedirect
.
Кроме того, сброс состояния можно выполнить, просто обработав действие LOGGED_IN
в редюсере. Если это действие произойдет, вы знаете, что можете безопасно сбросить состояние ошибки до значения null
.
Имеет ли это смысл? :D
Это имеет смысл, если есть только страница входа, но это немного более общий подход. Например, для страницы регистрации или страницы сброса пароля. Даже если вы вошли в систему, вы все равно сможете видеть эти страницы.
И эти флаги должны использоваться всеми этими страницами, потому что все они имеют одинаковый поток.
Надеюсь, теперь стало понятнее :)
Да, с моей точки зрения, «shouldRedirect» является слишком общим, чтобы ассоциироваться с определенным действием. Это может вызвать ошибки, когда вы каким-то образом забываете установить этот флаг, а затем страница перенаправляется на другую страницу, на которую она на самом деле не должна перенаправляться.
Но, конечно, если вы хотите очистить свое состояние, вы можете просто сделать это с помощью действия. Или вы можете прослушать изменение истории вне реакции, а затем запустить действие по очистке состояния. Это может быть предпочтительнее, потому что вам не нужно разбрасывать эту логику по всем компонентам, но вы можете хранить ее в одном месте.
Но это только возможное решение для государственной очистки. Я еще не уверен, как решить проблему перенаправления.
Также возникает вопрос: есть ли смысл вообще иметь эти флаги в состоянии? Если нет, то как реализовать этот поток?
@gaearon , если у вас есть время, я также хотел бы услышать ваши отзывы. Спасибо! :+1:
В моем приложении я перенаправляюсь в промежуточном программном обеспечении и не использую никаких флагов. Но я не использую реактивный маршрутизатор.
// 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);
};
}
Он не подвергался рефакторингу, он предназначен только для тестирования моего маршрутизатора :).
Это копирование и вставка из моего приложения и то, как я обрабатываю загрузку пользовательских данных и вход в систему. Я постараюсь прокомментировать код как можно лучше, и я буду реагировать на вопросы.
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
в состоянии приложения — это нормально, просто убедитесь, что оно находится в состоянии входа в систему, например state.auth.errorMessage
, а не errorMessage
верхнего уровня.
Я думаю, что наличие shouldRedirect в вашем состоянии неприемлемо и должно быть сделано в более функциональном подходе.
Я согласен. Вещи, которые полезны только один раз, не должны находиться в состоянии ИМО.
Кроме того, в соответствии со слабиной, я не думаю, что вы должны хранить isLoggedIn
в состоянии приложения, так как это более реально вычисляемое свойство, которое можно вычислить с помощью простого селектора, такого как const isUserLoggedIn = state => state.user.id !== null
См. https://github.com/faassen/reselect для получения дополнительной информации об использовании селектора.
@frederickfogerty спасибо за пример.
Однако вопрос: это отлично работает для входа в систему, потому что у вас обычно есть флаг loggedIn
в state
. Но как насчет других страниц, таких как регистрация, сброс пароля и т. д.?
Эти страницы ведут себя так же, как и вход в систему: если все в порядке, перенаправить, в противном случае показать сообщение об ошибке.
Итак, как вы справляетесь с такими случаями? Я изо всех сил пытаюсь найти правильный поток для этих случаев, и я не уверен, где должно жить состояние.
Вещи, которые полезны только один раз, не должны находиться в состоянии ИМО.
@gaearon Я согласен, но я все еще не уверен, какой правильный подход для этого. Вам все еще нужно где-то иметь состояние...
Вот как я это делаю, ничего не сохраняя в состоянии.
// 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. Пока ничего не видно, потому что я делаю это в свободное время:
https://auth0.com/blog/2015/04/09/добавление-аутентификации-к-вашему-реагированию-потоку-приложению/
Это чистый поток:
https://github.com/auth0/react-flux-jwt-authentication-sample
Может быть, это полезно для лучших практик?!
@эмменко
Итак, как вы справляетесь с такими случаями?
Пока вы используете вход в систему, isLoggedIn
(который не находится в состоянии приложения, но незначителен) все еще изменяется, и это перенаправит их. Неважно, откуда произошло это изменение состояния :)
Лучший подход, который я использовал, - добавить эту логику в коннектор, где функциональность необходима для его дочерних элементов, например, для авторизации, скажем, у вас есть компонент AppProtected
, где все, что является дочерним элементом этого компонента, нуждается в авторизации. , то вы помещаете эту логику в коннектор компонента AppProtected.
Вот как я это делаю, ничего не сохраняя в состоянии.
Мне определенно не нравится этот подход, который имеет двусторонний поток данных между пользовательским интерфейсом и 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'
}
Итак, мы получили какое-то состояние, которое не связано с состоянием приложения, и никому до него нет дела. Когда мы вернемся к этой форме в следующий раз, она будет показана стертой, а «Джон Сноу» исчезнет навсегда.
Похоже, мы модифицировали 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>
)
}
}
Когда пользователь нажимает кнопку отправки, он ожидает какого-либо ответа от пользовательского интерфейса, если это не удалось, и что делать дальше. Итак, мы хотим отслеживать состояние запроса для отображения экрана загрузки, перенаправления или отображения сообщения об ошибке.
Первая идея состоит в том, чтобы отправить некоторые внутренние действия, такие как 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
действий с хранилищами/редьюсерами… пока мы не остановимся и не подумаем о состоянии, которое мы на самом деле хотим отобразить.
Принадлежит ли submitted
к состоянию приложения или это просто состояние компонента, например значение поля name
. Действительно ли пользователь заботится о том, чтобы это состояние сохранялось в кеше, или он хочет видеть чистую форму при повторном посещении? Ответ: это зависит.
Но в случае чистых форм отправки мы могли бы предположить, что состояние формы не имеет ничего общего с состоянием приложения: мы хотим знать, является ли факт пользователем logged in
или нет в случае страницы входа, или мы не хотим чтобы узнать что-нибудь о том, изменил ли он пароль или потерпел неудачу: если запрос увенчается успехом, просто перенаправьте его куда-нибудь еще.
Так как насчет предложения, что нам не нужно это состояние где-то еще, кроме Компонента? Давайте попробуем.
Чтобы обеспечить более причудливую реализацию, мы могли бы предположить, что в 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
.
Прежде всего, давайте разделимся на проблемы: отслеживание действий и реакций на обновление состояния:
(Давайте сделаем это с точки зрения 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
, которые предоставляют пропсы submitted
и error
с обратным вызовом onSubmit
, а с другой стороны 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 спасибо за чтение :)
На самом деле сейчас я думаю о предложении @gaearon, которое он упомянул в предыдущем комментарии. Взгляните на обсуждение № 159, чтобы получить некоторые важные идеи.
В заключение — когда выйдет RR 1.0, мы расскажем об этом в рецепте «Использование с React Router».
Это можно отследить на https://github.com/rackt/redux/issues/637.
@stremlenye, у тебя потрясающий подход! Имеет ли смысл - вместо использования декоратора - использовать компонент более высокого порядка реакции-маршрутизатора, в который будут вложены все _unathorized_, и он будет отвечать за перенаправление, как только в хранилище авторизации будет пользователь?
<ReduxRouter>
<Route component={ Auth }>
<Route path="/" component={ Login } />
<Route path="/reset-password" component={ ResetPassword } />
</Route>
</ReduxRouter>
Что-то вроде этого, где Auth
заботится только о том, чтобы определить, вошел ли пользователь в систему. Таким образом, я все еще могу манипулировать своим магазином, отправляя действия из компонента Login
. Не так чисто, но меньше кода и легче разобраться :)
@tomazzaman Спасибо :)
Большинство приложений React, которые я написал, включали что-то вроде ApplicationContainer
, которые можно было использовать для той же цели без новой реализации Route
.
Если вы используете RR-v1.0.0-rc1
, вы можете достичь тех же целей с помощью хука onEnter
: просто проверьте, соответствует ли текущее состояние аутентифицированному или нет.
Я тоже ломал голову над этим, и, кажется, @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, если вы уже используете его для других компонентов приложения).
@sompylasar я за состояние приложения, я просто думаю, что пока пользователь находится ВНЕ моего приложения, я могу предоставить ему более простое приложение без магазина избыточности. мой магазин редуксов сработает, как только вы войдете в мое приложение.
этот подход работал для меня до этого самого момента, но, возможно, я пойму необходимость состояния приложения на этапе входа в мои будущие проекты
Вы можете на корневом уровне своего магазина разделить свой магазин на часть «приложение» и часть «пользователь/логин», каждая из которых имеет свой редуктор. Таким образом, в этом случае вы никогда не будете загрязнять редукторы приложений кодом редуктора входа в систему и наоборот.
От: SkateFreak < notifications @github.comnotifications@ github.com >
Кому ответить: ract/redux < [email protected] [email protected] >
Дата: Воскресенье, 13 декабря 2015 г., 13:48
Кому: ract/redux < [email protected] [email protected] >
Копия: Йорис Манс < [email protected] [email protected] >
Тема: Re: [redux] Лучшая практика обработки потока данных для страниц входа/регистрации с перенаправлением (#297)
@sompyl asarhttps://github.com/sompylasar Я за состояние приложения, я просто думаю, что пока оно используется ВНЕ моего приложения, я могу предоставить ему более простое приложение без магазина избыточности. мой магазин редуксов сработает, как только вы войдете в мое приложение.
этот подход работал для меня до этого самого момента, но, возможно, я пойму необходимость состояния приложения на этапе входа в мои будущие проекты
Ответьте на это письмо напрямую или просмотрите его на Gi tHubhttps://github.com/rackt/redux/issues/297#issuecomment -164287194.
поправьте меня, если я ошибаюсь, но разве целью хранилища потоков не является обмен данными и состоянием приложения между маршрутами и компонентами? зачем мне «уменьшать» свое состояние и использовать свои «действия» для поддержания верхнего состояния, если все это не имеет значения, когда пользователь входит в систему?
я не вижу преимуществ, хотя это очень организованный и чистый способ делать вещи. я управляю своим состоянием перед входом в свой компонент <Sign />
, который содержит SignIn, SignUp и ForgotPassword.
Потому что регистрация может быть более сложным процессом, чем просто запрос имени пользователя и пароля.
Все зависит от сложности вашего потока.
От: SkateFreak < notifications @github.comnotifications@ github.com >
Кому ответить: ract/redux < [email protected] [email protected] >
Дата: Воскресенье, 13 декабря 2015 г., 14:00
Кому: ract/redux < [email protected] [email protected] >
Копия: Йорис Манс < [email protected] [email protected] >
Тема: Re: [redux] Лучшая практика обработки потока данных для страниц входа/регистрации с перенаправлением (#297)
поправьте меня, если я ошибаюсь, но разве целью хранилища потоков не является обмен данными и состоянием приложения между маршрутами и компонентами? зачем мне «уменьшать» свое состояние и использовать свои «действия» для поддержания верхнего состояния, если все это не имеет значения, когда пользователь входит в систему?
я не вижу преимуществ, хотя это очень организованный и чистый способ делать вещи. я управляю своим состоянием перед входом в свой компонент, который содержит SignIn, SignUp и ForgotPassword.
Ответьте на это письмо напрямую или просмотрите его на Gi tHubhttps://github.com/rackt/redux/issues/297#issuecomment -164287893.
я согласен.
но я вижу SignUp как форму с состоянием, которого должно быть достаточно, независимо от того, насколько сложна imo...
с другой стороны, что я знаю о регистрации...
люди сумасшедшие
В настоящее время я встречаю некоторую похожую, но другую проблему.
Я работаю над таким потоком, как суперсложная регистрация, и у нас есть некоторая страница продолжения, позволяющая пользователю вводить необходимую информацию, каждая страница представляет собой форму. Поток выглядит так:
FormPage_A -> FormPage_B -> FormPage_C -> SuccessPage
Проблема в том, что мы не делаем никаких запросов API перед FormPage_C. Мы просто сохраняем данные в браузере на FormPage_A/B и отправляем их все в API на FormPage_C.
Итак, вопрос в том, должны ли мы помещать данные формы FormPage_A и FormPage_B в состояние приложения?
Если да, похоже, мы сохраняем некоторые временные данные, которые не влияют на пользовательский интерфейс, в состояние приложения. И поскольку состояние приложения redux живет в памяти, что произойдет, когда пользователь обновит страницу в FormPage_B?
Если нет, то куда его поставить? Должны ли мы построить еще один слой, например 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>
@ир- топливо
Спасибо за ваши предложения.
Кажется, мы все не хотим помещать эти временные вещи в состояние приложения.
Но когда пользователь обновляет страницу в половине потока
Просто убедитесь, что если пользователь решит перезагрузить страницу, вы снова начнете с самого начала.
Это кажется не очень хорошим пользовательским интерфейсом, мы мобильный сайт (это одна из причин, почему мы разделяем огромную форму на маленькие страницы), вводить данные на мобильном устройстве неудобно, и я думаю, что пользователь сойдет с ума, если потеряет всю информацию. вещи после нажатия кнопки обновления, может быть, они просто откажутся от этого.
Я думаю, что, возможно, сборка 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);
}
Это проясняет для меня некоторые вещи, но кажется утомительным добавлять что-то из нашего избыточного потока в наше приложение.
Меня действительно не волнует, что пользователь перезагрузит вашу страницу.
С другой стороны, вы говорите нам, что у вас сложный процесс регистрации с несколькими формами, а затем вы говорите, что это мобильный сайт. Не думаете ли вы, что это также сведет пользователей с ума и заставит их сдаться, если им придется заполнять слишком много данных на мобильном устройстве? Я думаю, что это даже хуже, чем отсутствие поддержки перезагрузки. Я не думаю, что многие сайты поддерживают перезагрузку страницы в середине процесса регистрации.
Я думаю, что это просто делает вещи намного сложнее для чего-то, что на самом деле не происходит часто. Я бы больше сосредоточился на попытках упростить процесс регистрации или разделить его на несколько этапов на вашем веб-сайте, чтобы у пользователя не сложилось впечатление, что он заполняет X страниц форм, прежде чем, наконец, активирует свою учетную запись.
И если вы действительно хотите поддерживать перезагрузку во время регистрации, просто сделайте так, чтобы родительский компонент, управляющий всеми этими шагами, сохранял свое состояние в localStorage
.
@kpaxqin Я бы выбрал подход «поддельный API». Зачем подделывать BTW, это просто API промежуточного хранилища, который вам нужен для вашего многоэтапного процесса. Не каждый API обязательно должен быть на сервере и доступен через HTTP или другой сетевой протокол.
В чем я согласен с @ir-fuel, так это в том, что следует сохранять все состояние многоэтапного процесса, а не только текущий шаг.
@ir-fuel Перезагрузка может происходить без явного намерения пользователя, например, пользователь может переключиться на другое мобильное приложение, такое как ноутбук, или на телефонный звонок, и браузер может потерять свое состояние из-за нехватки памяти на устройстве, поэтому он перезагрузится, когда пользователь вернется.
Проблема снова в том, что вы загрязняете глобальное состояние приложения чем-то, что вам нужно только на нескольких экранах в вашем веб-приложении.
@sompylasar @kpaxqin Я думаю, что @ir-fuel предложил правильный подход — если вам нужно что-то промежуточное, чтобы в конечном итоге сохраниться, сохраните его на стороне клиента: гораздо проще реализовать, на самом деле не вовлекает сервер в сохранение противоречивых данных, не не выполнять никаких http-запросов (что также является серьезной проблемой на мобильных устройствах) и чрезвычайно легко изменить подход в будущем.
А насчет сохранения каких-либо данных где-то — подумайте, как вы будете их очищать после сбоя процесса или уже сделанного — это была начальная тема темы.
Еще один вариант, который вы можете использовать — сохранить данные пользователя в URL-адресе. Если ваши формы состоят из пары текстовых полей, это может быть хорошим компромиссным решением.
Это не столько «сохранить его на стороне клиента или сервера», сколько вопрос «обрабатываю ли я этот« специальный »в моем родительском классе компонентов» против «выполняю ли я весь танец действия/редуктора/состояния».
Мне не нравится проходить действие/редукторы, если это временное состояние. Я делаю это только для реального состояния приложения.
@ir-топливо :+1:
@stremlenye Пожалуйста, прочитайте мой ответ еще раз. Я прямо говорю, что вам не нужно создавать API на стороне сервера, чтобы создать слой API сохранения.
@ir-fuel Невыполнение действий/редуктора отменяет предсказуемость и воспроизводимость переходов между состояниями.
@ir-fuel Вы можете легко очистить состояние своего приложения после отправки формы, чтобы оно не «загрязнялось».
РЕ: расчистка. Сделайте действие ЧИСТЫМ и добавьте кнопку в свой пользовательский интерфейс, чтобы сбросить процесс регистрации. Вы также можете сделать это через маршрутизатор (отправьте это действие при посещении исходного URL-адреса регистрации, и если регистрация началась, измените URL-адрес на что-то другое).
@sompylasar нет проблем сбросить ваше состояние с помощью явной кнопки - проблемы возникают после необработанных сбоев: трудно предсказать, как состояние может быть повреждено, и определить стратегию восстановления для каждого конкретного случая.
@stremlenye да, разработка программного обеспечения иногда бывает сложной. Предсказуемые состояния и переходы помогают смягчить это.
Не только это, но если ваше состояние больше, чем куча объектов json (которые я ненавижу, потому что все по соглашению и ничто не применяется), и вы используете что-то в строке Record
, тогда вы вам нужно объявить свойства ваших объектов заранее, и это включает в себя все временные вещи, которые вам когда-либо понадобятся.
Для меня создание чего-то с редуксом без использования Immutable.js — это просто несчастный случай, ожидающий своего часа.
Я знаю, что вы можете сделать запись Signup
и сделать ее ненулевой только при регистрации, но проблема более фундаментальна, потому что в вашем веб-приложении может быть много таких случаев, и это то, что беспокоит. меня.
@ir-топливо
Не только это, но если ваше состояние больше, чем куча объектов json (которые я ненавижу, потому что все по соглашению и ничего не применяется), и вы используете что-то в строке записи Immutable.js, вам нужно объявить свойства ваших объектов заранее, и это включает в себя все временные вещи, которые вам когда-либо понадобятся.
Для меня создание чего-то с редуксом без использования Immutable.js — это просто несчастный случай, ожидающий своего часа.
На данный момент я не уверен в использовании Immutable.js с избыточностью, он оказался слишком сложным для интеграции (и неэффективным). Глубокой заморозки во время разработки и тестирования, а также набора тестов должно быть достаточно, чтобы покрыть большинство случаев. И строгое соглашение, чтобы избежать изменения состояния в редьюсерах или других местах, где у вас есть ссылка.
Я знаю, что вы можете создать запись о регистрации, и при регистрации она будет не нулевой.
Да, это то, что я имел в виду.
но проблема более фундаментальна, потому что у вас может быть много таких случаев в вашем веб-приложении, и это меня беспокоит.
Это то, что мы называем явным состоянием приложения — в каждый момент времени мы знаем состояние приложения, которое состоит из множества компонентов. Если вы хотите сделать структуру состояния менее явной и более динамичной, следуйте https://github.com/tonyhb/redux-ui .
Вот и вся проблема JS-разработки. «Давайте придерживаться условностей». И именно поэтому люди начинают понимать, что это просто не работает в сложных средах, потому что люди совершают ошибки и приходят с такими решениями, как TypeScript и Immutable.js.
Почему было бы слишком сложно интегрировать? Если вы используете «Запись», вы используете все эти объекты (пока вы просто их читаете) как обычные объекты JS с точечной записью для доступа к свойствам, с преимуществом, которое вы объявляете заранее, какие свойства являются частью вашего объекта .
Я делаю все свои разработки с редуксом с помощью Immutable.js, без каких-либо проблем. Тот факт, что просто невозможно случайно изменить объект, является большим преимуществом в избыточности, а также удобна «безопасность типов» определения прототипов ваших объектов.
И если вы действительно хотите поддерживать перезагрузку во время регистрации, просто сделайте так, чтобы родительский компонент, управляющий всеми этими шагами, сохранял свое состояние в 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 нет проблем сбросить ваше состояние с помощью явной кнопки - проблемы возникают после необработанных сбоев: трудно предсказать, как состояние может быть повреждено, и определить стратегию восстановления для каждого конкретного случая.
@stremlenye Я согласен с этим, но я думаю, что эти конкретные случаи сбоев, скорее всего, будут бизнес-вещами, они всегда здесь, независимо от того, где мы помещаем состояние, и с ними нужно обращаться правильно, но они просто недостаточно четко описаны в момент. Единственное, что могут сделать разработчики, — упростить отслеживание, чтобы мы могли исправить это с меньшими усилиями.
А размещение состояния в родительском компоненте может затруднить анализ сложных ситуаций. С поддельным API мы мутируем это состояние действиями, и оно кажется более предсказуемым.
Но я согласен, что поддельный API выглядит слишком тяжеловесным в большинстве случаев при меньшей сложности.
Не просмотрел все сообщения выше. Здесь я просто даю свой подход: обнаруживать изменения свойств в componentWillReceiveProps.
Скажем, форма состояния: {loginPending, loginError}, при начале входа установите loginPending = true. В случае успеха или неудачи установите { loginPending: false, loginError: 'какая-то ошибка'. }.
Затем я могу определить, произошло ли событие успешного входа в компонент, и перенаправить страницу:
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
};
// ...
Вы считаете, что такой подход приемлем? Оно работает.
Я закончил с этим. Расскажите, что вы думаете о:
зарегистрировать избыточное действие:
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
, и этот сахар скрывает тот факт, что существует три состояния запроса (до, в процессе, после). Эти состояния скрываются в компоненте представления. Запуск запроса игнорируется, а ошибка запроса обрабатывается в представлении и не отправляется через избыточность, поэтому состояние приложения здесь не при чем. Если по какой-то причине компонент, содержащий это состояние, размонтируется, а затем перемонтируется, состояние запроса на регистрацию будет потеряно, а его результат останется необработанным, что может привести к последующим ошибкам (например, пользователь уже зарегистрировался).
Пользователь получает сообщение об ошибке, затем переходит на другую страницу, затем снова на страницу формы - форма пуста и ошибок нет.
Для этого вы можете отправить действие вроде USER_REGISTRATION_RESET
при монтировании компонента, которое сбрасывает ошибку в хранилище. Это действие следует обрабатывать только в том случае, если нет ожидающего запроса на регистрацию, иначе результат ожидающего запроса не будет обработан приложением.
спасибо за отзыв.
Вы подсластили свой код с помощью async/await, и этот сахар скрывает тот факт, что есть три состояния запроса (до, в процессе, после)
эти условия существуют независимо от того, использую ли я async/await или нет.
Эти состояния скрываются в компоненте представления.
как и предполагалось, это состояние компонента. Почему это должно быть в состоянии приложения?
Старт запроса игнорируется
в ходе выполнения...
и ошибка запроса обрабатывается в представлении и не отправляется через избыточность
как предполагалось
Если по какой-либо причине компонент, содержащий это состояние, размонтируется, а затем перемонтируется, состояние запроса на регистрацию будет потеряно, а его результат не будет обработан.
это неправда. пользователь будет зарегистрирован и перенаправлен, если запрос будет успешным, независимо от состояния или даже существования компонента. посмотрите на создателя действия 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?
Передача URL-адреса перенаправления в действие и дополнительный вызов метода next
с push
из react-router-redux
в промежуточном программном обеспечении.
Самый полезный комментарий
Я думаю, что вопрос немного глубже, чем просто обобщение «форм отправки» — это больше о том, что на самом деле представляет собой состояние и какие данные должны отображаться в
store
, а какие нет.Мне нравится думать о
View
части потоков данных как о чистой функцииrender(state): DOM
, но это не совсем так.Возьмем в качестве примера простой компонент формы, который отображает два поля ввода:
При вводе данных в эти поля выполняются некоторые внутренние изменения состояния, поэтому в конце мы получим что-то вроде этого:
Итак, мы получили какое-то состояние, которое не связано с состоянием приложения, и никому до него нет дела. Когда мы вернемся к этой форме в следующий раз, она будет показана стертой, а «Джон Сноу» исчезнет навсегда.
Похоже, мы модифицировали DOM, не касаясь потоков данных.
Предположим, что это форма подписки на какое-то письмо-уведомление. Теперь нам нужно взаимодействовать с внешним миром, и мы сделаем это с помощью нашего специального действия.
Когда пользователь нажимает кнопку отправки, он ожидает какого-либо ответа от пользовательского интерфейса, если это не удалось, и что делать дальше. Итак, мы хотим отслеживать состояние запроса для отображения экрана загрузки, перенаправления или отображения сообщения об ошибке.
Первая идея состоит в том, чтобы отправить некоторые внутренние действия, такие как
loading
,submitted
,failed
, чтобы за это отвечало состояние приложения. Потому что мы хотим визуализировать состояние и не думать о деталях.Но тут появляется дьявол: теперь у нас это состояние страницы сохраняется, и следующий вызов
render(state)
будет отображать форму в грязном состоянии, поэтому нам нужно создать действиеcleanup
, чтобы вызывать его каждый раз, когда мы монтируем форму.И для каждой формы, которая у нас есть, мы добавим один и тот же шаблон из
loading
,submitted
,failed
иcleanup
действий с хранилищами/редьюсерами… пока мы не остановимся и не подумаем о состоянии, которое мы на самом деле хотим отобразить.Принадлежит ли
submitted
к состоянию приложения или это просто состояние компонента, например значение поляname
. Действительно ли пользователь заботится о том, чтобы это состояние сохранялось в кеше, или он хочет видеть чистую форму при повторном посещении? Ответ: это зависит.Но в случае чистых форм отправки мы могли бы предположить, что состояние формы не имеет ничего общего с состоянием приложения: мы хотим знать, является ли факт пользователем
logged in
или нет в случае страницы входа, или мы не хотим чтобы узнать что-нибудь о том, изменил ли он пароль или потерпел неудачу: если запрос увенчается успехом, просто перенаправьте его куда-нибудь еще.Так как насчет предложения, что нам не нужно это состояние где-то еще, кроме Компонента? Давайте попробуем.
Чтобы обеспечить более причудливую реализацию, мы могли бы предположить, что в 2015 году мы живем в асинхронном мире, и у нас везде есть
Promises
, поэтому пусть наше действие возвращаетPromise
:На следующем шаге мы можем начать отслеживать действие в нашем
Component
:Похоже, у нас есть все необходимое для рендеринга компонента без загрязнения состояния приложения и избавления от необходимости его очистки.
Итак, мы почти у цели. Теперь мы можем начать обобщать поток
Component
.Прежде всего, давайте разделимся на проблемы: отслеживание действий и реакций на обновление состояния:
(Давайте сделаем это с точки зрения
redux
, чтобы оставаться в пределах репо)а также
Теперь, с одной стороны, у нас есть
decorator
, которые предоставляют пропсыsubmitted
иerror
с обратным вызовомonSubmit
, а с другой стороныdecorator
, которые куда-то перенаправляют, если получает свойствоsubmitted === true
. Давайте прикрепим их к нашей форме и посмотрим, что у нас получится.И вот она: чистая форма рендеринга без зависимостей библиотеки $#
routing
, реализацияflux
, приложениеstate
, делающее то, для чего оно было создано: отправка и перенаправление.Спасибо за чтение и извините, что отняли ваше время.