如何为提供者使用带有多个值的React Hooks上下文

RTW*_*RTW 3 reactjs react-hooks

在反应中分享一些全球价值和职能的最佳方法是什么?

现在我有一个ContextProvider,里面都有它们:

<AllContext.Provider
  value={{
    setProfile, // second function that changes profile object using useState to false or updated value
    profileReload, // function that triggers fetch profile object from server
    deviceTheme, // object
    setDeviceTheme, // second function that changes theme object using useState to false or updated value
    clickEvent, // click event
    usePopup, // second function of useState that trigers some popup
    popup, // Just pass this to usePopup component
    windowSize, // manyUpdates on resize (like 30 a sec, but maybe can debounce)
   windowScroll // manyUpdates on resize (like 30 a sec, but maybe can debounce)
  }}
>
Run Code Online (Sandbox Code Playgroud)

但是就像docs中的 sad一样:因为上下文使用引用标识来确定何时重新渲染,所以当提供者的父级重新渲染时,有些陷阱可能会在使用者中触发意外渲染。例如,以下代码将在提供商每次重新渲染时重新渲染所有使用者,因为始终为值创建新对象:

这不好:

<Provider value={{something: 'something'}}>
Run Code Online (Sandbox Code Playgroud)

还行吧:

this.state = {
      value: {something: 'something'},
    };
<Provider value={this.state.value}>
Run Code Online (Sandbox Code Playgroud)

我想在将来我可能会多达30个上下文提供者,而且它不是很友好:/

那么如何将这个全局值和函数传递给组件呢?我可以

  1. 为所有内容创建单独的contextProvider。
  2. 将诸如概要文件及其功能,主题及其功能之类的东西组合在一起(参考身份比什么呢?)
  3. 也许组只起作用是因为其自身不会改变吗?参考身份又如何呢?)
  4. 其他最简单的方法?

我在提供程序中使用的示例:

// Resize
  const [windowSize, windowSizeSet] = useState({
    innerWidth: window.innerWidth,
    innerHeight: window.innerHeight
  })
// profileReload
const profileReload = async () => {
    let profileData = await fetch('/profile')
    profileData = await profileData.json()

    if (profileData.error)
      return usePopup({ type: 'error', message: profileData.error })

    if (localStorage.getItem('deviceTheme')) {
      setDeviceTheme(JSON.parse(localStorage.getItem('deviceTheme'))) 
    } else if (profileData.theme) {
      setDeviceTheme(JSON.parse(JSON.stringify(profileData.theme)))
    } else {
      setDeviceTheme(settings.defaultTheme) 
    }
    setProfile(profileData)
  }

// Click event for menu close if clicked outside somewhere and other
const [clickEvent, setClickEvent] = useState(false)
const handleClick = event => {
  setClickEvent(event)
}
// Or in some component user can change theme just like that
setDeviceTheme({color: red})
Run Code Online (Sandbox Code Playgroud)

Rya*_*ell 9

从性能的角度出发,关于将哪些内容组合在一起的主要考虑是,较少地考虑将哪些内容一起使用,而更多地关注哪些内容一起更改。对于大多数一次(或至少很少)进入上下文的事物,您可以将它们放在一起而没有任何问题。但是,如果某些事情更频繁地混入该更改中,则可能需要将它们分开。

例如,deviceTheme对于给定的用户,我希望它是相当静态的,并且可能被大量组件使用。我想这popup可能在管理您当前是否打开了一个弹出窗口,因此它可能会随着与打开/关闭弹出窗口相关的每项操作而改变。如果popupdeviceTheme捆绑在同一上下文中,则每次popup更改都会导致依赖的所有组件deviceTheme也重新呈现。所以我可能会有一个单独的PopupContextwindowSize并且windowScroll可能会有类似的问题。使用哪种确切的方法可以深入了解领域,但是您可以AppContext针对不经常变化的部分,然后针对更频繁变化的事物提供更具体的上下文。

下面的CodeSandbox演示了useState和useContext之间的交互,其中上下文分为几种不同的方式以及一些按钮来更新上下文中保存的状态。

编辑zk58011yol

您可以转到该URL在完整的浏览器窗口中查看结果。我鼓励您首先了解结果的工作方式,然后查看代码并尝试使用它(如果您想了解其他情况)。

  • 我在我的答案中添加了一个 CodeSandbox,它应该有助于使这更具体,并允许您尝试不同的场景并查看行为。 (2认同)

Est*_*ask 5

这个答案已经很好地解释了如何构建上下文以提高效率。但最终目标是让上下文消费者仅在需要时更新。这取决于具体的情况,最好使用单个上下文还是多个上下文。

在这一点上,这个问题对于大多数全局状态 React 实现来说是常见的,例如 Redux。一个常见的解决方案是仅在需要时使用React.PureComponent,React.memoshouldComponentUpdatehook使消费者组件更新:

const SomeComponent = memo(({ theme }) => <div>{theme}</div>);

...

<AllContext>
  {({ deviceTheme }) => <SomeComponent theme={deviceTheme}/>
</AllContext>
Run Code Online (Sandbox Code Playgroud)

SomeComponent将仅在deviceTheme更新时重新渲染,即使上下文或父组件已更新。这可能是也可能不是可取的。