Open mowatermelon opened 6 years ago
https://developers.arcgis.com/javascript/latest/sample-code/sandbox/index.html?sample=frameworks-bootstrap https://developers.arcgis.com/javascript/latest/sample-code/sandbox/index.html?sample=views-composite-views https://developers.arcgis.com/javascript/latest/sample-code/sandbox/index.html?sample=intro-widgets https://developers.arcgis.com/javascript/latest/sample-code/sandbox/index.html?sample=editing-applyedits https://developers.arcgis.com/javascript/latest/sample-code/intro-popup/index.html
initLoad需要在mounted之前,所以应该是在created和beforeMount周期中进行数据初始化
区分draw类型成功
vue 源码学习
/**
* Define a reactive property on an Object.
*/
function defineReactive (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
if (!getter && arguments.length === 2) {
val = obj[key];
}
var setter = property && property.set;
var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter();
}
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}
在mapView执行hover事件的时候,就会触发beforeUpdate和updated这两个生命周期
https://www.jianshu.com/p/497f1a9ff33f
这是一篇有故事的文章 --- 来自一个weex在生产环境中相爱相杀的小码畜..
虽然weex
的口号是一次撰写 多端运行
, 但其实build
环节是有差异的, native
端构建需要使用weex-loader
, 而web
端则是使用vue-loader
,除此以外还有不少差异点, 所以webpack
需要两套配置.
使用webpack
生成两套bundle
,一套是基于vue-router
的web spa
, 另一套是native
端的多入口的bundlejs
首先假设我们在src/views
下开发了一堆页面
web端的入口文件有render.js
import weexVueRenderer from 'weex-vue-render'
Vue.use(weexVueRenderer)
main.js
import App from './App.vue'
import VueRouter from 'vue-router'
import routes from './routes'
Vue.use(VueRouter)
var router = new VueRouter({
routes
})
/* eslint-disable no-new */
new Vue({
el: '#root',
router,
render: h => h(App)
})
router.push('/')
App.vue
<template>
<transition name="fade" mode="out-in">
<router-view class=".container" />
</transition>
</template>
<script>
export default {
// ...
}
</script>
<style>
// ...
</style>
webpack.prod.conf.js
入口
const webConfig = merge(getConfig('vue'), {
entry: {
app: ['./src/render.js', './src/app.js']
},
output: {
path: path.resolve(distpath, './web'),
filename: 'js/[name].[chunkhash].js',
chunkFilename: 'js/[id].[chunkhash].js'
},
...
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
}
})
native端的打包流程其实就是将src/views
下的每个.vue
文件导出为一个个单独的vue
实例, 写一个node
脚本即可以实现
// build-entry.js
require('shelljs/global')
const path = require('path')
const fs = require('fs-extra')
const srcPath = path.resolve(__dirname, '../src/views') // 每个.vue页面
const entryPath = path.resolve(__dirname, '../entry/') // 存放入口文件的文件夹
const FILE_TYPE = '.vue'
const getEntryFileContent = path => {
return `// 入口文件
import App from '${path}${FILE_TYPE}'
/* eslint-disable no-new */
new Vue({
el: '#root',
render: h => h(App)
})
`
}
// 导出方法
module.exports = _ => {
// 删除原目录
rm('-rf', entryPath)
// 写入每个文件的入口文件
fs.readdirSync(srcPath).forEach(file => {
const fullpath = path.resolve(srcPath, file)
const extname = path.extname(fullpath)
const name = path.basename(file, extname)
if (fs.statSync(fullpath).isFile() && extname === FILE_TYPE) {
//写入vue渲染实例
fs.outputFileSync(path.resolve(entryPath, name + '.js'), getEntryFileContent('../src/views/' + name))
}
})
const entry = {}
// 放入多个entry
fs.readdirSync(entryPath).forEach(file => {
const name = path.basename(file, path.extname(path.resolve(entryPath, file)))
entry[name] = path.resolve(entryPath, name + '.js')
})
return entry
}
webpack.build.conf.js
中生成并打包多入口
const buildEntry = require('./build_entry')
// ..
// weex配置
const weexConfig = merge(getConfig('weex'), {
entry: buildEntry(), // 写入多入口
output: {
path: path.resolve(distPath, './weex'),
filename: 'js/[name].js' // weex环境无需使用hash名字
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'weex-loader'
}
]
}
})
module.exports = [webConfig, weexConfig]
最终效果
在vue
单文件中, 我们可以通过在vue-loader
中配置预处理器, 代码如下
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
scss: 'vue-style-loader!css-loader!sass-loader', // <style lang="scss">
sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax' // <style lang="sass">
}
}
}
而weex
在native环境下其实将css
处理成json
加载到模块中, 所以...
vue-loader
配置的预处理器在web环境下正常显示, 在native
中是无效的import 'index.css'
也是无效的研究weex-loader
源码后发现在.vue
中是无需显示配置loader
的, 只需要指定<style lang="stylus">
并且安装stylus stylus-loader
即可,weex-loader
会根据lang
去寻找对应的loader
.
但因为
scss
使用sass-loader
, 会报出scss-loader not found
, 但因为sass
默认会解析scss
语法, 所以直接设置lang="sass"
是可以写scss
语法的, 但是ide
就没有语法高亮了. 可以使用如下的写法
<style lang="sass">
@import './index.scss'
</style>
语法高亮, 完美!
虽然没有全局样式的概念, 但是支持单独import
样式文件
<style lang="sass">
@import './common.scss'
@import './variables.scss'
// ...
</style>
这方面官方文档已经有比较详细的描述, 但还是有几点值得注意的
weex
中的样式不支持简写, 所有类似margin: 0 0 10px 10px
的都是不支持的
android
下的view是有白色的默认颜色的, 而iOS如果不设置是没有默认颜色的, 这点需要注意
weex
默认使用750px * 1334px
作为适配尺寸, 实际渲染时由于浮点数的误差可能会存在几px
的误差, 出现细线等样式问题, 可以通过加减几个px
来调试
即使使用了预处理器, css
嵌套的写法也是会导致样式失效的
weex
下的页面跳转有三种形式
native -> weex
:weex
页面需要一个控制器作为容器, 此时就是native
间的跳转
weex -> native
: 需要通过module形式通过发送事件到native来实现跳转
weex -> weex
: 使用navigator模块, 假设两个weex
页面分别为a.js, b.js
, 可以定义mixin
方法
function isWeex () {
return process.env.COMPILE_ENV === 'weex' // 需要在webpack中自定义
}
export default {
methods: {
push (path) {
if (isWeex()) {
const toUrl = weex.config.bundleUrl.split('/').slice(0, -1).join('/') + '/' + path + '.js' // 将a.js的绝对地址转为b.js的绝对地址
weex.requireModule('navigator').push({
url: toUrl,
animated: 'true'
})
} else {
this.$router.push(path) // 使用vue-router
}
},
pop () {
if (isWeex()) {
weex.requireModule('navigator').pop({
animated: 'true'
})
} else {
window.history.back()
}
}
}
}
这样就组件里使用this.push(url), this.pop()
来跳转
iOS下页面跳转无需配置, 而android
是需要的, 使用weexpack platform add android
生成的项目是已配置的, 但官方的文档里并没有对于已存在的应用如何接入进行说明
android
中是通过intent-filter
来拦截跳转的<activity
android:name=".WXPageActivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="com.alibaba.weex.protocol.openurl"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="com.taobao.android.intent.category.WEEX"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:scheme="file"/>
</intent-filter>
</activity>
然后我们新建一个WXPageActivity
来代理所有weex
页面的渲染, 核心的代码如下
@Override
protected void onCreate(Bundle saveInstanceState) {
// ...
Uri uri = getIntent().getData();
Bundle bundle = getIntent().getExtras();
if (uri != null) {
mUri = uri;
}
if (bundle != null) {
String bundleUrl = bundle.getString("bundleUrl");
if (!TextUtils.isEmpty(bundleUrl)) {
mUri = Uri.parse(bundleUrl);
}
}
if (mUri == null) {
Toast.makeText(this, "the uri is empty!", Toast.LENGTH_SHORT).show();
finish();
return;
}
String path = mUri.toString();
// 传来的url参数总会带上http:/ 应该是个bug 可以自己判断是否本地url再去跳转
String jsPath = path.indexOf("weex/js/") > 0 ? path.replace("http:/", "") : path;
HashMap<String, Object> options = new HashMap<String, Object>();
options.put(WXSDKInstance.BUNDLE_URL, jsPath);
mWXSDKInstance = new WXSDKInstance(this);
mWXSDKInstance.registerRenderListener(this);
mWXSDKInstance.render("weex", WXFileUtils.loadAsset(jsPath, this), options, null, -1, -1, WXRenderStrategy.APPEND_ASYNC);
}
顺便说下... weex
官方没有提供可定制的nav
组件真的是很不方便..经常需要通过module
桥接native
来实现跳转需求
来自@荔枝我大哥 的补充
安卓和苹果方面可以在原生代码接管
navigator
这个模块,安卓方面只需要实现IActivityNavBarSetter
,苹果方面好像是WXNavigatorProtocol
,然后在app启动初始化weex时注册即可。
native -> weex
: 可以在native
端调用render
时传入的option
中自定义字段, 例如NSDictary *option = @{@"params": @{}}
, 在weex
中使用weex.config.params
取出数据
weex -> weex
: 使用storage
weex -> native
: 使用自定义module
官网有提到如何加载网络图片
但是加载本地图片的行为对于三端肯定是不一致的, 也就意味着我们得给
native
重新改一遍引用图片的路径再打包...
但是当然是有解决办法的啦
webpack
设置将图片资源单独打包, 这个很easy, 此时bundleJs
访问的图片路径就变成了/images/..
{
test: /\.(png|jpe?g|gif|svg)$/,
loader: 'url-loader',
query: {
limit: 1,
name: 'images/[hash:8].[name].[ext]'
}
}
native
中,iOS中一般放入mainBundle
, Android一般放入src/main/assets
, 接下来只要在imgloader
接口中扩展替换本地资源路径的代码就ok了iOS
代码如下:
- (id<WXImageOperationProtocol>)downloadImageWithURL:(NSString *)url imageFrame:(CGRect)imageFrame userInfo:(NSDictionary *)options completed:(void (^)(UIImage *, NSError *, BOOL))completedBlock{
if ([url hasPrefix:@"//"]) {
url = [@"http:" stringByAppendingString:url];
}
// 加载本地图片
if ([url hasPrefix:@"file://"]) {
NSString *newUrl = [url stringByReplacingOccurrencesOfString:@"/images/" withString:@"/"];
UIImage *image = [UIImage imageNamed:[newUrl substringFromIndex:7]];
completedBlock(image, nil, YES);
return (id<WXImageOperationProtocol>) self;
} else {
// 加载网络图片
return (id<WXImageOperationProtocol>)[[SDWebImageManager sharedManager]downloadImageWithURL:[NSURL URLWithString:url] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, finished);
}
}];
}
}
Android
代码如下:
@Override
public void setImage(final String url, final ImageView view,
WXImageQuality quality, final WXImageStrategy strategy) {
WXSDKManager.getInstance().postOnUiThread(new Runnable() {
@Override
public void run() {
if(view==null||view.getLayoutParams()==null){
return;
}
if (TextUtils.isEmpty(url)) {
view.setImageBitmap(null);
return;
}
String temp = url;
if (url.startsWith("//")) {
temp = "http:" + url;
}
if (temp.startsWith("/images/")) {
//过滤掉所有相对位置
temp = temp.replace("../", "");
temp = temp.replace("./", "");
//替换asset目录的配置
temp = temp.replace("/images/", "file:///android_asset/weex/images/");
Log.d("ImageAdapter", "url:" + temp);
}
if (view.getLayoutParams().width <= 0 || view.getLayoutParams().height <= 0) {
return;
}
if(!TextUtils.isEmpty(strategy.placeHolder)){
Picasso.Builder builder=new Picasso.Builder(WXEnvironment.getApplication());
Picasso picasso=builder.build();
picasso.load(Uri.parse(strategy.placeHolder)).into(view);
view.setTag(strategy.placeHolder.hashCode(),picasso);
}
Picasso.with(WXEnvironment.getApplication())
.load(temp)
.into(view, new Callback() {
@Override
public void onSuccess() {
if(strategy.getImageListener()!=null){
strategy.getImageListener().onImageFinish(url,view,true,null);
}
if(!TextUtils.isEmpty(strategy.placeHolder)){
((Picasso) view.getTag(strategy.placeHolder.hashCode())).cancelRequest(view);
}
}
@Override
public void onError() {
if(strategy.getImageListener()!=null){
strategy.getImageListener().onImageFinish(url,view,false,null);
}
}
});
}
},0);
}
可以使用google-diff-match-patch来实现, 拥有许多语言版本的实现, 思路如下:
bundlejs
的系统, 提供查询bundlejs
版本与下载的apiweex
页面时去服务端下载bundlejs
文件diff
两个版本的差异, 并返回diff
, native
端使用patch api
生成新版本的bundlejs
来自@荔枝我大哥的补充
我们所有的
jsBundle
全部加载的线上文件,通过http头信息
设置E-Tag
结合cache-control
来实现缓存策略
,最终效果就是,A.vue -> A.js
,app
第一次加载A.js
是从网络下载下来并且保存到本地,app
第二次加载A.js
是直接加载的保存到本地的A.js
文件,线上A.vue
被修改,A.vue -> A.js
,app
第三次加载A.js
时根据缓存策略会知道线上A.js
已经和本地A.js
有差异,于是重新下载A.js
到本地并加载. (整个流程通过http
缓存策略来实现,无需多余编码,参考https://developers.google.cn/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn)
还可以参考很多ReactNative
的成熟方案, 本质上都是js
的热更新
一般情况下, 我们会同时部署一套web
端界面, 若线上环境的weex
页面出现bug, 则使用webview加载web
版,推荐依赖服务端api来控制降级的切换
weex
的优势: 依托于vue
, 上手简单. 可以满足以vue
为技术主导的公司给native
双端提供简单/少底层交互/热更新需求的页面的需求
weex
的劣势:在native
端调整样式是我心中永远的痛..以及众所周知的生态问题, 维护组没有花太多精力解答社区问题, 官方文档错误太多, 导致我在看的时候就顺手提了几个PR(逃
对于文章中提到的没提到的问题, 欢迎来和笔者讨论, 或者参考我的weex-start-kit, 当然点个star也是极好的
https://github.com/justquanyin/third-payment