React-Native (expo) 检索初始主题(或任何初始化值)

React-Native (expo) Retrieve an Initial Theme (or any initializing value)

我想向我的(expo 增强型)react-native 应用程序介绍一个简单的可配置主题,最初关注背景颜色。我在实施动态方面取得了一些成功,例如用户可以在应用程序中选择一种 theme/background 颜色,该颜色会影响所有屏幕。我使用了一种相当典型的技术,其中 App.js 将其 (V5) NavigationContainer 及其主题包装在上下文提供程序中,例如

return (
  <SomeProvider>
       <NavigationContainer theme={AppDefaultTheme} ref={navigationRef}>
        ... stuff including navigators
       <NavigationContainer>
  </SomeProvider>
)

在组件方面,我们可以使用 useTheme 挂钩检索主题(最初是默认主题),然后更改背景或其他内容,并(暂时)将变异的主题持久保存到 Provider 的 reducer 中。 reducer 还可以将变异的主题持久化为类似 AsyncStorage 的内容,以供下次应用程序启动时使用。

应用程序重启是问题所在。

似乎无法及时从 App.js 中的冷存储(AsyncStorage 或其他)中检索当前主题以将其提供给 NavigationContainer

(记住这个) <NavigationContainer theme={AppDefaultTheme} ref={navigationRef}>

许多帖子指出没有 beforeRender 挂钩或功能 RN/expo。当我们在 useEffect 挂钩中拖回 AppDefaultTheme 时,App.js NavigationContainer 早已离开车站。

我猜想检索初始状态是不可能的,除非使用像 Redux 这样的外部状态管理器。 Redux 是如何解决这个问题的?我对“nonReduxLight”非常满意,例如redux 模式使用上述提供者包装器,颜色主题功能还不足以让我潜入那个深游泳池,但它越来越接近了。

Redux 会做我想做的事吗?有没有更简单的方法?

很高兴有兴趣。由于研究成果丰硕,我现在将回答我自己的问题。

Expo 提供 AppLoading 包,它出现在 'hold onto' 初始启动画面,而其他通常是异步操作被执行。

这会启用以下模式:

  1. App.js(或您的初始组件)根据应用程序状态在 render/return 方法中调用 AppLoading。状态不需要绑定到上下文,因此在创建上下文之前它在 App.js 中工作正常。
  2. AppLoading 在初始屏幕上阻止组件渲染并调用包含渲染前要完成的工作的函数。
  3. 在调用的函数中执行您需要执行的任何操作来初始化应用程序。在这个特定的示例中,我们希望在 App.js 访问应用程序并将其包装在上下文中之前从冷存储中检索可修改的主题。调用函数中的水合操作通常是异步的,但我认为不需要。
  4. 当承诺兑现时,例如你已经水化了你想从网络或冷存储中获取的任何东西,翻转暂停启动画面的状态,这允许进一步渲染,并使用你水合的结果。在这种特殊情况下,使用检索到的主题来初始化导航器

我在 Expo 40 中使用它,这种模式非常有效。从架构上讲,这是一个很好的模式……它在生命周期中插入了一个“在渲染任何东西之前”,这不如没有“在渲染这个组件之前”事件那么好,但是为了预执行水合的目的,它是完美的。

它完全不依赖于上下文,因此可以在 App.js 调用上下文来包装应用程序之前使用它。

一些注释,然后是一些示例:

  1. 我只在 Expo 中测试过,但组件注释说明它可以 通过导入初始依赖项或在 vanilla react-native 中使用 两个
  2. package documentation and example are great, but refer to class-based components. If you love functional components, as I do, here's a great .
  3. AppLoading 组件似乎是 Expo SplashScreen 组件的便利包装器,它添加了一些我现在不需要的额外功能。你可能会。
  4. 非常酷...可以在多个组件中使用。例如,您可能有一个加载主题的组件,根据我们的主要用例可能在 App.js 中。 App.js 工作不需要访问上下文,实际上必须在创建上下文之前发生,根据本文的大部分内容。
  5. 但是您可能有另一个组件需要从 Web 或冷存储中加载一些首选项,并且它确实 需要访问 Context。没问题。在第二个组件中放置 另一个 AppLoading 实现并水合。

不要混淆... 两者 AppLoading 调用都在等待同一组事件来释放启动画面,所以这不是解决 'no before render' 一般问题。但它确实看起来像是“渲染前 任何东西 问题的解决方案。

最后一个……似乎有很多使用 Redux 进行水合作用的问题的解决方案——它们可能都很好。然而,这种情况并没有上升到重写大量愉快地使用上下文挂钩而不是 Redux 的应用程序的程度。如果我用 Redux 学习和重写,我可能会在那里补水。

好了,废话不多说了。这是该模式的基础知识。 getColorTheme 是一种自定义方法,可以执行获取冷存储数据所需的任何操作。

在 App.js 或任何首先加载的内容中:

import React, { useState } from 'react';
import AppLoading from 'expo-app-loading';
... whatever else you need
    
function App() {

  // This bit of component state controls whether apploading is finished or not
  const [isReady, setReady] = useState(false);  
  // This piece of component state holds the hydrated theme to pass to navigation
  const [colorTheme, setColorTheme] = useState({});
  ...
  // Invoked while the splash screen is showed.  Initialize resources here
  const _cacheResourcesAsync = async () => {
    const storedColorTheme = await getColorTheme(); // an app specific method to fetch theme from cold storage
    setColorTheme(storedColorTheme)  // now stash the theme in state for retrieval in render
    return;
  }

and eventually render and invoke our method using AppLoading:
        
  return (
    // Carry out initializing actions, then release the splash screen
    isReady === false ? (<AppLoading
       startAsync={_cacheResourcesAsync}
       onFinish={() => setReady(true)}
       onError={console.warn}
      />) :
        
      <MyContextProvider>
      <NavigationContainer theme={colorTheme} ...>