如何将函数传递给自己的 React 上下文提供程序,让您可以编辑状态?

How pass a function into an own react context provider which lets you edit the state?

我有以下代码:

import React, { useState } from 'react'

export const NumberContext = React.createContext()

export function NumberProvider({ children }) {
  const [numbers, setNumbers] = useState([])

  function addNumber(number) {
    const copy = [...numbers]
    copy.push(number)
    setNumbers(copy)
  }

  return (
    <NumberContext.Provider value={[numbers, addNumber]}>
      {children}
    </NumberContext.Provider>
  )
}
export function App() {
  return (
      <NumberProvider>
        <DisplayNumbers />
        <AlterNumbers />
      </NumberProvider>
  )
}
export function DisplayNumbers() {
  const [numbers, addNumbers] = useContext(NumberContext)

  return (<p>{numbers}</p>)
}

如果我现在像这样通过提供程序中的上下文 api 调用 addNumber() 函数

export function AlterNumbers() {
  const [numbers, addNumber] = useContext(NumberContext)

  function alterNumbers(){
    addNumber(1)

    setTimeout(() => {
      addNumber(2)
    }, 3000)
  }

  return (<button onClick={alterNumbers}>)
}

numbers等于[2]而不是[1, 2]

我的提供程序函数 NumberProvider 中的状态不像 NumberContext.Provider 中那样更新。我怎样才能防止这种情况发生?实现这一目标的设计模式会是什么样子?

因为 useState 的设置器是异步的,一个调用会覆盖另一个:

const NumberConsumer = () => {
 const [numbers, addNumber] = useContext(NumberContext);

 addNumber(1);               // Will update numbers to [1] in "future"
 setTimeout(() => {          // Will update numbers to [2] in "future"
    addNumber(2);            // The call within the timeout,
                             // is not aware of the previous state
 }, 3000);

 return <h1>{JSON.stringify(numbers)}</h1>;
};

你应该使用 functional useState:

setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});

如评论中所述,您应该使用 useEffect:

将逻辑更改为更强大的内容
export function ToastProvider({ children }) {
  const [toasts, setToasts] = useState([])
  const [key, setKey] = useState(0)

  function showToast(label, message) {
    setKey(key + 1)
    setToasts([
      ...toasts,
      {
        key,
        label,
        message,
      },
    ])
  }

  useEffect(() => {
    setTimeout(() => {
      const copy = [...toasts]
      copy.pop()
      setToasts(copy)
    }, 5000)
  }, toasts);

  return (
    <ToastContext.Provider value={[toasts, showToast]}>
      {children}
    </ToastContext.Provider>
  )
}