2

在我的 react-typescript 应用程序中,我正在尝试使用一个上下文提供程序来封装属性和方法并将它们公开给消费者:

const StockPriceConsumer: React.FC = () => {
  const stockPrice = useContext(myContext);
  let val = stockPrice.val;
  useEffect(() => {
    stockPrice.fetch();
  }, [val]);
  return <h1>{val}</h1>;
};

问题是以下警告:

React Hook useEffect 缺少依赖项:'stockPrice'。包括它或删除依赖数组。eslint(react-hooks/exhaustive-deps)

stockPrice对我来说,将(基本上是提供者的 API)包含到 useEffect 的依赖项中没有任何意义。只有包含股票价格的实际价值以防止无限调用 useEffect 函数才有意义。

问题:我尝试使用的方法有什么问题,或者我可以忽略这个警告吗?


提供者:

interface StockPrice {
  val: number;
  fetch: () => void;
}

const initialStockPrice = {val: NaN, fetch: () => {}};

type Action = {
  type: string;
  payload: any;
};

const stockPriceReducer = (state: StockPrice, action: Action): StockPrice => {
  if (action.type === 'fetch') {
    return {...state, val: action.payload};
  }
  return {...state};
};

const myContext = React.createContext<StockPrice>(initialStockPrice);

const StockPriceProvider: React.FC = ({children}) => {
  const [state, dispatch] = React.useReducer(stockPriceReducer, initialStockPrice);
  const contextVal  = {
    ...state,
    fetch: (): void => {
      setTimeout(() => {
        dispatch({type: 'fetch', payload: 200});
      }, 200);
    },
  };
  return <myContext.Provider value={contextVal}>{children}</myContext.Provider>;
};
4

2 回答 2

1

我建议从提供者控制整个获取逻辑:

const StockPriceProvider = ({children}) => {
  const [price, setPrice] = React.useState(NaN);

  useEffect(() => {
    const fetchPrice = () => {
      window.fetch('http...')
       .then(response => response.json())
       .then(data => setPrice(data.price))
    }
    const intervalId = setInterval(fetchPrice, 200)
    return () => clearInterval(intervalId)
  }, [])

  return <myContext.Provider value={price}>{children}</myContext.Provider>;
};

const StockPriceConsumer = () => {
  const stockPrice = useContext(myContext);
  return <h1>{stockPrice}</h1>;
};

...作为原始方法中几个问题的解决方案:

  1. 你真的想只取val不同的东西吗?如果 2 次渲染之间的股票价格相同,则不会执行 useEffect。
  2. fetch每次<StockPriceProvider>渲染都需要创建一个新方法吗?这确实不适合 useEffect 的依赖关系。

    • 如果两者都可以,请随意禁用 eslint 警告
    • 如果您想在安装了消费者后以 200 毫秒的间隔继续获取:
  // StockPriceProvider
  ...
    fetch: useCallback(() => dispatch({type: 'fetch', payload: 200}), [])
  ...
  // StockPriceConsumer
  ...
    useEffect(() => {
      const i = setInterval(fetch, 200)
      return () => clearInterval(i)
    }, [fetch])
  ...
于 2020-05-23T09:05:47.377 回答
0

这里的重要概念是 react 通过引用相等来比较对象。这意味着每次引用(而不是内容)更改时都会触发重新渲染。根据经验,您始终需要定义要通过useCallback和传递给子组件的对象/函数useMemo

因此,在您的情况下: fetch 函数将变为:

const fetch = useCallback(() => {
    setTimeout(() => {
        dispatch({ type: 'fetch', payload: 200 });
    }, 1000);
}, []);

空数组意味着这个函数只会在组件被挂载时定义。接着:

let {val, fetch} = stockPrice;

 useEffect(() => {
    fetch();
 }, [val, fetch]);

这意味着 useEffect 的回调将仅在fetchval更改时执行。由于fetch只会定义一次,实际上这意味着只有val更改才会触发效果的回调。

另外,我可以想象你只想在isNaN(val)这样的情况下触发提取:

let {val, fetch} = stockPrice;

 useEffect(() => {
    if(isNaN(val)) {
        fetch();
    }
 }, [val, fetch]);

话虽如此,这段代码还有一个更大的问题!

您应该重新考虑使用方式,setTimeout因为回调可以在组件已经卸载时运行,这可能会导致不同的错误。在这些情况下,您应该useEffect在卸载组件之前清除所有异步操作。所以这是我的建议:

import React, { useCallback, useContext, useEffect } from 'react';
interface StockPrice {
    val: number;
    setFetched: () => void;
}

const initialStockPrice = { val: NaN, setFetched: () => { } };

type Action = {
    type: string;
    payload: any;
};

const stockPriceReducer = (state: StockPrice, action: Action): StockPrice => {
    if (action.type === 'fetch') {
        return { ...state, val: action.payload };
    }
    return { ...state };
};

const myContext = React.createContext<StockPrice>(initialStockPrice);

const StockPriceProvider: React.FC = ({ children }) => {
    const [state, dispatch] = React.useReducer(
        stockPriceReducer,
        initialStockPrice
    );
    const setFetched = useCallback(() => {
        dispatch({ type: 'fetch', payload: 200 });
    }, []);
    const contextVal = {
        ...state,
        setFetched,
    };
    return <myContext.Provider value={contextVal}>{children}</myContext.Provider>;
};

const StockPriceConsumer: React.FC = () => {

    const stockPrice = useContext(myContext);
    const {val, setFetched} = stockPrice;

    useEffect(() => {
        let handle = -1;
        if(isNaN(val)) {
            let handle = setTimeout(() => { // Or whatever async operation
                setFetched();
            }, 200);
        }
        return () => clearTimeout(handle); // Clear timeout before unmounting.
    }, [val, setFetched]);
    return <h1>{stockPrice.val.toString()}</h1>;
};
于 2020-05-23T10:32:57.043 回答