Вложение компонента контейнера в презентационный компонент

Я пытаюсь реорганизовать свое приложение для разделения презентационных и контейнерных компонентов. Мои компоненты контейнера - это всего лишь компоненты, связанные с вызовами connect() от реакции-редукта, которые отображают состояния и создатели действий в реквизитах презентационных компонентов.

ToDo-list.container.js

import React, {Component} from 'react';
import {connect} from 'react-redux';

import {fetchTodos} from '../actions/todo.actions';
import TodoList from '../components/todo-list.component';

export default connect(({todo}) => ({state: {todo}}), {fetchTodos})(TodoList);

ToDo-list.component.jsx

import React, {Component} from 'react';

import TodoContainer from '../containers/todo.container';

export default class TodoList extends Component {
    componentDidMount () {
        this.props.fetchTodos();
    }

    render () {
        const todoState = this.props.state.todo;

        return (
            <ul className="list-unstyled todo-list">
                {todoState.order.map(id => {
                    const todo = todoState.todos[id];
                    return <li key={todo.id}><TodoContainer todo={todo} /></li>;
                })}
            </ul>
        );
    }
};

todo.container.js

import React, {Component} from 'react';
import {connect} from 'react-redux';

import {createTodo, updateTodo, deleteTodo} from '../actions/todo.actions';
import Todo from '../components/todo.component';

export default connect(null, {createTodo, updateTodo, deleteTodo})(Todo);

todo.component.jsx

import React, {Component} from 'react';

import '../styles/todo.component.css';

export default class Todo extends Component {
    render () {
        return (
            <div className="todo">
                {todo.description}
            </div>
        );
    }
};

То, что я пытаюсь понять, это следующее: я знаю, что я не должен встраивать элемент <TodoContainer /> внутри TodoList, потому что TodoList является презентационным компонентом, и он должен только вставлять в него другие презентационные компоненты, Но если я заменю его только презентационным компонентом <Todo />, тогда мне нужно сопоставить все оповещения о поддержке и поддержке действий в TodoListContainer, которые будут нужны компоненту Todo, и передавать их по цепочке вручную в качестве реквизита. Этого я, конечно, хочу избежать, особенно если я начинаю вкладывать больше уровней или начинать в зависимости от большего количества реквизитов, поступающих из Redux.

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

Ответ 1

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

Посмотрите на размещение детей и использование this.props.children для переноса дочерних элементов в презентационный компонент.

Пример (удаленный код для краткости)

Список (презентационный компонент)

import React, { Component, PropTypes } from 'react';

export default class List extends Component {
  static propTypes = {
    children: PropTypes.node
  }

  render () {
    return (
      <div className="generic-list-markup">
        {this.props.children} <----- wrapping all children
      </div>
    );
  }
}

Todo (презентационный компонент)

import React, { Component, PropTypes } from 'react';

export default class Todo extends Component {
  static propTypes = {
    description: PropTypes.string.isRequired
  }

  render () {
    return (
      <div className="generic-list-markup">
        {this.props.description}
      </div>
    );
  }
}

TodoList (контейнерный компонент)

import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { createTodo, updateTodo, deleteTodo } from 'actions';
import List from 'components/List';
import Todo from 'components/Todo';

export class TodoList extends Component {
  static propTypes = {
    todos: PropTypes.array.isRequired,
    create: PropTypes.func.isRequired
  }

  render () {
    return (
      <div>
        <List> <---------- using our presentational component
          {this.props.todos.map((todo, key) =>
            <Todo key={key} description={todo.description} />)}
        </List>
        <a href="#" onClick={this.props.create}>Add Todo</a>
      </div>
    );
  }
}

const stateToProps = state => ({
  todos: state.todos
});

const dispatchToProps = dispatch = ({
  create: () => dispatch(createTodo())
});

export default connect(stateToProps, dispatchToProps)(TodoList);

DashboardView (презентационный компонент)

import React, { Component } from 'react';
import TodoList from 'containers/TodoList';

export default class DashboardView extends Component {
  render () {
    return (
      <div>
        <TodoList />
      </div>
    );
  }
};