资讯动态

expo-font 完全指南:在 Expo 与 React Native 中运行时加载字体

发布时间:2026/9/11 16:17:53 来源:尧图企业网站定制
expo-font 完全指南在 Expo 与 React Native 中运行时加载字体【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo导读expo-font是 Expo SDK 中负责字体加载与使用的核心模块其官方定位为Load fonts at runtime and use them in React Native components在运行时加载字体并在 React Native 组件中使用。本指南以 packages/expo-font/README.md 为主体骨架结合仓库内 源码实现 与 config plugin 的底层原理系统讲解字体在 Expo 托管项目与裸 React Native 项目中的安装方式、运行时加载 API、Web 端font-face注入机制、原生端字体注册原理以及构建期字体嵌入方案。读完本文你将能够熟练运用loadAsync、useFonts等 API 完成字体加载理解其背后的缓存与去重策略并在 app config 中通过 config plugin 将字体静态嵌入原生工程。一、expo-font 是什么设计目标与适用场景expo-font的职责非常单一而明确在运行时把字体资源加载进来并使其可以被 React Native 的Text组件通过fontFamilystyle prop 直接使用。它不负责字体设计、字体渲染而是充当字体资源 → 平台字体系统之间的桥梁Android / iOS原生将字体文件注册进平台字体管理器iOS 为 CoreText 的CTFontManagerRegisterFontsForURLAndroid 为系统字体机制随后原生Text组件即可按fontFamily名称解析。Web浏览器自动生成并注入font-faceCSS 块到一个共享的style元素中无需开发者手写任何 CSS。从 Font.types.ts 的类型定义可以看出一个FontSource可以是四种形态URI 字符串、模块 ID即require(./assets/fonts/xxx.ttf)返回的数字、expo-asset的Asset实例或是带有uri/display/testString字段的FontResource对象。这种多形态设计让开发者既能加载本地打包的静态字体也能加载远程 URL 字体。适用场景应用启动时加载自定义字体、按需加载图标字体如expo/vector-icons底层即依赖 expo-font、在 Web 端按需注入字体样式等。二、安装托管项目与裸项目两条路径原 README 明确区分了两种安装场景本仓库源码亦分别对应不同的集成方式。2.1 托管ManagedExpo 项目对于托管 Expo 项目官方推荐直接查阅 SDK 文档 中的安装指引。在托管工作流下运行以下命令即可完成依赖安装与原生配置通过 EAS /expo run构建时自动生效npx expo install expo-fontnpx expo install会依据当前 SDK 版本自动挑选与之兼容的expo-font版本避免手动锁版本的麻烦。在本次仓库中package.json 记录的版本为57.0.1。2.2 裸BareReact Native 项目裸项目必须先保证expo包本身已被正确安装与配置即expo-modules-core等原生模块体系可用随后添加 npm 依赖npx expo install expo-fontAndroid 配置无需任何额外设置README 原文No additional set up necessary.。从仓库结构看Android 侧的字体注册完全由运行时原生模块完成不存在需要手动修改的清单文件。iOS 配置安装 npm 包之后运行npx pod-install这一步会为 iOS 工程安装 CocoaPods 依赖将expo-font的原生代码见 ios/FontLoaderModule.swift链接进应用。依赖声明在裸项目中expo-font的 peerDependencies 要求宿主环境提供expo、react、react-native因此在安装expo-font前必须已有可用的 Expo Modules 环境。三、核心 API运行时字体加载全解析expo-font在运行时层面暴露了loadAsync、isLoaded、isLoading、getLoadedFonts等函数以及 React Hooks 形式的useFonts。入口统一从 src/index.ts 导出。3.1 loadAsync加载单字体或字体映射loadAsync(fontFamilyOrFontMap: string | Recordstring, FontSource, source?: FontSource): Promisevoid调用形态有两种形态一单个字体第一个参数为fontFamily名称字符串第二个参数为字体来源import * as Font from expo-font; await Font.loadAsync(SpaceMono, require(./assets/fonts/SpaceMono-Regular.ttf));形态二字体映射表一次加载多个字体await Font.loadAsync({ SpaceMono: require(./assets/fonts/SpaceMono-Regular.ttf), SpaceMono-Bold: require(./assets/fonts/SpaceMono-Bold.ttf), });加载完成后Text组件即可使用fontFamily: SpaceMono引用对应字体。需要注意对应 Font.ts 的实现约束不能混用两种形态当第一个参数是对象时如果还传入第二个source参数会抛出CodedError(ERR_FONT_API)提示第二个参数只能配合字符串形式使用。字体名不能为空若 source 缺失会抛出CodedError(ERR_FONT_SOURCE)。幂等性当fontFamily已被加载时再次调用会直接返回不会重复替换该字体。底层加载流程源码级loadAsync并非简单地直连原生模块其内部经过了多层封装Font.ts FontLoader.ts查重isLoaded先通过isLoaded判断该字体是否已加载命中 JS 侧缓存或原生侧已注册列表已加载则直接返回。并发去重loadPromises维护一个loadPromises映射若同一字体正在加载中后续并发调用会复用同一个 Promise而不是重复发起加载——源码注释明确说明这是为了避免同一种字体被加载 n 次Font.ts。资源归一化getAssetForSource在 FontLoader.ts 中Asset实例原样返回字符串被当作远程 URI 调用Asset.fromURI数字require模块 ID被转换为Asset.fromModuleFontResource对象则递归取其uri再归一化。下载与注册调用asset.downloadAsync()确保字体文件就绪然后调用原生模块ExpoFontLoader.loadAsync(name, asset.localUri)FontLoader.ts。若下载失败抛出CodedError(ERR_DOWNLOAD)。标记完成注册成功后调用markLoaded写入 JS 侧缓存memory.ts并清理loadPromises中的条目。Web 端与服务端渲染的特殊路径在 Font.ts 中可以看到一个刻意设计loadAsync没有使用async关键字因为 Web 端静态渲染阶段必须同步收集所有字体。在服务端Platform.OS web typeof window undefined时字体走registerStaticFont注册为静态资源供 SSR 输出style或link relpreload标签在浏览器端则通过 ExpoFontLoader.web.ts 注入样式。3.2 isLoaded / isLoading / getLoadedFonts加载状态查询isLoaded(fontFamily): boolean同步判断字体是否加载完成。Web 端检查 JS 缓存或font-face规则是否存在Font.ts原生端通过getLoadedFonts结果构建缓存判断memory.ts。isLoading(fontFamily): boolean判断字体是否仍在加载中实现即检查fontFamily in loadPromisesFont.ts。getLoadedFonts(): string[]返回所有已加载字体的名称数组可直接用于fontFamilystyle prop。文档注释指出它同时包含构建期通过 config plugin 嵌入的字体与运行时loadAsync加载的字体Font.ts。3.3 useFontsReact Hooks 形式import { useFonts } from expo-font; export default function App() { const [loaded, error] useFonts({ Inter-Black: require(./assets/fonts/Inter-Black.otf), }); if (!loaded !error) { return null; } // 字体就绪后渲染 return Text style{{ fontFamily: Inter-Black }}Hello/Text; }useFonts返回[loaded, error]二元组FontHooks.tsloaded字体是否加载完成。error加载过程中遇到的错误可用于开发期提示。其实现内部对客户端与服务端做了区分FontHooks.ts在服务端渲染时使用useStaticFonts直接同步调用loadAsync并返回已加载在浏览器中则使用useRuntimeFonts通过useStateuseEffect异步加载并利用isMapLoaded在 Web 水合rehydration阶段复用静态渲染时已加载的字体。此外源码注释明确指出字体映射表动态变化时不会重新加载字体the fonts are not reloaded when you dynamically change the font map因此需要动态换字体时应重新挂载组件或使用loadAsync。四、配置参数详解FontSource 与 FontDisplay4.1 FontSource 的四种形态形态类型说明示例本地模块numberrequire()返回的模块 ID构建期打包进应用require(./assets/fonts/SpaceMono.ttf)远程 URIstring字体文件的网络地址https://example.com/font.ttfAsset 实例Assetexpo-asset管理的资源对象Asset.fromModule(moduleId)FontResourceobject带uri、display、testString字段的对象见下表其中FontResource的字段定义见 Font.types.tsuri字体文件地址字符串或模块 ID。display设置浏览器端该字体的font-display属性取值见下方FontDisplay枚举仅 Web 生效。testString传给 FontFace Observer 的自定义测试字符串用于 Web 端判断字体是否实际渲染仅 Web 生效。4.2 FontDisplay 枚举Web 字体显示策略FontDisplay对应 CSSfont-face的font-display属性默认值为AUTOFont.types.ts取值含义AUTO默认由浏览器/平台决定显示策略通常表现为字体加载完成前文本不可见适合按钮、横幅等需要特定字形效果的场景SWAP先用系统回退字体立即渲染文本字体加载完成后替换内容秒开通常最推荐BLOCK字体加载完成前文本完全不可见若字体加载失败则什么都不显示调试缺失文本时建议关闭FALLBACK折中策略前 100ms 文本不可见之后用回退字体渲染并继续后台加载OPTIONAL与FALLBACK类似但浏览器会根据网络速度或资源压力决定是否加载字体源码注释补充了两个重要细节该值在浏览器中写入生成的font-faceCSS 块不能基于使用它的元素动态修改而在原生平台fontDisplay虽然不生效但主流旗舰设备iOS、Samsung、Pixel 等的默认行为已近似模拟SWAP的效果One Plus 等个别设备行为略有差异Font.types.ts。4.3 Web 端实现原理在浏览器环境中loadAsync最终进入 ExpoFontLoader.web.ts所有注入的font-face规则统一放在一个 id 为expo-generated-fonts的style元素中ExpoFontLoader.web.tsCSS 模板形如font-face{font-family:...;src:url(...);font-display:...}ExpoFontLoader.web.ts。通过fontfaceobserver库监听字体实际加载状态并传入resource.testString与 12000ms 超时ExpoFontLoader.web.ts。在 iOS Safari、Safari、Edge、IE 等浏览器中会跳过字体加载监听这些浏览器与 FontFaceObserver 存在已知兼容问题ExpoFontLoader.web.ts直接返回已解析的 Promise。五、构建期字体嵌入config plugin 方案原 README 在 Font.ts 的 API 文档注释中特别强调We recommend using the config plugin instead whenever possible只要条件允许优先使用 config plugin。这是因为构建期嵌入的字体不需要在运行时下载/注册启动更快且无网络依赖。5.1 在 app config 中声明字体在app.json/app.config.js中通过expo-font的插件声明字体文件路径相对项目根目录{ expo: { plugins: [ [ expo-font, { fonts: [ ./assets/fonts/SpaceMono-Regular.ttf, ./assets/fonts/SpaceMono-Bold.ttf ] } ] ] } }插件支持的FontProps结构见 plugin/src/withFonts.tsfonts字体文件路径数组同时应用到 iOS 与 Android取props.fonts与对应平台字段的并集。android.fontsAndroid 专属声明支持对象语法可为 XML 字体指定自定义 family 名称见下文。ios.fontsiOS 专属声明family 名称从字体文件本身读取。插件执行逻辑withFonts.ts先合并fonts与ios.fonts调用withFontsIos再合并fonts与android.fonts调用withFontsAndroid最后通过createRunOncePlugin保证每个构建过程只执行一次。5.2 Android 对象语法与可变字体Variable Font支持对于 Androidandroid.fonts支持两种元素withFonts.ts字符串字体文件路径。FontObject为同一个fontFamily提供多个字重定义格式为{ fontFamily: MyVariableFont, path: ./assets/fonts/MyVariableFont.ttf, fontDefinitions: [ { weight: 400 }, // Regular { weight: 700 }, // Bold { weight: 700, axes: { wght: 650 } }, // 按 wght 轴实例化 ], }其中FontDefinition的字段包括path静态字体时每个定义可指向不同字体文件。weight字重数字决定fontWeightJS prop 匹配哪个 face。stylenormal|italic。axes可变字体的轴实例化参数如{ slnt: -10 }生成斜体字形。注释特别说明weight只决定 JS prop 匹配axes决定实际绘制效果二者可以分离如weight: 700配{ wght: 650 }表示匹配 bold 请求但文件按 650 字重绘制wght默认取weight值仅声明style: italic并不会让字形倾斜必须配合slnt或ital轴。仓库还保留了 OpenType 注册表中的五个标准轴标签类型ital、opsz、slnt、wdth、wght同时允许自定义四字符轴标签如GRADwithFonts.ts。插件的单元测试位于 plugin/src/tests包括withFontsAndroid-test.ts与utils-test.ts覆盖了 Android 字体嵌入与工具函数的预期行为可作为理解插件输入输出约定的参考。六、原生侧原理字体注册与别名管理6.1 iOSCoreText 注册 PostScript 名称别名iOS 侧的核心模块是 FontLoaderModule.swift。loadAsync的底层流程为若该 family 别名已注册过先反注册旧字体否则 App 重载时CTFontManagerRegisterFontsForURL会因字体名重复而失败FontLoaderModule.swift。通过 CoreText 注册字体文件。读取字体文件内提供的全部 PostScript 名称并为每个命名实例可变字体的每个字重实例建立PostScript 名 →fontFamilyAlias的别名映射FontLoaderModule.swift这样fontWeightstyle prop 才能解析到正确的字重 face。只把应用提供的别名记入registeredFonts——这正是Font.isLoaded的判断依据也是loadAsync去重跳过的依据FontLoaderModule.swift。源码注释特别强调了两点兼容性约定getLoadedFonts与loadAsync会暴露在globalThis.expo.modules.ExpoFontLoader下可能被 Expo 之外的消费者如 react-native-vector-icons使用因此函数签名与属性名不能随意变更FontLoaderModule.swift。6.2 Android / iOS 通用JS 侧缓存策略memory.ts 实现了跨端一致的缓存语义markLoaded把已加载字体写入 JS 侧布尔缓存避免每次isLoaded都查询原生模块。isLoadedNative首次未命中缓存时会调用原生getLoadedFonts一次性同步本地缓存再判断memory.ts。loadPromises表用于并发去重purgeCache与purgeFontFamilyFromCache服务于unloadAllAsync/unloadAsync等卸载 API。6.3 其他导出能力除了运行时加载expo-font还提供了测试辅助 API标注hidden主要用于测试环境unloadAllAsync()卸载全部自定义字体与unloadAsync(fontFamilyOrFontMap, options?)按名称卸载指定字体Font.ts。此外Android/iOS 平台还提供renderToImageAsync(glyphs, options)将文本用指定字体渲染为图片FontUtils.ts。七、使用建议与注意事项优先构建期嵌入静态、不变的自定义字体应通过 config plugin 嵌入运行时loadAsync仅用于远程字体、按需字体或动态场景。善用并发去重多处同时调用loadAsync加载同一字体是安全的内部会合并为同一次加载。加载失败兜底官方注释建议用try/catch/finally包裹loadAsync避免字体加载失败阻塞应用启动。Web 端字体名称唯一Web 端每个fontFamily对应一个font-face规则同名重复加载会被getFontFaceRulesMatchingResource判定已存在而跳过ExpoFontLoader.web.ts。版本兼容expo-font依赖expo、react、react-native作为 peerDependenciespackage.json版本应与当前 SDK 保持一致托管项目务必使用npx expo install安装。八、仓库导航进一步阅读README本文主体运行时 API 实现类型定义FontSource / FontResource / FontDisplayReact HooksuseFonts资源归一化与下载JS 侧缓存与并发去重Web 端 font-face 注入iOS 原生模块config plugin 声明与实现插件单元测试包元数据与依赖【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价