React hooks 实时搜索更新数据

1,450 阅读1分钟

有这么一个需求,有一个输入框,当用户在输入框中输入关键字时进行实时搜索,且最后返回结果匹配最后的输入词。

拆分一下需求,有这么几个关键点

  • 实时自动搜索
  • 结果匹配最后的关键词

再继续拆分,还需要以下几个点

  • 节流:如果真的用户每输入一个词就发起一次搜索,将会非常发起无效的接口请求,因此需要做一个节流
  • 竞态:请求结果返回的顺序不能保证一致。

debounce hook

const useDebounce = (initialValue, delay) => {
  const [value, setValue] = useState(initialValue);
  const debouncedSetValue = useRef(
    debounce((val) => {
      setValue(val);
    }, delay)
  );
  useEffect(() => debouncedSetValue.current(initialValue), [initialValue]);
  return [value];
};

fetchData hook

const useFectchData = (func, params, initialValue) => {
  const [state, dispatch] = useReducer(
    (state, action) => {
      switch (action.type) {
        case "FETCH_INIT":
          return { ...state, isFetching: true, isError: false };
        case "FETCH_SUCCESS":
          return {
            ...state,
            isFetching: false,
            isError: false,
            data: action.payload,
          };
        case "FETCH_FAILURE":
          return {
            ...state,
            isFetching: false,
            isError: true,
          };
      }
    },
    {
      isError: false,
      isFetching: false,
      data: initialValue,
    }
  );

  const [debouncedParams] = useDebounce(params, 500);

  useEffect(() => {
    let didCancel = false;
    const fetchData = async () => {
      try {
        dispatch({ type: "FETCH_INIT" });
        const res = await func(params);
        if (didCancel) return;
        dispatch({ type: "FETCH_SUCCESS", payload: res });
      } catch (_) {
        dispatch({ type: "FETCH_FAILURE" });
      }
    };
    fetchData();
    return () => {
      didCancel = true;
    };
  }, [debouncedParams]);

  return [state];
};

组件

function App() {
  const [searchParams, setSearchParams] = useState({
    query: "react",
    current: 1,
    pageSize: 20,
  });

  const [{ data: searchResult, isFetching }] = useFectchData(
    fetchData,
    searchParams,
    {}
  );

  const { tableData, pagination } = useMemo(() => {
    const tableData = searchResult.data || [];
    const pagination = {
      current: (searchResult?.current || 0) + 1,
      total: searchResult?.total || 0,
      pageSize: searchResult?.pageSize || 20,
    };
    return { tableData, pagination };
  }, [searchResult]);

  const tableColumns = useMemo(() => {
    return [
      { title: "标题", dataIndex: "title" },
      { title: "作者", dataIndex: "author" },
      {
        title: "发布时间",
        dataIndex: "created_at",
        render: (val) => dayjs(val).format("YYYY-MM-DD"),
      },
    ];
  }, []);

  return (
    <div>
      <Input
        style={{ width: 200, margin: 8 }}
        value={searchParams.query}
        onChange={(event) => {
          setSearchParams({ ...searchParams, query: event.target.value });
        }}
      />
      <Button
        style={{ display: "block" }}
        loading={isFetching}
        style={{ margin: 8 }}
        onClick={() => {
          setSearchParams({ ...searchParams });
        }}
      >
        search
      </Button>
      <Table
        pagination={{
          ...pagination,
          onChange: (current, pageSize) => {
            setSearchParams({
              ...searchParams,
              current: current - 1,
              pageSize,
            });
          },
        }}
        style={{ width: 1000, margin: 8 }}
        bordered
        rowKey="objectID"
        loading={isFetching}
        columns={tableColumns}
        dataSource={tableData}
      ></Table>
    </div>
  );
}

参考

www.robinwieruch.de/react-hooks… overreacted.io/zh-hans/a-c…