javascript - 叶状组件中的 connect() 是 react+redux 中反模式的标志吗?

标签 javascript redux immutable.js react-redux normalizr

目前正在做一个 React + redux 项目。

我也在使用 normalizr处理数据结构和reselect为应用程序组件收集正确的数据。

一切似乎都运作良好。

我发现自己处于这样一种情况,其中叶状组件需要存储中的数据,因此我需要connect() 组件来实现它。

举个简单的例子,假设该应用是一个图书编辑系统,多个用户收集反馈。

Book
    Chapters
        Chapter
            Comments
        Comments
    Comments

在应用的不同层级,用户可以贡献内容和/或提供评论。

假设我正在渲染一个章节,它有内容(和作者)和评论(每个都有自己的内容和作者)。

目前我会根据 ID connect()reselect 章节内容。

因为数据库是用normalizr规范化的,所以我真的只得到章节的基本内容字段,以及作者的用户ID。

为了呈现评论,我会使用一个连接组件,它可以重新选择链接到章节的评论,然后单独呈现每个评论组件。

同样,因为数据库是用normalizr规范化的,所以我真的只得到基本内容和评论作者的用户ID。

现在,为了呈现像作者徽章这样简单的东西,我需要使用另一个连接的组件从我拥有的用户 ID 中获取用户详细信息(在呈现章节作者和每个评论作者时)。

组件会像这样简单:

@connect(
    createSelector(
        (state) => state.entities.get('users'),
        (state,props) => props.id,
        (users,id) => ( { user:users.get(id)})
    )
)
class User extends Component {

    render() {
        const { user } = this.props

        if (!user)
            return null
        return <div className='user'>
                    <Avatar name={`${user.first_name} ${user.last_name}`} size={24} round={true}  />
                </div>

    }
}

User.propTypes = {
    id : PropTypes.string.isRequired
}

export default User

而且它看起来工作正常。

我尝试反其道而行之,并在更高级别对数据进行反规范化,例如,章节数据将直接嵌入用户数据,而不仅仅是用户 ID,并将其直接传递给用户 –但这似乎只会让选择器变得非常复杂,而且因为我的数据是不可变的,所以它每次都会重新创建对象。

所以,我的问题是,是否有叶状组件(如上面的 User)connect() 到商店以呈现反模式的迹象?

我是在做正确的事情,还是以错误的方式看待这个问题?

最佳答案

我认为您的直觉是正确的。在任何级别(包括叶节点)连接组件都没有错,只要 API 有意义——也就是说,给定一些 Prop ,您可以推断组件的输出。

智能组件与dumb组件的概念有点过时了。相反,最好考虑连接组件与未连接组件。在考虑是创建连接组件还是未连接组件时,需要考虑一些事项。

模块边界

如果您将应用划分为更小的模块,通常最好将它们的交互限制在较小的 API 表面。例如,说 userscomments在单独的模块中,那么我会说它对 <Comment> 更有意义使用连接的组件 <User id={comment.userId}/>组件,而不是让它自己获取用户数据。

单一职责原则

连接组件承担太多责任是一种代码味道。例如,<Comment>组件的职责可以是抓取评论数据并呈现它,并以 Action 分派(dispatch)的形式处理用户交互(与评论)。如果它需要处理抓取用户数据,并处理与用户模块的交互,那么它就做得太多了。最好将相关职责委托(delegate)给另一个连接的组件。

这也称为“胖 Controller ”问题。

性能

通过在顶部有一个大的连接组件向下传递数据,它实际上会对性能产生负面影响。这是因为每次状态更改都会更新顶级引用,然后每个组件都会重新渲染,React 需要对所有组件执行协调。

Redux 通过假设它们是纯连接的组件来优化它们(即,如果 prop 引用相同,则跳过重新渲染)。如果连接叶节点,则状态更改只会重新渲染受影响的叶节点——跳过大量协调。这可以在这里看到:https://github.com/mweststrate/redux-todomvc/blob/master/components/TodoItem.js

重用和可测试性

我最后想提的是重用和测试。如果您需要 1) 将其连接到状态原子的另一部分,2) 直接传入数据(例如,我已经有 user 数据,所以我只想要一个纯渲染),则连接组件不可重用。同样,连接的组件更难测试,因为您需要先设置它们的环境,然后才能呈现它们(例如,创建商店,将商店传递给 <Provider> 等)。

可以通过在有意义的地方同时导出连接和未连接的组件来缓解这个问题。

export const Comment = ({ comment }) => (
  <p>
    <User id={comment.userId}/>
   { comment.text }
  </p>
)

export default connect((state, props) => ({
  comment: state.comments[props.id]
}))(Comment)


// later on...
import Comment, { Comment as Unconnected } from './comment'

关于javascript - 叶状组件中的 connect() 是 react+redux 中反模式的标志吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37108790/

相关文章:

javascript - 在嵌套对象中搜索特定键的值

javascript - 如何删除#!来自网址

react-native - Redux reducer 没有更新 immutable.js Redux 存储

javascript - 为什么在使用 isMap() 检查后 Flow 无法判断我的对象是不可变映射?

javascript - 创建具有属性的 jQuery 对象的首选方法是什么?

javascript - 使用 JavaScript 发出跨站点请求的最佳方式?

javascript - 从 React 中的按键捕获键码

javascript - immutable.js 可以与服务器上的 redis 互换吗?

reactjs - React、Redux 和不可变

reactjs - 更新 Redux 存储以从数组中删除某些内容不会重新渲染