React-Native (expo) Получить начальную тему (или любое значение инициализации)

Я хотел бы представить простую настраиваемую тему для моего (расширенного) приложения, ориентированного на реакцию, с первоначальным акцентом на цвет фона. У меня был некоторый успех в реализации динамических аспектов, например. пользователь может выбрать цвет темы/фона в приложении, и этот цвет влияет на все экраны. Я использовал довольно типичную технику, когда App.js оборачивает свой (V5) NavigationContainer и его тему в поставщика контекста, например.

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

На стороне компонента мы можем получить тему (изначально используемую по умолчанию) с помощью хука useTheme, затем изменить фон или что-то еще и (временно) сохранить измененную тему для состояния в редьюсере провайдера. Редюсер также может сохранить мутированную тему во что-то вроде AsyncStorage для следующего запуска приложения.

Проблема возникает из-за перезапуска приложения.

Кажется, что нет способа получить текущую тему из холодного хранилища (AsyncStorage или другого) в App.js вовремя, чтобы передать ее в NavigationContainer.

(запомните это) <NavigationContainer theme={AppDefaultTheme} ref={navigationRef}>

Многочисленные сообщения указывают на то, что в RN/expo нет хука или возможности перед Render. И к тому времени, когда мы возвращаем AppDefaultTheme в хук useEffect, контейнер навигации App.js уже давно покинул станцию.

Я предполагаю, что получение начального состояния просто невозможно, если, возможно, не используется внешний менеджер состояний, такой как Redux. Является ли Redux тем, как люди решили эту проблему? Я был очень доволен nonReduxLight, например. шаблоны сокращения с использованием оболочек Provider в соответствии с вышеизложенным, и функции цветовой темы недостаточно, чтобы заставить меня погрузиться в этот глубокий бассейн, но это приближается.

Будет ли Redux делать то, что я хочу? Есть ли более простой способ?


person rpc    schedule 12.03.2021    source источник


Ответы (1)


Рад интересу. Сейчас я отвечу на свой вопрос, так как исследования были плодотворными.

Expo предлагает пакет AppLoading, который, по-видимому, «удерживает» начальную заставку, пока выполняются другие, обычно асинхронные операции.

Это позволяет использовать следующий шаблон:

  1. App.js (или ваш исходный компонент) вызывает AppLoading в методе рендеринга/возврата в зависимости от состояния приложения. Состояние не нужно привязывать к контексту, поэтому оно отлично работает в App.js до создания контекста.
  2. AppLoading блокирует отрисовку компонента на экране-заставке и вызывает функцию, содержащую работу, которую необходимо выполнить перед отрисовкой.
  3. В вызванной функции сделайте все, что вам нужно сделать, чтобы инициализировать приложение. В этом конкретном примере мы хотим получить модифицируемую тему из холодного хранилища до того, как App.js получит доступ к приложению и поместит его в контекст. Операции увлажнения в вызываемой функции обычно асинхронны, но я думаю, что это не обязательно.
  4. Когда обещания выполняются, например. вы гидратировали все, что хотели получить из Интернета или холодного хранилища, переворачиваете состояние, которое держит заставку в ожидании, что позволяет дальнейший рендеринг, и потребляете результаты, которые вы гидратировали. В этом конкретном случае используйте полученную тему для инициализации навигатора.

Я использую его в Expo 40, и этот шаблон работает очень хорошо. С архитектурной точки зрения это хороший шаблон... он вставляет в жизненный цикл что-либо перед рендерингом, что не так хорошо, как отсутствие события перед рендерингом этого компонента, но для целей гидратации перед выполнением это идеально.

Он вообще не зависит от контекста, поэтому его можно использовать в App.js до вызова контекста для переноса приложения.

Пара заметок, затем несколько примеров:

  1. Я тестировал только в Экспо, но в примечаниях к компонентам указано, что его можно использовать в ванильном реагировании, импортировав начальную зависимость или две.
  2. документация по пакету и пример великолепны, но обратитесь к классу- компоненты на основе. Если вы, как и я, любите функциональные компоненты, вот отличный пример функционального компонента.
  3. Компонент AppLoading представляет собой удобную оболочку вокруг компонента Expo SplashScreen. , который добавляет некоторые дополнительные функции, которые мне сейчас не нужны. Ты можешь.
  4. Невероятно круто... можно использовать более чем в одном компоненте. Например, у вас может быть компонент, который загружает тему, возможно, в App.js для нашего основного варианта использования. Работа App.js не требует доступа к контексту и фактически должна выполняться до создания контекста, согласно основной части этой статьи.
  5. Но у вас может быть другой компонент, которому нужно загрузить некоторые настройки из Интернета или из холодного хранилища, и ему действительно нужен доступ к контексту. Без проблем. Поместите другую реализацию AppLoading во второй компонент и избавьтесь от нее.

Не путайте... оба вызова AppLoading ожидают одного и того же набора событий для выпуска экрана-заставки, так что это не способ обойти общую проблему "нет перед рендерингом". Но это похоже на решение проблемы «до рендеринга всего».

И последнее... кажется, есть много решений этой проблемы, которые используют 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} ...>
person rpc    schedule 22.03.2021