React & Redux: connect() для нескольких компонентов и лучших практик

Я работаю над своим первым проектом React/Redux, и у меня есть небольшой вопрос. Я прочитал документацию и просмотрел обучающие материалы, доступные по адресу https://egghead.io/lessons/javascript-redux-generating-containers-with-connect-from-react-redux-visibletodolist.

Но у меня все еще есть один вопрос. Это о странице входа. Итак, у меня есть презентационный компонент с именем LoginForm:

компоненты/LoginForm.js

import { Component, PropTypes } from 'react'

class LoginForm extends Component {
   render () {
      return (
         <div>
            <form action="#" onSubmitLogin={(e) => this.handleSubmit(e)}>
               <input type="text" ref={node => { this.login = node }} />
               <input type="password" ref={node => { this.password = node }} />
               <input type="submit" value="Login" />
            </form>
         </div>
      )
   }

   handleSubmit(e) {
      e.preventDefault();
      this.props.onSubmitLogin(this.login.value, this.password.value);
   }
}

LoginForm.propTypes = {
   onSubmitLogin: PropTypes.func.isRequired
};

export default LoginForm;

И компонент контейнера с именем Login, который передает данные моему компоненту. Используя agent-redux-router, я вызываю этот контейнер (а не компонент presentationnal):

контейнеры/Login.js

import { connect } from 'react-redux'
import { login } from '../actions/creators/userActionCreators'
import LoginForm from '../components/LoginForm'

const mapDispatchToProps = (dispatch) => {
   return {
      onSubmitLogin: (id, pass) => dispatch(login(id, pass))
   }
};

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

Как вы можете видеть, я использую метод connect предоставляемый сокращением для создания моего контейнера.

Мой вопрос следующий:

Если я хочу, чтобы мой контейнер Login использовал несколько представлений (например: LoginForm и errorList для отображения ошибок), мне нужно сделать это вручную (без подключения, потому что connect принимает только один аргумент). Что-то вроде:

class Login extends Component {

   render() {
      return (
         <div>
            <errorList />
            <LoginForm onSubmitLogin={ (id, pass) => dispatch(login(id, pass)) } />
         </div>
      )
   }

}

Это плохая практика? Лучше ли создать другой презентационный компонент (LoginPage), который использует как errorList, так и LoginForm и создает контейнер (Login), который connect к LoginPage?


EDIT: Если я создаю третий презентационный компонент (LoginPage), мне придется передавать данные дважды. Container → LoginPage → LoginForm & ErrorList: Container → LoginPage → LoginForm & ErrorList. Даже с контекстом, похоже, это не путь.

Ответ 1

Я думаю, что то, что у вас есть в вашем втором примере, очень близко. Вы можете создать только один компонент контейнера, который подключил и отобразил несколько презентационных компонентов.

В вашем первом примере фактически нет отдельного компонента контейнера:

import { connect } from 'react-redux'
import { login } from '../actions/creators/userActionCreators'
import LoginForm from '../components/LoginForm'

const mapDispatchToProps = (dispatch) => {
   return {
      onSubmitLogin: (id, pass) => dispatch(login(id, pass))
   }
};

// 'LoginForm' is being passed, so it would be the "container"
// component in this scenario
export default connect(null, mapDispatchToProps)(LoginForm);

Несмотря на то, что это в отдельном модуле, то, что вы здесь LoginForm напрямую связано с вашим LoginForm.

Вместо этого вы можете сделать что-то вроде этого:

контейнеры/Login.js

import { connect } from 'react-redux'
import { login } from '../actions/creators/userActionCreators'
import LoginForm from '../components/LoginForm'
import ErrorList from '../components/ErrorList'

class Login extends Component {

   render() {
      const { onSubmitLogin, errors } = this.props;

      return (
         <div>
            <ErrorList errors={errors} />
            <LoginForm onSubmitLogin={onSubmitLogin} />
         </div>
      )
   }

}

const mapDispatchToProps = (dispatch) => {
   return {
      onSubmitLogin: (id, pass) => dispatch(login(id, pass))
   }
};

const mapStateToProps = (state) => {
   return {
       errors: state.errors
   };
};

export default connect(mapStateToProps, mapDispatchToProps)(Login);

Обратите внимание, что компонент Login теперь передается для connect, что делает его "контейнерным" компонентом, и тогда как errorList и LoginForm могут быть презентационными. Все их данные могут быть переданы через реквизиты с помощью контейнера Login.

Ответ 2

Я действительно верю, что вам нужно собрать все ваши компоненты в качестве презентационных компонентов. На данный момент вам нужен компонент контейнера, вы можете использовать {connect} над одним из существующих презентаций и конвертировать в контейнер.

Но это только мой взгляд с коротким опытом в Реактике.