mowatermelon / studyNode

Learning record
MIT License
4 stars 1 forks source link

2018/04/17 #106

Open mowatermelon opened 6 years ago

mowatermelon commented 6 years ago

https://github.com/justquanyin/third-payment

var _ = require("underscore");

var common = {
  err : ""
};

common.getErr = function(){
  return this.err;
}

common.setErr = function(err){
  this.err = err;
}

common.vefiyConf = function(config){

  if(config.weixin){
    for(var key in config.weixin){
      var weixin = config.weixin[key];

      if(!weixin.appid){
        this.setErr("微信: appid 配置错误");
        return false;
      }
      if(!weixin.mch_id){
        this.setErr("微信: 商户号 配置错误");
        return false;
      }
      if(!weixin.key){
        this.setErr("微信: 加密串 配置错误");
        return false;
      }
    }
  }

  if(config.alipay){

    for(var key in config.alipay){
      var alipay = config.alipay[key];
      if(!alipay.seller_id){
        this.setErr("支付宝: seller_id 配置错误");
        return false;
      }
      if(!alipay.partner){
        this.setErr("支付宝: partner 配置错误");
        return false;
      }
      if(!alipay.app_id){
        this.setErr("支付宝: app_id 配置错误");
        return false;
      }
    } 
  }

  return true;

}

common.queryize = function(params, quoted) {
  if (!params) {
    return null;
  }
  return Object.keys(params).sort().map(function(k) {
    return k + '=' + (quoted ? ('"' + params[k] + '"') : params[k]);
  }).join('&');
}

module.exports = common;
mowatermelon commented 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

mowatermelon commented 6 years ago

initLoad需要在mounted之前,所以应该是在created和beforeMount周期中进行数据初始化

图片

mowatermelon commented 6 years ago

区分draw类型成功

图片

mowatermelon commented 6 years ago

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();
    }
  });
}
mowatermelon commented 6 years ago

在mapView执行hover事件的时候,就会触发beforeUpdate和updated这两个生命周期

mowatermelon commented 6 years ago

https://www.jianshu.com/p/497f1a9ff33f

可能是史上最全的weex踩坑攻略

这是一篇有故事的文章 --- 来自一个weex在生产环境中相爱相杀的小码畜..

image.png

故事一: Build

虽然weex的口号是一次撰写 多端运行, 但其实build环节是有差异的, native端构建需要使用weex-loader, 而web端则是使用vue-loader,除此以外还有不少差异点, 所以webpack需要两套配置.

最佳实践

使用webpack生成两套bundle,一套是基于vue-routerweb spa, 另一套是native端的多入口的bundlejs

首先假设我们在src/views下开发了一堆页面

image.png

build web配置

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'
        }
    ]
 }
})

build native配置

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]

最终效果

image.png image.png

image.png

故事二: 使用预处理器

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加载到模块中, 所以...

image

解决问题一

研究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>

语法高亮, 完美!

image

解决问题二

虽然没有全局样式的概念, 但是支持单独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下的页面跳转有三种形式

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()来跳转

跳转配置

  1. 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重新改一遍引用图片的路径再打包...

img

但是当然是有解决办法的啦

img

{
     test: /\.(png|jpe?g|gif|svg)$/,
     loader: 'url-loader',
     query: {
       limit: 1,
       name: 'images/[hash:8].[name].[ext]'
     }
   }

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来实现, 拥有许多语言版本的实现, 思路如下:

方案二

来自@荔枝我大哥的补充

我们所有的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也是极好的

image.png

mowatermelon commented 6 years ago

https://blog.csdn.net/chaunceyw/article/details/78151222

【前端前沿看点】深度本质分析对比weex和react native

摘要: # 前言 weex的思想是多个平台,只写一套代码,而react-native的思想是多个平台可以写多套代码,但其使用的是同一套语言框架。 weex的目标在于抹平各个平台的差异性,从而简化应用开发。而react-native承认了各个平台之间的差异,退而求其次,在语言和框架层面对平台进行抽象,从方法论的角度去解决多平台开发的问题。 进一步浏览weex和react-native的代码之后,可

前言

weex的思想是多个平台,只写一套代码,而react-native的思想是多个平台可以写多套代码,但其使用的是同一套语言框架。
weex的目标在于抹平各个平台的差异性,从而简化应用开发。而react-native承认了各个平台之间的差异,退而求其次,在语言和框架层面对平台进行抽象,从方法论的角度去解决多平台开发的问题。

进一步浏览weex和react-native的代码之后,可以得出如下的公式。

weex = Vue.js + H5/Native
react-native = React + Native

总的来说,其差异性如下表格所示。

dimension weex react-native
js framework Vue.js React
principle write once, run anywhere learn once, write anywhere

个人观点,weex和react-native最核心的区别就是这两个。然而就只这两个维度的同步,导致了weex和react-native完全不一样的发展方向。

Vue.js vs React

维度 Vue.js React
定位 UI框架 UI框架
目标平台 Web 多平台
架构 MVVM React
数据流 数据绑定 单向数据流动
组件系统
响应式
开发语言 html/css/js all in js
flexbox 支持 支持
外围框架 能和其他js框架整合使用 能和其他js框架整合使用
渲染机制 real DOM Virtual DOM
动画 支持 支持
级别 轻量级 重量级

weex vs react-native

维度 weex react-native
思想 write once, run anywhere learn once, write anywhere
试用场景 简单明了 难易双修
扩展 为了保证各平台的一致性,一次扩展得在各个平台都实现 不同平台可自由扩展
社区 内测开源 15年3月开源,社区非常活跃
支持 alibaba支持 facebook支持
组件丰富程度 基本只有自带的10余种 除了自带的,还有js.coach上社区贡献的,还是比较丰富的
上手难度 容易 困难
调式 暂时log调试 有专门的调试工具,chrome调试,较为完善
IDE 文本编辑器 Nuclide/文本编辑器

Vue.js

Vue.js虽然是Evan You个人开发的开源项目,其社区活跃度以及代码质量还是值得一提的。在写此文章之际,Vue.js在Github上的Star达到了21099,Fork达到了2186。虽然相比于react的Star数44108,Fork数7610还有一定距离,但考虑到作为个人开发者能有如此多的人关注参与,该框架的优秀程度恐怕不会低于React。

Vue.js的文档资料非常齐全,而且其本身的设计非常简洁,因此绝大部分开发者只要花费少量的时间就能快速上手Vue.js。其中VUE.JS: A (RE)INTRODUCTION是 Vue.js的作者Evan You对Vue.js的介绍,非常值得一看。我想这可能也是weex团队选择Vue.js入手的原因之一吧。对Vue.js有兴趣的同学可以移步Vue.js Guide自行学习。

Vue.js用来构建交互式web界面的,其主要的关注点和React是一样的,都是立足于View层。
Vue.js最核心的部分是两个Reactive Data Binding以及Composable View Components。还值得特别关注的是,其保留了html、css、js分离的写法,使得现有的前端开发者在开发的时候能保持原有的习惯。

响应式数据绑定

Vue.js将界面开发分成了三个部分,分别是View、ViewModel和Model,这种划分在客户端开发看来是非常合理的,也经过了实际的检验。以HelloWorld为例来说明,示例来源Vue.js Guide

<!-- this is our View -->
<div id="example-1">
  Hello {{ name }}!
</div>

<!-- this is our Model -->
var exampleData = {
  name: 'Vue.js'
}

<!-- this is our ViewModel -->
var exampleVM = new Vue({
  el: '#example-1',
  data: exampleData
})

这就是经典的MVVM模式,Vue.js通过极简的API,将数据和视图绑定在一起,也就是所谓的Data-Binding。
这样,当数据变化的时候,界面就能变化,实现了数据驱动的响应式变成。

组件化

当我们在写网页的时候,本质上就是构造DOM树,DOM树则是由div、span等元素组成的。div、span这样的元素是构建大型应用的基础。其实Vue.js或者其他的UI框架基本也是一样的,他们都会提供自己的组件系统。这些组件就类似div元素,一般具有一下特征:

  • 小巧精致
  • 能重用
  • 自包含,高内聚

当我们使用Vue.js开发应用的时候,就是搭建特定的组件树。这些组件可以是Vue.js定义的,也可以是开发者自己定义的,这非常重要。

看个组件化的例子。

// example组件定义
var Example = Vue.extend({
  template: '<div>{{ message }}</div>',
  data: function () {
    return {
      message: 'Hello Vue.js!'
    }
  }
})

// register it with the tag <example>
Vue.component('example', Example)

// example组件使用
<example></example>

组件间数据传递

Vue.js的组件都是有自己独立的scope的,因此子组件是不能直接访问到父组件的数据的。数据一般都是通过props来传递的,示例说明。

// define component
Vue.component('child', {
  // declare the props
  props: ['msg'],
  // the prop can be used inside templates, and will also
  // be set as `this.msg`
  template: '<span>{{ msg }}</span>'
})

// usage
<child msg="hello!"></child>

上述方式只能实现组件树从上往下传递数据,在Vue.js中,会有大量的场景需要子组件向父组件传输数据,甚至兄弟组件之间传递数据,一般这种时候就需要使用以下几种能力。

  • 子组件获取父组件的能力(this.$parent
  • 自定义事件的能力 ($on, $emit, $dispatch, $broadcast)

想要了解详情,请移步Parent-Child Communication

样式、逻辑和界面的分离

前端开发经过这么多年的发展,html、css和js的分开编写应当是顺理成章的,Vue.js没有打破这个规则,通过 style 、 template 、 script 将样式、视图和数据逻辑进行了分离。详见下面示例,来源于VUE.JS: A (RE)INTRODUCTION

<!-- css -->
<style>
.message {
  color: red;
}
</style>

<!-- template -->
<template>
  <div class="message">{{ message }}</div>
</template>

<!-- js -->
<script>
export default {
  props: ['message'],
  created() {
    console.log('MyComponent created!')
  }
}
</script>

React

React可能是现在前端开发中最炙手可热的UI框架了。在React的首页最明显的位置上展示者关于React的最核心的三个思想,它们分别是:

  • Declarative(声明式)
  • Component-Based(组件化)
  • Learn Once, Write AnyWhere(一学多写)

声明式

React和Vue.js的组件的使用都是声明式的,声明式的编写方式会比命令式的编写更加的直观。关于声明式和命令式的区别,可以参考Declarative programmingImperative programming,这里就不加详述了。

组件化

诚然React和Vue.js在编写大型程序的时候都是构建一颗组件树,但React和Vue.js的组件却有着不小的差异。先来看一个React组件的示例(来源React官网)。

var HelloMessage = React.createClass({
  render: function() {
    return <div style={divStyle}>Hello {this.props.name}</div>;
  }

  // style
  var divStyle = {
    color: 'white',
    backgroundImage: 'url(' + imgUrl + ')',
    WebkitTransition: 'all', // note the capital 'W' here
    msTransition: 'all' // 'ms' is the only lowercase vendor prefix
  };
});

ReactDOM.render(<HelloMessage name="John" />, mountNode);

在React中,一切都是js,视图、逻辑和样式都是通过js来写的。通过js来统一颠覆了html、css和js分离的原则,当然是褒贬不一了。在Vue.js中,分离带来了清晰度,逻辑、视图、样式和数据可以分别处理,但在React中,一切都需要重新组织,甚至需要新的配套框架和设计模式,比如新的语言JSX就是用来简化js带来的麻烦的。但all in js让很多事情变得简单,js的快速发展也让React脱离了css和html发展限制,可以实现更多的可能性,优化、扩展以及其他很多事情,就只要单纯考虑js就可以了,而不必收到css和html的限制。

当然,这样带来的后果就是学习曲线的陡然增加,React甚至带来了新的JSX语法,同时考虑到React全新的React思想,开发者想要开发生产环境的app,尤其是在将现有app用React重写的时候,成本是要比Vue.js高出不止一个数量级的。

组件间数据传递

React推崇的是单向数据流动,也就是说,数据最好是从组件树的顶端往叶子节点流动,尽量少的出现反向流动的情况。
用另外一种方式来说,React希望实现的immutable data,数据的不变性。只读的数据能给我们带来非常多的好处,并发、简化逻辑、数据统一管理等等。

React提供了props和state两种数据类型,props是实现数据从父组件往子组件传递的机制,而state则是提供了一种机制来实现组件的自更新,facebook是建议尽量少用该特性,因为其违反了immutable data和单向数据流动的设定。

因为React的数据设定让其数据管理成为一个问题,业界出现了一些解决方案,其中最为著名的应该就是redux/flux了,有兴趣的同学可以上github搜搜,都是开源的。

一学多写

React背后是强大的facebook在开发维护,其目的不是要简单的创建一种新的js的UI框架,相反,其想要让React成为平台无感知的UI开发思想。什么意思呢?就是本节要说的learn once,write anywhere。facebook认为每个平台不可能完全一样,Web、Android、iOS、Windows Phone甚至Xbox,以及未来会出现的各种平台,他们都会有自己的发展理念和发展路劲,不可能做到完全一样,但不管平台如何变化,基于平台之上,创建Virtual DOM,React通过控制Virtual DOM来实现界面变化。
也就是说Virtual DOM相当于是一个中间层,隔离平台的差异,从而实现统一的开发方式。

不敢说这样的想法一定能成功,但就现在的发展势头来看,机会还是非常大的。尤其对于开发者来说极具吸引力,如果这一想法成为现实,以后React就可能像DOM一样成为业界统一的标准。那对于iOS开发者来说,在Android上面开发会跟在iOS上开发一样,不需要学习全新的Java语言,Android系统,更不要说各种Java特有的艰深复杂的工具了。

Native

个人感觉weex和react-native最大的不同是在Vue.js和React层面。这一点在react-native的命名上就非常容易看出来。在react-native刚出来的时候,其和React的关系是react-native依赖React。

"dependencies": {
  "react": "~14.0.0"
}

而现在react-native和react则是同级别的。

"peerDependencies": {
  "react": "~15.1.0"
}

react-native中最重要的文件名字也是Library,主要提供了一系列Native的能力。

查看weex的源码,native部分的作用几乎是一样的,主要就是提供了一些列Native的组件,以及其他的一些能力。

这也就是为什么本文将两者的Native合为一谈的原因,他们的本质是差不多的。

  • 提供了js和native交互的桥梁Bridge
  • 提供了一系列组件
  • 提供了flexbox布局支持
  • 提供了事件支持
  • ……

当然,因为weex和react-native的设计思想的差异,在native部分也存在差异,但我觉得这是因为js需要导致的,仅就native而言,两者并没有特别大的不一样。

也许在不远的将来,native部分会出来一个比较核心的框架,抽象出在构建App时js和native交互所需要的基本能力,同时提供扩展方式,让各种类似react-native\weex这样的框架可以专注于js层的设计。也许react-native就走在这条路上,谁知道呢?

展望

动态化的世界越来越精彩,对于weex和react-native的了解才刚刚入门,需要更多实操经验来深刻体会到两者的博大精深。weex和react-native各有千秋,开源的魅力也正是在此。