Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

关于Transfer组件的性能问题的建议 #2860

Closed
codering opened this issue Aug 27, 2016 · 12 comments
Closed

关于Transfer组件的性能问题的建议 #2860

codering opened this issue Aug 27, 2016 · 12 comments
Assignees

Comments

@codering
Copy link

当Transer组件内维护的数据达到1000左右,初次渲染及checkbox的操作会感觉到明显的卡顿, 尽管曾经优化过(#2112) 。

然后今天我试着用react-lazy-load,看下能否提升性能
transfer/list.jsx#L165 简单改成

 <Lazyload key={item.key}  height={30} >
        <li onClick={() => this.handleSelect(item)} key={item.key} title={renderedText}>
          <Checkbox checked={checkedKeys.some(key => key === item.key)} />
          <span>{renderedEl}</span>
        </li>
 </Lazyload>

在chrome 51.0.2704.79 Ubuntu 16.04 (64-bit) 测试2000条数据, 发现性能可以提升1倍(无具体性能数据,只是从体验上)

所以我在想antd或rc可否内部实现一个lazyload组件,对可能会有大数据量展示的组件提供lazy属性?

@afc163
Copy link
Member

afc163 commented Aug 27, 2016

如果合适的话,可以直接引入 Lazyload 。

@benjycui
Copy link
Contributor

感觉 LazyLoad 集成进 antd 的意义不大。

这个组件有两个主要属性,分别是 heightoffset

  • 由于允许用户自定义 render,所以真实的 height 不渲染出来是不知道的
  • offset 的计算则需要参考 height,所以要自动生成也是很尴尬

然后现在 @codering 的写法,渲染出来应该是 ul > div > li,这种结构 React 是会报错的,所以 LazyLoad 需要增加 component 属性以允许用户自定义 wrapper,这个可以通过 PR 解决。

现阶段可以推荐用户使用 LazyLoad 优化性能,但不集成进 antd。

@benjycui
Copy link
Contributor

还是有两个方案可以实现惰性渲染:

  • 仿 react-infinite 的 API 支持 containerHeight elementHeight,这样就有足够的信息只渲染需要渲染的数据,不过 elementHeight 对于不等高的元素就是鸡肋。。
  • 增加 renderCount: number 为需要渲染的数据行数,监听 scroll 事件并对比 scrollTop 与已经渲染元素的总高度,可以实时调整需要渲染的数据集。

@afc163
Copy link
Member

afc163 commented Aug 29, 2016

@codering
Copy link
Author

源码发现 Transfer 对外暴露了一个body属性,我暂时用这个解决下 😆

import React, {Component} from 'react'
import Animate from 'rc-animate'
import {Transfer, Checkbox} from 'antd'
import Search from 'antd/lib/transfer/search'
import LazyLoad from 'react-lazy-load'

function isRenderResultPlainObject(result) {
  return result && !React.isValidElement(result) &&
    Object.prototype.toString.call(result) === '[object Object]';
}

export default class LazyTransfer extends Component {

      constructor(props) {
        super(props);
        this.state = {
          mounted: false,
        };
      }

      componentDidMount() {
        this.timer = setTimeout(() => {
          this.setState({
            mounted: true,
          });
        }, 0);
      }

      componentWillUnmount() {
        clearTimeout(this.timer);
      }

      render() {
            const renderBody = (props) => {
                const {dataSource, render, showSearch, prefixCls, filter, filterOption, 
                  handleFilter,handleClear, handleSelect, checkedKeys, searchPlaceholder, notFoundContent }  = props

                const  matchFilter = (filterText, item, text) => {
                  if (filterOption) {
                    return filterOption(filterText, item);
                  }
                  return text.indexOf(filterText) >= 0;
                }

                const  _handleSelect = (selectedItem) => {
                  const result = checkedKeys.some((key) => key === selectedItem.key);
                  handleSelect(selectedItem, !result);
                }

                const showItems = dataSource.map(item => {
                const renderResult = render(item);
                  let renderedText;
                  let renderedEl;

                  if (isRenderResultPlainObject(renderResult)) {
                    renderedText = renderResult.value;
                    renderedEl = renderResult.label;
                  } else {
                    renderedText = renderResult;
                    renderedEl = renderResult;
                  }

                  if (filter && filter.trim() && !matchFilter(filter, item, renderedText)) {
                    return null;
                  }
                  return (
                    <LazyLoad height={30} key={item.key} >
                      <li onClick={() => _handleSelect(item)} key={item.key} title={renderedText}>
                        <Checkbox checked={checkedKeys.some(key => key === item.key)} />
                        <span>{renderedEl}</span>
                      </li>
                    </LazyLoad>
                  );
                }).filter(item => !!item);

                return (
                  <div className={showSearch ? `${prefixCls}-body ${prefixCls}-body-with-search` : `${prefixCls}-body`}>
                    {showSearch ? <div className={`${prefixCls}-body-search-wrapper`}>
                      <Search prefixCls={`${prefixCls}-search`}
                        onChange={handleFilter}
                        handleClear={handleClear}
                        placeholder={searchPlaceholder || '请输入搜索内容'}
                        value={filter}
                      />
                    </div> : null}
                    <Animate component="ul"
                      transitionName={this.state.mounted ? `${prefixCls}-highlight` : ''}
                      transitionLeave={false}
                    >
                      {showItems.length > 0
                        ? showItems
                        : <div className={`${prefixCls}-body-not-found`}>{notFoundContent || '列表为空'}</div>}
                    </Animate>
                  </div>
                )
            }
            return <Transfer {...this.props} body={renderBody} />
      }
}

@codering
Copy link
Author

要是把checkbox换掉,比如我现在就把它简单处理成选中时颜色为红色, 性能也会有提升的。
是否考虑用其他的方式展现“选中状态”? 或者 item render的范围放大些,让使用者去处理整个li 的内容?

@benjycui
Copy link
Contributor

benjycui commented Aug 31, 2016

或者 item render的范围放大些,让使用者去处理整个li 的内容?

对一个 UI 库而言,这样太坑了吧,交互和样式都被改了。。

@afc163
Copy link
Member

afc163 commented Oct 17, 2016

加一个 itemRender(item): React.Node@benjycui

@benjycui
Copy link
Contributor

已经有这个 API 了。

https://ant.design/components/transfer/#API

image

@afc163
Copy link
Member

afc163 commented Nov 3, 2016

感觉楼主的建议还是可行的,默认集成 react-lazy-load ,并加一个 itemHeight (默认 32px)用于配合自定义内容。我来搞搞试试。

@benjycui
Copy link
Contributor

benjycui commented Nov 4, 2016

#2860 (comment)

仿 react-infinite 的 API 支持 containerHeight elementHeight,这样就有足够的信息只渲染需要渲染的数据,不过 elementHeight 对于不等高的元素就是鸡肋。。

elementHeight 或者可以替换成一个函数 getElementHeight,这样处理不等高的情况应该会比较容易了。

增加 renderCount: number 为需要渲染的数据行数,监听 scroll 事件并对比 scrollTop 与已经渲染元素的总高度,可以实时调整需要渲染的数据集。

其实与 @yesmeck #3715 (comment) 提到的 react-virtualized 相似。

@yesmeck yesmeck closed this as completed in 7373ce0 Nov 6, 2016
@lock
Copy link

lock bot commented May 3, 2018

This thread has been automatically locked because it has not had recent activity. Please open a new issue for related bugs and link to relevant comments in this thread.

@lock lock bot locked as resolved and limited conversation to collaborators May 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants