封装第三方原生库
了解如何使用 Expo Modules API 为两个单独的原生库创建一个简单的封装器。
Expo 模块使得在 React Native 项目中轻松使用为 Android 和 iOS 构建的原生外部库成为可能。本教程重点介绍如何利用 Expo Modules API 使用可在两个原生平台上访问的两个类似库来创建径向图表。
¥Expo modules make it possible to easily use native, external libraries built for Android and iOS in React Native projects. This tutorial focuses on utilizing the Expo Modules API to create radial charts using two similar libraries accessible on both native platforms.
iOS 库的灵感来自 Android 库,因此它们都具有相似的 API 和功能。这使它们成为本教程的一个很好的例子。
¥The iOS library is inspired by the Android library, so they both have similar API and functionality. This makes them a good example for this tutorial.

In this video you will learn how to wrap native libraries using Expo Modules API.
1
创建一个新模块
¥Create a new module
以下步骤假定新模块是在新的 Expo 项目中创建的。但是,你可以按照其他说明在现有项目中创建新模块。
¥The following steps assume that the new module is created inside a new Expo project. However, you can create a new module inside an existing project by following the alternative instructions.
从一个新项目开始
¥Start with a new project
创建一个新的空 Expo 模块,该模块可以在 npm 上发布并在任何 Expo 应用中使用,方法是运行以下命令:
¥Create a new empty Expo module that can be published on npm and utilized in any Expo app by running the following command:
-
npx create-expo-module expo-radial-chart
提示:如果你不打算发送此库,请在终端窗口中按 return 接受所有提示以接受默认值。
现在,打开新创建的 expo-radial-chart
目录开始编辑原生代码。
¥Now, open the newly created expo-radial-chart
directory to start editing the native code.
从现有项目开始
¥Start with an existing project
或者,你可以使用新模块作为现有项目内的视图。在项目目录中运行以下命令:
¥Alternatively, you can use the new module as a view inside the existing project. Run the following command in your project's directory:
-
npx create-expo-module --local expo-radial-chart
现在,打开新创建的 modules/expo-radial-chart
目录开始编辑原生代码。
¥Now, open the newly created modules/expo-radial-chart
directory to start editing the native code.
2
运行示例项目
¥Run the example project
为了验证一切是否正常运行,让我们运行示例项目。在终端窗口中,启动 TypeScript 编译器来监视更改并重建模块 JavaScript:
¥To verify that everything is functioning correctly, let's run the example project. In a terminal window, start the TypeScript compiler to watch for changes and rebuild the module JavaScript:
# Run this in the root of the project to start the TypeScript compiler
-
npm run build
在另一个终端窗口中,编译并运行示例应用:
¥In another terminal window, compile and run the example app:
-
cd example
# Run the example app on Android
-
npx expo run:android
# Run the example app on iOS
-
npx expo run:ios
3
添加原生依赖
¥Add native dependencies
通过编辑 android/build.gradle 和 ios/ExpoRadialChart.podspec 文件将原生依赖添加到模块:
¥Add the native dependencies to the module by editing the android/build.gradle and ios/ExpoRadialChart.podspec files:
dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
+ implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
}
s.static_framework = true
s.dependency 'ExpoModulesCore'
+ s.dependency 'DGCharts', '~> 5.1.0'
# Swift/Objective-C compatibility
Are you trying to use a .aar
dependency?
在 android 目录中,创建另一个名为 libs 的目录并将 .aar 文件放入其中。然后,从自动链接中将文件添加为 Gradle 项目:
¥Inside the android directory, create another directory called libs and place the .aar file inside it. Then, add the file as a Gradle project from autolinking:
"android": {
+ "gradleAarProjects": [
+ {
+ "name": "test-aar",
+ "aarFilePath": "android/libs/test.aar"
+ }
+ ],
"modules": [
最后,将依赖添加到 android/build.gradle 文件中的 dependencies
列表中,使用依赖的指定名称加上 ${project.name}$
前缀:
¥Finally, add the dependency to the dependencies
list in the android/build.gradle file, using the dependency's specified name with ${project.name}$
prefix:
dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
+ implementation project(":${project.name}\$test-aar")
}
在 android 目录中,创建另一个名为 libs 的目录并将 .aar 文件放入其中。然后,将目录添加为存储库:
¥Inside the android directory, create another directory called libs and place the .aar file inside it. Then, add the directory as a repository:
repositories {
mavenCentral()
+ flatDir {
+ dirs 'libs'
+ }
}
最后,将依赖添加到 dependencies
列表中。使用包路径代替文件名,其中末尾包含 @aar
:
¥Finally, add the dependency to the dependencies
list. Instead of the filename, use the package path, which includes the @aar
at the end:
dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
+ implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0@aar'
}
Are you trying to use an .xcframework
or .framework
dependency?
在 iOS 上,你还可以通过使用 vendored_framework
配置选项来使用打包为框架的依赖。
¥On iOS, you can also use dependencies bundled as a framework by using the vendored_framework
config option.
s.static_framework = true
s.dependency 'ExpoModulesCore'
+ s.vendored_frameworks = 'Frameworks/MyFramework.framework'
# Swift/Objective-C compatibility
4
定义一个 API
¥Define an API
要在应用中使用该模块,请定义属性的类型。它接受一系列列表 - 每个系列都有一个颜色和一个百分比值。
¥To use the module in the app, define the types for the props. It accepts a list of series — each with a color and a percentage value.
import { ViewStyle } from 'react-native/types';
export type ChangeEventPayload = {
value: string;
};
type Series = {
color: string;
percentage: number;
};
export type ExpoRadialChartViewProps = {
style?: ViewStyle;
data: Series[];
};
由于此示例中的模块未针对 Web 实现,因此让我们替换 src/ExpoRadialChartView.web.tsx 文件:
¥Since the module isn't implemented for web in this example, let's replace the src/ExpoRadialChartView.web.tsx file:
import * as React from 'react';
export default function ExpoRadialChartView() {
return <div>Not implemented</div>;
}
5
在 Android 上实现该模块
¥Implement the module on Android
现在,你可以通过编辑占位符文件并进行以下更改来实现原生功能:
¥Now you can implement the native functionality by editing the placeholder files with the following changes:
-
创建一个
PieChart
实例并设置其layoutParams
以匹配父视图。然后,使用addView
函数将其添加到视图层次结构中。¥Create a
PieChart
instance and set itslayoutParams
to match the parent view. Then, add it to the view hierarchy using theaddView
function. -
定义接受
Series
对象列表的setChartData
函数。你可以迭代列表,为每个系列创建PieEntry
并将颜色存储在单独的列表中。¥Define a
setChartData
function that accepts a list ofSeries
objects. You can iterate over the list, create aPieEntry
for each series and store the colors in a separate list. -
创建一个
PieDataSet
,使用它创建一个PieData
对象,并将其设置为PieChart
实例上的数据。¥Create a
PieDataSet
, use it to create aPieData
object, and set it as data on thePieChart
instance.
package expo.modules.radialchart
import android.content.Context
import android.graphics.Color
import androidx.annotation.ColorInt
import com.github.mikephil.charting.charts.PieChart
import com.github.mikephil.charting.data.PieData
import com.github.mikephil.charting.data.PieDataSet
import com.github.mikephil.charting.data.PieEntry
import expo.modules.kotlin.AppContext
import expo.modules.kotlin.records.Field
import expo.modules.kotlin.records.Record
import expo.modules.kotlin.views.ExpoView
class Series : Record {
@Field
val color: String = "#ff0000"
@Field
val percentage: Float = 0.0f
}
class ExpoRadialChartView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
internal val chartView = PieChart(context).also {
it.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
addView(it)
}
fun setChartData(data: ArrayList<Series>) {
val entries: ArrayList<PieEntry> = ArrayList()
val colors: ArrayList<Int> = ArrayList()
for (series in data) {
entries.add(PieEntry(series.percentage))
colors.add(Color.parseColor(series.color))
}
val dataSet = PieDataSet(entries, "DataSet");
dataSet.colors = colors;
val pieData = PieData(dataSet);
chartView.data = pieData;
chartView.invalidate();
}
}
你还需要使用 Prop
函数来定义 data
prop,并在 prop 更改时调用原生 setChartData
函数:
¥You also need to use the Prop
function to define the data
prop and call the native setChartData
function when the prop changes:
package expo.modules.radialchart
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class ExpoRadialChartModule : Module() {
override fun definition() = ModuleDefinition {
Name("ExpoRadialChart")
View(ExpoRadialChartView::class) {
Prop("data") { view: ExpoRadialChartView, prop: ArrayList<Series> ->
view.setChartData(prop);
}
}
}
}
6
在 iOS 上实现该模块
¥Implement the module on iOS
现在,你可以通过编辑占位符文件并进行以下更改来实现原生功能:
¥Now you can implement the native functionality by editing the placeholder files with the following changes:
-
创建一个新的
PieChartView
实例并使用addSubview
函数将其添加到视图层次结构中。¥Create a new
PieChartView
instance and use theaddSubview
function to add it to the view hierarchy. -
设置
clipsToBounds
属性并覆盖layoutSubviews
函数以确保图表视图始终与父视图大小相同。¥Set the
clipsToBounds
property and override thelayoutSubviews
function to make sure the chart view is always the same size as the parent view. -
创建一个
setChartData
函数,该函数接受一系列列表,使用数据创建PieChartDataSet
实例,并将其分配给PieChartView
实例的data
属性。¥Create a
setChartData
function that accepts a list of series, creates aPieChartDataSet
instance with the data, and assigns it to thedata
property of thePieChartView
instance.
import ExpoModulesCore
import DGCharts
struct Series: Record {
@Field
var color: UIColor = UIColor.black
@Field
var percentage: Double = 0
}
class ExpoRadialChartView: ExpoView {
let chartView = PieChartView()
required init(appContext: AppContext? = nil) {
super.init(appContext: appContext)
clipsToBounds = true
addSubview(chartView)
}
override func layoutSubviews() {
chartView.frame = bounds
}
func setChartData(data: [Series]) {
let set1 = PieChartDataSet(entries: data.map({ (series: Series) -> PieChartDataEntry in
return PieChartDataEntry(value: series.percentage)
}))
set1.colors = data.map({ (series: Series) -> UIColor in
return series.color
})
let chartData: PieChartData = [set1]
chartView.data = chartData
}
}
你还需要使用 Prop
函数来定义 data
prop,并在 prop 更改时调用原生 setChartData
函数:
¥You also need to use the Prop
function to define the data
prop and call the native setChartData
function when the prop changes:
import ExpoModulesCore
public class ExpoRadialChartModule: Module {
public func definition() -> ModuleDefinition {
Name("ExpoRadialChart")
View(ExpoRadialChartView.self) {
Prop("data") { (view: ExpoRadialChartView, prop: [Series]) in
view.setChartData(data: prop)
}
}
}
}
7
编写一个示例应用来使用该模块
¥Write an example app to use the module
你可以更新示例目录中的应用来测试该模块。使用 ExpoRadialChartView
组件渲染具有三个切片的饼图:
¥You can update the app inside the example directory to test the module. Use the ExpoRadialChartView
component to render a pie chart with three slices:
import { ExpoRadialChartView } from 'expo-radial-chart';
import { StyleSheet } from 'react-native';
export default function App() {
return (
<ExpoRadialChartView
style={styles.container}
data={[
{
color: '#ff0000',
percentage: 0.5,
},
{
color: '#00ff00',
percentage: 0.2,
},
{
color: '#0000ff',
percentage: 0.3,
},
]}
/>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
提示:如果你在现有应用中创建了模块,请确保使用相对导入直接从模块目录中导入它:import { ExpoRadialChartView } from '../modules/expo-radial-chart';
8
重建并启动你的应用
¥Rebuild and launch your application
为了确保你的应用在两个平台上成功构建,请重新运行步骤 2 中的构建命令。在任何平台上成功构建应用后,你将看到一个包含三个切片的饼图:
¥To make sure your app builds successfully on both platforms, rerun the build commands from step 2. After the app is successfully built on any of the platform you'll see a pie chart with three slices:
恭喜!你已经使用 Expo Modules API 为两个单独的第三方原生库创建了第一个简单封装器。
¥Congratulations! You have created your first simple wrapper around two separate third-party native libraries using Expo Modules API.
下一步
¥Next step
使用 Kotlin 和 Swift 创建原生模块的参考。