如何将React组件分为演示和容器组件

时间:2018-08-20 12:13:17

标签: javascript reactjs react-redux components mern

仍然是新的反应和还原,并且一直在开发我现在正在使用的MERN用户注册应用程序。

在redux文档中,我发现创建者建议在将redux与react集成时建议将其代码分成两种类型的组件:Presentational(关注事物外观)和Container(关注事物如何运作)。参见https://redux.js.org/basics/usagewithreact

我认为这样可以更好地管理应用程序和扩展性。

对于不熟悉的人,这里是优点的很好解释:https://www.youtube.com/watch?v=NazjKgJp7sQ

我只是在努力理解概念并以这种方式重写代码而苦苦挣扎。

这里是我用来显示用户创建的评论的帖子组件的示例。它从作为道具传递的更高级别组件中的帖子中接收数据。作为回报,我将所有带有自举样式的标记都应用了。我正在通过创建事件处理程序来订阅导入并使用的redux操作。

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import classnames from 'classnames';
import { Link } from 'react-router-dom';
import { deletePost, addLike, removeLike } from '../../actions/postActions';

class PostItem extends Component {
  onDeleteClick(id) {
    this.props.deletePost(id);
  }

  onLikeClick(id) {
    this.props.addLike(id);
  }

  onUnlikeClick(id) {
    this.props.removeLike(id);
  }

  findUserLike(likes) {
    const { auth } = this.props;
    if (likes.filter(like => like.user === auth.user.id).length > 0) {
      return true;
    } else {
      return false;
    }
  }

  render() {
    const { post, auth, showActions } = this.props;

    return (
      <div className="card card-body mb-3">
        <div className="row">
          <div className="col-md-2">
            <a href="profile.html">
              <img
                className="rounded-circle d-none d-md-block"
                src={post.avatar}
                alt=""
              />
            </a>
            <br />
            <p className="text-center">{post.name}</p>
          </div>
          <div className="col-md-10">
            <p className="lead">{post.text}</p>
            {showActions ? (
              <span>
                <button
                  onClick={this.onLikeClick.bind(this, post._id)}
                  type="button"
                  className="btn btn-light mr-1"
                >
                  <i
                    className={classnames('fas fa-thumbs-up', {
                      'text-info': this.findUserLike(post.likes)
                    })}
                  />
                  <span className="badge badge-light">{post.likes.length}</span>
                </button>
                <button
                  onClick={this.onUnlikeClick.bind(this, post._id)}
                  type="button"
                  className="btn btn-light mr-1"
                >
                  <i className="text-secondary fas fa-thumbs-down" />
                </button>
                <Link to={`/post/${post._id}`} className="btn btn-info mr-1">
                  Comments
                </Link>
                {post.user === auth.user.id ? (
                  <button
                    onClick={this.onDeleteClick.bind(this, post._id)}
                    type="button"
                    className="btn btn-danger mr-1"
                  >
                    <i className="fas fa-times" />
                  </button>
                ) : null}
              </span>
            ) : null}
          </div>
        </div>
      </div>
    );
  }
}

PostItem.defaultProps = {
  showActions: true,
};

PostItem.propTypes = {
  deletePost: PropTypes.func.isRequired,
  addLike: PropTypes.func.isRequired,
  removeLike: PropTypes.func.isRequired,
  post: PropTypes.object.isRequired,
  auth: PropTypes.object.isRequired,
};

const mapStateToProps = state => ({
  auth: state.auth,
});

export default connect(mapStateToProps, { deletePost, addLike, removeLike })(PostItem);

如您所见,代码没有我想要的那么整洁和紧凑。我的目标是使呈现组件不知道redux,并在此处进行所有样式和引导工作,而容器组件具有redux和connect功能。有人知道我该怎么做吗?

我看到人们使用connect将这些类型的组件链接在一起:

const PostItemContainer = connect(
    mapStateToProps, 
    { deletePost, addLike, removeLike }
)(PostItem);

export default PostItemContainer;

但是我不知道如何在实践中实现这一目标。 如果您能帮助我解释并提供一些示例代码,那就太好了。

谢谢!

2 个答案:

答案 0 :(得分:1)

我总是将我的html(presentation)代码放在另一个文件中,作为响应,他们将它们称为无状态组件,

关键组件是PostItemComponent,它对Redux一无所知。

请参见以下代码:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import classnames from 'classnames';
import { Link } from 'react-router-dom';
import { deletePost, addLike, removeLike } from '../../actions/postActions';

const PostItemComponent = ({
    post,
    showActions,
    auth,
    onLikeClick,
    findUserLike,
    onUnlikeClick,
    onDeleteClick
}) => {
    return (
        <div className="card card-body mb-3">
            <div className="row">
                <div className="col-md-2">
                    <a href="profile.html">
                        <img
                            className="rounded-circle d-none d-md-block"
                            src={post.avatar}
                            alt=""
                        />
                    </a>
                    <br />
                    <p className="text-center">{post.name}</p>
                </div>
                <div className="col-md-10">
                    <p className="lead">{post.text}</p>
                    {showActions ? (
                        <span>
                            <button
                                onClick={(event) => onLikeClick(event, post._id)}
                                type="button"
                                className="btn btn-light mr-1">
                                <i
                                    className={classnames('fas fa-thumbs-up', {
                                        'text-info': findUserLike(post.likes)
                                    })}
                                />
                                <span className="badge badge-light">{post.likes.length}</span>
                            </button>
                            <button
                                onClick={(event) => onUnlikeClick(event, post._id)}
                                type="button"
                                className="btn btn-light mr-1"
                            >
                                <i className="text-secondary fas fa-thumbs-down" />
                            </button>
                            <Link to={`/post/${post._id}`} className="btn btn-info mr-1">
                                Comments
                            </Link>
                            {post.user === auth.user.id ? (
                                <button
                                    onClick={(event) => onDeleteClick(event, post._id)}
                                    type="button"
                                    className="btn btn-danger mr-1"
                                >
                                    <i className="fas fa-times" />
                                </button>
                            ) : null}
                        </span>
                    ) : null}
                </div>
            </div>
        </div>
    );
};

class PostItem extends Component {
    constructor(props) {
        super(props);
        this.onDeleteClick = this.onDeleteClick.bind(this);
        this.onLikeClick = this.onLikeClick.bind(this);
        this.onUnlikeClick = this.onUnlikeClick.bind(this);
        this.findUserLike = this.findUserLike.bind(this);
    }
    onDeleteClick(event, id) {
        event.preventDefault();
        this.props.deletePost(id);
    }

    onLikeClick(event, id) {
        event.preventDefault();
        this.props.addLike(id);
    }

    onUnlikeClick(event, id) {
        event.preventDefault();
        this.props.removeLike(id);
    }

    findUserLike(likes) {
        const { auth } = this.props;
        if (likes.filter(like => like.user === auth.user.id).length > 0) {
            return true;
        } else {
            return false;
        }
    }

    render() {
        const { post, auth, showActions } = this.props;
        return (
            <PostItemComponent
                post={post}
                auth={auth}
                showActions={showActions}
                onDeleteClick={this.onDeleteClick}
                onLikeClick={this.onLikeClick}
                onUnlikeClick={this.onUnlikeClick}
            />
        );
    }
}

PostItem.defaultProps = {
    showActions: true,
};

PostItem.propTypes = {
    deletePost: PropTypes.func.isRequired,
    addLike: PropTypes.func.isRequired,
    removeLike: PropTypes.func.isRequired,
    post: PropTypes.object.isRequired,
    auth: PropTypes.object.isRequired,
};

const mapStateToProps = state => ({
    auth: state.auth,
});

export default connect(mapStateToProps, { deletePost, addLike, removeLike })(PostItem);

答案 1 :(得分:0)

这与@jsDevia的答案非常相似,但由于您说您的Post组件已经连接到Redux,因此在这里我没有创建单独的组件。因此,您可以抓住所有动作创建者并在其中声明,然后将其传递给您的PostItem组件。

第二个区别是我使用功能组件而不是类组件,因为这里不需要任何状态或生命周期方法。

第三个差异很小。我从您的onClick处理程序中删除了所有绑定。对于this范围问题,我将箭头函数用于处理程序。同样,我们不需要任何参数,例如post._id来传递这些函数,因为这里已经有post作为道具。这是分离组件的美妙之处。

在回调处理程序中使用bind或箭头函数会导致大型应用程序出现性能问题,这些大型应用程序包含许多组件,例如Post。由于这些函数每次都将重新创建,因此会重新创建。但是,使用函数引用可以防止这种情况。

const PostItem = ({
  post,
  deletePost,
  addLike,
  removeLike,
  auth,
  showActions,
}) => {

  const onDeleteClick = () => deletePost(post._id);
  const onLikeClick = () => addLike(post._id);
  const onUnlikeClick = () => removeLike(post._id);
  const findUserLike = likes => {
    if (likes.filter(like => like.user === auth.user.id).length > 0) {
      return true;
    } else {
      return false;
    }
  };

  return (
    <div className="card card-body mb-3">
      <div className="row">
        <div className="col-md-2">
          <a href="profile.html">
            <img
              className="rounded-circle d-none d-md-block"
              src={post.avatar}
              alt=""
            />
          </a>
          <br />
          <p className="text-center">{post.name}</p>
        </div>
        <div className="col-md-10">
          <p className="lead">{post.text}</p>
          {showActions ? (
            <span>
              <button
                onClick={onLikeClick}
                type="button"
                className="btn btn-light mr-1"
              >
                <i
                  className={classnames("fas fa-thumbs-up", {
                    "text-info": findUserLike(post.likes),
                  })}
                />
                <span className="badge badge-light">{post.likes.length}</span>
              </button>
              <button
                onClick={onUnlikeClick}
                type="button"
                className="btn btn-light mr-1"
              >
                <i className="text-secondary fas fa-thumbs-down" />
              </button>
              <Link to={`/post/${post._id}`} className="btn btn-info mr-1">
                Comments
              </Link>
              {post.user === auth.user.id ? (
                <button
                  onClick={onDeleteClick}
                  type="button"
                  className="btn btn-danger mr-1"
                >
                  <i className="fas fa-times" />
                </button>
              ) : null}
            </span>
          ) : null}
        </div>
      </div>
    </div>
  );
};

顺便说一句,不要与Redux文档中给出的示例作斗争。我认为对于新来者来说有点复杂。