当前位置: 首页 > 知识库问答 >
问题:

前端 - react渲染长列表引发的性能问题?

巫经义
2023-08-09

起因

页面
问题的起因是左侧模版列表在开发时没有测试最多五百条数据的极限,当数据列表达到五百条的时候,就单纯的改变checkbox组件状态(选中或者不选中之间切换),就很慢,看了一下性能分析,一个click事件在4倍降低CPU的情况下长达4s的长任务,我不理解 :)

放大图

这里我在控制台中,发现列表并没有重新渲染所有节点,而且也没有接口调用。仅仅五百个节点就这样,虚拟dom性能有点...
console

顺便补充一下:react版本是18.2.0,组件库是"antd": "^4.21.0"。难道Fiber树对于虚拟dom树没有提升,还是我使用方式不对?
组件书写结构大概是这样:

const GroupItem = (props: GroupItemProps) => {   .....}const TpTemplate = () => {   return           <>{             <CheckboxGroup              className="group-check"              value={checkedList}              onChange={onChange}              style={{ width: '100%' }}            >              {TpTemplateList?.map(group => (                <div className="group-container" key={group?.id}>                  <Checkbox value={group.id} className="group-check" />                  <GroupItem                   {...props}                  >                    {group.name}                  </GroupItem>                </div>              ))}              {!(TpTemplateList?.length > 0) && <Empty />}            </CheckboxGroup>          }</>}

完整代码有点多,其中有使用'ahooks','styled-components'

const { TextArea } = Inputconst CheckboxGroup = Checkbox.Groupinterface GroupItemProps {  activeId: string | undefined  children: string  id: string  typeName: string  onDelete: (id: string) => void | Promise<any>  onClick: () => void | Promise<any>  checked: boolean  describe: string  searchName: string  editForm: FormInstance<any>  getTpTemplateList: (name: string) => Promise<Result<TemplateListResult[]>>  setVisibleCompile: React.Dispatch<    React.SetStateAction<{      visible: boolean      editId: string      name: string    }>  >  setDeleteModal: React.Dispatch<boolean>  setCheckedList: React.Dispatch<React.SetStateAction<any[]>>}const GroupItem = (props: GroupItemProps) => {  const {    children,    id,    onClick,    activeId,    checked,    describe,    getTpTemplateList,    setVisibleCompile,    setDeleteModal,    setCheckedList,    editForm,    searchName,  } = props  const isActive = id === activeId  const handleSwitchChange = async (e: boolean) => {    try {      await updateTpTemplateStatus({ template_id: id, is_enable: e ? 1 : 2 })      await getTpTemplateList(searchName)    } catch (error: any) {      if (error.code !== 200) {        Message.error(error.msg || '操作失败')      }    }  }  const handleEditClick = (e: React.MouseEvent) => {    e.stopPropagation()    setVisibleCompile({ visible: true, editId: id, name: children })    editForm.setFieldsValue({      name: children,      describe: describe,    })  }  const handleDeleteClick = () => {    setDeleteModal(true)    setCheckedList([id])  }  return (    <GroupItemContainer isActive={isActive} onClick={onClick}>      <Typography.Text        ellipsis={{ tooltip: { getPopupContainer: () => document.body } }}        className="text"        key={id}      >        {children}      </Typography.Text>      <Switch        checkedChildren="开"        unCheckedChildren="关"        size="small"        style={{ marginRight: 10 }}        checked={checked}        // loading={updateStatusLoading}        onChange={handleSwitchChange}      />      <Dropdown        overlay={          <Menu            items={[              {                key: '1',                label: (                  <div className="operate" onClick={handleEditClick}>                    <IconFont type={'icon-huabanfuben'} className="delete-icon" />                    <div>编辑</div>                  </div>                ),              },              {                key: '2',                label: (                  <div className="operate" onClick={handleDeleteClick}>                    <IconFont type={'icon-shanchu-m'} className="delete-icon" />                    <div>删除</div>                  </div>                ),              },            ]}          />        }      >        <IconFont type="icon-gengduocaozuo" />      </Dropdown>    </GroupItemContainer>  )}const MemoizedGroupItem = React.memo(GroupItem)const TpTemplate = () => {  const [searchName, setSearchName] = useState<string>('')  /**获取att&ck列表 */  // const [getAttCkList, AttCkList] = useGetEndPointAttackList()  /**获取模板列表 */  const [getTpTemplateList, TpTemplateList, tpTemplateLoading] = useGetTpTemplateList()  const [selectGroup, setSelectGroup] = useState<TemplateListResult>(null)  /**编辑模版 */  const [visibleCompile, setVisibleCompile] = useState<{    visible: boolean    editId: string    name: string  }>({    visible: false,    editId: '',    name: '',  })  /**添加模版 */  const [visibleModal, setVisibleModal] = useState<boolean>(false)  const [deleteModal, setDeleteModal] = useState<boolean>(false)  const [checkedList, setCheckedList] = useState([])  const [createForm] = Form.useForm()  const [editForm] = Form.useForm()  const { run: setSearchValue } = useDebounceFn(    (name: string) => {      setSearchName(name)    },    {      wait: 500,    },  )  useEffect(() => {    getTpTemplateList(searchName).catch(() => {})  }, [searchName])  const hasSelected = checkedList.length > 0  useEffect(() => {    getTpTemplateList('')      .then(res => {        if (res?.data?.length > 0) {          setSelectGroup(res?.data?.[0])        } else {          setSelectGroup(null)        }      })      .catch(() => {})  }, [])  const onFinish = async values => {    const { name, describe } = values    try {      if (visibleCompile.visible) {        setVisibleCompile({ visible: false, editId: '', name: '' })        await updateTpTemplate({ template_id: visibleCompile.editId, name, describe }).then(() =>          setSelectGroup(pre => ({            ...pre,            id: visibleCompile.editId,            name,            describe,          })),        )        await getTpTemplateList(searchName)        editForm.resetFields()      } else {        setVisibleModal(false)        await createTpTemplate({          name,          describe,        })        await getTpTemplateList(searchName)        createForm.resetFields()      }    } catch (error: any) {      if (error.code !== 200) {        Message.error(error.msg || '操作失败')      }    }  }  const onChange = list => {    setCheckedList(list)  }  return (    <UserContainer>      <LeftCard title={<SignTitle show={false}>模板列表</SignTitle>}>        <SearchGroup>          <SearchInputTwo            inputProps={{              placeholder: '请输入模板名称',              onChange: e => {                setSearchValue(e.target.value)              },            }}          />        </SearchGroup>        <BatchOperate hasSelected={hasSelected}>          <Delete            active={hasSelected}            onClick={() => {              if (hasSelected) setDeleteModal(true)            }}          >            <SvgIcon              name={hasSelected ? 'batch_remove' : 'batch_remove_ash'}              className="icon-delete"            />            批量删除          </Delete>        </BatchOperate>        <DeleteModal          text="此操作将永久删除,是否继续?"          visible={deleteModal}          // confirmLoading={batchDeleteLoading}          onOk={async () => {            try {              setDeleteModal(false)              await deleteTpTemplate(checkedList)              await getTpTemplateList(searchName)              setCheckedList([])              setSelectGroup(TpTemplateList[0])            } catch (error: any) {              Message.error(error?.msg || '删除模板列表失败')            }          }}          onCancel={() => {            setDeleteModal(false)            setCheckedList([])          }}        />        <GroupListContainer>          <Skeleton loading={tpTemplateLoading}>            <CheckboxGroup              className="group-check"              value={checkedList}              onChange={onChange}              style={{ width: '100%' }}            >              {TpTemplateList?.map(group => (                <div className="group-container" key={group?.id}>                  <Checkbox value={group.id} className="group-check" />                  <MemoizedGroupItem                    // typeId={group?.TypeId}                    activeId={selectGroup?.id}                    id={group.id}                    checked={group?.is_enable == 1}                    typeName={group?.name}                    describe={group?.describe}                    onDelete={() => setDeleteModal(true)}                    onClick={() => {                      setSelectGroup(group)                    }}                    editForm={editForm}                    getTpTemplateList={getTpTemplateList}                    setVisibleCompile={setVisibleCompile}                    setDeleteModal={setDeleteModal}                    searchName={searchName}                    setCheckedList={setCheckedList}                  >                    {group.name}                  </MemoizedGroupItem>                </div>              ))}              {!(TpTemplateList?.length > 0) && <Empty />}            </CheckboxGroup>          </Skeleton>        </GroupListContainer>        <LeftCardFooter>          <Button            type={'primary'}            style={{ width: '100%' }}            onClick={() => {              setVisibleModal(true)            }}          >            添加模板          </Button>        </LeftCardFooter>        <Modal          style={{ width: '450px' }}          title={<SignTitle show>添加模板</SignTitle>}          centered          // confirmLoading={createLoading}          getContainer={() => document.body}          visible={visibleModal}          // destroyOnClose          onCancel={() => {            createForm.resetFields()            setVisibleModal(false)          }}          onOk={() => createForm.submit()}          // getContainer={false}        >          <Form form={createForm} onFinish={onFinish} layout="vertical">            <div className="add-payload">              <Form.Item                name={'name'}                label="模板名称:"                rules={[                  { required: true, message: '请输入模板名称' },                  { max: 15, message: '模板名称最多为15个字符' },                  () => ({                    validator(_, value) {                      if ((TpTemplateList || [])?.some(group => group.name === value)) {                        return Promise.reject(new Error('模板名称已存在'))                      }                      return Promise.resolve()                    },                    validateTrigger: 'onSubmit',                  }),                ]}                style={{ display: 'flex', flexDirection: 'column' }}              >                <Input placeholder="请输入(15字)" type="string" onChange={e => {}} />              </Form.Item>              <Form.Item                name={'describe'}                label="模板描述"                rules={[{ required: true, message: '请输入模板描述' }]}              >                <TextArea                  showCount                  maxLength={4096}                  style={{ height: 120, resize: 'none' }}                  // onChange={onChange}                />              </Form.Item>            </div>          </Form>        </Modal>        <Modal          title={<SignTitle>编辑模板</SignTitle>}          centered          // confirmLoading={updateLoading}          getContainer={() => document.body}          visible={visibleCompile.visible}          onOk={() => editForm.submit()}          onCancel={() => {            editForm.resetFields()            setVisibleCompile({ visible: false, editId: '', name: '' })          }}          // getContainer={false}        >          <Form form={editForm} onFinish={onFinish} layout="vertical">            <Form.Item              name={'name'}              label="模板名称:"              rules={[                { required: true, message: '模板名称不能为空' },                { max: 15, message: '模板名称最多为15个字' },                () => ({                  validator(_, value) {                    if (                      visibleCompile.name !== value &&                      (TpTemplateList || [])?.some(group => group.name === value)                    ) {                      return Promise.reject(new Error('已存在'))                    }                    return Promise.resolve()                  },                  validateTrigger: 'onSubmit',                }),              ]}              // initialValue={selectGroup?.name}              style={{ display: 'flex', flexDirection: 'column' }}            >              <Input placeholder="请输入(15字)" type="string" onChange={e => {}} />            </Form.Item>            <Form.Item              name={'describe'}              label="模板描述"              rules={[{ required: true, message: '模板描述不能为空' }]}              // initialValue={selectGroup?.describe}            >              <TextArea showCount maxLength={4096} style={{ height: 120, resize: 'none' }} />            </Form.Item>          </Form>        </Modal>      </LeftCard>      <RightContainer>        {TpTemplateList?.length > 0 ? (          <SubTemplate            key={selectGroup?.id}            id={selectGroup?.id}            // attCkList={AttCkList}            describe={selectGroup?.describe}          />        ) : (          <div style={{ marginTop: 200 }}>            <Empty image={EmptyImg} />          </div>        )}      </RightContainer>    </UserContainer>  )}export default TpTemplate

有大佬解惑吗?万分感谢!

共有1个答案

尉迟韬
2023-08-09

GroupItem用React.memo:

const GroupItem = React.memo((props: GroupItemProps) => {  // ...});

第三方库:

import { FixedSizeList as List } from 'react-window';const TpTemplate = () => {  // ...  return (    <List      height={500} // 视窗的高度      itemCount={TpTemplateList?.length || 0}      itemSize={50} // 每个列表项的高度    >      {({ index, style }) => {        const group = TpTemplateList[index];        return (          <div style={style} key={group?.id}>            <Checkbox value={group.id} className="group-check" />            <GroupItem {...props}>{group.name}</GroupItem>          </div>        );      }}    </List>  );};
 类似资料:
  • 本文向大家介绍react怎么提高列表渲染的性能?相关面试题,主要包含被问及react怎么提高列表渲染的性能?时的应答技巧和注意事项,需要的朋友参考一下 使用webpack 做代码分割。 使用hooks。

  • 问题内容: 我以TodoList为例来反映我的问题,但是显然我的实际代码更加复杂。 我有一些这样的伪代码。 我所有的数据都是不可变的,并且使用了并且一切正常。修改待办事项数据后,仅 重新渲染父项和已编辑的待办事项。 问题是,随着用户的滚动,我的列表有时会变得很大。并且,当更新单个待办事项时,渲染 父项,调用shouldComponentUpdate所有待办事项以及渲染单个待办事项所花费的时间越来越

  • 根据数据库里面获取到的数据信息渲染表格,起初根据数据结构生成了四列,但是由于后续数据库里面会有数据结构上的变化,会增加字段,那我要如何在表格中去追加这部分新数据,从而在前端渲染出一个新的表格————就是说原来四列变六列,并把对应数据也一同渲染上去。

  • Mpx中的列表渲染与原生小程序中完全一致,详情可以查看这里 值得注意的是wx:key与Vue中的key属性的区别,不能使用数据绑定,只能传递普通字符串将数组item中的对应属性作为key,或者传入保留关键字*this将item本身作为key 下面是简单示例: <template> <!-- 使用数组中元素的 id属性/保留关键字*this 作为key值 --> <view wx:for=

  • 在上文中,我们从多个角度讨论了如何优化页面加载性能。但一个用户体验良好的页面,不仅要快速加载,还需要有一系列流畅的交互。从而,这一节我们把目光投向页面渲染性能。 渲染流程 浏览器在渲染页面前,首先会将 HTML 文本内容解析为 DOM,将 CSS 解析为 CSSOM。DOM 和 CSSOM 都是树状数据结构,两者相互独立,但又有相似之处。DOM 树描述了 HTML 标签的属性,以及标签之间的嵌套关

  • 在使用 nuxt 时,nuxt 可以使用 usefetch 进行请求,底层的实现是 ofetch 这个库,这个库支持在服务器端和客户端进行请求,nuxt 做了优化,如果服务器端有请求过的数据会序列化传输到客户端,这样客户端在水合时就不用再发起请求。而在使用 nextjs 时,使用的是 fetch 进行请求,nextjs 对 fetch 进行了扩展,增加了缓存的功能,但是我发现这个扩展的 fetch