叶状组件中的connect()是React + redux中反模式的标志吗? [英] Is connect() in leaf-like components a sign of anti-pattern in react+redux?

查看:52
本文介绍了叶状组件中的connect()是React + redux中反模式的标志吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目前正在研究React + Redux项目.

我还使用 normalizr 来处理数据结构和解决方案

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

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

模块边界

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

单一责任原则

责任重大的连接组件是代码异味.例如,< Comment> 组件的职责是获取注释数据,然后进行渲染,并以动作分派的形式处理用户交互(带有注释).如果它需要处理获取用户数据以及处理与用户模块的交互,那么它做得太多了.最好将相关职责委派给另一个连接的组件.

这也称为胖控制器"问题.

性能

由于在顶部具有较大的连接组件,可以向下传输数据,因此实际上会对性能产生负面影响.这是因为每次状态更改都会更新顶级引用,然后重新渲染每个组件,并且React将需要对所有组件执行协调.

Redux通过假定所连接的组件是纯净的来优化它们(即,如果prop引用相同,则跳过重新渲染).如果连接叶节点,则状态更改将仅重新呈现受影响的叶节点-跳过大量对帐.在这里可以看到它的作用: https://github.com/mweststrate/redux-todomvc/blob/master/components/TodoItem.js

重复使用和可测试性

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

可以通过在有意义的地方导出连接和未连接的组件来缓解此问题.

  export const Comment =({comment})=>(< p><用户ID = {comment.userId}/>{comment.text}</p>)导出默认的connect((state,props)=>({评论:state.comments [props.id]}))(评论)//稍后的...从"./comment"导入评论,{评论为未连接} 

Currently working on a react + redux project.

I'm also using normalizr to handle the data structure and reselect to gather the right data for the app components.

All seems to be working well.

I find myself in a situation where a leaf-like component needs data from the store, and thus I need to connect() the component to implement it.

As a simplified example, imagine the app is a book editing system with multiple users gathering feedback.

Book
    Chapters
        Chapter
            Comments
        Comments
    Comments

At different levels of the app, users may contribute to the content and/or provide comments.

Consider I'm rendering a Chapter, it has content (and an author), and comments (each with their own content and author).

Currently I would connect() and reselect the chapter content based on the ID.

Because the database is normalised with normalizr, I'm really only getting the basic content fields of the chapter, and the user ID of the author.

To render the comments, I would use a connected component that can reselect the comments linked to the chapter, then render each comment component individually.

Again, because the database is normalised with normalizr, I really only get the basic content and the user ID of the comment author.

Now, to render something as simple as an author badge, I need to use another connected component to fetch the user details from the user ID I have (both when rendering the chapter author and for each individual comment author).

The component would be something simple like this:

@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

And it seemingly works fine.

I've tried to do the opposite and de-normalise the data back at a higher level so that for example chapter data would embed the user data directly, rather than just the user ID, and pass it on directly to User – but that only seemed to just make really complicated selectors, and because my data is immutable, it just re-creates objects every time.

So, my question is, is having leaf-like component (like User above) connect() to the store to render a sign of anti-pattern?

Am I doing the right thing, or looking at this the wrong way?

解决方案

I think your intuition is correct. Nothing wrong with connecting components at any level (including leaf nodes), as long as the API makes sense -- that is, given some props you can reason about the output of the component.

The notion of smart vs dumb components is a bit outdated. Rather, it is better to think about connected vs unconnected components. When considering whether you create a connected vs unconnected components, there are a few things to consider.

Module boundaries

If you divided your app into smaller modules, it is usually better to constrain their interactions to a small API surface. For example, say that users and comments are in separate modules, then I would say it makes more sense for <Comment> component to use a connected <User id={comment.userId}/> component rather than having it grab the user data out itself.

Single Responsibility Principle

A connected component that has too much responsibility is a code smell. For example, the <Comment> component's responsibility can be to grab comment data, and render it, and handle user interaction (with the comment) in the form of action dispatches. If it needs to handle grabbing user data, and handling interactions with user module, then it is doing too much. It is better to delegate related responsibilities to another connected component.

This is also known as the "fat-controller" problem.

Performance

By having a big connected component at the top that passes data down, it actually negatively impacts performance. This is because each state change will update the top-level reference, then each component will get re-rendered, and React will need to perform reconciliation for all the components.

Redux optimizes connected components by assuming they are pure (i.e. if prop references are the same, then skip re-render). If you connect the leaf nodes, then a change in state will only re-render affected leaf nodes -- skipping a lot of reconciliation. This can be seen in action here: https://github.com/mweststrate/redux-todomvc/blob/master/components/TodoItem.js

Reuse and testability

The last thing I want to mention is reuse and testing. A connected component is not reusable if you need to 1) connect it to another part of the state atom, 2) pass in the data directly (e.g. I already have user data, so I just want a pure render). In the same token, connected components are harder to test because you need to setup their environment first before you can render them (e.g. create store, pass store to <Provider>, etc.).

This problem can be mitigated by exporting both connected and unconnected components in places where they make sense.

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'

这篇关于叶状组件中的connect()是React + redux中反模式的标志吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆