本文详解 Expo 中 useFonts 返回 isLoaded 为 true 却仍无法渲染自定义字体的根本原因——未正确触发 SplashScreen 隐藏逻辑,并提供可立即生效的修复方案与最佳实践。
本文详解 expo 中 `usefonts` 返回 `isloaded` 为 `true` 却仍无法渲染自定义字体的根本原因——未正确触发 splashscreen 隐藏逻辑,并提供可立即生效的修复方案与最佳实践。
在 Expo 应用中,使用 expo-font 的 useFonts Hook 加载自定义字体是一个常见操作。但开发者常遇到一个看似矛盾的现象:isLoaded 返回 true,控制台无报错,路径确认无误,字体文件也已正确注册,然而 <Text style={{fontFamily: ‘Typographica’}}>Hello</Text> 依然回退到系统默认字体(如 San Francisco 或 Roboto),直到手动注释 / 取消注释代码才“意外”生效。
根本原因在于:useFonts 仅负责异步加载并返回加载状态,它本身不阻塞渲染,也不自动触发 UI 重绘或 SplashScreen 隐藏。而你当前代码中关键的 handleOnLayout 是一个被 useCallback 包裹的函数,却从未被调用!
const handleOnLayout = useCallback(async () => {if (isLoaded) {await SplashScreen.hideAsync(); } }, [isLoaded]); // ❌ 此函数被定义,但从未执行 —— 它只是个“待命的函数”,不是副作用逻辑
useCallback 的作用是缓存函数引用以避免不必要的子组件重渲染,但它不会自动运行。因此,即使 isLoaded === true,SplashScreen.hideAsync() 也不会执行,导致 Splash Screen 持续遮盖内容,且后续文本因字体尚未被“正式激活”(需配合布局完成与重绘)而无法正确应用。
✅ 正确做法:将字体加载完成后的副作用逻辑移至 useEffect 中,并依赖 isLoaded 触发:
import React, {useState, useEffect} from 'react'; import {useNavigation} from '@react-navigation/native'; import {StyleSheet, Text, View, TouchableOpacity, ScrollView} from 'react-native'; import {useFonts} from 'expo-font'; import * as SplashScreen from 'expo-splash-screen'; // 防止 Splash Screen 自动隐藏(必须在组件外或顶层调用)SplashScreen.preventAutoHideAsync(); const HomeScreen = () => {const [isLoaded] = useFonts({'Typographica': require('nocram/assets/fonts/Typographica.ttf'), 'NeulisAltRegular': require('nocram/assets/fonts/NeulisAlt-Regular.ttf'), 'NeulisAltExtraBold': require('nocram/assets/fonts/NeulisAlt-ExtraBold.ttf'), }); // ✅ 使用 useEffect 响应 isLoaded 变化,主动执行隐藏逻辑 useEffect(() => { if (isLoaded) {SplashScreen.hideAsync().catch(console.error); } }, [isLoaded]); // ⚠️ 注意:务必确保组件在 isLoaded 为 true 前不渲染依赖自定义字体的 UI // 否则可能出现闪烁或回退字体。推荐结合条件渲染:if (!isLoaded) {return null; // 或显示 loading placeholder} return (<View style={styles.container}> <Text style={[styles.title, { fontFamily: 'Typographica'}]}>Welcome to Nocram</Text> <Text style={[styles.body, { fontFamily: 'NeulisAltRegular'}]}>Practice makes perfect.</Text> </View> ); }; const styles = StyleSheet.create({container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#fff'}, title: {fontSize: 28, fontWeight: 'normal'}, body: {fontSize: 16, marginTop: 12}, }); export default HomeScreen;
? 关键注意事项:
- SplashScreen.preventAutoHideAsync() 必须在组件挂载前调用 (通常放在 App.js 顶部或 _layout.tsx 中),否则可能因时机问题失效;
- 避免在 isLoaded === false 时渲染 fontFamily 样式 ,否则 React Native 会尝试应用未就绪字体,导致不可预测的回退行为;
- 若使用 expo-router,建议在 app/_layout.tsx 中统一处理字体加载与 Splash 控制,提升首屏体验一致性;
- 在生产环境(尤其是 EAS Build),请确保字体文件已包含在 assetBundlePatterns 中(app.json 或 app.config.js),否则 iOS/Android 打包后字体路径会失效。
遵循以上模式,即可彻底解决“字体已加载却无法渲染”的典型陷阱——本质不是字体没加载,而是 UI 生命周期未与加载状态正确同步。