上一节Vue在非浏览器环境下的尝试我们利用了weex在vue中的dom实现成功的在非浏览器环境中Vue的实例,接下来我们将Vue集成到iOS当中,利用JavaScriptCore来实现界面的布局与动态数据绑定。
「一、搭建基于vue的weex编译产物环境」创建一个vue的工程
vue create ios-vue-demo
利用vue cli脚手架新建一个简单的实例工程,在原来的实例工程上小小改造一下,简化一下布局的元素
<template>
<div class="app">
<div class="box"></div>
<div>{{ msg }}</div>
<button @click="click">事件按钮</button>
</div>
</template>
<script>
export default {
name: "App",
components: {},
data: function() {
return {
msg: "iOS集成Vue测试工程",
};
},
methods: {
click: function() {
this.msg = "iOS集成Vue测试工程,我被点击了一下";
},
},
};
</script>
<style>
html,
body {
height: 100%;
}
.app {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.box {
width: 100px;
height: 100px;
background-color: red;
margin-bottom: 20px;
}
</style>
将界面的代码简化改成这样,运行的效果如下:

接下来,利用webpack替换掉原来的npm run serve执行的命令
const path = require("path");
const VueLoaderPlugin = require("vue-loader/lib/plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "web.bundle.js",
},
devServer: {
contentBase: path.join(__dirname, "dist"),
compress: true,
port: 9000,
},
mode: "development",
module: {
rules: [
{
test: /.(png|jpg|gif)$/,
use: [
{
loader: "file-loader",
options: {
name: "[name].[ext]",
pulbicPath: "./dist/asset/images",
outputPath: "./asset/images",
},
},
],
},
{
test: /.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /.vue$/,
use: "vue-loader",
},
],
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: "./public/index.html",
filename: "index.html",
}),
],
};
在package.json中增加一个命令
"start:dev": "webpack-dev-server --config webpack.config.web.js --open"
执行
npm run start:dev
可以达到与之前执行vue脚手架提供的npm run serve
一样的效果了。
通过以上的webpack配置,启动一个web server来监听代码,通过这个配置可以在开发阶段更好的在浏览器中调试界面。
使用webpack可以为接下来打包基于weex的vue产物做准备。
「二、利用weex-loader来打包vue」
首先改造一下entry main.wx.js
/* eslint-disable no-undef */
import App from "./App.vue";
App.el = "#app";
new Vue(App);
然后在webpack.config.web.js的基础上稍稍改动一下,生成新的配置文件webpack.config.wx.js
代码如下:
const path = require("path");
const webpack = require("webpack");
module.exports = {
entry: "./src/main.wx.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "wx.bundle.js",
},
mode: "development",
module: {
rules: [
{
test: /.(png|jpg|gif)$/,
use: [
{
loader: "file-loader",
options: {
name: "[name].[ext]",
pulbicPath: "./dist/asset/images",
outputPath: "./asset/images",
},
},
],
},
{
test: /.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /.vue(?[^?]+)?$/,
use: [
{
loader: "weex-loader",
},
],
},
],
},
plugins: [
new webpack.BannerPlugin({
banner: '// { "framework": "Vue"} n',
raw: true,
exclude: "Vue",
}),
],
};
在package.json
中添加一个新的命令,用于建构产物:
"build:wx": "npx webpack --config webpack.config.wx.js"
「三、将weex-js-framework与wx.bundle.js集成到iOS环境中」
新建一个工程,简化一下并js-framework.js
与wx.bundle.js
,结构如下:
新建的WXJSContext用来封装所有的JS的操作逻辑,这里只是一个Demo,所以把所有的逻辑全部放在WXJSContext中完成。
初始化console.log函数回调
- (void)_setupConsoleLog {
_context[@"console"][@"log"] = ^(NSString *message) {
NSLog(@"Javascript log: %@",message);
};
}
加载js-framework.js
- (void)_setupWxJSFramework {
NSString *path = [[NSBundle mainBundle] pathForResource:@"js-framework" ofType:@"js"];
NSError *error;
NSString *code = [[NSString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
[_context evaluateScript:code];
}
hook js-framework
中的callNative方法,当加载模板代码创建Vue创建节点,添加节点,更新节点的布局属性与显示属性时就会回调
- (void)_hookCallNative {
_context[@"callNative"] = ^(JSValue *a, JSValue *b) {
if([a isString] && [b isObject]) {
NSString *instanceId = [a toString];
NSData *data = [NSJSONSerialization dataWithJSONObject:[b toDictionary] options:NSJSONWritingFragmentsAllowed error:NULL];
NSString *info = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"实例id: %@, 数据: %@", instanceId, info);
}
};
}
准备好执行wx.bundle.js
所需要的环境
1.加载完js-framework后就会在global object上挂载一系列的方法
2.通过调用global object的createInstanceContext,传递参数为实例的标识符,这里是”1″
3.创建好p实例上下文后,通过这个实例可以拿到Vue的具体定义
4.将Vue的定义挂载到global object上就可以成加载模板代码时,使用到Vue时找到
- (void)_prepareVueEnvironment {
/// 加载完js-framework后就会在global object上挂载一系列的方法
/// 通过调用global object的createInstanceContext,传递参数为实例的标识符,这里是"1"
/// 创建好p实例上下文后,通过这个实例可以拿到Vue的具体定义
/// 将Vue的定义挂载到global object上就可以成加载模板代码时,使用到Vue时找到
JSGlobalContextRef globalContextRef = _context.JSGlobalContextRef;
JSObjectRef globalObjectRef = JSContextGetGlobalObject(globalContextRef);
JSValue *v = [[_context globalObject] invokeMethod:@"createInstanceContext" withArguments:@[@"1"]];
NSArray *keys = ExtractPropertyNamesFromJSValue(v);
for(NSString *key in keys) {
if([key isEqualToString:@"Vue"]) {
/// 获取全局对象globalObject的prototype(原型链)
JSValueRef valueRef = JSObjectGetPrototype(globalContextRef, globalObjectRef);
/// 将Vue的值转化成一个Vue对象
JSObjectRef objectRef = JSValueToObject(globalContextRef, [v valueForProperty:key].JSValueRef, NULL);
/// 在Vue的原型链上增加globalObject
JSObjectSetPrototype(globalContextRef, objectRef, valueRef);
}
JSStringRef propertyName = JSStringCreateWithUTF8CString([key cStringUsingEncoding:NSUTF8StringEncoding]);
JSObjectSetProperty(globalContextRef, globalObjectRef, propertyName, [v valueForProperty:key].JSValueRef, 0, NULL);
}
}
/// 提取JSValue中的属性名称
NS_INLINE NSArray *ExtractPropertyNamesFromJSValue(JSValue *jsValue) {
NSMutableArray *keys = NULL;
JSContextRef contextRef = jsValue.context.JSGlobalContextRef;
JSValueRef exception = NULL;
JSObjectRef jsObjectRef = JSValueToObject(contextRef, jsValue.JSValueRef, &exception);
if(exception != NULL) {
NSLog(@"JSValueToObject Exception");
return @[];
}
BOOL somethingWrong = NO;
if (jsObjectRef != NULL) {
JSPropertyNameArrayRef allKeyRefs = JSObjectCopyPropertyNames(contextRef, jsObjectRef);
size_t keyCount = JSPropertyNameArrayGetCount(allKeyRefs);
keys = [[NSMutableArray alloc] initWithCapacity:keyCount];
for (size_t i = 0; i < keyCount; i ++) {
JSStringRef nameRef = JSPropertyNameArrayGetNameAtIndex(allKeyRefs, i);
size_t len = JSStringGetMaximumUTF8CStringSize(nameRef);
if (len > 1024) {
somethingWrong = YES;
break;
}
char* buf = (char*)malloc(len + 5);
if (buf == NULL) {
somethingWrong = YES;
break;
}
bzero(buf, len + 5);
if (JSStringGetUTF8CString(nameRef, buf, len + 5) > 0) {
NSString* keyString = [NSString stringWithUTF8String:buf];
if ([keyString length] == 0) {
somethingWrong = YES;
free(buf);
break;
}
[keys addObject:keyString];
}
else {
somethingWrong = YES;
free(buf);
break;
}
free(buf);
}
JSPropertyNameArrayRelease(allKeyRefs);
} else {
somethingWrong = YES;
}
if (somethingWrong) {
// may contain retain-cycle.
keys = (NSMutableArray*)[[jsValue toDictionary] allKeys];
}
return keys;
}
加载wx.bundle.js
模板代码
- (void)_loadBundleJSCode {
NSString *codePath = [[NSBundle mainBundle] pathForResource:@"wx.bundle" ofType:@"js"];
NSString *code = [[NSString alloc] initWithContentsOfFile:codePath encoding:NSUTF8StringEncoding error:NULL];
[_context evaluateScript:code];
}
运行一下看到结果如下:
日志数据如下,通过不同的回调钩子,可以完成节点树的创建与更新。
Vue
已经帮我们完成了数据的动态绑定,更新节点的patch.我们只需要利用好这个回调方法,将Vue
的节点同步到原生层面,就可以完成渲染了。
实例id: 1, 数据: {"0":{"module":"dom","method":"createBody","args":[{"attr":{"@styleScope":"data-v-7ba5bd90"},"style":{"flexDirection":"column","justifyContent":"center","display":"flex","alignItems":"center"},"type":"div","ref":"_root"}]}}
实例id: 1, 数据: {"0":{"module":"dom","method":"addElement","args":["_root",{"attr":{"@styleScope":"data-v-7ba5bd90"},"style":{"marginBottom":"20","backgroundColor":"#FF0000","width":"100","height":"100"},"type":"div","ref":"8"},-1]}}
实例id: 1, 数据: {"0":{"module":"dom","method":"addElement","args":["_root",{"attr":{"@styleScope":"data-v-7ba5bd90"},"style":{},"type":"div","ref":"10"},-1]}}
实例id: 1, 数据: {"0":{"module":"dom","method":"addElement","args":["10",{"attr":{"value":"iOS集成Vue测试工程"},"style":{},"type":"text","ref":"12"},-1]}}
实例id: 1, 数据: {"0":{"module":"dom","method":"addElement","args":["_root",{"attr":{"@styleScope":"data-v-7ba5bd90"},"style":{},"type":"button","event":["click"],"ref":"14"},-1]}}
实例id: 1, 数据: {"0":{"module":"dom","method":"addElement","args":["14",{"attr":{"value":"事件按钮"},"style":{},"type":"text","ref":"16"},-1]}}
实例id: 1, 数据: {"0":{"module":"dom","method":"createFinish","args":[]}}
通过hook callNative
这个方法我们可以在本地建立一个Node Tree,将Node Tree交给Yoga进行Flex布局,最终渲染出这个Node Tree.
这一小节就到此,下一节,我们来具体实现这个渲染的过程并完成事件的传递。
原文始发于微信公众号(程序猿搬砖):在iOS中集成Vue
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/71968.html