还请帮我标题并更好地表达问题.
我在react/redux架构中遇到了一个问题,我找不到很多例子.
我的应用程序依赖于不变性,引用相等性和PureRenderMixin
性能.但我发现很难用这种架构创建更通用和可重用的组件.
可以说我有这样的通用ListView
:
const ListView = React.createClass({
mixins: [PureRenderMixin],
propTypes: {
items: arrayOf(shape({
icon: string.isRequired,
title: string.isRequired,
description: string.isRequired,
})).isRequired,
},
render() {
return (
<ul>
{this.props.items.map((item, idx) => (
<li key={idx}>
<img src={item.icon} />
<h3>{item.title}</h3>
<p>{item.description}</p>
</li>
)}
</ul>
);
},
});
Run Code Online (Sandbox Code Playgroud)
然后我想使用列表视图来显示redux商店中的用户.他们有这种结构:
const usersList = [
{ name: 'John Appleseed', age: 18, avatar: 'generic-user.png' },
{ name: 'Kate Example', age: 32, avatar: 'kate.png' },
];
Run Code Online (Sandbox Code Playgroud)
到目前为止,这在我的应用程序中是非常正常的.我通常把它渲染成:
<ListView items={usersList.map(user => ({
title: user.name,
icon: user.avatar,
description: `Age: ${user.age}`,
}))} />
Run Code Online (Sandbox Code Playgroud)
问题是map
打破参考平等.结果
usersList.map(...)
将永远是新的列表对象.所以即使usersList
自上次渲染以来没有改变,ListView
也无法知道它仍然具有相同的列表items
.
我可以看到三种解决方案,但我不太喜欢它们.
解决方案1:将transformItem
-function作为参数传递,并将items
其作为原始对象的数组
const ListView = React.createClass({
mixins: [PureRenderMixin],
propTypes: {
items: arrayOf(object).isRequired,
transformItem: func,
},
getDefaultProps() {
return {
transformItem: item => item,
}
},
render() {
return (
<ul>
{this.props.items.map((item, idx) => {
const transformedItem = this.props.transformItem(item);
return (
<li key={idx}>
<img src={transformedItem.icon} />
<h3>{transformedItem.title}</h3>
<p>{transformedItem.description}</p>
</li>
);
})}
</ul>
);
},
});
Run Code Online (Sandbox Code Playgroud)
这将非常有效.然后,我可以像这样呈现我的用户列表:
<ListView
items={usersList}
transformItem={user => ({
title: user.name,
icon: user.avatar,
description: `Age: ${user.age}`,
})}
/>
Run Code Online (Sandbox Code Playgroud)
ListView只会在usersList
更改时重新呈现.但是我在途中丢失了道具类型验证.
解决方案2:创建一个包含以下内容的专用组件ListView
:
const UsersList = React.createClass({
mixins: [PureRenderMixin],
propTypes: {
items: arrayOf(shape({
name: string.isRequired,
avatar: string.isRequired,
age: number.isRequired,
})),
},
render() {
return (
<ListView
items={this.props.items.map(user => ({
title: user.name,
icon: user.avatar,
description: user.age,
}))}
/>
);
}
});
Run Code Online (Sandbox Code Playgroud)
但那是更多的代码!如果我可以使用无状态组件会更好,但我还没弄清楚它们是否像普通组件一样工作PureRenderMixin
.(无状态组件遗憾地仍然是未记录的反应特征)
const UsersList = ({ users }) => (
<ListView
items={users.map(user => ({
title: user.name,
icon: user.avatar,
description: user.age,
}))}
/>
);
Run Code Online (Sandbox Code Playgroud)
解决方案3:创建一个通用的纯转换组件
const ParameterTransformer = React.createClass({
mixin: [PureRenderMixin],
propTypes: {
component: object.isRequired,
transformers: arrayOf(func).isRequired,
},
render() {
let transformed = {};
this.props.transformers.forEach((transformer, propName) => {
transformed[propName] = transformer(this.props[propName]);
});
return (
<this.props.component
{...this.props}
{...transformed}
>
{this.props.children}
</this.props.component>
);
}
});
Run Code Online (Sandbox Code Playgroud)
并使用它
<ParameterTransformer
component={ListView}
items={usersList}
transformers={{
items: user => ({
title: user.name,
icon: user.avatar,
description: `Age: ${user.age}`,
})
}}
/>
Run Code Online (Sandbox Code Playgroud)
这些是唯一的解决方案,还是我错过了什么?
这些解决方案都不适合我目前正在开发的组件.第一名最合适,但我觉得如果这是我选择继续的轨道,那么我制作的各种通用组件应该具有这些transform
属性.而且,在创建此类组件时,我无法使用React prop类型验证,这是一个主要的缺点.
解决方案2可能是最具语义正确性的,至少在本ListView
例中是这样.但我认为它非常冗长,而且它与我的真实用例不太匹配.
解决方案3太神奇,难以理解.
还有另一种常用于Redux的解决方案:memoized选择器.
像Reselect这样的库提供了一种方法来将函数(如地图调用)包装在另一个函数中,该函数检查调用之间引用相等的参数,并在参数未更改时返回缓存值.
您的场景是备忘选择器的完美示例 - 因为您强制执行不变性,您可以假设只要您的usersList
引用相同,您的调用输出map
将是相同的.选择器可以缓存map
函数的返回值,直到usersList
引用更改为止.
使用Reselect创建一个memoized选择器,首先需要一个(或多个)输入选择器和一个result函数.输入选择器通常用于从父状态对象中选择子项; 在您的情况下,您直接从您拥有的对象中进行选择,因此您可以将身份函数作为第一个参数传递.第二个参数result函数是生成由选择器缓存的值的函数.
var createSelector = require('reselect').createSelector; // ES5
import { createSelector } from 'reselect'; // ES6
var usersListItemSelector = createSelector(
ul => ul, // input selector
ul => ul.map(user => { // result selector
title: user.name,
icon: user.avatar,
description: `Age: ${user.age}`,
})
);
// ...
<ListView items={usersListItemSelector(usersList)} />
Run Code Online (Sandbox Code Playgroud)
现在,每次React渲染你的ListView
组件时,你的memoized选择器都会被调用,map
只有usersList
传入不同的结果才会调用你的结果函数.
归档时间: |
|
查看次数: |
1594 次 |
最近记录: |