在 React Context 中存储和调用函数会导致奇怪的行为

Storing and calling functions in React Context leads to weird behavior

我试图在 React 上下文中存储一个简单的回调,该回调由一个组件设置,然后由另一个组件调用,以促进它们之间的通信。这是因为这两个组件没有任何父子关系,并且位于应用程序的不同子树中。而且,我想将 Component1 的自定义事件处理程序分配给 Component2 的点击行为。

因此,我使用函数(回调)及其 setter

创建了上下文
import { createContext, useContext, useState } from "react"
interface ContextShape {
    myCallback?: (searchInput: string) => void
    setMyCallback: (onSearchCallback: (searchInput: string) => void) => void
}

const MyContext = createContext<ContextShape>({
    myCallback: () => {},
    setMyCallback: () => {},
})

export const useMyContext = () => useContext(MyContext)

export const MyContextProvider: React.FC = (props) => {
    const [myCallback, setMyCallback] = useState<(searchInput: string) => void>(() => {})

    return (
        <MyContext.Provider
            value={{
                myCallback: myCallback,
                setMyCallback: (clientCallback) => {
                    setMyCallback(clientCallback)
                },
            }}
        >
            {props.children}
        </MyContext.Provider>
    )
}

发起点击事件的组件,调用回调如下:

const { myCallback } = useMyContext()

const handleKeyPress = (
    e: React.KeyboardEvent<HTMLInputElement>,
    sVal: string | undefined
) => {
    if (e.code === "Enter" && myCallback) {
        myCallback(sVal || "")
    }
}

而且,我正在尝试从我的应用中的一个页面设置回调:

useEffect(() => {
    // eslint-disable-next-line no-console
    setMyCallback(() => (text: string) => console.log("Hii" + text))
}, [])

注意到奇怪的事情了吗?在设置回调时,我必须将它包装在另一个函数中 (() => {... my callback...})。只有这样,这才符合预期。如果我直接传递回调,那么它会立即被调用,而不是被存储并稍后调用。 (我广泛检查了它是否在某处被错误调用,但代码无意中没有执行 myCallback() )。这对我来说是出乎意料的,因为我预计 (text: string) => {...} 就足以进行回调,并不表示我想立即调用该函数。

你必须创建一个柯里化函数作为回调的原因是状态更新器也将回调函数作为它的参数,它会立即调用:

例如:

setMyCallback((prev) => {
   console.log(prev, 'prev state');
});

现在,如果您只是将回调函数传递给 setMyCallback 函数,它将被视为

setMyCallback((text: string) => console.log("Hii" + text));

因此 console.log() 被立即调用。

您可以将代码更新为如下以避免此类问题:

export const MyContextProvider: React.FC = (props) => {
    const [myCallback, setMyCallback] = useState<(searchInput: string) => void>(() => {})

    return (
        <MyContext.Provider
            value={{
                myCallback: myCallback,
                setMyCallback: (clientCallback) => {
                    // Already use a callback function
                    setMyCallback(() => clientCallback)
                },
            }}
        >
            {props.children}
        </MyContext.Provider>
    )
}

然后用作

useEffect(() => {
    // eslint-disable-next-line no-console
    setMyCallback((text: string) => console.log("Hii" + text))
}, []);