我可以忽略useContext的详尽Deps警告吗?

时间:2020-05-23 08:15:47

标签: reactjs react-context use-effect use-reducer

在我的react-typescript应用程序中,我试图使用一个上下文提供程序,该提供程序封装属性和方法并将其公开给使用者:

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

问题是以下警告:

反应挂钩useEffect缺少依赖项:'stockPrice'。要么 包含它或删除依赖项 数组。 eslint(反应钩/穷举滴水)

对我来说,将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>;
};

2 个答案:

答案 0 :(得分: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不同的内容吗?如果两个渲染之间的股价相同,则将不会执行useEffect。
  2. 您是否需要在每次渲染fetch时创建一个新的<StockPriceProvider>方法?确实不适合useEffect的依赖项。

    • 如果两者都还可以,请随时禁用eslint警告
    • 如果您希望在装入使用者时一直保持200ms的时间获取:
  // StockPriceProvider
  ...
    fetch: useCallback(() => dispatch({type: 'fetch', payload: 200}), [])
  ...
  // StockPriceConsumer
  ...
    useEffect(() => {
      const i = setInterval(fetch, 200)
      return () => clearInterval(i)
    }, [fetch])
  ...

答案 1 :(得分:0)

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

因此,在您的情况下: 提取功能将变为:

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>;
};