NativeScript Vue集成 #
Vue 集成概述 #
NativeScript 与 Vue 完美结合,让你可以使用 Vue 的响应式系统开发原生应用。
text
┌─────────────────────────────────────────────────────────────┐
│ Vue NativeScript │
├─────────────────────────────────────────────────────────────┤
│ │
│ 核心特性 │
│ ├── Vue 3 支持 │
│ ├── 组合式 API │
│ ├── 单文件组件 │
│ ├── 响应式系统 │
│ └── Vuex 状态管理 │
│ │
│ NativeScript 扩展 │
│ ├── 原生 UI 组件 │
│ ├── 原生路由 │
│ └── 原生模块 │
│ │
└─────────────────────────────────────────────────────────────┘
项目创建 #
创建 Vue 项目 #
bash
ns create my-app --vue
项目结构 #
text
my-app/
├── app/
│ ├── App.vue
│ ├── main.ts
│ ├── components/
│ │ └── Home.vue
│ ├── screens/
│ │ └── Detail.vue
│ └── router/
│ └── index.ts
├── App_Resources/
├── package.json
└── tsconfig.json
应用入口 #
main.ts #
typescript
// main.ts
import { createApp } from 'nativescript-vue';
import Home from './components/Home.vue';
const app = createApp(Home);
app.start();
App.vue #
vue
<!-- App.vue -->
<template>
<Frame>
<Home />
</Frame>
</template>
<script>
import Home from './components/Home.vue';
export default {
components: { Home }
};
</script>
组件开发 #
单文件组件 #
vue
<!-- components/Home.vue -->
<template>
<Page>
<ActionBar title="Home">
<ActionItem text="Add" @tap="onAdd" />
</ActionBar>
<GridLayout>
<ListView :items="items" class="list-group">
<template v-slot:default="{ item }">
<GridLayout columns="*, auto" @tap="onItemTap(item)">
<Label :text="item.title" col="0" />
<Label :text="item.price" col="1" />
</GridLayout>
</template>
</ListView>
</GridLayout>
</Page>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const items = ref([
{ id: 1, title: 'Item 1', price: '$10' },
{ id: 2, title: 'Item 2', price: '$20' },
{ id: 3, title: 'Item 3', price: '$30' }
]);
const onAdd = () => {
items.value.push({
id: items.value.length + 1,
title: `Item ${items.value.length + 1}`,
price: '$0'
});
};
const onItemTap = (item) => {
console.log('Tapped:', item);
};
return {
items,
onAdd,
onItemTap
};
}
};
</script>
<style scoped>
.list-group {
background-color: #f5f5f5;
}
</style>
组合式 API #
vue
<!-- components/UserProfile.vue -->
<template>
<GridLayout>
<StackLayout>
<Image :src="user.avatar" width="100" height="100" borderRadius="50" />
<Label :text="user.name" class="h2" />
<Label :text="user.email" class="text-muted" />
<Button text="Edit" @tap="editMode = true" v-if="!editMode" />
<StackLayout v-else>
<TextField v-model="editName" hint="Name" />
<TextField v-model="editEmail" hint="Email" keyboardType="email" />
<Button text="Save" @tap="saveProfile" />
<Button text="Cancel" @tap="editMode = false" />
</StackLayout>
</StackLayout>
</GridLayout>
</template>
<script>
import { ref, reactive, computed } from 'vue';
export default {
props: {
userId: {
type: Number,
required: true
}
},
setup(props) {
const user = reactive({
name: 'John Doe',
email: 'john@example.com',
avatar: '~/assets/avatar.png'
});
const editMode = ref(false);
const editName = ref(user.name);
const editEmail = ref(user.email);
const saveProfile = () => {
user.name = editName.value;
user.email = editEmail.value;
editMode.value = false;
};
return {
user,
editMode,
editName,
editEmail,
saveProfile
};
}
};
</script>
选项式 API #
vue
<!-- components/Counter.vue -->
<template>
<GridLayout>
<StackLayout>
<Label :text="count" class="h1" />
<Button text="Increment" @tap="increment" />
<Button text="Decrement" @tap="decrement" />
<Button text="Reset" @tap="reset" />
</StackLayout>
</GridLayout>
</template>
<script>
export default {
data() {
return {
count: 0
};
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
},
reset() {
this.count = 0;
}
},
computed: {
isPositive() {
return this.count > 0;
}
}
};
</script>
路由配置 #
安装路由 #
bash
npm install @nativescript/vue-router
配置路由 #
typescript
// router/index.ts
import { createRouter, createWebHashHistory } from '@nativescript/vue-router';
import Home from '../components/Home.vue';
import Detail from '../components/Detail.vue';
import Settings from '../components/Settings.vue';
const routes = [
{ path: '/', component: Home },
{ path: '/detail/:id', component: Detail, props: true },
{ path: '/settings', component: Settings }
];
const router = createRouter({
history: createWebHashHistory(),
routes
});
export default router;
使用路由 #
typescript
// main.ts
import { createApp } from 'nativescript-vue';
import App from './App.vue';
import router from './router';
const app = createApp(App);
app.use(router);
app.start();
vue
<!-- App.vue -->
<template>
<Frame>
<router-view />
</Frame>
</template>
导航 #
vue
<!-- Home.vue -->
<template>
<Page>
<ActionBar title="Home" />
<GridLayout>
<Button text="Go to Detail" @tap="goToDetail(1)" />
</GridLayout>
</Page>
</template>
<script>
import { useRouter } from '@nativescript/vue-router';
export default {
setup() {
const router = useRouter();
const goToDetail = (id) => {
router.push(`/detail/${id}`);
};
return {
goToDetail
};
}
};
</script>
获取路由参数 #
vue
<!-- Detail.vue -->
<template>
<Page>
<ActionBar title="Detail">
<NavigationButton text="Back" @tap="goBack" />
</ActionBar>
<GridLayout>
<Label :text="'Item ID: ' + id" />
</GridLayout>
</Page>
</template>
<script>
import { useRouter, useRoute } from '@nativescript/vue-router';
export default {
props: ['id'],
setup(props) {
const router = useRouter();
const route = useRoute();
const goBack = () => {
router.back();
};
return {
goBack
};
}
};
</script>
状态管理 #
使用 Vuex #
typescript
// store/index.ts
import { createStore } from 'vuex';
export default createStore({
state: {
user: null,
items: [],
isLoading: false
},
getters: {
isLoggedIn: state => !!state.user,
itemCount: state => state.items.length
},
mutations: {
SET_USER(state, user) {
state.user = user;
},
SET_ITEMS(state, items) {
state.items = items;
},
ADD_ITEM(state, item) {
state.items.push(item);
},
SET_LOADING(state, isLoading) {
state.isLoading = isLoading;
}
},
actions: {
async fetchItems({ commit }) {
commit('SET_LOADING', true);
try {
const items = await api.getItems();
commit('SET_ITEMS', items);
} finally {
commit('SET_LOADING', false);
}
},
async login({ commit }, credentials) {
const user = await api.login(credentials);
commit('SET_USER', user);
}
}
});
使用 Store #
typescript
// main.ts
import { createApp } from 'nativescript-vue';
import App from './App.vue';
import store from './store';
const app = createApp(App);
app.use(store);
app.start();
vue
<!-- components/Items.vue -->
<template>
<Page>
<ActionBar title="Items">
<ActionItem text="Add" @tap="addItem" />
</ActionBar>
<GridLayout>
<ActivityIndicator :busy="isLoading" v-if="isLoading" />
<ListView :items="items" v-else>
<template v-slot:default="{ item }">
<Label :text="item.title" />
</template>
</ListView>
</GridLayout>
</Page>
</template>
<script>
import { computed } from 'vue';
import { useStore } from 'vuex';
export default {
setup() {
const store = useStore();
const items = computed(() => store.state.items);
const isLoading = computed(() => store.state.isLoading);
const loadItems = () => {
store.dispatch('fetchItems');
};
const addItem = () => {
store.commit('ADD_ITEM', {
id: Date.now(),
title: 'New Item'
});
};
loadItems();
return {
items,
isLoading,
addItem
};
}
};
</script>
组件通信 #
Props 和 Events #
vue
<!-- 父组件 -->
<template>
<GridLayout>
<ChildComponent :data="parentData" @update="onUpdate" />
</GridLayout>
</template>
<script>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';
export default {
components: { ChildComponent },
setup() {
const parentData = ref('Hello');
const onUpdate = (newValue) => {
parentData.value = newValue;
};
return {
parentData,
onUpdate
};
}
};
</script>
<!-- 子组件 -->
<template>
<Label :text="data" @tap="updateData" />
</template>
<script>
export default {
props: ['data'],
emits: ['update'],
setup(props, { emit }) {
const updateData = () => {
emit('update', 'New Value');
};
return {
updateData
};
}
};
</script>
Provide/Inject #
vue
<!-- 祖先组件 -->
<template>
<ChildComponent />
</template>
<script>
import { provide, ref } from 'vue';
import ChildComponent from './ChildComponent.vue';
export default {
components: { ChildComponent },
setup() {
const theme = ref('light');
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light';
};
provide('theme', theme);
provide('toggleTheme', toggleTheme);
return {
theme,
toggleTheme
};
}
};
</script>
<!-- 后代组件 -->
<template>
<Label :text="theme" @tap="toggleTheme" />
</template>
<script>
import { inject } from 'vue';
export default {
setup() {
const theme = inject('theme');
const toggleTheme = inject('toggleTheme');
return {
theme,
toggleTheme
};
}
};
</script>
最佳实践 #
组件设计原则 #
text
┌─────────────────────────────────────────────────────────────┐
│ 组件设计原则 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 单一职责 │
│ 每个组件只做一件事 │
│ │
│ 2. Props 向下,Events 向上 │
│ 数据单向流动 │
│ │
│ 3. 组合优于继承 │
│ 使用组合式 API │
│ │
│ 4. 合理拆分 │
│ 大组件拆分为小组件 │
│ │
│ 5. 命名清晰 │
│ 组件名描述其功能 │
│ │
└─────────────────────────────────────────────────────────────┘
下一步 #
现在你已经掌握了 Vue 集成,接下来学习 React集成,了解如何与 React 框架集成!
最后更新:2026-03-29