¬React Native (with Expo) useContext() 的规则是什么?它并不总是在渲染前加载

问题描述

我正在尝试在我的 React-Native Expo 应用程序(在 android 上)中实现一个主题,该主题通过切换开关进行更改。尽管互联网上有大量指南,但大多数指南都不完整或过于简单(扩展性很差)。无论如何,我几乎可以工作了,下面的代码不是原始问题,而是最小的问题。


我想我的 ThemeContext.js 很好,因为在一种情况下(下面的 App1)主题确实改变了。

ThemeContext.js:

import React from 'react';

export const themes = {
  light: {
      foreground: '#000000',background: '#eeeeee',special: 'salmon',},dark: {
      foreground: '#ffffff',background: '#222222',special: 'violet',};

const ThemeContext = React.createContext({theme: themes.light});

function ThemeProvider(props){
    console.log(`theme context provider component called`)
    const [theme,setTheme] = React.useState(themes.light);
    const toggleTheme = () => {
        console.log(`toggle called`);
        setTheme(
            theme === themes.dark
              ? themes.light
              : themes.dark);   
    }
    return (
        <ThemeContext.Provider value={{theme,toggleTheme}}>
            {props.children}
        </ThemeContext.Provider>
    );
}
export default ThemeContext;
export {ThemeProvider};

以下是应用程序本身:

App.js:

import React,{useContext,useState } from "react";
import {StyleSheet,Switch,Text,View,SafeAreaView } from "react-native";
import ThemeContext,{ThemeProvider} from './ThemeContext';

function ThemetoggleSwitch({textColor}){
    const {toggleTheme} = useContext(ThemeContext);
    const [isEnabled,setIsEnabled] = useState(false);
    const toggleSwitch = () => {
        setIsEnabled(prevIoUsstate => !prevIoUsstate);
        toggleTheme();
    }
    return (
        <View style={{flex: 1,flexDirection:'row',alignItems: "center",paddingLeft:20,paddingTop:20}}>
            <View>
                <Text style={{color: textColor,fontSize:12}}>Dark Theme:</Text>
            </View>
            <Switch
                trackColor={{ false: "#767577",true: "#81b0ff" }}
                thumbColor={isEnabled ? "#f5dd4b" : "#f4f3f4"}
                ios_backgroundColor="#3e3e3e"
                onValueChange={toggleSwitch}
                value={isEnabled}
            />
        </View>
    );
}

function TextViewWithTheme(){
  const {theme} = useContext(ThemeContext);
  return (
    <View style={{backgroundColor: theme.background}}>
        <Text style={{fontSize:17,color:theme.foreground}}>
            Did I change color?
        </Text>
    </View>
  );
}

function ThemeTestApp1(){
  const {theme} = useContext(ThemeContext);
  return (
    <SafeAreaView style={{...styles.container,backgroundColor:theme.special}}>
        <ThemetoggleSwitch />
        <TextViewWithTheme/>
        <View style={{backgroundColor: theme.background}}>
          <Text style={{fontSize:17,color:theme.foreground}}>
                      Did I change color?
          </Text>
        </View>
      </SafeAreaView>
  );
}

function App1(){
  return (
    <ThemeProvider>
      <ThemeTestApp1/>
    </ThemeProvider>
  );
}

function App2() {
  const {theme} = useContext(ThemeContext);
  return (
    <ThemeProvider>
      <SafeAreaView style={{...styles.container,color:theme.foreground}}>
                      Did I change color?
          </Text>
        </View>
      </SafeAreaView>    
    </ThemeProvider>
  );
}

//change here between App1 and App2 to see the difference
export default function App(){
  return <App1/>;
}

const styles = StyleSheet.create({
  container: {
    flex: 1,});

App 组件中,您可以轻松地在 App1 和 App2 之间切换并查看差异。

在 App1 中,我在 ThemeProvider 中调用 ThemeTestApp1。这里一切正常:切换开关时所有颜色都会改变(好吧,除了第一次加载应用程序,背景都是白色/未定义,但这可能是一个不同的问题)。

另一方面,在 App2 中,我在 App2 的函数体中使用了 ThemeContext,并在 ThemeProvider 中使用了 ThemeTestApp1 的 return 语句代码。据称,App1 和 App2 应该以相同的方式工作,但事实并非如此。出于某种原因,即使 App1 工作正常,App2 也不会在渲染前“加载”主题(这是我的猜测)。


所以问题是:如何让 App2 正常工作而不创建更多不必要的组件?


如果这里的规则是“使用最接近使用它的组件的 useContext”,那么这并不能解释为什么 App1 中 ThemeTestApp1 中的 View 会改变颜色,而在 App2 中却不会?在这两种情况下,它都不是最深的组件,也不是根。

此外,according to the docs

“作为提供者后代的所有消费者都将重新渲染 每当 Provider 的 value 属性发生变化时。传播自 提供给其后代消费者(包括 .contextType 和 useContext) 不受 shouldComponentUpdate 方法的影响,因此 即使祖先组件跳过更新,消费者也会更新。”

所以,据我所知,这应该不是问题。

如果你认为这里的上下文是一个对象而不是一个原始对象(如上面链接中的警告部分所述),那么他们会说它会渲染太多而不是太少。

链接a snack I made(虽然它与在 devtools/metro-bundler 上运行 expo 产生的结果略有不同,但要点仍然存在)

谢谢

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)