Weex學習與實踐(一):Weex,你需要知道的事
本文主要介紹包括Weex基本介紹、Weex源碼結構、初始化工程、we代碼結構、Weex的生命周期、Weex的工作原理、頁面間通信、boxmodel & flexbox、weex的缺點
基本介紹
A framework for building Mobile cross-platform UI
怎么解釋它呢?我的理解就是weex = react-native +vue ,使用vue的API風格,兩端的實現方式則和react-native,weex 比rn的優點就是一次編寫三端運行。
IDE: Sublime Text + vue-syntax-highlight
命令行工具:weex-toolkit
調試工具: weex-devtool
weex分為組件component和模塊(module)以及事件
weex-components : weex-components
組件 就是各種標記組件,比如div 、slider、indicator等 通過下面這種方式使用
<div>
<image src="..."></image>
<text>...</text>
</div>
js模塊
let modal = require('@weex-module/modal'); modal.toast({ "message":"我是提示框", "duration":2 });
其他的還有stream,dom,animation之類的
事件
<div onviewappear="viewappear" onviewdisappear="viewdisappear">
...
</div>
Weex源碼結構
package.json
node_modules依賴,更重要的是里面包含了npm run xxx 等快捷命令。比如之前我們運行node.js程序是這樣的:$ node xx.js。這里我們可以把它配置化,例如package.json文件中scripts的 “build:config”: “node build/config.frameworks.js”,其實就是npm run build:config 相當於執行了node build/config.frameworks.js
start文件: 啟動程序文件,里面包換編譯和啟動腳本:
examples: 示例Demo
android/ios/html: 各平台代碼
build:打包各平台的腳本,配置在package.json中。
初始化工程
初始化工程前需要先安裝 homebrew,然后按照下面步驟創建一個工程。
$ brew install node //通過brew安裝node $ npm install -g weex-toolkit //通過node安裝 weex-toolkit $ sudo gem install cocoapods //安裝iOS包管理工具 cocoapods $ weex init //創建項目的文件 $ npm install //依賴安裝 package.json文件 $ npm run dev //項目編譯 $ npm run serve //啟動輕量服務器
這時有可能提示
npm WARN babel-loader@6.2.5 requires a peer of babel-core@^6.0.0 but none was installed.
你需要再
npm install babel-core
這時,打開瀏覽器,輸入http://127.0.0.1:8080, 就會看到這個項目的效果:
參考鏈接:第3篇 初始化工程
npm run dev 干了什么呢?
先看 package.json 文件
{ "name": "demo1", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "webpack", "dev": "webpack --watch", "serve": "serve -p 8080", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "serve": "^1.4.0", "webpack": "^1.13.1", "weex-html5": "0.2.18", "weex-loader": "^0.1.5" } }
npm run dev實際上相當於 webpack –watch
webpack實際上是執行了默認的webpack.config.js配置文件
webpack.config.js 引入webpack和weex-loader,entry屬性是表示入口文件,output表示輸出文件,默認輸出到dist文件夾。
require('webpack') require('weex-loader') var path = require('path') module.exports = { entry: { main: path.join(__dirname, 'src', 'main.we?entry=true') }, output: { path: 'dist', filename: '[name].js' }, module: { loaders: [ { test: /\.we(\?[^?]+)?$/, loaders: ['weex-loader'] } ] } }
不過這個自動產生的webpack.config.js的文件有個坑就是,你添加一個新的we文件,他不會自動build為js文件 可以手動添加 entry: { main: path.join(__dirname, 'src', 'main.we?entry=true'), translate: path.join(__dirname, 'src', 'translate.we?entry=true') }
不過推薦的是自己遍歷所有的we文件
require('webpack')
require('weex-loader')
var path = require('path')
var fs = require('fs');
var entry = {};
function walk(dir, root) {
var directory = path.join(__dirname, root, dir);
fs.readdirSync(directory)
.forEach(function(file) {
var fullpath = path.join(directory, file);
var stat = fs.statSync(fullpath);
var extname = path.extname(fullpath);
if (stat.isFile() &&
(extname === '.we')) {
var name = path.join(root, 'build', dir, path.basename(file, extname));
entry[name] = fullpath + '?entry=true';
} else if (stat.isDirectory() &&
file !== 'build') {
var subdir = path.join(dir, file);
walk(subdir, root);
}
});
}
walk('./', 'src');
module.exports = {
entry: entry,
output: {
path: '.',
filename: '[name].js'
},
module: {
loaders: [
{
test: /\.we(\?[^?]+)?$/,
loaders: ['weex-loader']
}
]
}
}
duqian291902259 - webpack.config.js 第3篇 初始化工程
入口文件index.html
這里可以參考 Integrate Weex HTML5 to your project
we代碼結構
template內必須包含唯一的根節點作為父容器, div就是一個很好的選擇,里面則是一些Native Components
style 支持盒子模型和Flexbox
weex內置了響應式的支持,頁面的寬度是以750來做為標准,自動適配所有手機;
<template> <div> <div>子組件</div> <div>子組件</div> </div> </template> <style> </style> <script> module.exports = { data: function () { return { x: 1, y: 2 } } methods: { foo: function () { console.log('foo') } }, computed: { z: function () { return this.x + this.y } }, events: { custom: function (e) { console.log(e) } }, init: function () {}, created: function () {}, ready: function () {} } </script>
script里面包含很多ViewModel Options,
data methods computed init, created, ready events
如果需要在模板里實現更多的邏輯判斷,你可以使用’computed property’.
created是生命周期函數,這個時候模板還沒有被渲染,常用來在這里定義數據的更新和獲取;
ready是生命周期函數,這個時候模板被渲染,常用來做一些自己上報等;
weex- references - Weex Cheat Sheet
顯然we文件的這些代碼是不會被 native app 識別的,我們要想辦法讓這些代碼可運行。所以我們同時做了三件事:
1.在本地用一個叫做 transformer 的工具把這套代碼轉成純 JavaScript 代碼
2.在客戶端運行一個 JavaScript 引擎,隨時接收 JavaScript 代碼
3.在客戶端設計一套 JS Bridge,讓 native 代碼可以和 JavaScript 引擎相互通信
所以緊接着第二步,就是用 transformer 對代碼進行轉換,變成客戶端可運行的 JavaScript 代碼
原圖:本地開發時的 Weex Transformer 工作原理
在 transformer 中,我們主要的工作就是對 HTML、CSS、JavaScript 代碼進行解析和重組。這里我們用到了三個非常重要的庫:
HTML 解析工具:htmlparser
CSS 解析工具:cssom
JavaScript 解析工具:uglify-js
Weex的生命周期
<script> module.exports = { data: {}, methods: {}, init: function () { console.log('在初始化內部變量,並且添加了事件功能后被觸發'); }, created: function () { console.log('完成數據綁定之后,模板編譯之前被觸發'); }, ready: function () { console.log('模板已經編譯並且生成了 Virtual DOM 之后被觸發'); }, destroyed: function () { console.log('在頁面被銷毀時調用'); } } </script>
init內一般用於初始化一些內部變量,綁定一些自定義事件,這時還沒有數據綁定,沒有創建vdom,所以不能通過this獲取到data和methods,也不能獲取vdom的節點
created 完成了數據綁定 ,但還未開始編譯模板,可以通過this獲取data和methods,但不能獲取vdom的節點
ready表示渲染完成 ,從子組件往上觸發
destroyed 組件銷毀,比如頁面跳轉,從子組件開始往上觸發
Weex的工作原理
頁面間通信
頁面跳轉是通過指定下一個頁面的url,然后通過openurl或者push的方式來跳轉
獲取url的方式可以通過下面這段JS代碼
function getAppBaseUrl(self) {
var dir ='examples'
var url = self.$getConfig().bundleUrl;
var bundleUrl = url;
bundleUrl = new String(bundleUrl);
var nativeBase;
var isAndroidAssets = bundleUrl.indexOf('file://assets/') >= 0;
var isiOSAssets = bundleUrl.indexOf('file:///') >= 0;
if (isAndroidAssets) {
nativeBase = 'file://assets/';
}
else if (isiOSAssets) {
nativeBase = bundleUrl.substring(0, bundleUrl.lastIndexOf('/') + 1);
}
else {
var host = 'localhost:12580';
var matches = /\/\/([^\/]+?)\//.exec(self.$getConfig().bundleUrl);
if (matches && matches.length >= 2) {
host = matches[1];
}
nativeBase = 'http://' + host + '/' + dir + '/build/';
}
var h5Base = './index.html?page=./' + dir + '/build/';
//Native端
var base = nativeBase;
//H5端
if (typeof window === 'object') {
base = h5Base;
}
return base
}
頁面通信有兩種方式
1.通過 url 參數傳遞。
/**
* 獲取URL參數
*/
getUrlParam: function (key) {
var t = this.$getConfig().bundleUrl;
var reg = new RegExp('[?|&]' + key + '=([^&]+)');
var match = t.match(reg);
return match && match[1];
}
2.通過 localStorage 數據存儲。
如果是組件間通信不是頁面通信,則參考:組件之間通信 - (Communicate Between Components)
boxmodel & flexbox
weex支持boxmodel 和flexbox
下面這個是boxmodel
關於flexbox,可以看我的這篇文章react-native的第一課 - flexbox布局
weex的缺點
1.Weex將整個app的寬度定死在750px,然后其他都是根據scale進行計算的,會導致適配不方便。
2.目前不支持iOS的presentViewController方法
3.很多組件和模塊需要自己擴展(比如datepicker,iconfont,攝像頭,二維碼等)
幾個小問題
1.之前weex是只支持es5,現在可以支持es6了。精華 新版weex-loader@0.3.0-alpha,歡迎試用
2.怎么斷點調試?
目前是可以斷點調試的,可以參考下面文章
3.weex支持本地圖片嗎?
根據官方答疑是可以的,但是我目前還沒有嘗試成功。
4.weex-x的使用?
5.promise怎么使用?
6.熱更新方案是什么?
最后,希望有越來越多的人把weex用起來。
轉載請注原文鏈接:http://coderyi.com/posts/weex1/
Weex學習與實踐(二):iOS集成的tips
本文主要介紹包括iOS上集成Weex、iOS上擴展組件、iOS上擴展module
iOS上集成Weex
集成weex,需要WeexSDK、WXDevtool兩個庫以及阿里未開源的ATSDK-Weex。
目前官方的alibaba/Weex倉庫里面
pod 'WeexSDK', :path=>'../sdk/'
pod 'WXDevtool', :path=>'../WXDevtool/'
pod 'ATSDK-Weex', '0.0.1'
它們直接使用的weex倉庫的sdk,但是weex主倉庫的WXDevtool已經不維護了,需要替換成,weexteam/weex-devtool-iOS的代碼。
另外也可以直接從cocoapods的源pod倉庫,但是cocoapods的源都是打包成framwork,很多文件並沒有設置為public,所以導致很多頭文件沒有暴露出來。
當然你也可以不用通過cocoapods集成,直接把代碼拉進工程就可以,如果發生 Unknown type name 'NSString'
你可能需要把layout.c文件右側的type改為Objective-C Source,或者直接修改為layout.m。
基本上JS頁面是在WXDemoViewController工作的,你可能需要接收頁面刷新的通知,以支持實時刷新[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationRefreshInstance:) name:@"RefreshInstance" object:nil];
頁面會維護一個WXSDKInstance實例,WXSDKInstance就是weex渲染的實例對象,提供了很多頁面渲染相關的接口,比如renderWithURL、refreshInstance、destroyInstance等
weex SDK 集成到工程 (integrate to ios)
iOS上擴展組件
目前官方iOS這一塊組件的代碼在WeexSDK的component里面,組件有限,只有image,list,scroller等,如果想要實現自己的組件,首先需要繼承WXComponent類。
然后實現方法
- (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance
{}
注意這個方法不在主線程,這里面接收一些js傳過來的參數,以在js端寫的image標記為例子
<image style="width: 100;height: 100;margin-top:20;margin-right:20;margin-left:220" src=></image>
到objc端就是
ref:701
type:image
styles:
{
height = 100;
marginLeft = 220;
marginRight = 20;
marginTop = 20;
width = 100;
}
attributes:
{
src = "https://avatars.githubusercontent.com/u/9892522?v=3";
}
ref(結點的唯一標識符)
然后在loadView的時候可能需要返回自己的objc組件
- (UIView *)loadView
{
return [[WXImageView alloc] init];
}
然后你可以通過復寫addEvent方法來增加一個change(UIControlEventValueChanged)、click(UIControlEventTouchUpInside)等事件
如果是image組件的話,你可能需要接收圖片地址,這個時候需要通過實現了WXImgLoaderProtocol的WXImgLoaderDefaultImpl來處理,WXImgLoaderDefaultImpl實現了downloadImageWithURL方法,這里面通過SDWebImage來下載一張圖片。
iOS上擴展module
這一塊的代碼在module分組里面,包括網絡庫stream,持久化storage等,你可以擴展自己module。
需要做的是實現WXModuleProtocol協議,並且寫自己的方法就可以了,這里需要通過weex的宏把需要public的方法導出
WX_EXPORT_METHOD(@selector(fetch:callback:progressCallback:))
在module中目前是沒有view的,但是你可以通過由js傳過來的ref值拿到
WXComponent *targetComponent = [self.weexInstance componentForRef:nodeRef];
CALayer *layer = targetComponent.layer;
UIView *view = targetComponent.view;
注意點
1.如果設置js文件在bundle中載入的話,需要把build的js文件拖入工程,如果的你的we里面一開始就調用了js文件,由於只會buildwe文件,所以還需要把之前的js文件拖入工程,另外examples里面判斷iOSAssets是這樣的
var isiOSAssets = bundleUrl.indexOf('file:///') >= 0 && bundleUrl.indexOf('WeexDemo.app') > 0;
你如果拖入自己的工程需要把WeexDemo.app的判斷去掉
轉載請注原文鏈接:http://coderyi.com/posts/weex2/
3、Weex學習與實踐(三):iOS原理篇
本文主要介紹包括WeexSDK-iOS主要類介紹、Weex頁面iOS端渲染流程、JS調用iOS方法
主要類
WXSDKEngine
WXSDKEngine主要用於初始化WeexSDK的環境
一開始會載入配置文件main.js並且注冊一些默認的組件、模塊以及handler
+ (void)initSDKEnviroment:(NSString *)script { [self _registerDefaultComponents]; [self _registerDefaultModules]; [self _registerDefaultHandlers]; [[WXSDKManager bridgeMgr] executeJsFramework:script]; }
WXSDKInstance
一個WXSDKInstance就對應一個UIViewController,對應一個weex頁面。
主要用來渲染頁面,一般通過renderWithURL方法,然后能夠接收一些回調和一些視圖相關的方法
onCreate //根視圖rootView創建的時候
renderFinish//視圖渲染完成
componentForRef //通過視圖索引拿到對應的組件視圖
WXBridgeManager
WXBridgeManager 是JS與iOS通過JSCore交互的類,相關的類還有WXBridgeContext、WXJSCoreBridge。
比如調用JS
- (void)executeJsMethod:(WXBridgeMethod *)method
{
if (!method) return;
__weak typeof(self) weakSelf = self;
WXPerformBlockOnBridgeThread(^(){
[weakSelf.bridgeCtx executeJsMethod:method];
});
}
JS調用native的話需要通過WXJSCoreBridge的registerCallNative方法
WXComponent
組件基類,自己實現iOS端的組件需要繼承它。相關的還有負責組件初始化的工廠類WXComponentFactory,以及WXComponentManager
WXModuleProtocol
自定義module需要實現的協議
Weex頁面iOS端渲染流程
首先在ViewController里的render放初始化WXSDKInstance,因為render會支持實時刷新,所以每次都需要先銷毀這個實例。
[_instance destroyInstance];
_instance = [[WXSDKInstance alloc] init];
然后WXSDKManager會保存instanceId
[WXSDKManager storeInstance:self forID:_instanceId];
然后會調用renderWithURL方法來載入script,在這里會判斷是本地文件還是需要從服務器下載,
- (void)renderWithURL:(NSURL *)url options:(NSDictionary *)options data:(id)data{
if ([url isFileURL]) {
//from local
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *path = [url path];
NSData *scriptData = [[NSFileManager defaultManager] contentsAtPath:path];
NSString *script = [[NSString alloc] initWithData:scriptData encoding:NSUTF8StringEncoding];
[weakSelf renderView:script options:newOptions data:data];
});
}else{
//from server
}
}
然后就會根據script文件渲染視圖
[weakSelf renderView:script options:newOptions data:data];
在這個方法里面首先會創建根視圖,當創建完成時WXSDKInstance會收到onCreate的回調
//TODO WXRootView
WXPerformBlockOnMainThread(^{
self.rootView = [[WXView alloc] initWithFrame:self.frame];
if(self.onCreate) {
self.onCreate(self.rootView);
}
});
之后再通過bridge調用JS方法來開始創建實例
[self callJSMethod:@"createInstance" args:args];
然后這里會判斷JSFramework也就是main.js有沒有加載完成,然后再通過WXJSBridge的JSContext來執行js方法
- (void)callJSMethod:(NSString *)method args:(NSArray *)args
{
[[_jsContext globalObject] invokeMethod:method withArguments:args];
}
最后main.js會調用WXSDKInstance的createFinish方法來結束頁面的渲染
JS調用iOS方法
首先要注冊一個組件
[self registerModule:@"dom" withClass:NSClassFromString(@"WXDomModule")];
注冊module的時候 會通過下面方法
+ (void)registerModule:(NSString *)name withClass:(Class)clazz
{
WXAssert(name && clazz, @"Fail to register the module, please check if the parameters are correct !");
NSString *moduleName = [WXModuleFactory registerModule:name withClass:clazz];
NSDictionary *dict = [WXModuleFactory moduleMethodMapsWithName:moduleName];
[[WXSDKManager bridgeMgr] registerModules:dict];
}
把所有通過宏注冊的方法發送給js端
WX_EXPORT_METHOD(@selector(createBody:))
這會把方法暴露出來,並且方法名字是”wx_export_method_“加代碼所在行號,wx_export_method_25
組件、模塊 是給js端用的,而handler則是給objc自己用的,所以不用發送消息給js端
然后通過methodForSelector拿到WX_EXPORT_METHOD方法的返回值,並且保存到methods中
- (void)registerModuleMethods {
if ([currentClass respondsToSelector:selector]) {
method = ((NSString* (*)(id, SEL))[currentClass methodForSelector:selector])(currentClass, selector);
}
[_methods setObject:method forKey:name];
}
然后拿到WXModuleConfig組成的_moduleMap之后再發送給JS端
[[WXSDKManager bridgeMgr] registerModules:dict];
最后需要自己callNative的回調,當JS調用時就會傳值到這里
- (void)registerCallNative:(WXJSCallNative)callNative
{
NSInteger (^callNativeBlock)(JSValue *, JSValue *, JSValue *) = ^(JSValue *instance, JSValue *tasks, JSValue *callback){
NSString *instanceId = [instance toString];
NSArray *tasksArray = [tasks toArray];
NSString *callbackId = [callback toString];
return callNative(instanceId, tasksArray, callbackId);
};
_jsContext[@"callNative"] = callNativeBlock;
}
tasks里面包括方法的一些相關信息,包括module(比如dom),method(比如updateFinish),args
weex-devtool-iOS
weex-devtool-iOS 其實是 PonyDebugger的衍生品。
轉載請注原文鏈接:http://coderyi.com/posts/weex3/