在我的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>;
};
答案 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>;
};
...作为原始应用程序中几个问题的解决方案:
val
不同的内容吗?如果两个渲染之间的股价相同,则将不会执行useEffect。您是否需要在每次渲染fetch
时创建一个新的<StockPriceProvider>
方法?确实不适合useEffect的依赖项。
// StockPriceProvider
...
fetch: useCallback(() => dispatch({type: 'fetch', payload: 200}), [])
...
// StockPriceConsumer
...
useEffect(() => {
const i = setInterval(fetch, 200)
return () => clearInterval(i)
}, [fetch])
...
答案 1 :(得分:0)
这里的重要概念是react通过引用相等来比较对象。这意味着每次引用(而不是内容)更改时,都会触发重新渲染。根据经验,您总是需要通过useCallback
和useMemo
定义要传递给子组件的对象/功能。
因此,在您的情况下: 提取功能将变为:
const fetch = useCallback(() => {
setTimeout(() => {
dispatch({ type: 'fetch', payload: 200 });
}, 1000);
}, []);
空数组意味着仅在安装组件时才定义此功能。然后:
let {val, fetch} = stockPrice;
useEffect(() => {
fetch();
}, [val, fetch]);
这意味着useEffect的回调仅在fetch
或val
更改时执行。由于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>;
};