在跨端开发面面谈系列中,基于WebView的Hybrid开发模式一文总结了该模式的发展路线和优化方式,并介绍了Ionic框架的应用。本文继续该系列,探讨对NativeScript开发框架的理解。由于未在生产环境的复杂应用中深入使用,文中观点可能不够准确,更多是结合个人经验和文章阅读的初步理解。
从WebView到Native
基于WebView的混合开发模式广泛应用,开发者进行了各种优化以提升体验、抹平差异,但Web App运行在WebView容器中,与Native App存在天然性能差距。因此出现了新思路:保持Web技术栈的开发体验,内置运行时环境,通过映射使最终用户操作原生视图,提供接近原生体验的高性能应用。
NativeScript是首个实现这一思路的框架,开发者使用Web技术栈开发,构建出的App均为原生视图,从理论上打破了WebView性能天花板,跨端开发技术从WebView迈向了Native侧。
NativeScript Hello World
NativeScript支持多种前端技术栈,最初采用MVVM模式,以原生JavaScript业务逻辑、XML视图模板和CSS样式开发,后来引入对Angular 2的支持并持续跟进迭代,当前社区也提供了Vue开发支持。
不引入其他前端框架时,demo结构包括以下组件。XML模板示例如下:
<Page loaded="loaded">
<Page.actionBar>
<ActionBar title="Sign in"></ActionBar>
</Page.actionBar>
<StackLayout orientation="vertical">
<Image src="~/images/logo.png" stretch="none" horizontalAlignment="center" ></Image>
<TextField hint="Email Address" id="email" text="{{ email }}" keyboardType="email" autocorrect="false" autocapitalizationType="none"/>
<TextField hint="Password" text="{{password}}" secure="true"/>
<Button text="Sign in" tap="signIn"/>
<Button text="Sign up for Groceries" class="link" tap="register"/>
</StackLayout>
</Page>
JavaScript模型及视图模型为:
//login.js
var frameModule = require('ui/frame');
var UserViewModel = require('../../shared/view-models/user-view-model')
var dialogsModule = require('ui/dialogs');
var user = new UserViewModel()
var page;
var email;
exports.loaded=function (args) {
page=args.object;
page.bindingContext = user;
}
exports.signIn = function () {
user.login()
.catch(function (error) {
dialogsModule.alert({
message:'Unfortunately we could not find your account',
okButtonText:'OK'
})
})
.then(function () {
frameModule.topmost().navigate("views/list/list")
})
}
exports.register=function () {
var topmost = frameModule.topmost();
topmost.navigate('views/register/register');
}
//user-view-model.js
var config = require("../../shared/config");
var fetchModule = require("fetch");
var observableModule = require("data/observable");
function User(info) {
info = info || {};
var viewModel = new observableModule.fromObject({
email: info.email || "",
password: info.password || ""
});
viewModel.login = function() {
return fetchModule.fetch(config.apiUrl + "user/" + config.appKey + "/login", {
method: "POST",
body: JSON.stringify({
username: viewModel.get("email"),
password: viewModel.get("password")
}),
headers: getCommonHeaders()
})
.then(handleErrors)
.then(function(response) {
return response.json();
})
.then(function(data) {
config.token = data._kmd.authtoken;
});
};
viewModel.register = function() {
return fetchModule.fetch(config.apiUrl + "user/" + config.appKey, {
method: "POST",
body: JSON.stringify({
username: viewModel.get("email"),
email: viewModel.get("email"),
password: viewModel.get("password")
}),
headers: getCommonHeaders()
}).then(handleErrors)
};
return viewModel;
}
function getCommonHeaders() {
return {
"Content-Type": "application/json",
"Authorization": config.appUserHeader
}
}
function handleErrors(response) {
if (!response.ok) {
console.log('error')
console.log(JSON.stringify(response));
throw Error(response.statusText);
}
return response;
}
module.exports = User;
样式文件login.css为:
TextField {
border-width: 5;
border-style: solid;
border-color: #034793;
}
Image {
margin: 10;
}
Button {
margin: 10;
padding: 10;
}
从代码可见,视图模板使用了NativeScript封装的组件系统,包括布局组件和具体视图组件。框架提供了事件绑定和数据绑定处理,在view model与model之间也提供了工具方法,支持异步请求和数据更新。
NativeScript提供对Angular的支持,推荐阅读使用NativeScript和Angular2构建跨平台APP一文,其中使用Angular和NativeScript实现了iOS计算器示例。
NativeScript Inside
NativeScript运行机制核心包括NativeScript Runtimes、Core Modules、CLI和Plugins。深入看其内部实现原理,NativeScript的一大卖点是直接获取全部系统及第三方模块的公共API调用。以Android为例,这是通过反射机制实现。在通过CLI构建应用时,框架会反射获取所有原生公共API,将这些信息以二进制结构作为元数据打进应用包。当JavaScript端调用API时,Runtime根据元数据信息反射调用相应API。
基于NativeScript Runtime获取Native API Access,框架提供了Core Modules抽象层,包含对视图、系统API、布局的统一抽象,其“一次编写,到处运行”的核心基于这一跨平台抽象层封装实现。当已有Module不能满足需求时,NativeScript支持Plugins扩展,可以选择社区第三方plugin或实现自己的plugin。plugin可以是对已有组件的组合包装,也可以是对原生组件的自定义封装。
Bye,NativeScript
初了解NativeScript时感觉“看上去很美”。对于前端开发者,无需切换技术栈,可选用原生JavaScript、Angular或Vue,利用框架组件库和API封装快速开发高性能跨平台应用。但深入了解后,NativeScript主要存在以下三个问题。
首先,“write everything in JavaScript”是伪命题。NativeScript强调始终使用JavaScript,无需学习Java或Object-C。但在实际项目中,当复杂度超过Hello World demo时,很可能遇到个性化特性,框架未实现且社区无现有实现。此时需通过plugin机制扩展,而plugin需要以JavaScript实现原生逻辑。例如,扩展更复杂的ListView时,参考官方实现,在Android和iOS平台需分别用JavaScript编写原生代码。这让前端开发者面临挑战,因为需熟悉原生API;而让客户端开发者用JavaScript实现也增加复杂度。即使在普通业务开发中,也可能出现大段以JavaScript完成原生逻辑的代码,例如在计算器应用中为安卓平台实现文字自适应缩放,需基于Canvas计算宽度,代码混杂在业务中,可读性差。
其次,“run JavaScript on the main UI thread”可能是炸弹。NativeScript将JavaScript运行在UI线程,以去掉异步通信、提升性能,并声称常见异步场景已由框架处理。但如果开发者在代码中编写耗时操作,会直接阻塞UI线程,导致性能问题。相比将JavaScript运行在独立线程的模式,这种设计更易因代码失误引发问题。
最后,Integrating existing native apps支持不足。React Native因热更新和便捷集成能力而火爆,而NativeScript早期是非此即彼的排他性框架。虽然当前版本支持集成进原生应用,但文档不完善,集成复杂度较高,支持刚起步且不成熟。
最后
NativeScript问世已久但未大火。去年React专利风波时曾有推荐,但风潮过后无下文。个人探索后决定从入门到放弃。未在生产环境长期使用,感受可能不准确,但不推荐使用该框架。如果偏爱Angular,可考虑Ionic;偏爱Vue,可了解Weex。跨端开发有多种方案,不必局限于NativeScript的坑。
参考文章
- How NativeScript Works
- The Benefits of NativeScript’s Single Threading Model
- 使用NativeScript和Angular2构建跨平台APP