【已解决】React子组件重复渲染

ps:刚开始用react,所以我写的很烂,并且也不是很理解为什么我按照别人的教程一一做了就是失败了。

一、代码结构

1. 父组件A结构中有B、C两个子组件

        - useEffect中请求categoryList传递给B,请求list传递给C的table使用

        - tabActive状态传递给C的tab使用

        - onFilter方法用来筛选table中数据,由B组件表单提交时触发

2. B组件中有表单,接收父组件传递过来的categoryList和onFilter

3. C组件:一个Tab组件和一个Table组件

        - 表格每一行有操作onBatch,点击会触发A组件中的批量更新数据方法

二、问题描述

当在C组件切换tab或者onBatch时,只要A中有state改变,都会触发B组件的重复渲染,实际B组件的依赖数据是没有更新的,比较浪费

三、解决过程

1. 使用React.memo将B组件包裹,使用useCallback包裹onFilter方法。结果:依旧会引起B组件重复渲染。

2. 在第一步的结果上使用useMemo又缓存了categoryList。结果:依旧重复渲染。

3. 查看官方文档找到为什么1和2失败。

4. 寻求大佬朋友的帮助,告知我代码中很多useCallback都是无效的,让我使用useCallback包裹B组件,并传递依赖项categoryList和onFilter。结果:依旧重复渲染,在方法中的打印语句多次执行

5. 抱着试试的心态,用useMemo包裹了B组件并传递依赖项。结果:解决!

四、总结:需要完整阅读一遍官方文档,以及hooks的使用规范,再接再厉!

五、源码(删除部分无关代码)

父组件A

import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
  DataType,
  FilterProductInfo,
  PageInfo,
  Category,
  TableComponentRef
} from "../type";
import Filter from "./components/Filter";
import Table from "./components/Table";
import GoodsApi from "@/api/interface/goods";
import { TabsProps, message } from "antd";
import { productStatusConst } from "../const";

export default function GoodsManagement() {
  const [categoryList, setCatagoryList] = useState<Category[]>([]);
  const [filterData, setFilterData] = useState<FilterProductInfo>({});
  const [tabActive, setTabActive] = useState<number>(0);
  const [tabItems, setTabItems] = useState<TabsProps["items"]>(
    productStatusConst.list
  );
  const [tableData, setTableData] = useState<DataType[]>([]);
  const [page, setPage] = useState<PageInfo>({ pageIndex: 1, pageSize: 10 });
  const [pageTotal, setPageTotal] = useState<number>(0);
  const [loading, setLoading] = useState<boolean>(false);
  const tableRef = useRef<TableComponentRef>(null);

  const onFilter = useCallback(
    (form: FilterProductInfo) => {
      const data = {
        ...form,
        status: form.status ?? tabActive
      };
      setFilterData(data);
      setTabActive(data.status);
      setPage({ pageIndex: 1, pageSize: 10 });
    },
    [tabActive]
  );

  // 表格批量操作
  const onOperate = async ({
    type,
    productIds
  }: {
    type: number;
    productIds: string[];
  }) => {
    console.log("执行批量操作", type, productIds);
    const data = { productIds };
    try {
      if (type === productStatusConst.USING) {
        await GoodsApi.unableProduct(data);
      } else {
        await GoodsApi.enableProduct(data);
      }
    } catch (error) {
      console.log(error);
    }
    message.success("操作成功");
    // 批量操作完成后列表恢复到第一页 清空选中项
    setPage({ ...page, pageIndex: 1 });
    getProductCount({});
    tableRef.current?.setSelectedKeys([]);
  };

  const getProductList = useCallback(
    async (data: FilterProductInfo) => {
      console.log("getProductList调用了,当前数据", data);
      setLoading(true);
      try {
        const { list, totalSize } = await GoodsApi.getProductList(data);
        setTableData(list);
        setPageTotal(totalSize);
      } catch (error) {
        console.log("error==>", error);
      } finally {
        setLoading(false);
      }
    },
    [setTableData, setPageTotal, setLoading]
  );

  const getCategoryList = useCallback(async () => {
    console.log("调用getCategoryList了");
    const { list } = await GoodsApi.getCategoryList();
    setCatagoryList(list);
  }, [setCatagoryList]);

  const getProductCount = useCallback(
    async (data: FilterProductInfo) => {
      try {
        const { enableCount, incompleteCount, unableCount } =
          await GoodsApi.getProductCount(data);
        const tabsItem = productStatusConst.list.map(item => {
          const labelMap = {
            [productStatusConst.USING]: `${item.label} ${enableCount}`,
            [productStatusConst.STOP]: `${item.label} ${unableCount}`,
            [productStatusConst.TO_BE_COMPLETE]: `${item.label} ${incompleteCount}`
          };
          return { ...item, label: labelMap[item.value] || "" };
        });
        setTabItems(tabsItem);
      } catch (error) {
        console.log("error==>", error);
      }
    },
    [setTabItems]
  );

  useEffect(() => {
    console.log("当前tab选中", tabActive);

    getProductList({ ...page, status: tabActive, ...filterData });
  }, [page, tabActive, filterData, getProductList]);

  useEffect(() => {
    getCategoryList();
  }, [getCategoryList]);

  useEffect(() => {
    getProductCount({});
  }, [getProductCount]);

  return (
    <>
      {useMemo(
        () => (
          <Filter categoryList={categoryList} onFilter={onFilter} />
        ),
        [categoryList, onFilter]
      )}
      <Table
        ref={tableRef}
        tableData={tableData}
        page={page}
        total={pageTotal}
        tabActive={tabActive}
        loading={loading}
        tabItems={tabItems}
        onChange={(page: PageInfo) => setPage(page)}
        onChangeTab={(active: string) => setTabActive(Number(active))}
        onBatch={payload => onOperate(payload)}
      />
    </>
  );
}

子组件B 

import { Button, Form, Input, Row, Col, Select, Card } from "antd";
import { Category, FilterProductInfo } from "../../type";
import { productStatusConst, platformConst } from "../../const";

const SearchForm = ({
  categoryList,
  onFilter
}: {
  categoryList: Category[];
  onFilter: (data: FilterProductInfo) => void;
}) => {
  console.log("筛选组件渲染了!");
  const [form] = Form.useForm();

  const onSubmit = () => {
    const data = form.getFieldsValue();
    onFilter(data);
  };

  const onCancel = () => {
    form.resetFields();
  };

  return (
    <Card bordered={false} className="mt-4">
      <Form layout={"inline"} form={form}>
        <Row gutter={[16, 16]} wrap>
          <Col span={8}>
            <Form.Item label="商品名称" name="productName">
              <Input placeholder="关键字" allowClear />
            </Form.Item>
          </Col>
          <Col span={8}>
            <Form.Item label="商品品类" name="categoryId">
              <Select placeholder="请选择商品类目" allowClear>
                {categoryList.map(item => (
                  <Select.Option key={item.categoryId} value={item.categoryId}>
                    {item.categoryDisplayName}
                  </Select.Option>
                ))}
              </Select>
            </Form.Item>
          </Col>
          <Col span={8}>
            <Form.Item label="商品品牌" name="brandName">
              <Input placeholder="关键字" allowClear />
            </Form.Item>
          </Col>
          <Col span={8}>
            <Form.Item
              label="商品状态"
              name="status"
              initialValue={productStatusConst.USING}
            >
              <Select placeholder="请选择商品状态" allowClear>
                {productStatusConst.list.map(item => (
                  <Select.Option key={item.value} value={item.value}>
                    {item.label}
                  </Select.Option>
                ))}
              </Select>
            </Form.Item>
          </Col>
          <Col span={8}>
            <Form.Item label="商品平台" name="platform">
              <Select placeholder="请选择销售平台(可多选)" allowClear>
                {platformConst.map(item => (
                  <Select.Option key={item.value} value={item.value}>
                    {item.text}
                  </Select.Option>
                ))}
              </Select>
            </Form.Item>
          </Col>
          <Col span={8}></Col>
          <Col span={8}>
            <Button onClick={onSubmit}>查询</Button>
            <Button className="ml-2" onClick={onCancel}>
              重置
            </Button>
          </Col>
        </Row>
      </Form>
    </Card>
  );
};

export default SearchForm;

子组件C

import { Table, Button, Tabs, Card, Tooltip, message } from "antd";
import { ExclamationCircleFilled } from "@ant-design/icons";
import type { ColumnsType } from "antd/es/table";
import { DataType, GoodsTableProps } from "../../type";
import MainImage from "./MainImage";
import {
  deliveryTimeConst,
  productStatusConst
} from "../../const";
import { useState, useMemo, forwardRef, useImperativeHandle } from "react";
import PopConfirm from "@/components/PopConfirm";


const GoodsTable = forwardRef(function GoodsTable(
  {
    tableData,
    page,
    total,
    tabActive,
    loading,
    tabItems,
    onChange,
    onChangeTab,
    onBatch
  }: GoodsTableProps,
  ref
) {

  const columns: ColumnsType<DataType> = [
    {
      title: "商品名称",
      dataIndex: "productName",
      fixed: "left",
      width: 120,
    },
    {
      title: "零售价",
      dataIndex: "priceTag",
      fixed: "left",
      width: 80,
      render: text => {
        return `¥${Math.round(text / 100).toFixed(2)}`;
      }
    },

    {
      title: "品牌名称",
      dataIndex: "brandName",
      width: 80,
      render: text => (
        <Tooltip placement="topLeft" title={text}>
          {text}
        </Tooltip>
      )
    },
    {
      title: "商品品类",
      dataIndex: "categoryName",
      width: 80
    },
    {
      title: "商品库存",
      key: "inventory",
      dataIndex: "inventory",
      width: 80
    },
    {
      title: "运费险",
      dataIndex: "isSupportFreightInsurance",
      width: 90,
      render: text => {
        return text === 1 ? "支持" : "不支持";
      }
    },
    {
      title: "发货时间",
      dataIndex: "deliveryTime",
      width: 120,
      render: text => {
        return deliveryTimeConst[text];
      }
    },
    {
      title: "发货快递",
      key: "express",
      dataIndex: "express",
      width: 80
    },
    {
      title: "商品状态",
      key: "status",
      dataIndex: "status",
      width: 80,
      render: text => {
        return productStatusConst[text];
      }
    },
    {
      title: "操作",
      key: "operation",
      dataIndex: "operation",
      fixed: "right",
      align: "center",
      width: 100,
      render: (_text, record) => {
        return (
          <div>
            {record.status === productStatusConst.TO_BE_COMPLETE ? (
              ""
            ) : (
              <PopConfirm
                title="提示"
                description={`确定要${operateText}该商品吗?`}
                onConfirm={() =>
                  onBatch({
                    type: tabActive,
                    productIds: [String(record.productId)]
                  })
                }
              >
                {record.status !== (undefined || null) ? (
                  <Button type="link" className="text-theme">
                    {record.status === 1 ? "启用" : "禁用"}
                  </Button>
                ) : (
                  ""
                )}
              </PopConfirm>
            )}
          </div>
        );
      }
    }
  ];

  const onPageChange = (page: number, pageSize: number) => {
    // 将分页信息传递给父组件
    onChange({
      pageIndex: page,
      pageSize: pageSize
    });
  };

  const operateText = useMemo(() => {
    if (tabActive === productStatusConst.USING)
      return productStatusConst[productStatusConst.STOP];
    if (tabActive === productStatusConst.STOP)
      return productStatusConst[productStatusConst.USING];
  }, [tabActive]);

 
  const [batchLoading, setBatchLoading] = useState<boolean>(false);
  const onBatchOperation = async () => {
    if (!selectedRowKeys?.length) return message.warning("请选择商品");
    setBatchLoading(true);
    await onBatch({ type: tabActive, productIds: selectedRowKeys });
    setBatchLoading(false);
  };


  const paginationConfig = {
    current: page.pageIndex,
    pageSize: page.pageSize,
    total: total,
    pageSizeOptions: [10, 20, 50],
    onChange: onPageChange
  };

  useImperativeHandle(
    ref,
    () => {
      return {
        setSelectedKeys
      };
    },
    []
  );

  return (
    <Card bordered={false} className="mt-4">
      <Tabs
        items={tabItems}
        activeKey={String(tabActive)}
        onChange={active => onChangeTab(active)}
      />
   
      <Table
        rowKey="productId"
        loading={loading}
        columns={columns}
        dataSource={tableData}
        scroll={{ x: 1200 }}
        pageSizeOptions={[10, 20, 50]}
        pagination={paginationConfig}
      />
    </Card>
  );
});

export default GoodsTable;

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值