【開源】騰訊 Omio 發佈 - 全面兼容 IE8 和移動端

騰訊 Omio 發佈 - 全面兼容 IE8 和移動端

在微信支付、手機QQ、騰訊TEG、騰訊IEG等團隊已經可以使用 Omi 應用於大量的 to b 的項目以及內部管理系統,爲了達到 Omi 全覆蓋,兼容 to c 端各類瀏覽器環境,因此有了 Omio, 擁有幾乎和 Omi 如出一轍的語法。css

兼容老瀏覽器的 Omi 版本,→ Githubhtml


當即使用

$ npm i omi-cli -g             
$ omi init-o my-app   
$ cd my-app           
$ npm start                     
$ npm run build               
複製代碼

與 omi 不一樣之處

omio 擁有 omi同樣的語法,可是也有一些差別須要注意:react

  • omio 支持 staticCss,omi 是不支持的

cssstaticCss 的區別是 ? 例如:webpack

render() {
  return (
    <div>
      <my-ele name={this.name}></my-ele>
      <my-ele name={this.name}></my-ele>
      <my-ele name={this.name}></my-ele>
    </div>
  )
}
複製代碼

如上面的例子,css方法會渲染三次,並插入到 head,而staticCss 只會渲染一次。 當你 update 組件或者 setState 時候,css方法會渲染三次,並更新head裏對應三個地方的樣式,staticCss 再也不渲染。git

  • Omio 不支持 slot, 請使用 props.children 代替,像 react 同樣
  • Omio 支持 store 注入,但不支持 store path updating
  • Omio 不支持 render array,將來可能支持
  • Omio 不支持 fire 觸發自定義事件,能夠和 react 同樣使用 props.xxx() 去觸發。Omi 同時支持 fire and props.xxx() 兩種方式。

Omi 項目中使用 Omio

先安裝:github

npm i omio
複製代碼

配置 Webpack Aliasweb

若是你想在已經存在的 omi 項目下使用 omio,你可使用下面配置,不用任何代碼更改:ajax

module.exports = {
  //...
  resolve: {
    alias: {
      omi: 'omio'
    }
  }
};
複製代碼

兼容 IE 踩坑

第一坑 - 僞數組

IE下 querySelectorAll 出來的僞數組,沒有 array 相關的方法:npm

const codes = document.querySelectorAll('xxx')
//掛了
codesArr.forEach(() => {

})
複製代碼

須要轉成真數組:數組

const codes = Array.prototype.slice.call(document.querySelectorAll('xxx'))
複製代碼

第二坑 - 靜態屬性丟失

這是 Omi 的源碼:

function define(name, ctor) {
  if (ctor.is === 'WeElement') {
    options.mapping[name] = ctor;
    if (ctor.data && !ctor.pure) {
      ctor.updatePath = getUpdatePath(ctor.data);
    }
  }
}
複製代碼

可是在 IE 下進入不了 if 條件!!Omi 源碼裏明明有有靜態屬性:

class WeElement {
  static is = 'WeElement'

  constructor(props, store) {
    ...
  }
  ...
  ...
  render() { }
}
複製代碼

爲何丟失了呢?追根溯源一下:

使用 define:

define('my-p', class extends WeElement {
  render(props) {
    return props.children[0]
  }
})
複製代碼

編譯後的代碼:

define('my-p', function (_WeElement) {
  _inherits(_class, _WeElement);

  function _class() {
    _classCallCheck(this, _class);

    return _possibleConstructorReturn(this, _WeElement.apply(this, arguments));
  }

  _class.prototype.render = function render$$1(props) {
    return props.children[0];
  };

  return _class;
}(WeElement));
複製代碼

那麼問題就出在 _inherits 過程當中把靜態屬性 is 丟失了!

function _inherits(subClass, superClass) {
  subClass.prototype = Object.create(superClass && superClass.prototype, { 
    constructor: { 
      value: subClass, 
      enumerable: false, 
      writable: true, 
      configurable: true 
    } 
  }); 
  if (superClass) {
    Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  } 
}
複製代碼

好,因爲是編譯註入代碼,後面可能也須要支持純函數的組件定義,因此這樣解決了:

function define(name, ctor) {
  //if (ctor.is === 'WeElement') {
    options.mapping[name] = ctor;
    if (ctor.data && !ctor.pure) {
      ctor.updatePath = getUpdatePath(ctor.data);
    }
  //}
}
複製代碼

第三坑 - Object.assign IE 不支持

因爲 Omio 源碼裏使用了 Object.assign,因此這裏須要 polyfill 一下:

if (typeof Object.assign != 'function') {
  // Must be writable: true, enumerable: false, configurable: true
  Object.defineProperty(Object, "assign", {
    value: function assign(target, varArgs) { // .length of function is 2
 'use strict';
      if (target == null) { // TypeError if undefined or null
        throw new TypeError('Cannot convert undefined or null to object');
      }

      var to = Object(target);

      for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];

        if (nextSource != null) { // Skip over if undefined or null
          for (var nextKey in nextSource) {
            // Avoid bugs when hasOwnProperty is shadowed
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true
  });
}
複製代碼

因爲 IE9 支持了 ES5, webpack 編譯出來的 es5,因此並不須要引入 es5-shim 來兼容。

第四坑 - Proxy 不支持

由於須要監聽數據變化,Omi 使用的是 Proxy,因此這裏須要一個降級方案 - obaa 庫,監放任意對象的任意變化。

安裝 obaa

npm install obaa
複製代碼

使用

observe object:

var obj = { a: 1 };
obaa(obj, function (name, value , old) {
    console.log(name + "__" + value + "__" + old);
});
obj.a = 2; //a__2__1 
複製代碼

observe array:

var arr = [1, 2, 3];
obaa(arr, function (name, value, old) {
    console.log(name + "__" + value+"__"+old);
});
arr.push(4);//Array-push__[1,2,3,4]__[1,2,3] 
arr[3] = 5;//3__5__4
複製代碼

observe class instance:

var User = function (name, age) {
    this.name = name;
    this.age = age;
    //observe name only
    obaa(this, ["name"], function (name, value, oldValue) {
        console.log(name + "__" + value + "__" + oldValue);
    });
}
var user = new User("lisi", 25);
user.name = "wangwu";//name__wangwu__lisi 
user.age = 20; //nothing output
複製代碼

其餘:

arr.push(111) //trigger observe callback
//every method of array has a pureXXX function
arr.purePush(111) //don't trigger observe callback

arr.size(2) //trigger observe callback
arr.length = 2 //don't trigger observe callback

//if obj.c is undefined
obaa.set(obj, 'c', 3)
obj.c = 4 //trigger observe callback

//if obj.c is undefined
obj.c = 3
obj.c = 4 //don't trigger observe callback
複製代碼

第五坑 - MVVM 的 mappingjs 不支持

mappingjs 徹底利用的 proxy,因此數據 mapping 的過程當中會自動更新視圖。可是切換成 obaa 以後,發現數組 length 更新視圖不會更新,數組增長視圖不會更新。review 了 mappingjs 發現:

  • mappingjs 使用了 array.length 改變數組長度
  • mappingjs 使用 array[index] 增長元素

這樣在 obaa 是不容許的,否則的話沒法監聽到變化, obaa 要求:

  • 使用 array.size(len) 改變數組長度
  • 使用 array.push 增長元素

因此就有了 mappingjs-omio, 這樣的話, Omio 一樣可使用真正的 MVVM 架構。

Omio 實戰

md2site 徹底使用 omio 打造,擁有良好的閱讀體驗和兼容性。

兼容 IE8

第一坑 - 關鍵字做爲 key

const map = {
  var: 'view',
  switch: 'switch'
}
複製代碼

要改爲:

const map = {
  'var': 'view',
  'switch': 'switch'
}
複製代碼

關鍵字不能做爲 JSON 的 key。

第二坑 - Object.assign polyfill 不可用

Object.assign polyfill 使用了 Object.defineProperty, IE8 下報錯,因此把 Object.assign 替換成了 object-assign

'use strict'
/* eslint-disable no-unused-vars */
var getOwnPropertySymbols = Object.getOwnPropertySymbols
var hasOwnProperty = Object.prototype.hasOwnProperty
var propIsEnumerable = Object.prototype.propertyIsEnumerable

function toObject(val) {
  if (val === null || val === undefined) {
    throw new TypeError('Object.assign cannot be called with null or undefined')
  }

  return Object(val)
}

export function assign(target, source) {
  var from
  var to = toObject(target)
  var symbols

  for (var s = 1; s < arguments.length; s++) {
    from = Object(arguments[s])

    for (var key in from) {
      if (hasOwnProperty.call(from, key)) {
        to[key] = from[key]
      }
    }

    if (getOwnPropertySymbols) {
      symbols = getOwnPropertySymbols(from)
      for (var i = 0; i < symbols.length; i++) {
        if (propIsEnumerable.call(from, symbols[i])) {
          to[symbols[i]] = from[symbols[i]]
        }
      }
    }
  }

  return to
}
複製代碼

第三坑 - Object.create 不可用

使用 polyfill 而且要註釋掉下面的代碼!由於傳遞二個參數無法 polyfill!

if (typeof Object.create !== 'function') {
  Object.create = function(proto, propertiesObject) {
    if (typeof proto !== 'object' && typeof proto !== 'function') {
      throw new TypeError('Object prototype may only be an Object: ' + proto)
    } else if (proto === null) {
      throw new Error(
        "This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument."
      )
    }

    // if (typeof propertiesObject != 'undefined') {
    // throw new Error("This browser's implementation of Object.create is a shim and doesn't support a second argument.");
    // }

    function F() {}
    F.prototype = proto

    return new F()
  }
}
複製代碼

第四坑 - text 節點設置屬性

//ie8 error
try {
  out[ATTR_KEY] = true
} catch (e) {}
複製代碼

直接 try catch 包起來,測試下來目前不影響正常使用。

第五坑 - addEventListener 和 removeEventListener

這裏直接使用了 mdn 的 polyfill,其餘 polyfill 都有坑!

if (!Element.prototype.addEventListener) {
  var oListeners = {};
  function runListeners(oEvent) {
    if (!oEvent) { oEvent = window.event; }
    for (var iLstId = 0, iElId = 0, oEvtListeners = oListeners[oEvent.type]; iElId < oEvtListeners.aEls.length; iElId++) {
      if (oEvtListeners.aEls[iElId] === this) {
        for (iLstId; iLstId < oEvtListeners.aEvts[iElId].length; iLstId++) { oEvtListeners.aEvts[iElId][iLstId].call(this, oEvent); }
        break;
      }
    }
  }
  Element.prototype.addEventListener = function (sEventType, fListener /*, useCapture (will be ignored!) */) {
    if (oListeners.hasOwnProperty(sEventType)) {
      var oEvtListeners = oListeners[sEventType];
      for (var nElIdx = -1, iElId = 0; iElId < oEvtListeners.aEls.length; iElId++) {
        if (oEvtListeners.aEls[iElId] === this) { nElIdx = iElId; break; }
      }
      if (nElIdx === -1) {
        oEvtListeners.aEls.push(this);
        oEvtListeners.aEvts.push([fListener]);
        this["on" + sEventType] = runListeners;
      } else {
        var aElListeners = oEvtListeners.aEvts[nElIdx];
        if (this["on" + sEventType] !== runListeners) {
          aElListeners.splice(0);
          this["on" + sEventType] = runListeners;
        }
        for (var iLstId = 0; iLstId < aElListeners.length; iLstId++) {
          if (aElListeners[iLstId] === fListener) { return; }
        }
        aElListeners.push(fListener);
      }
    } else {
      oListeners[sEventType] = { aEls: [this], aEvts: [[fListener]] };
      this["on" + sEventType] = runListeners;
    }
  };
  Element.prototype.removeEventListener = function (sEventType, fListener /*, useCapture (will be ignored!) */) {
    if (!oListeners.hasOwnProperty(sEventType)) { return; }
    var oEvtListeners = oListeners[sEventType];
    for (var nElIdx = -1, iElId = 0; iElId < oEvtListeners.aEls.length; iElId++) {
      if (oEvtListeners.aEls[iElId] === this) { nElIdx = iElId; break; }
    }
    if (nElIdx === -1) { return; }
    for (var iLstId = 0, aElListeners = oEvtListeners.aEvts[nElIdx]; iLstId < aElListeners.length; iLstId++) {
      if (aElListeners[iLstId] === fListener) { aElListeners.splice(iLstId, 1); }
    }
  };
}
複製代碼

第六坑 - string trim 不支持

if (!String.prototype.trim) {
  String.prototype.trim = function () {
    return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')
  }
}
複製代碼

第七坑 - 數據監聽

import { render, WeElement, define } from '../../src/omi'

define('my-counter', class extends WeElement {
  //ie8 不能使用 observe
  //static observe = true

  data = {
    count: 1
  }

  sub = () => {
    this.data.count--
    //手動 update
    this.update()
  }

  add = () => {
    this.data.count++
    //手動 update
    this.update()
  }

  render() {
    return (
      <div> <button onClick={this.sub}>-</button> <span>{this.data.count}</span> <button onClick={this.add}>+</button> </div>
    )
  }
})

render(<my-counter />, 'body') 複製代碼

若是你不須要兼容 IE8,你可使用 static observe = true 進行數據監聽自動更新視圖。

第八坑 - ES5 Shim

<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.7/es5-shim.min.js"></script>
複製代碼

開始使用吧

→ Omi Github

相關文章
相關標籤/搜索