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