返回博客首页
← 所有文章

用于 NativeScript 的全新 Vue

2017 年 6 月 6 日 — 作者:Jen Looper

logo

您是否曾经渴望尝试软件的早期版本?在分叉一个新构建的仓库并在您的移动模拟器上尝试它时,有一种独特的魅力和兴奋感,展现出它所有的闪亮之处。当我查看由优秀的 Igor Randjelovic 构建的项目时,我感受到了这种感觉,该项目集成了用于 NativeScript 的 Vue.js。是否希望使用 Vue.js 而不是 Angular 或纯原生 NativeScript 来编写您的 NativeScript 应用程序?不用再犹豫了。

有兴趣了解更多关于 NativeScript 和 Vue 的信息吗?观看 YouTube 上的 NativeScript-Vue 2.0 网络研讨会


为什么选择 Vue.js?

目前,开发 NativeScript 应用程序的标准方法是编写“纯原生”NativeScript 或使用 Angular,Angular 还提供了在 Web 和移动设备之间共享代码的机会。然而,许多人一直在要求将 Vue.js 集成到 NativeScript 中:这是我们在 Aha 想法列表 中第二受欢迎的“想法”请求,仅次于对 Windows 通用平台的支持,该请求被标记为“已计划”。我们很幸运有一位优秀的社区成员主动为 NativeScript 构建了 Vue。这个仓库刚刚在 4 月中旬启动,已经获得了超过 160 个星标。如果您有兴趣帮助这项工作,欢迎提交 PR,并且可以在 NativeScript 社区 Slack#vue 频道上进行充分讨论。

对于那些不太熟悉如今每周都会涌现出的大量 JavaScript 框架的人来说,Vue.js 是一种轻量级的“渐进式框架”,旨在构建用户界面。与 Angular 等更大、更固执己见的框架不同,Vue 旨在实现增量采用 - 只将其用于视图层,或通过利用各种支持库来构建完整的单页应用程序。Vue 与其他框架(包括 React 和 Angular)之间的出色比较表明,Vue 的创建者从这两个框架中汲取了最佳实践:Vue 使用虚拟 DOM(类似于 React),并且其语法类似于 Angular 的语法。

基准测试 表明,在 Web 上,Vue 的性能略微优于 Angular 和 React,并且比 Angular 更轻量级:

“具有 AOT 编译和 tree-shaking 的最新版本的 Angular 已能够大幅减小其大小。但是,包含 Vuex + vue-router 的完整功能的 Vue 2 项目(约 30kb 压缩后)仍然明显轻于 angular-cli 生成的开箱即用、经过 AOT 编译的应用程序(约 130kb 压缩后)。” 来源
您可以在 Full-Stack 播客 上收听 Vue 的创建者 Evan You 的讲解,了解 Vue 架构背后的思考。



虽然此集成尚处于早期阶段,但您可以 在此 试用它。分叉仓库并按照 此处的 指示进行开发设置。

请注意,目前该项目在 Android 模拟器上的运行更加稳定,而不是 iOS 模拟器。

当您从 vue-sample 文件夹运行项目时,您将看到如下所示的 Reddit 阅读器示例

vuesample-reddit

通过编辑 vue-sample/app 文件夹中的 package.json 并引用该文件夹中的各种模板来更改起始模板:


{
  "main": "app-with-list-view.js",
  "name": "nativescript-template-tutorial",
  "version": "1.0.1"
}

将第一行更改为“main”: “app.js”,您将获得对标准 NativeScript 点击挑战的全新体验(我不会破坏惊喜)

tap-challenge-vue


为什么选择 NativeScript 与 Vue?

为了回答这个问题,我询问了 Igor 创作它的灵感来源。他告诉我,他之前几次听说过 NativeScript,但从未真正深入研究过,认为它是一种编译成 JavaScript 的某种脚本语言。当他开始学习渐进式 Web 应用程序(目前一个热门话题)时,他决定尝试构建一个。由于他大学提供的日程安排应用程序性能缓慢,他感到沮丧,于是决定构建一个简约的 Web 应用程序,以便他能够快速轻松地查看课程表,即使是在脱机状态下。他大约在 10 个小时内完成了工作,对结果感到满意,但仍然感觉不太对劲。特别是启动时间感觉很慢:

time-table

由于他喜欢修改代码,因此他决定尝试制作一个真正的原生应用程序。Igor 听说过 Weex,这是一个构建在 Vue 之上的原生应用程序平台,但很快遇到了 Weex 文档不完善的问题。事实上,他完全不知道如何使用 Weex,此外该项目也已经有一段时间没有活动了。进一步的研究使他找到了这个 Github 问题,引起了他的兴趣。他观看了一些介绍视频,所有这些都得出了 NativeScript 非常易于上手的结论。大约 5 分钟后,他的手机上运行了一个简单的纯原生 NativeScript 应用程序。

Igor 对此进展感到兴奋,于是决定尝试几个不同的 Hello World 示例,包括 Angular 示例。“我不是 Angular 的狂热粉丝,我完全偏向于 Vue……无论如何,我对 NativeScript 中的 Vue 感到兴奋,这两者似乎是天作之合,而且我惊讶地发现没有人尝试过将两者集成在一起。”查看 Github 问题,很明显很多人希望看到它实现。很快,他开始探索 Vue 的内部结构,并得出结论,将其与 NativeScript 集成应该不会太难,因为 Vue 不依赖于浏览器。起初,他不知道从哪里开始,但通过修改少量代码,他设法让一个原生标签通过 Vue 进行渲染。“这太令人兴奋了,我甚至在 Twitter 上发布了它,看到大家的兴趣真是太棒了。”


“我倾向于对事情过于兴奋,然后因为脑海中不断涌现的想法而难以入睡。这次也不例外;我相信我在最初的 3-4 天里总共只睡了大约 8 个小时。越来越多的东西开始工作,而且付出的努力很少,这让我更加兴奋。”Igor 感谢 Evan You(Vue.js 的创建者)提供了卓越且可扩展的架构,也感谢 Weex 为使用 Vue 进行移动应用程序开发提供了一种路线图。通过复制粘贴一些代码片段,他逐渐了解了事情应该如何进行,并开始组织代码。“我不得不深入研究我从未见过的 Vue 内部结构。使用框架与理解框架的内部结构有很大不同。老实说,它仍然有点神秘,但我对它的工作方式有了有益的见解。”

由于 Igor 的考试日程安排,该项目的进展现在变得缓慢了一些,但请放心,一旦考试结束,他将再次投入大量精力!“我也希望有其他人加入进来,他们和我一样兴奋。”


给我看看代码!

Vue/NativeScript 模板可以分解成几个部分。一个简单的示例如下所示:


const Vue = require('nativescript-vue/dist/index')
new Vue({
    data: {
        test: 'testing',
        test2: 50,
        test3: 30
    },
    template: `
        
            <stack-layout>
                <button @tap="onTap">whatever
                <text-field v-model="test"></text-field>
                <slider v-model.number="test2">
                <slider v-model.number="test3" minvalue="-10" maxvalue="50" style="margin-top: 15;">
                
                
                
                
            </stack-layout>
        
    `,
    methods: {
        onTap() {
            this.test = 'changed'
            this.test2 = 42
        }
    }
}).$start()

首先在第一行将 Vue 包含到应用程序项目中,其中通过 require 导入 Vue 库。然后应用程序的页面表示为新的 Vue({}) 组件。

在一个组件中,我们通常包含数据、模板和方法。数据可能包含必须在它们可以在模板中使用之前设置的一组属性。在上面的 Reddit 示例中,设置了在弹出窗口中使用的默认 subreddit

data: {
  subreddit: '/r/funny'
},

模板包含构建 NativeScript 表示层所需的标记。标记与 Angular 非常相似,但有一些区别:事件使用 @ 设计:<button @tap="onTap">whatever</button>,但绑定包含熟悉的波浪括号。

方法包含页面方法的标准语法:


methods: {
        onTap() {
            this.test = 'changed'
            this.test2 = 42
        }
    }


在一个 .js 文件中,您可以构建多个页面。通过在我的 Firebase 实例上使用简单的 REST API 调用,我能够构建我的 QuickNoms 食谱应用程序的大部分内容,所有内容都重新使用 Vue 完成了:


const Vue = require('nativescript-vue/dist/index')
const VueRouter = require('vue-router')
Vue.use(VueRouter)
global.process = {env: {}} // hack! a build process should replace process.env's with static strings.
const http = require("http")
const SegmentedBarItem = require('tns-core-modules/ui/segmented-bar').SegmentedBarItem
Vue.prototype.$http = http
const Recipe = {
    data: function(){
        return {
            recipe: []
        }
    },
    created() {
        var id = this.$route.params.id
        this.fetchOneRecipe(id)
        var firstItem = new SegmentedBarItem();
        var secondItem = new SegmentedBarItem();
        var thirdItem = new SegmentedBarItem();
        firstItem.title = "Ingredients";
        secondItem.title = "Tools";
        thirdItem.title = "Procedure";
        this.recipeSteps = [ firstItem, secondItem, thirdItem ];    
    },
    template: `
    <stack-layout>
    <img :src="recipe.image" height="25%" stretch="aspectFill">            
        <stack-layout class="innerCard">
               
            <segmented-bar class="bar" bordercolor="#8AC215" :items="recipeSteps" selectedbackgroundcolor="#8AC215" #sb="" selectedindex="0" @selectedindexchange="changeTab(sb)"></segmented-bar>
            <stack-layout verticalalignment="top">
                <scroll-view height="75%" verticalalignment="top">
                    
                </scroll-view>
                <stack-layout height="25%" verticalalignment="center">
                </stack-layout>
            </stack-layout>
        </stack-layout>
    </stack-layout>
    `,
    methods: {
        fetchOneRecipe(id){
            this.$http.getJSON(`https://quicknoms-91e39.firebaseio.com/Recipes.json?orderBy="$key"&equalTo="${id}"`).then((res) => {
                this.recipe = res;
                for( var key in res) {
                    this.recipe.name = res[key].Name
                    this.recipe.image = res[key].Image
                    this.recipe.notes = res[key].Notes
                    this.recipe.procedure = res[key].Method
                }
                console.log(JSON.stringify(this.recipe))                
            }).catch((err) => {
                console.log('err..' + err)
            })
        },
        changeTab(id){
         switch (id) {
            case 0:
                this.procedure = this.ingredients; 
                break;
            case 1:
                this.procedure = this.tools; 
                break;
            case 2:
                this.procedure = this.method; 
                break;            
         }
    }
    }
}
const Recipes = {
    data: function(){
        return {
            recipes: []
        }
    },
    created() {
        this.fetchRecipes()
    },
    template: `
            <scroll-view class="green">
                <wrap-layout horizontalalignment="center">      
                    <stack-layout style="margin-left: 10" class="card" width="45%" v-for="(recipe, i) in recipes" key="i">
                        <stack-layout horizontalalignment="center" @tap="$router.push({ name:'recipe',params: {id: recipe.id} })">
                            <img :src="recipe.image">          
                            
                        </stack-layout>
                    </stack-layout>           
                </wrap-layout>
            </scroll-view>
    `
    ,
    methods: {
        
        fetchRecipes() {
            this.$http.getJSON(`https://quicknoms-91e39.firebaseio.com/Recipes.json`).then((res) => {
                for( var key in res) {
                    this.recipes.unshift({id : key, name: res[key].Name, image: res[key].Image})                    
                } 
            }).catch((err) => {
                console.log('err..' + err)
            })
        }
    }
}
const router = new VueRouter({
    routes: [
        {path: '/recipes', component: Recipes},
        {path: '/recipe/:id', name: 'recipe', component: Recipe},
        {path: '*', redirect: '/recipes'}
    ]
})
router.replace('/recipes')
new Vue({
    router,
    template: `
        
            <stack-layout>
                <router-view></router-view>
            </stack-layout>
        
    `   
}).$start()

当然,当您超过两个页面时,将所有组件放在一个文件中就会变得很麻烦。将来计划支持将作为单独的 .vue 文件导入到页面中的单文件组件。

结果让我非常高兴!

vuenoms

虽然此项目还处于非常初步的阶段,但我们已经可以感受到使用 Vue.js 构建 NativeScript 应用程序的工作方式。如果您有兴趣提供帮助,请加入我们 Slack,在 Github 上查看项目并加入乐趣!