【问题标题】:Can I ignore exhaustive-deps warning for useContext?我可以忽略 useContext 的详尽警告吗?
【发布时间】:2020-09-10 03:13:49
【问题描述】:

在我的 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>;
};

【问题讨论】:

  • 经验法则:你想忽略的感觉 react-hooks/exhaustive-deps == 你的副作用正在对抗 Thinking in React 第 4 步:确定你的州应该住在哪里

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


【解决方案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. 是否需要在每次渲染&lt;StockPriceProvider&gt; 时创建一个新的fetch 方法?这确实不适合 useEffect 的依赖。

    • 如果两者都正常,请随意禁用 eslint 警告
    • 如果您希望在安装了消费者后以 200 毫秒的间隔继续获取:
  // StockPriceProvider
  ...
    fetch: useCallback(() => dispatch({type: 'fetch', payload: 200}), [])
  ...
  // StockPriceConsumer
  ...
    useEffect(() => {
      const i = setInterval(fetch, 200)
      return () => clearInterval(i)
    }, [fetch])
  ...

【讨论】:

    【解决方案2】:

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

    所以在你的情况下: 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>;
    };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-11-21
      • 2017-10-19
      • 1970-01-01
      • 1970-01-01
      • 2017-03-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多