騰訊 Omio 釋出 - 全面相容 IE8 和移動端
在微信支付、手機QQ、騰訊TEG、騰訊IEG等團隊已經能夠使用 Omi 應用於大量的 to b 的專案以及內部管理系統,為了達到 Omi 全覆蓋,相容 to c 端各種瀏覽器環境,所以有了 Omio, 擁有幾乎和 Omi 一模一樣的語法。
相容老瀏覽器的 Omi 版本,→ Github
立即使用
$ npm i omi-cli -g
$ omi init-o my-app
$ cd my-app
$ npm start
$ npm run build
複製程式碼
與 omi 不同之處
omio 擁有 omi一樣的語法,但是也有一些差異需要注意:
- omio 支援
staticCss
,omi 是不支援的
css
和 staticCss
的區別是 ? 例如:
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
不再渲染。
- Omio 不支援 slot, 請使用
props.children
代替,像 react 一樣 - Omio 支援 store 注入,但不支援 store path updating
- Omio 不支援 render array,未來可能支援
- Omio 不支援
fire
觸發自定義事件,可以和 react 一樣使用props.xxx()
去觸發。Omi 同時支援fire
andprops.xxx()
兩種方式。
Omi 專案中使用 Omio
先安裝:
npm i omio
複製程式碼
配置 Webpack Alias
如果你想在已經存在的 omi 專案下使用 omio,你可以使用下面配置,不用任何程式碼更改:
module.exports = {
//...
resolve: {
alias: {
omi: 'omio'
}
}
};
複製程式碼
相容 IE 踩坑
第一坑 - 偽陣列
IE下 querySelectorAll 出來的偽陣列,沒有 array 相關的方法:
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>
複製程式碼