本文详解 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 生命周期未与加载状态正确同步。

















