React / Redux:分离表现和容器组件的困境。

时间:2017-06-23 00:52:34

标签: javascript reactjs redux react-redux react-jsx

这是我所拥有的简化表示:

container.js

import Presentation from './Presentation';
import { connect } from 'react-redux';

export default connect(
    ({ someState}) => ({ ...someState }),
    (dispatch) => {
        onClick(e) => ({})
    }
)(Presentation);

pesentation.js

export default ({ items, onClick}) => (
    <table>
        <tbody>
            {items.map((item, index) => (
                <tr>
                    <td onClick={onClick}>{item.a}</td>
                    <td>
                        <div onClick={onClick}>{item.b}</div>
                    </td>
                </tr>
            ))}
        </tbody>
    </table>
);

让我们说,我希望能够访问处理程序中每行的index变量。 首先想到的解决方案是使用闭包来捕获index

container.js

onClick(e, index) => ({});

presentation.js

<td onClick={e => onClick(e, index)}>{item.a}</td>
<td>
    <div onClick={e => onClick(e, index)}>{item.b}</div>
</td>

它有效,但看起来很脏,所以我决定尽可能找到另一种解决方案。 接下来就是在每个元素上使用data-index属性:

container.js

onClick({ target: { dataset: { index } } }) => ({});

presentation.js

<td data-index={index} onClick={onClick}>{item.a}</td>
<td>
    <div data-index={index} onClick={onClick}>{item.b}</div>
</td>

也许它更好,但是在多个元素上使用具有相同值的数据属性的需求看起来是多余的。如果我有20个元素怎么办? 好的,没问题!我们只需将其移至父容器:

container.js

onClick(e) => {
    const {index} = e.target.closest('tr').dataset;
};

presentation.js

<tr data-index={index}>
    <td onClick={onClick}><td>
    <td>
        <div onClick={onClick}>{item.b}</div>
    </td>
</tr>

好的,下一个问题就解决了。可是等等。这样,容器严重依赖于表示组件布局。它通过引入对tr标记的引用而与视图耦合。如果在某些时候标记将被更改为例如div,该怎么办? 我想出的最终解决方案是为容器标签元素添加一个CSS类,它从容器组件传递到表示:

container.js

import Presentation from './Presentation';
import { connect } from 'react-redux';

const ITEM_CONTAINER_CLASS = 'item-cotainer';

export default connect(
    ({ someState }) => ({ ...someState, ITEM_CONTAINER_CLASS }),
    (dispatch) => {
        onClick(e) => {
            const {index} = e.target.closest(`.${ITEM_CONTAINER_CLASS }`).dataset;
        }
    }
)(Presentation);

pesentation.js

export default ({ items, onClick, ITEM_CONTAINER_CLASS  }) => (
    <table>
        <tbody>
            {items.map((item, index) => (
                <tr className={ITEM_CONTAINER_CLASS}>
                    <td onClick={onClick}>{item.a}</td>
                    <td>
                        <div onClick={onClick}>{item.b}</div>
                    </td>
                </tr>
            ))}
        </tbody>
    </table>
);

但是,它有一个小缺点:它需要DOM操作来获取索引。

感谢阅读(如果有人)。我想知道,如果还有其他选择吗?

2 个答案:

答案 0 :(得分:4)

这是典型的React / Redux困境。我的答案是提取您的&#34;项目视图&#34;到组件并添加onItemClick道具。当然,您的商品视图应该会收到要显示的item,并且可以将其作为参数发送到您的容器。

e参数属于您的演示文稿,而不属于您的操作创建者,即业务逻辑。并且在React中根本不需要将数据存储在DOM中(比如使用data-属性)。

像这样(我倾向于从下到上,从简单到复杂):

//Item.js
export default class Item extends Component {
   constructor(props){
     super(props)
     this.handleClick = this.handleClick.bind(this)
   }
   handleClick(e){
     e.preventDefault() //this e stays here
     this.props.onItemClick(this.props.item)
   }
   render(){
      const { item } = this.props
      return (
        <tr className={ITEM_CONTAINER_CLASS}>
          <td onClick={this.handleClick}>{item.a}</td>
          <td onClick={this.handleClick}>{item.b}</td>
        </tr>
      )
   }
}

// ItemList (Presentation)
export default ItemList = ({ items, onClick}) => (
  <table>
    <tbody>
      {
        items.map((item, index) => 
          <Item item={item} onItemClick={ onClick } />
        )
      }
    </tbody>
  </table>
)

// Container.js - just change your mapDispatchToProps to accept the full item, or whatever you're interested in (perhaps only the id)
import ItemList from './ItemList';
import { connect } from 'react-redux';

export default connect(
    ({ someState}) => ({ ...someState }),
    {
       onClick(item){  
         return yourActionCreatorWith(item.id)
       }
    }
)(Presentation);

通过这种方式,您可以在需要派遣时获得所需的一切。此外,创建您的&#34;项目视图&#34;作为一个单独的组件,您可以单独测试它,甚至可以在需要时重复使用它。

看起来可能要多得多,但相信我,它会得到回报。

答案 1 :(得分:0)

另见接受的答案。 这是解决方案:

container.js

import Presentation from './presentation';
import { connect } from 'react-redux';

export default connect(
    ({ someState }) => ({ ...someState }),
    (dispatch) => {
        onClick(index) => {}
    }
)(Presentation);

presentation.js

import Item from './item.js';

export default ({ items, onClick }) => (
    <table>
        <tbody>
            {items.map((item, index) => <Item index={index} item={item} onClick={onClick} />)}
        </tbody>
    </table>
);

item.js

export default ({ index, item, onClick }) => {
    const onItemClick = () => onClick(index);
    return (
        <tr>
            <td onClick={onItemClick}>{item.a}</td>
            <td>
                <div onClick={onItemClick}>{item.b}</div>
            </td>
        </tr>
    )
};