欢迎,React 爱好者和像我这样的业余爱好者!今天我有一个难题要考考大家。
假设您想以两列结构渲染项目列表。每个项目都是一个单独的组件。例如,假设我们有一个专辑列表,并且希望将其渲染为一个全页面两列列表。每个“专辑”都是一个 React 组件。

现在假设您正在使用的 CSS 框架要求您渲染出这样的两列布局…
<div class="columns">
<div class="column"> Column 1 </div>
<div class="column"> Column 2 </div>
<div class="columns">
这意味着为了正确渲染专辑,您必须打开一个 columns
div 标签,渲染两个专辑,然后关闭该标签。您一遍又一遍地这样做,直到所有专辑都渲染出来。
我通过将集合分成块并在单独的渲染函数中以条件方式渲染每个其他专辑来解决此问题。该渲染函数仅针对每个其他项目调用。
class App extends Component {
state = {albums: [] }
async componentDidMount() {
let data = Array.from(await GetAlbums());
this.setState({ albums: data } );
}
render() {
return (
<section className="section">
{this.state.albums.map((album, index) => {
// use the modulus operator to determine even items return index % 2 ?
this.renderAlbums(index) : '';
})}
</section>
)
}
renderAlbums(index) {
// two albums at a time - the current and previous item
let albums = [this.state.albums[index - 1], this.state.albums[index]];
return (
<div className="columns" key={index}>
{albums.map(album => {
return (
<Album album={album} />
);
})}
</div>
);
}
}
另一种方法是将专辑数组分解成一个二维数组并对其进行迭代。下面的第一个突出显示的代码块将数组拆分。第二个是大大简化的渲染逻辑。
class App extends Component {
state = {albums: []}
async componentDidMount() {
let data = Array.from(await GetAlbums());
// split the original array into a collection of two item sets
data.forEach((item, index) => {
if (index % 2) {
albums.push([data[index - 1], data[index]]);
}
});
this.setState({
albums: albums
});
}
render() {
return (
<section className="section">
{this.state.albums.map((album, index) => {
return (
<div className="columns">
<Album album={album[0]}></Album>
<Album album={album[1]}></Album>
</div>
)
})}
</section>
)
}
}
这使 JSX 清理了很多,但现在我冗余地输入了 Album
组件,这感觉不太对。
Sarah Drasner 指出我甚至没有考虑这里更重要的场景之一,那就是未知底部场景。
未知底部
我上面提供的两种解决方案都假设从 fetch 收到的结果集是最终的。但如果它不是呢?
如果我们正在从服务器流式传输数据(类似于 RxJs 样式),并且我们不知道我们将收到多少次结果集,也不知道给定集中有多少个项目。这确实使事情变得复杂,并且完全破坏了提议的解决方案。实际上,我们可以继续说这两个解决方案都不是理想的,因为它们无法扩展到这种用例。
我觉得这里最简单的解决方案是在 CSS 中修复它。让 CSS 以预期的的方式处理布局。我仍然认为查看如何使用 JSX 执行此操作很重要,因为现实世界中有人每天都必须处理这样的恶作剧。需求并不总是我们希望它们成为的样子。
您将如何做?
我的问题就是这样——您将如何做到这一点?是否有更简洁、更高效的方法?如何才能做到这一点,使其能够扩展到未知底部?好奇的人(尤其是我的)很想了解。
我根本不会使用上面的 div。
我会使用完整的 css 将它们并排浮动。
.col {
box-sizing: border-box;
width: 50%;
}
.col:nth-child(odd) {
margin-right: 0.5rem
}
.col:nth-child(even) {
margin-left: 0.5rem;
}
.col:not(:nth-child(0)),
.col:not(:nth-child(0)) {
margin-top: 1rem;
}
第一个 .col 需要一个 display: inline-block; 无法编辑评论 哭泣
我认为这最终是正确的答案。我试图想象自己身处无法接触 CSS 的境地。如果我必须出于任何原因使用逻辑而不是布局来执行此渲染。
此外,“天啊,我无法编辑我的评论”这件事也发生在我身上。
我知道加载整个包可能不是最佳选择,但使用 React-Bootstrap,您可以轻松地使用它们的列/行方法,而无需执行所有这些操作。模块化程度也更高。
为什么不使用 flexbox 属性 flex-flow: column wrap 进行布局?这将允许您轻松重复您的专辑组件。
是的,甚至比我的传统 CSS 解决方案更好。在办公室,我们一个月前才停止对 IE9 和 IE10 的支持。仍然需要习惯于您可以使用 flexbox。
哦 - 很好!
就像 Samantha 提到的那样,我想知道还有多少人仍在支持 IE。几周前我在 Nodevember 做了一个会议,调查了房间里的人,有相当数量的人举手说他们仍然支持旧版 IE。
这正是我一直默认使用的方法。作为一名函数式程序员,很容易默认使用逻辑来处理几乎所有事情。但是,KISS 规定使用可用的东西,而不是编写新的东西。CSS 非常强大,而且通常情况下,大多数类似这样的问题都可以通过简单地使用一些 HTML 和 CSS 来解决。
此外,如果您使用的是像
styled-components
这样新颖的东西,则无需担心访问 CSS 文件,因为它们位于您的样式组件中。 :)就是这样。我本来想问一下,“row-twoColumns, row-twoColumns”结构是否有严格的要求。如果没有,我认为这是整体上最好的选择,除了旧浏览器支持。
+1 支持 Flexbox。当我阅读开头的段落时,这是我的第一想法。
除非您需要支持 IE 恶魔,否则在这种情况下无需将自己绑定到框架。
我最初将其设置为一个修改 CSS 不可行 的实例。这是因为我使用的是 Bulma,并且开箱即用,Bulma 似乎不支持多行列,即使指定了 50% 的宽度(
is-half
),并且我不想对其进行修补。在 Samantha 启动此操作后,我返回并查看了文档,结果发现 Bulma 完全可以做到这一点。您只需添加
is-multiline
类。https://codepen.io/burkeholland/pen/XVrPON
阅读手册,Burke!
同意 Jennifer 的观点,它应该用网格、flex 甚至浮动来完成。布局不是 JS 的问题。我认为任何框架都不会给你这样的限制。如果它不合适,您不必使用它们提供的所有类。我很想知道为什么您认为它不适合接收流式数据。你能解释一下吗?
我的意思是我的 JSX 渲染解决方案不合适。它基于固定数据集来确定何时打开和关闭“columns”标签。这意味着,如果您有流式数据,只要传入的数据集都是偶数,一切就都很好。但是,第一次遇到项目数量为奇数的数据集时,页面的其余部分布局就会出错。
不确定您所说的流式数据是什么意思。组件的渲染函数每次运行时都可以访问固定数据集。它将生成一个影子 DOM 并将其与实际 DOM 中的内容进行比较,然后替换需要替换的内容。
例如
– 第 1 次渲染时,您有 2 个项目,因此它渲染两列
– 第 2 次渲染时,您有 3 个项目,因此它渲染影子 DOM,意识到需要渲染一个只有一列的额外行
– 第 3 次渲染时,您有 5 个项目,因此它更新第二行并渲染第三行,其中只有一列
您可能需要将逻辑从 Angular 移植到 React,请参阅 http://rintoj.github.io/angular2-virtual-scroll/
很酷的插件!尽管 React 本身也有虚拟滚动,但我更关心如何有条件地将未知数量的元素包装在一个标签中。
如果你需要虚拟化,还可以使用
react-virtualized
虽然请求的是 JS 解决方案,但我现在用手机,看看我能否描述一个解决方案。
为什么不使用取模运算符来关闭容器 div 并为每个偶数列表索引打开一个新的容器呢?然后,如果它们的索引与原始列表保持一致,则将新项目追加到列表中就变得微不足道了。
我认为我已经在这么做了,除非我理解错了。这确实有效,但如果单个列表发生变化(例如数据流而不是固定集合),那么索引就会失效。
虽然我同意 CSS Flexbox 可能是我会最终采用的方法,但我很好奇是否可以使用
map
来使列表的结尾动态化?附注:请原谅我可能出现的任何语法错误(我正在用手机),并且我真的希望评论能显示出来,因为我看到无法编辑评论。
啊哈,当然还有错别字。在附注中...
String.replace("m", "d", "come")
代码在我的手机上似乎没有完全渲染,但它传达了这个想法。
是的!这确实可以消除解决方案 2 中的第二个
<Album>
标签。但是,它提出了一个新的问题——现在第一次迭代没有办法定义一个key
,因为它是一个二维数组。此外,渲染语法现在看起来几乎更难阅读了。你怎么看?
我们公司也遇到了类似的问题,我们发布了一个 React 组件来解决它,因为我对其他的替代方案不满意。它支持多布局砌体式(masonry-lite)逻辑和无限滚动,它会在你向下滚动时从 DOM 中删除元素以减少浏览器的压力。你可以在此处查看其运行情况
https://www.themaven.net/globallead
只需继续向下滚动:)
以及 GitHub(公开的代码实际上有点过时了,如果有人感兴趣,请发邮件,我会从我们的私有仓库中重新发布最新版本)
https://github.com/themaven-net/cascade
这非常有趣。我注意到在滚动过程中有一点卡顿。这是由砌体布局引起的?
我宁愿不重新造轮子,而是使用 React-bootstrap 库的网格系统来解决这个问题。它允许你为大、中、小屏幕设置显示的列数,使一切都变得流畅。
我不熟悉 React,但总的来说,我相信最好使用列表(ul、ol)来列出项目,然后使用 CSS 将布局更改为你想要的任何样式。
从语义上讲这是合理的,而且更容易操作。
情况可能确实如此——我绝对不是“最佳实践先生”。使用不是列表的“重复器”有什么具体原因不好吗?我想不出有什么。
免责声明:我开始从事开发工作的时间不长,所以绝不是该领域的专家。
我认为没有必要非得使用列表来重复元素,但在本例中,项目是根据布局而不是其上下文进行包装的。
这与以下情况没有太大区别
这将每 2 个项目分组到一个组中,暗示这两个项目之间存在关系,但在本例中并非如此。更准确的表示是
除了更准确地表示数据外,它也更灵活。
假设我们要实现一个过滤器来隐藏项目 2。
使用
< ul>
,你可以简单地删除< li>
。使用列
< div>
,你必须清空列表,然后在不包含项目 2 的情况下重新填充它,以避免在第 1 行留下空白。它没有错,但列表更好。例如,使用语音,你可以说“选择列表中的第 5 个”。其他网站也可以使用你的列表,例如:谷歌在搜索结果底部的蓝色链接中使用了维基百科的索引列表。
我认为我会用 Python 来解决这个问题。
Python 永远是答案。问题无关紧要。
我 fork 了你的示例,似乎将多个 Album 放到每个列中就可以正常工作。 https://codepen.io/dna113p/project/editor/ApjRav
我的天啊——这真的很好。奇数放在第一列,偶数放在第二列。
但是对于“未知底部”的情况会发生什么?如果你以奇数结尾,它将放在第一列。当下一组项目进来时,你将从 1 开始,而那个 1 也将放在第一列。
我对未知底部的处理方式不太确定,而且我对 RxJS 也不太了解,在我看来,只要你有一种方法可以使用 setState 将新流式传输的项目添加到你的 albums 数组中,那么 React 就会正确渲染新的 albums。
这里有很多很好的答案从 CSS 方面解决了这个问题,这可能是正确的方法,但为了回答你的问题,即使这不是“正确”的问题...
在 React 中处理数组时,有两种情况会变得很奇怪...
第一种情况是当你想从数组的每个元素生成 0 个或多个条目时。在这种情况下,你应该使用
第二种情况是当你想根据数量或其他某些条件对数组项目进行分组时。在这种情况下,你有几个选择。你仍然可以使用数组 reduce 技巧,并且只在达到阈值时“推送”(对于简单的情况足够好),或者你可以使用生成器作为转换器(对于复杂的逻辑更好)。
reduce 技术:https://codepen.io/jamon/pen/VywvXL
生成器技术:https://codepen.io/jamon/pen/QaWbPd
所以这个答案对我来说超级吸引人——特别是
reduce
函数,因为我从未使用过它。在
reduce
的情况下,是否有办法使累加器全局化,以便如果数据流中出现了新的项目块,就可以将其添加到累加器中?此外,reduce 相对于 map 的优势是什么?由于你只返回累加器而不是每个单独的项目,所以是性能方面的原因吗?
为了澄清“无底”部分,你应该让 React 为你处理这部分——当你接近底部时,请求更多结果,渲染更多结果。如果你想更好地管理内存,你可以“释放”不在屏幕上的 DOM 中的结果并偏移位置以进行补偿(这部分也是一个难题,但它是一个已被广泛解决的难题,并且可以找到很多示例)
“未知底部”是一个无关紧要的问题,你不应该关心它;一旦你进入“无限滚动”领域,你需要保留额外的状态(例如,已获取的 albums、滚动位置)并根据该状态进行渲染。
回到你最初的难题,我发现最 DRY 的解决方案是使用 lodash/chunk 和两个 map
https://codepen.io/otsdr/project/editor/XLpxdO
“现在假设你正在使用的 CSS 框架要求你渲染……”
哈哈哈哈哈哈……不。
那些荒谬的要求!
但说真的——我以前在医疗保健行业工作,由于所有无法控制的限制,我们不得不跳过一些非常愚蠢的障碍。这个例子是人为编造的,但这种困境是真实存在的。
难道不应该就这样工作吗?
第一列a
第二列a
第一列b
第二列b
第一列c
第二列c
…?
很多人似乎都错过了这个谜题的重点。重点是**不要**修改CSS。这就是“谜题”的部分。重点是保持在描述的布局范围内。你不会去做《纽约时报》的纵横填字游戏,然后仅仅重新排列方块来适应你的答案,对吧?
无论如何,我不喜欢在组件内部添加额外的渲染函数。我认为这会使逻辑变得复杂,而将这些渲染函数分解成它们自己的组件则更加清晰。
我同样也不喜欢“预计算”状态,就像第二个示例那样。它也会使事情变得复杂。我们始终可以使用
componentShouldUpdate
来防止不必要的重新计算。我认为第一种方法的简化版本,使用单个渲染函数,是我会选择的。类似于
我注意到一些问题
数组索引不应该用作键。
建议的实现似乎假设项目数量为偶数?如果专辑数量为奇数,则最后一个专辑将不会渲染。
我同意你关于额外渲染函数的观点。渲染是一个抽象。每次添加另一个渲染函数时,都会创建另一个抽象,这使得难以理解原始渲染。
关于“预计算”状态的有趣想法。我不喜欢它,因为它又是另一个你必须意识到的变异,以便理解整个流程。
我在这条评论中学到了很多东西。谢谢!
我没有将我示例中的重要代码粘贴到内联,我想这可能对在手机上或其他方式阅读本文的人有所帮助。
这允许
这与代码本身无关,但我欣赏你的音乐选择:)
#永不忘记
React 16 允许设置组件在 DOM 中渲染的位置。
因此,每个奇数列通常会渲染 .columns > .column。
每个偶数列将渲染 .column 到最后一个 .columns 中。
嗯。
由于重点是CSS不能更改
这也使我能够在稍后某个时间点说,如果底层CSS支持,将其从2列更改为3列或其他类似内容(更改Grid标签上的@columns),或者CSS以某种方式更改,使得以某种方式构建DOM来获得这些列变得不再必要,例如
然后
我非常喜欢这个解决方案的一点是,Grid现在是一个单独的组件,这将简化渲染逻辑。你只需要考虑存在一个网格。如果你想知道网格是如何工作的,你可以查看网格组件。
你不能只使用flexbox,或者以其他某种方式依赖CSS来实现吗?尝试使用div来解决它是在混合关注点,这正是导致你遇到问题的根本原因。
你绝对是对的。我在这里试图说明的重点是,有时我们无法控制环境,我们必须发挥创造力。在我的职业生涯中,我更多地是被给了一个巨大的烂摊子,然后被告知“让它工作起来”,而不是一张白板。
我当然对讨论感兴趣,但主要想说的是你良好的音乐品味。
;)