首页 > 解决方案 > 使用 React Hooks 时,如何处理来自父组件的函数参数?

问题描述

这是一个演示:https ://stackblitz.com/edit/react-ts-rttbjn

这个演示按我的意愿工作。但是在 中PagingList.tsx,会抛出 ESLint 警告:

ESLint: React Hook useEffect has a missing dependency: 'loadAfter'. Either include it or remove the dependency array.(react-hooks/exhaustive-deps)

但是使用[loadAfter]不起作用。

所以我很困惑,为什么loadAfter重新渲染时会改变?最佳做法是什么?

PS:非工作演示,无限重新渲染:https ://stackblitz.com/edit/react-ts-kl67zc

主要代码片段:

PS: .eslintrc.js:

// eslint-disable-next-line no-undef
module.exports = {
  root: true,
  parser: "@typescript-eslint/parser",
  plugins: ["@typescript-eslint", "jest"],
  extends: [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier/@typescript-eslint",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "plugin:jest/recommended",
  ],
  settings: {
    react: {
      version: "16.13",
    },
  },
};

index.tsx

import React, { FunctionComponent, useCallback } from "react";
import { PagingList } from "./PagingList";
import ReactDOM from "react-dom";

type Piece = {
  id: number;
};

const MAX_COUNT = 20;

async function getAfter(
  size: number,
  id?: number
): Promise<[Piece[], boolean]> {
  const from = Math.max(0, id || 0);
  const toExclusive = Math.min(from + size, MAX_COUNT);
  const items = Array.from({
    length: toExclusive - from,
  }).map((_, offset) => ({ id: from + offset + 1 }));
  return new Promise((resolve) =>
    setTimeout(() => resolve([items, toExclusive < MAX_COUNT]), 1000)
  );
}

async function getBefore(
  size: number,
  id?: number
): Promise<[Piece[], boolean]> {
  const toExclusive = Math.max(0, (id || 0) - 1);
  const from = Math.max(toExclusive - size, 0);
  const items = Array.from({
    length: toExclusive - from,
  }).map((_, offset) => ({ id: from + offset + 1 }));
  return new Promise((resolve) =>
    setTimeout(() => resolve([items, from > 0]), 1000)
  );
}

const App: FunctionComponent = () => {
  const Child = (piece: Piece) => (
    <div key={piece.id}>
      <span>Id: </span> {piece.id}
    </div>
  );

  const getAfterCallback = useCallback(
    (size: number, id?: number) => getAfter(size, id),
    []
  );

  const getBeforeCallback = useCallback(
    (size: number, id?: number) => getBefore(size, id),
    []
  );

  return (
    <PagingList
      childFunction={Child}
      getAfter={getAfterCallback}
      getBefore={getBeforeCallback}
    />
  );
};

ReactDOM.render(<App />, document.getElementById('root'));

PagingList.tsx [Workable but with error]

import React, {
  FunctionComponent,
  useCallback,
  useEffect,
  useState,
} from "react";

const QUERY_SIZE = 10;

type Data = { id: number };

interface Props {
  childFunction: (data: Data) => JSX.Element;
  getAfter: (next: number, after?: number) => Promise<[Data[], boolean]>;
  getBefore: (last: number, before?: number) => Promise<[Data[], boolean]>;
}

export const PagingList: FunctionComponent<Props> = ({
  childFunction,
  getAfter,
  getBefore,
}) => {
  const [after, setAfter] = useState<number | undefined>(undefined);
  const [before, setBefore] = useState<number | undefined>(undefined);
  const [data, setData] = useState<Data[]>([]);
  const [hasNext, setHasNext] = useState(false);
  const [hasLast, setHasLast] = useState(false);
  const [isLoading, setLoading] = useState(false);

  const loadAfter = useCallback(async () => {
    if (isLoading) {
      console.log("is loading");
      return;
    }

    setLoading(true);
    const [d, b] = await getAfter(QUERY_SIZE, after);
    setHasNext(b);
    setData(d);
    setAfter(d[d.length - 1].id);
    setBefore(d[0].id);
    setHasLast(true);
    setLoading(false);
  }, [after, getAfter, isLoading]);

  const loadBefore = useCallback(async () => {
    if (isLoading) {
      console.log("is loading");
      return;
    }

    setLoading(true);
    const [d, b] = await getBefore(QUERY_SIZE, before);
    setHasLast(b);
    setData(d);
    setAfter(d[d.length - 1].id);
    setBefore(d[0].id);
    setHasLast(true);
    setLoading(false);
  }, [before, getBefore, isLoading]);

  useEffect(() => {
    loadAfter();
    // ESLint: React Hook useEffect has a missing dependency: 'loadAfter'. Either include it or remove the dependency array.(react-hooks/exhaustive-deps)
    // But `[loadAfter]` as dep array is not working
  }, [getAfter]);

  const body = () => {
    if (isLoading) {
      return <div>Loading...</div>;
    } else {
      return (
        <div>
          <div>{data.map((d) => childFunction(d))}</div>
          <button disabled={!hasLast} onClick={loadBefore}>
            Last Page
          </button>
          <button disabled={!hasNext} onClick={loadAfter}>
            Next Page
          </button>
        </div>
      );
    }
  };

  return body();
};

PagingList.tsx [Not Working but no warning]

import React, {
  FunctionComponent,
  useCallback,
  useEffect,
  useState,
} from "react";

const QUERY_SIZE = 10;

type Data = { id: number };

interface Props {
  childFunction: (data: Data) => JSX.Element;
  getAfter: (next: number, after?: number) => Promise<[Data[], boolean]>;
  getBefore: (last: number, before?: number) => Promise<[Data[], boolean]>;
}

export const PagingList: FunctionComponent<Props> = ({
  childFunction,
  getAfter,
  getBefore,
}) => {
  const [after, setAfter] = useState<number | undefined>(undefined);
  const [before, setBefore] = useState<number | undefined>(undefined);
  const [data, setData] = useState<Data[]>([]);
  const [hasNext, setHasNext] = useState(false);
  const [hasLast, setHasLast] = useState(false);
  const [isLoading, setLoading] = useState(false);

  const loadAfter = useCallback(async () => {
    if (isLoading) {
      console.log("is loading");
      return;
    }

    setLoading(true);
    const [d, b] = await getAfter(QUERY_SIZE, after);
    setHasNext(b);
    setData(d);
    setAfter(d[d.length - 1].id);
    setBefore(d[0].id);
    setHasLast(true);
    setLoading(false);
  }, [after, getAfter, isLoading]);

  const loadBefore = useCallback(async () => {
    if (isLoading) {
      console.log("is loading");
      return;
    }

    setLoading(true);
    const [d, b] = await getBefore(QUERY_SIZE, before);
    setHasLast(b);
    setData(d);
    setAfter(d[d.length - 1].id);
    setBefore(d[0].id);
    setHasLast(true);
    setLoading(false);
  }, [before, getBefore, isLoading]);

  useEffect(() => {
    loadAfter();
    // Now re-render infinitely, but now eslint warning
  }, [loadAfter]);

  const body = () => {
    if (isLoading) {
      return <div>Loading...</div>;
    } else {
      return (
        <div>
          <div>{data.map((d) => childFunction(d))}</div>
          <button disabled={!hasLast} onClick={loadBefore}>
            Last Page
          </button>
          <button disabled={!hasNext} onClick={loadAfter}>
            Next Page
          </button>
        </div>
      );
    }
  };

  return body();
};

标签: reactjsreact-hookseslint-plugin-react-hooks

解决方案


推荐阅读