alibaba / hooks

A high-quality & reliable React Hooks library. https://ahooks.pages.dev/
https://ahooks.js.org/
MIT License
14.07k stars 2.71k forks source link

useDeepCompareEffect 存在依赖变更但是不触发的场景 #2663

Open cydiacen opened 2 weeks ago

cydiacen commented 2 weeks ago

复现组件:

function Components() {
      const [value, setValue] = useState([
        {
          label: '1'
        }
      ]);
      useDeepCompareEffect(() => {
        console.log('触发');
      }, [value]);

      const handleSearch = (valueStr: string) => {
        const newValue = [...value];
        // newValue[0].label = valueStr;  // 这么写不会触发  useDeepCompareEffect
        newValue[0] = { label: valueStr }; // 这么写可以 触发  useDeepCompareEffect
        setValue(newValue);
      };

      const handleClick = () => {
        console.log('点击', value);
      };

      return (
        <div id='box'>
          {value.map((item, index) => {
            return (
              <div key={index}>
                <Input
                  value={item.label}
                  onChange={(e) => {
                    handleSearch(e.target.value);
                  }}
                />
                <Button onClick={handleClick}>获取</Button>
              </div>
            );
          })}
        </div>
      );
    }

本质上是因为ref缓存了之前的对象,如果是通过直接修改对象的值进行的变更,那么在比较的时候,也是拿的最新的已经产生变更的ref进行比较。这样子并不会触发useDeepCompareEffect 的回调,可以考虑改成clone快照 使其与外部环境解耦。 https://github.com/alibaba/hooks/blob/c7bb04c42bd8800164a112fd448c263bd16b63d4/packages/hooks/src/createDeepCompareEffect/index.ts#L15

ruixingshi commented 2 weeks ago

我觉得这个问题的本质是setState时,newValue[0].label = valueStr,仅对value进行了浅拷贝,label的引用地址是没变的。 useDeepCompareEffect 使用的react-fast-compare 是递归比较,先比较引用,再进行值对比。因此label的比较结果认为是相同的。

如虑改成clone,useDeepCompareEffect的性能会变得很差,甚至即使dependence没变时,也会出现大量无效对比。

caorushizi commented 2 weeks ago

建议使用immerjs