如何将 React 组件划分为展示组件和容器组件
How to Divide React Components into Presentational and Container Components
对 React 和 redux 还是个新手,一直在开发 MERN 用户注册应用程序,我现在开始工作了。
在 redux 文档中,我发现创建者建议在将 redux 与 react 集成时将他们的代码拆分为两种类型的组件:Presentational(关注事物的外观)和 Container(关注事物的工作方式)。参见 https://redux.js.org/basics/usagewithreact。
我认为这可以更好地管理和扩展应用程序。
对于不熟悉的人,这里有一个很好的优势解释:https://www.youtube.com/watch?v=NazjKgJp7sQ
我只是纠结于概念的把握和代码的重写。
这是我编写的用于显示用户创建的评论的 post 组件的示例。它正在从更高级别的组件中的 post 接收数据作为道具传递。在 return 中,我所有的标记都应用了 bootstrap 样式。我正在订阅我通过创建事件处理程序导入和使用的 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,并在此处执行所有样式和 bootstrap 操作,而容器组件具有 redux 和连接功能。有谁知道我应该如何处理这个问题?
我看到人们一起使用连接到 link 这些类型的组件:
const PostItemContainer = connect(
mapStateToProps,
{ deletePost, addLike, removeLike }
)(PostItem);
export default PostItemContainer;
但我不知道如何在实践中实现这一目标。
如果您能帮我解释并提供一些示例代码,那将是非常棒的。
提前致谢!
我总是将我的 html 类(演示)代码放在另一个文件中,在反应中他们称之为无状态组件,
关键组件是 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);
这是一个与@jsDevia 的回答非常相似的建议,但我没有在这里创建一个单独的组件,因为你说你的 Post
组件已经连接到 Redux。因此,您可以获取所有动作创建者并在那里声明并将它们传递给您的 PostItem
组件。
第二个区别是我使用功能组件而不是 class 组件,因为这里不需要任何状态或生命周期方法。
第三个区别很小。我从您的 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 文档中给出的示例。我认为这对新手来说有点复杂。
对 React 和 redux 还是个新手,一直在开发 MERN 用户注册应用程序,我现在开始工作了。
在 redux 文档中,我发现创建者建议在将 redux 与 react 集成时将他们的代码拆分为两种类型的组件:Presentational(关注事物的外观)和 Container(关注事物的工作方式)。参见 https://redux.js.org/basics/usagewithreact。
我认为这可以更好地管理和扩展应用程序。
对于不熟悉的人,这里有一个很好的优势解释:https://www.youtube.com/watch?v=NazjKgJp7sQ
我只是纠结于概念的把握和代码的重写。
这是我编写的用于显示用户创建的评论的 post 组件的示例。它正在从更高级别的组件中的 post 接收数据作为道具传递。在 return 中,我所有的标记都应用了 bootstrap 样式。我正在订阅我通过创建事件处理程序导入和使用的 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,并在此处执行所有样式和 bootstrap 操作,而容器组件具有 redux 和连接功能。有谁知道我应该如何处理这个问题?
我看到人们一起使用连接到 link 这些类型的组件:
const PostItemContainer = connect(
mapStateToProps,
{ deletePost, addLike, removeLike }
)(PostItem);
export default PostItemContainer;
但我不知道如何在实践中实现这一目标。 如果您能帮我解释并提供一些示例代码,那将是非常棒的。
提前致谢!
我总是将我的 html 类(演示)代码放在另一个文件中,在反应中他们称之为无状态组件,
关键组件是 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);
这是一个与@jsDevia 的回答非常相似的建议,但我没有在这里创建一个单独的组件,因为你说你的 Post
组件已经连接到 Redux。因此,您可以获取所有动作创建者并在那里声明并将它们传递给您的 PostItem
组件。
第二个区别是我使用功能组件而不是 class 组件,因为这里不需要任何状态或生命周期方法。
第三个区别很小。我从您的 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 文档中给出的示例。我认为这对新手来说有点复杂。