为什么在我不更改 useEffect 挂钩的任何依赖项时触发 'exhaustive-deps' 规则?

Why is the 'exhaustive-deps' rule being triggered while I don't change any dependancies of my useEffect hook?

我已经构建了这个通用的 dropdown/select 组件,其中包含用于获取数据集的异步函数。出于某种原因,我收到消息 The 'fetchData' function makes dependencies of useEffect Hook (at line 48) change on every render。要解决此问题,请将 'fetchData' 的定义包装在它自己的 useCallback() Hook 中。eslintreact-hooks/exhaustive-deps.

我没有从我的 useEffect 挂钩中更改依赖项的任何值,因为这些属性由我的 redux 切片控制....

Select 分量:

import React, { useEffect } from 'react';
import { Form, Select, Typography } from 'antd';
import PropTypes from 'prop-types';
import styled from 'styled-components';

const StyledSelect = styled(Select)`
  &.ant-select-loading .ant-select-selection-item {
    display: none;
  }
`;

const { Text } = Typography;

const CustomSelect = ({
  endpointKey,
  dataKey,
  customLabel,
  required = false,
  dataSet,
  fetchDataSet,
  disabled = false,
  fullOptionHeight = false,
  onChange = null,
  showSearch = false,
  setLoading = null,
}) => {
  const fetchData = async (searchText) => {
    if (setLoading) {
      setLoading(true);
    }
    await fetchDataSet({ endpointKey, searchText });
    if (setLoading) {
      setLoading(false);
    }
  };

  useEffect(() => {
    const dataSetPresent = !!dataSet.data && !!Object.keys(dataSet.data).length;
    const hasError = dataSet.errorMessage && dataSet.errorMessage.length;
    if (
      !dataSetPresent &&
      dataSet.loadingStatus !== 'loading' &&
      dataSet.loadingStatus !== 'loaded' &&
      !hasError
    ) {
      fetchData();
    }
  }, [fetchData, dataSet]);

  const { loadingStatus, data, errorMessage } = dataSet;

  const label = customLabel || endpointKey;
  const formErrorMessage = 'Please select ' + label.toLowerCase();
  const placeholder = `-- Select a ${label.toLowerCase()} --`;

  const renderSelect = () => {
    if (errorMessage !== '') {
      return <Text type="danger">{errorMessage}</Text>;
    }
    return (
      <StyledSelect
        disabled={loadingStatus === 'loading' || disabled}
        loading={loadingStatus === 'loading'}
        placeholder={placeholder}
        optionFilterProp="children"
        style={{ maxWidth: '500px' }}
        size="large"
        onChange={onChange}
        showSearch={showSearch}
        onSearch={(value) => {
          if (showSearch) {
            fetchData(value);
          }
        }}
      >
        {Object.keys(data).map((dataObject) => {
          return (
            <Select.Option
              className={`${fullOptionHeight ? 'full-option-height' : ''}`}
              value={dataObject}
              key={dataObject}
            >
              {data[dataObject]}
            </Select.Option>
          );
        })}
      </StyledSelect>
    );
  };

  return (
    <Form.Item
      label={label}
      name={dataKey}
      rules={[{ required, message: formErrorMessage }]}
    >
      {renderSelect()}
    </Form.Item>
  );
};

CustomSelect.propTypes = {
  endpointKey: PropTypes.string.isRequired,
  dataKey: PropTypes.oneOfType([PropTypes.array, PropTypes.string]),
  customLabel: PropTypes.string,
  required: PropTypes.bool,
  fetchDataSet: PropTypes.func,
  showSearch: PropTypes.bool,
  dataSet: PropTypes.object,
  disabled: PropTypes.bool,
  fullOptionHeight: PropTypes.bool,
  onChange: PropTypes.func,
  setLoading: PropTypes.func,
};

export default CustomSelect;

使用异步挂钩和状态更改处理的 React Slice:

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import callApi from 'utils/api';

export const SELECT_KEY = 'select';

export const fetchDataSet = createAsyncThunk(
  'select/request-data',
  async ({ endpointKey, searchText }) => {
    const endpoint = `data/${endpointKey}`;
    try {
      const { data } = await callApi({
        endpoint,
      });
      return data;
    } catch (error) {
      console.error('ERROR', error);
      throw error;
    }
  }
);

export const selectSlice = createSlice({
  name: SELECT_KEY,
  initialState: {},
  reducers: {},
  extraReducers: {
    [fetchDataSet.pending]: (state, action) => {
      const key = action.meta.arg.endpointKey;
      return {
        ...state,
        [key]: {
          loadingStatus: 'loading',
          errorMessage: '',
          data: {},
        },
      };
    },
    [fetchDataSet.fulfilled]: (state, action) => {
      const key = action.meta.arg.endpointKey;
      return {
        ...state,
        [key]: {
          loadingStatus: 'loaded',
          errorMessage: '',
          data: action.payload,
        },
      };
    },
    [fetchDataSet.rejected]: (state, action) => {
      const key = action.meta.arg.endpointKey;
      return {
        ...state,
        [key]: {
          loadingStatus: 'error',
          errorMessage: action.error.message,
          data: {},
        },
      };
    },
  },
});

export const selectReducer = selectSlice.reducer;

export const dataSetSelector = (state, key) => state.select[key] || {};

您实际上在每个渲染周期都更改了依赖项的值,因为您在每个周期都重新声明 fetchData。该警告建议您简单地记住此回调函数,以便为效果挂钩提供稳定的参考。

const fetchData = useCallback(async (searchText) => {
  if (setLoading) {
    setLoading(true);
  }
  await fetchDataSet({ endpointKey, searchText });
  if (setLoading) {
    setLoading(false);
  }
}, [endpointKey]); // <-- add any other necessary dependencies for the callback