NativeScript 是一个使用 JavaScript、TypeScript 或任何转译为 JavaScript 的框架,为 iOS、Android、visionOS 等平台构建真正原生应用程序的框架。
项目概述
NativeScript 提供:
- 直接原生 API 访问 - 大多数平台 API 无需包装器,无需插件
- 跨平台开发 - 单一代码库支持多个平台
- 框架灵活性 - 支持 Angular、Vue、Solid、Svelte、React 或纯 TypeScript
- 原生性能 - 纯原生 UI 渲染
项目结构
典型的 NativeScript 项目结构:
my-app/
├── App_Resources/ # 平台特定资源 (图标, 配置, 原生代码)
│ ├── Android/ # Android 特定资源
│ │ └── src/main/res/ # Android 资源 (drawable, values 等)
│ └── iOS/ # iOS 特定资源
│ └── src/ # Swift/ObjC 原生代码
├── src/ # 应用程序源代码
│ ├── app.ts # 应用程序入口点
│ ├── app.css # 全局样式
│ └── ... # 组件, 页面等
├── nativescript.config.ts # NativeScript 配置
├── package.json # 依赖项
├── tsconfig.json # TypeScript 配置
└── webpack.config.js # 构建配置 (Vite 中可选)
核心导入
始终从 @nativescript/core 导入:
typescript
import {
Application,
Observable,
Frame,
Page,
Color,
Utils,
Device,
Screen,
isAndroid,
isIOS,
File,
Folder,
knownFolders,
Http,
ImageSource,
ObservableArray,
} from '@nativescript/core'
UI 组件
布局容器
GridLayout- 表格式布局,带有行和列StackLayout- 将子元素垂直或水平堆叠FlexboxLayout- 类似 CSS Flexbox 的布局AbsoluteLayout- 使用绝对坐标定位子元素DockLayout- 将子元素停靠到边缘WrapLayout- 将子元素换行到下一行/列
导航组件
Frame- 导航容器Page- 屏幕内容容器ActionBar- 顶部导航栏
常用组件
Label- 文本显示Button- 可点击按钮TextField- 单行文本输入TextView- 多行文本输入Image- 图像显示ListView- 可滚动列表,支持视图回收ScrollView- 可滚动容器WebView- 内嵌网页浏览器Switch,Slider,Progress,ActivityIndicatorDatePicker,TimePicker,ListPickerSearchBar,SegmentedBar,TabView
最佳实践
1. 视图绑定 - 使用直接属性绑定
不良做法:
xml
<Label text="{{ getMyText() }}" />
良好做法:
xml
<Label text="{{ myText }}" />
直接属性绑定提供 1-1 数据投影,以获得最佳的视图渲染性能。
2. ListView - 为条件布局使用模板选择器
不良做法: 在 ListView 项目内使用 v-if/ngIf 会导致滚动期间的视图创建/销毁。
良好做法: 为不同的行布局使用 itemTemplateSelector:
xml
<ListView items="{{ items }}" itemTemplateSelector="{{ selectTemplate }}">
<ListView.itemTemplates>
<template key="header">
<Label text="{{ title }}" class="header" />
</template>
<template key="item">
<Label text="{{ name }}" />
</template>
</ListView.itemTemplates>
</ListView>
typescript
selectItemTemplate(item, index, items) {
return item.isHeader ? 'header' : 'item';
}
3. 可见性 vs v-if/ngIf
v-if/ngIf- 完全销毁/重建视图 (滚动期间开销大)visibility: 'collapse'或hidden: true- 隐藏视图但不销毁它visibility: 'hidden'- 隐藏但保留布局空间
对于滚动等性能关键场景,请使用 hidden 或 visibility。
4. iOS 代理 - 始终保留引用
不良做法:
typescript
controller.delegate = MyDelegateImpl.initWithOwner(this)
良好做法:
typescript
this.delegateRef = MyDelegateImpl.initWithOwner(this)
controller.delegate = this.delegateRef
不保留代理引用会导致垃圾回收问题。
5. 定时器和间隔 - 始终清理
typescript
// 存储引用
this.intervalId = setInterval(() => { /* ... */ }, 1000)
// 在适当的生命周期中清理
clearInterval(this.intervalId)
失控的定时器会导致内存泄漏和意外行为。
扩展原生类
Android (使用 @NativeClass 装饰器)
typescript
@NativeClass()
class MyButton extends android.widget.Button {
constructor() {
super()
return global.__native(this)
}
setEnabled(enabled: boolean): void {
this.super.setEnabled(enabled)
}
}
iOS (使用 @NativeClass 装饰器)
typescript
@NativeClass()
class MyViewController extends UIViewController {
static ObjCProtocols = [UITableViewDelegate]
viewDidLoad(): void {
super.viewDidLoad()
// 设置代码
}
}
// 在其他地方使用时正确导出
export { MyViewController }
单文件中的跨平台原生类
typescript
let customClass
function setupCustomClass() {
if (__ANDROID__) {
@NativeClass()
class CustomClass extends android.view.View {
// Android 实现
}
customClass = CustomClass
} else {
@NativeClass()
class CustomClass extends NSObject {
// iOS 实现
}
customClass = CustomClass
}
}
setupCustomClass()
使用 Observable 进行数据绑定
typescript
import { Observable, EventData } from '@nativescript/core'
export class ViewModel extends Observable {
private _counter: number = 0
get counter(): number {
return this._counter
}
set counter(value: number) {
if (this._counter !== value) {
this._counter = value
this.notifyPropertyChange('counter', value)
}
}
onTap(args: EventData) {
this.counter++
}
}
平台条件判断
除了所有 NativeScript 捆绑器 (webpack, vite 等) 提供的全局宏外,@nativescript/core 还提供了各种条件辅助函数:
typescript
import { isAndroid, isIOS, isVisionOS } from '@nativescript/core'
if (isAndroid) {
// Android 特定代码
}
if (isIOS) {
// iOS 特定代码
}
if (isVisionOS) {
// visionOS 特定代码
}
// 构建时宏 (针对其他平台编译时会被移除)
if (__ANDROID__) {
// 仅编译为 Android
}
if (__IOS__) {
// 仅编译为 iOS
}
if (__VISIONOS__) {
// 仅编译为 visionOS
}
if (__APPLE__) {
// 编译为 iOS 和 visionOS
}
if (__DEV__) {
// 仅开发环境代码
}
平台特定文件
使用平台后缀实现完全不同的实现:
my-component.android.ts- 仅 Androidmy-component.ios.ts- 仅 iOS
或者在同一文件中使用条件判断,适用于有平台差异的共享逻辑。
使用 Workers 的多线程
typescript
// main-thread.ts
const worker = new Worker(new URL('./my-script.worker', import.meta.url))
worker.postMessage({ data: 'process this' })
worker.onmessage = (e) => {
console.log('Result:', e.data)
}
worker.onerror = (e) => {
console.error('Worker error:', e.message)
}
// 完成后始终终止
worker.terminate()
typescript
// my-script.worker.ts
self.onmessage = (e) => {
const result = processData(e.data)
self.postMessage(result)
}
手势
typescript
import { GestureTypes, GestureEventData } from '@nativescript/core'
// 在代码中
view.on(GestureTypes.tap, (args: GestureEventData) => {
console.log('Tapped!')
})
// 可用手势:
// tap, doubleTap, longPress, swipe, pan, pinch, rotation, touch
CSS 动画
css
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.animate-in {
animation-name: fadeIn;
animation-duration: 0.3s;
animation-fill-mode: forwards;
}
可动画的 CSS 属性
opacitybackground-colortransform: translate(x, y)transform: scale(x, y)transform: rotate(deg)
添加原生代码
使用 CLI 添加原生代码文件:
bash
# Swift
ns native add swift AwesomeClass
# Objective-C
ns native add objective-c OtherClass
# Kotlin
ns native add kotlin com.company.AwesomeClass
# Java
ns native add java com.company.OtherClass
文件放置在 App_Resources/{platform}/src/ 中。
框架特定模式
纯 TypeScript
typescript
import { Application } from '@nativescript/core'
Application.run({ moduleName: 'app-root' })
Angular
typescript
import { Component, NO_ERRORS_SCHEMA } from '@angular/core'
import { NativeScriptCommonModule } from '@nativescript/angular'
@Component({
selector: 'app-root',
templateUrl: './app.html',
imports: [NativeScriptCommonModule],
schemas: [NO_ERRORS_SCHEMA], // NativeScript 元素必需
})
export class App {}
Vue
typescript
import { createApp } from 'nativescript-vue'
import Home from './Home.vue'
createApp(Home).start()
Solid
tsx
const App = () => {
return (
<stackLayout>
<label text="Hello Solid!" />
</stackLayout>
)
}
Svelte
svelte
<script lang="ts">
let message = 'Hello Svelte!'
</script>
<stackLayout>
<label text={message} />
</stackLayout>
React
tsx
const App = () => {
return (
<stackLayout>
<label text="Hello React!" />
</stackLayout>
)
}
配置 (nativescript.config.ts)
typescript
import { NativeScriptConfig } from '@nativescript/core'
export default {
id: 'org.nativescript.myapp',
appPath: 'src',
appResourcesPath: 'App_Resources',
android: {
v8Flags: '--expose_gc',
markingMode: 'none',
},
ios: {
// iOS 特定配置
},
} as NativeScriptConfig
安全配置
NativeScript 包含安全选项,用于控制敏感运行时行为,特别是关于远程 ES 模块导入。
远程模块安全
NativeScript 支持从远程 URL 动态 import()。这在开发期间很有用,但在生产环境中存在安全影响,因为 NativeScript 代码具有对原生平台 API 的直接访问权限 (文件系统、钥匙串、网络、摄像头等)。
| 模式 | 远程模块 |
|---|---|
| 调试 | ✅ 始终允许 |
| 生产 | ❌ 默认阻止 |
在生产环境中启用远程模块
如果需要在生产环境中使用远程 ES 模块,请明确选择加入:
typescript
import { NativeScriptConfig } from '@nativescript/core'
export default {
id: 'org.nativescript.myapp',
appPath: 'src',
security: {
allowRemoteModules: true
}
} as NativeScriptConfig
使用白名单 (推荐)
限制为特定可信来源:
typescript
export default {
// ...
security: {
allowRemoteModules: true,
remoteModuleAllowlist: [
'https://cdn.yourcompany.com/modules/',
'https://esm.sh/@yourorg/'
]
}
} as NativeScriptConfig
白名单使用前缀匹配 --- 如果 URL 以任何条目开头,则允许该 URL。
安全最佳实践
- 默认保持生产环境安全 - 除非必要,否则不要启用
- 使用狭窄的白名单 - 特定路径,而不是宽泛域名
- 在 URL 中固定版本 - 使用不可变、带版本的 URL
- 切勿使用用户控制的 URL - 注入漏洞风险
有关全面的安全指南,请参阅 安全指南。
常用 CLI 命令
bash
# 创建新项目
ns create myApp --template @nativescript/template-blank
# 在设备/模拟器上运行
ns run android
ns run ios
# 在设备/模拟器上调试
ns debug android
ns debug ios
# 禁用 HMR 以进行标准实时重载
ns debug android --no-hmr
ns debug ios --no-hmr
# 构建发布版
ns build android --release
ns build ios --release
# 清理项目 (在 App_Resources 更改后运行)
ns clean
# 添加原生代码
ns native add swift MyClass
ns native add objective-c OtherAwesomeClass
ns native add kotlin com.example.MyClass
ns native add java com.company.OtherAwesomeClass
自定义视图的属性系统
typescript
import { Property, View } from '@nativescript/core'
class MyView extends View {
// 定义自动同步原生的属性
}
export const myProperty = new Property<MyView, string>({
name: 'myProperty',
defaultValue: '',
affectsLayout: true,
})
myProperty.register(MyView)
追踪和错误处理
Trace 模块提供了超越 console.log 的强大调试和错误处理功能:
- 控制记录哪些类别的消息
- 通过单次调用禁用所有追踪以用于生产
- 创建自定义追踪写入器以进行专门输出
- 实现自定义错误处理器以进行崩溃报告
基础追踪设置
typescript
import { Trace } from '@nativescript/core'
// 1. 设置要追踪的类别 (在 app.ts 中)
Trace.setCategories(Trace.categories.concat('MyApp', 'MyApp.Network'))
// 2. 启用追踪
Trace.enable()
// 3. 在整个应用程序中写入追踪消息
Trace.write('User logged in', 'MyApp', Trace.messageType.info)
Trace.write('API call failed', 'MyApp.Network', Trace.messageType.error)
// 4. 在生产环境中禁用
if (!__DEV__) {
Trace.disable()
}
内置追踪类别
typescript
Trace.categories.VisualTreeEvents // 视图生命周期事件
Trace.categories.Layout // 布局计算
Trace.categories.Style // CSS/样式
Trace.categories.ViewHierarchy // 视图树变化
Trace.categories.NativeLifecycle // 原生平台生命周期
Trace.categories.Navigation // Frame 导航
Trace.categories.Binding // 数据绑定
Trace.categories.BindingError // 数据绑定错误
Trace.categories.Error // 一般错误
Trace.categories.Animation // 动画事件
Trace.categories.Transition // 页面过渡
Trace.categories.All // 所有类别
// 将内置类别与自定义类别结合
Trace.setCategories(Trace.categories.concat('MyCategory1', 'MyCategory2'))
消息类型
typescript
Trace.messageType.log // 0 - 一般日志
Trace.messageType.info // 1 - 信息
Trace.messageType.warn // 2 - 警告
Trace.messageType.error // 3 - 错误
自定义追踪写入器
创建自定义写入器以格式化输出或将日志发送到外部服务:
typescript
import { Trace, TraceWriter } from '@nativescript/core'
const TimestampTraceWriter: TraceWriter = {
write(message, category, type) {
const timestamp = new Date().toISOString()
const typeLabel = ['LOG', 'INFO', 'WARN', 'ERROR'][type] || 'LOG'
console.log(`[${timestamp}] [${typeLabel}] [${category}] ${message}`)
// 可选: 发送到外部日志服务
if (type === Trace.messageType.error) {
sendToLogService({ timestamp, category, message, type })
}
}
}
// 用自定义写入器替换默认写入器
Trace.clearWriters()
Trace.addWriter(TimestampTraceWriter)
自定义错误处理器
注册自定义错误处理器以进行集中错误管理:
typescript
import { Trace, TraceErrorHandler } from '@nativescript/core'
const errorHandler: TraceErrorHandler = {
handlerError(err: Error) {
if (__DEV__) {
// 开发: 记录详细错误
Trace.write(
`${err.message}\n${err.stack}`,
'unhandled-error',
Trace.messageType.error
)
// 可选地重新抛出以在调试器中查看
throw err
} else {
// 生产: 报告给分析/崩溃报告
reportToCrashlytics(err)
}
}
}
// 注册错误处理器 (在 app.ts 中)
Trace.setErrorHandler(errorHandler)
// 将错误传递给处理器
try {
riskyOperation()
} catch (err) {
Trace.error(err)
}
全局应用程序错误事件
处理未捕获的错误和 Promise 拒绝:
typescript
import { Application } from '@nativescript/core'
// 未捕获的 JavaScript 错误
Application.on(Application.uncaughtErrorEvent, (args) => {
const error = args.error
console.error('Uncaught error:', error.message)
console.error('Stack:', error.stack)
// 报告给崩溃服务
reportError(error)
// 可选地防止应用程序崩溃 (谨慎使用)
// args.cancel = true
})
// 未处理的 Promise 拒绝
Application.on(Application.discardedErrorEvent, (args) => {
console.error('Unhandled promise rejection:', args.error)
reportError(args.error)
})
完整错误处理设置
typescript
// app.ts - 推荐的错误处理设置
import { Application, Trace, TraceErrorHandler } from '@nativescript/core'
// 1. 设置追踪类别
Trace.setCategories(Trace.categories.concat('App', 'App.Error'))
// 2. 在开发环境中启用追踪
if (__DEV__) {
Trace.enable()
}
// 3. 自定义错误处理器
const errorHandler: TraceErrorHandler = {
handlerError(err: Error) {
Trace.write(err.message, 'App.Error', Trace.messageType.error)
if (!__DEV__) {
// 发送到 Sentry, Crashlytics 等
reportToCrashService({
message: err.message,
stack: err.stack,
timestamp: Date.now()
})
}
}
}
Trace.setErrorHandler(errorHandler)
// 4. 全局错误处理器
Application.on(Application.uncaughtErrorEvent, (args) => {
Trace.error(args.error)
})
Application.on(Application.discardedErrorEvent, (args) => {
Trace.error(args.error)
})
// 5. 启动应用程序
Application.run({ moduleName: 'app-root' })
追踪 API 快速参考
typescript
// 类别
Trace.setCategories(categories: string) // 设置允许的类别
Trace.addCategories(categories: string) // 添加到现有类别
Trace.isCategorySet(category: string) // 检查类别是否已设置
// 写入
Trace.write(message, category, type?) // 写入追踪消息
Trace.error(error: Error | string) // 将错误传递给处理器
// 写入器
Trace.addWriter(writer: TraceWriter) // 添加自定义写入器
Trace.removeWriter(writer: TraceWriter) // 移除写入器
Trace.clearWriters() // 移除所有写入器
// 错误处理
Trace.setErrorHandler(handler) // 设置自定义错误处理器
Trace.getErrorHandler() // 获取当前错误处理器
// 启用/禁用
Trace.enable() // 启用追踪
Trace.disable() // 禁用追踪
Trace.isEnabled() // 检查是否已启用
性能提示
- 最小化布局嵌套 - 深层层次结构会损害性能
- 使用 GridLayout - 对于复杂布局最灵活且性能最好
- 避免方法绑定 - 使用属性绑定代替
- 使用模板选择器 - 用于条件 ListView 行
- 在滚动期间优先使用
hidden/visibility- 而不是v-if/ngIf - 清理资源 - 定时器、监听器、观察器
- 使用 Workers - 用于将繁重计算移出主线程
- 优化图像 - 为每种密度提供适当大小的图像
Android Drawable 密度
将图像放置在适当文件夹中:
App_Resources/Android/src/main/res/drawable-mdpi/- 1xApp_Resources/Android/src/main/res/drawable-hdpi/- 1.5xApp_Resources/Android/src/main/res/drawable-xhdpi/- 2xApp_Resources/Android/src/main/res/drawable-xxhdpi/- 3xApp_Resources/Android/src/main/res/drawable-xxxhdpi/- 4x
iOS 资产目录
将图像放置在 App_Resources/iOS/Assets.xcassets/ 中,并带有适当的 @2x 和 @3x 后缀。
创建自定义视图元素
当内置元素无法满足需求时,创建自定义原生元素。
自定义视图的组成
每个自定义 NativeScript 视图必须包含:
- (必需) 扩展任何 NativeScript View 的类
- (必需 )
createNativeView: 构造并返回平台原生视图 - (可选 )
initNativeView: 创建后执行初始化 - (可选 )
disposeNativeView: 移除时清理资源
自定义视图的项目结构
./my-custom-view/
├── common.ts # 共享逻辑
├── index.android.ts # Android 实现
├── index.ios.ts # iOS 实现
└── index.d.ts # 类型定义
基础自定义视图示例
typescript
import { View } from '@nativescript/core'
export class CustomView extends View {
createNativeView() {
// iOS: 返回 UIView 实例
// Android: 返回 android.view.View 实例
}
initNativeView() {
// 初始化代码
}
disposeNativeView() {
// 清理代码
}
}
扩展现有视图
可以扩展现有的任何 NativeScript 视图:
typescript
import { GridLayout, Label, Color, Property, booleanConverter, CoreTypes } from '@nativescript/core'
export class Checkbox extends GridLayout {
checked = false
defaultColor = new Color('#dddddd')
selectionColor = new Color('#4CAF50')
private _iconLabel: Label
constructor() {
super()
this.horizontalAlignment = 'center'
this.verticalAlignment = 'middle'
this._iconLabel = new Label()
this._iconLabel.text = String.fromCharCode(0xf764) // 圆形图标
this._iconLabel.className = 'mat' // Material Design Icons 字体
this._iconLabel.color = this.defaultColor
this.addChild(this._iconLabel)
}
toggle() {
this.checked = !this.checked
this._iconLabel.text = this.checked
? String.fromCharCode(0xf5e0) // 勾选标记
: String.fromCharCode(0xf764) // 圆形
this._iconLabel.color = this.checked ? this.selectionColor : this.defaultColor
}
}
定义可自定义属性
typescript
import { Property, booleanConverter, Color } from '@nativescript/core'
const checkedProperty = new Property<Checkbox, boolean>({
name: 'checked',
defaultValue: false,
valueConverter: booleanConverter,
})
const sizeProperty = new Property<Checkbox, number>({
name: 'size',
defaultValue: 24,
affectsLayout: true,
})
const selectionColorProperty = new Property<Checkbox, Color>({
name: 'selectionColor',
equalityComparer: Color.equals,
valueConverter: (v) => new Color(v),
})
// 实现 setNative 处理器
export class Checkbox extends GridLayout {
[checkedProperty.setNative](value: boolean) {
this.checked = value
this._updateVisual()
}
[sizeProperty.setNative](value: number) {
this._iconLabel.fontSize = value
}
[selectionColorProperty.setNative](value: Color) {
this.selectionColor = value
}
}
// 注册属性
checkedProperty.register(Checkbox)
sizeProperty.register(Checkbox)
selectionColorProperty.register(Checkbox)
按框架注册自定义元素
TypeScript (XML)
xml
<Page xmlns:custom="./checkbox">
<StackLayout>
<custom:Checkbox checked="true" size="32" />
</StackLayout>
</Page>
Angular
typescript
import { registerElement } from '@nativescript/angular'
import { Checkbox } from './checkbox'
registerElement('Checkbox', () => Checkbox)
Vue
typescript
import { registerElement } from 'nativescript-vue'
import { Checkbox } from './checkbox'
registerElement('Checkbox', () => Checkbox)
Svelte
typescript
import { registerNativeViewElement } from '@nativescript-community/svelte-native/dom'
import { Checkbox } from './checkbox'
registerNativeViewElement('checkbox', () => Checkbox)
React
typescript
import { registerElement } from 'react-nativescript'
import { Checkbox } from './checkbox'
registerElement('checkbox', () => Checkbox)
Solid
typescript
import { registerElement } from 'dominative'
import { Checkbox } from './checkbox'
registerElement('checkbox', Checkbox)
自定义现有视图元素
扩展现有元素以自定义行为:
typescript
// index.ios.ts - 使用更大字体的自定义 ListPicker
import { ListPicker } from '@nativescript/core'
export class CustomListPicker extends ListPicker {
private _delegate: ListPickerDelegateImpl
initNativeView() {
this._delegate = ListPickerDelegateImpl.initWithOwner(new WeakRef(this))
this.nativeViewProtected.delegate = this._delegate
}
}
@NativeClass()
class ListPickerDelegateImpl extends NSObject implements UIPickerViewDelegate {
static ObjCProtocols = [UIPickerViewDelegate]
private _owner: WeakRef<ListPicker>
static initWithOwner(owner: WeakRef<ListPicker>): ListPickerDelegateImpl {
const delegate = ListPickerDelegateImpl.new() as ListPickerDelegateImpl
delegate._owner = owner
return delegate
}
pickerViewViewForRowForComponentReusingView(pickerView: UIPickerView, row: number): UIView {
const owner = this._owner?.deref()
const label = UILabel.new()
label.font = UIFont.systemFontOfSize(26) // 自定义字体大小
label.text = owner?.items[row]
label.textAlignment = NSTextAlignment.Center
return label
}
}
createNativeView 平台示例
typescript
// iOS
createNativeView() {
const config = WKWebViewConfiguration.new()
return new WKWebView({ frame: CGRectZero, configuration: config })
}
// Android
createNativeView() {
return new android.webkit.WebView(this._context)
}
资源
- 文档: https://docs.nativescript.org
- API 参考: https://docs.nativescript.org/api/
- 插件: https://docs.nativescript.org/plugins/
- GitHub: https://github.com/NativeScript/NativeScript
- NativeScript Preview with Stackblitz: https://preview.nativescript.org/
- JavaScript 入门模板: https://nativescript.new/javascript
- TypeScript 入门模板: https://nativescript.new/typescript
- Angular 入门模板: https://nativescript.new/angular
- React 入门模板: https://nativescript.new/react
- Solid 入门模板: https://nativescript.new/solid
- Svelte 入门模板: https://nativescript.new/svelte
- Vue 入门模板: https://nativescript.new/vue
- Vue 3 入门模板: https://nativescript.new/vue3
https://docs.nativescript.org/assets/agentic/NATIVESCRIPT.md