在 react hook 中使用 debounce

4,887 阅读2分钟

最近在 react 使用中遇到了一个问题,react 事件池的问题,react 文档是这样解释的:

SyntheticEvent 是合并而来。这意味着 SyntheticEvent 对象可能会被重用,而且在事件回调函数被调用后,所有的属性都会无效。出于性能考虑,你不能通过异步访问事件。

如果你想异步访问事件属性,你需在事件上调用 event.persist(),此方法会从池中移除合成事件,允许用户代码保留对事件的引用。

具体表现可以看下下面这一段代码

function onClick(event) {
  console.log(event); // => nullified object.
  console.log(event.type); // => "click"
  const eventType = event.type; // => "click"

  // 异步访问
  setTimeout(function() {
    console.log(event.type); // 报错
    console.log(eventType); // => "click"
  }, 0);

  // setState是异步的 报错
  this.setState({ clickEvent: event });

  // 正常
  this.setState({ eventType });
}

由此可见,下面这段代码输入框输入的时候绝对会报错

import React, { useState } from 'react';
import { debounce } from '../../util';

export default () => {
  const [span, setSpan] = useState('');

  const onChange = debounce((e: React.SyntheticEvent) => {
    const val = (e.target as HTMLInputElement).value;
    setSpan(val);
  }, 300);
  return (
    <div>
      <input type="text" onChange={onChange} />
      <span>{span}</span>
    </div>
  );
};

以下是几种解决方案

回调函数参数处理

onChange 的回调参数做一下处理,直接传递 value,代码如下:

import React, { useState } from 'react';
import { debounce } from '../../util';

export default () => {
  const [span, setSpan] = useState('');

  const onChange = debounce((val: string) => {
    setSpan(val);
  }, 300);
  return (
    <div>
      <input
        type="text"
        onChange={(e: React.SyntheticEvent) => onChange((e.target as HTMLInputElement).value)}
      />
      <span>{span}</span>
    </div>
  );
};

修改一下可以改成这样

import React, { useState } from 'react';
import { debounce } from '../../util';

export default () => {
  const [span, setSpan] = useState('');
  const debounceFn = debounce((val: string) => {
    setSpan(val);
  }, 300);

  const onChange = (e: React.SyntheticEvent) => {
    const val = (e.target as HTMLInputElement).value;
    debounceFn(val);
  };
  return (
    <div>
      <input type="text" onChange={onChange} />
      <span>{span}</span>
    </div>
  );
};

hook 去处理

既然都用到了 react hook 为什么不尝试下用自定义的 hook 来处理,自定义一个 useDebouncehook 来处理:

import { useState, useEffect } from 'react';

export default function useDebounce<T>(value: T, delay: number = 300): T {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}

然后再组件中这样使用:

import React, { useState } from 'react';
import useDebounce from '../../util/hooks/debounce';

export default () => {
  const [span, setSpan] = useState('');
  const debounceSpan = useDebounce(span);

  const onChange = (e: React.SyntheticEvent) => {
    const val = (e.target as HTMLInputElement).value;
    setSpan(val);
  };
  return (
    <div>
      <input type="text" onChange={onChange} />
      <span>{debounceSpan}</span>
    </div>
  );
};

useDebounce 的原理和防抖动函数实现的原理是一样的。