理想是豐滿的,現實是骨感的,react早期的版本雖然號稱支持IE8,可是頁面總會不自覺切換到奇異模式下,致使報錯。所以必須讓react連IE6,7都支持,這纔是最安全。但React自己並不支持IE6,7,所以anu使有用武之地了。javascript
https://github.com/RubyLouvre...html
但光是anu不行,兼容IE是一個系統性的工程,涉及到打包壓縮,各類polyfill墊片。前端
首先說一下anu如何支持低版本瀏覽器。anu自己沒有用到過高級的API,像Object.defineProperty, Object.seal, Object.freeze, Proxy, WeakMap等沒法 模擬的新API,anu一個也沒有用,而const, let, 箭頭函數,es6模塊,經過babel編譯就能夠搞定了。java
而框架用到的一些es5,es6方法,我已經提供了一個叫polyfill的文件爲你們準備好,你們也可使用bable.polyfill實現兼容。node
Array.prototype.forEachreact
Function.prototype.bindwebpack
JSONgit
window.consolees6
Object.keysgithub
Object.is
Object.assign
Array.isArray
https://github.com/RubyLouvre...
剩下就是事件系統的兼容。React爲了實現一個全能的事件系統,3萬行的react-dom,有一半是在搞事件的。事件系統之因此這麼難寫,是由於React要實現整個標準事件流,從捕獲階段到target階段再到冒泡階段。若是能獲取事件源對象到document這一路通過的全部元素,就能實現事件流了。可是在IE下,只有冒泡階段,而且許多重要的表單事件不支持冒泡到document。爲了事件冒泡,自jQuery時代起,前端高手們已經摸索出一套方案了。使用另外一個類似的事件來假裝不冒泡事件,冒泡到document後,而後變成原來的事件觸發對應的事件。
好比說IE下,使用focusin冒充focus, focusout冒充blur。chrome下,則經過addEventListener的第三個參加爲true,強制讓focus, blur被document捕獲到。
//Ie6-9 if(msie < 9){ eventHooks.onFocus = function(dom) { addEvent(dom, "focusin", function(e) { addEvent.fire(dom, "focus"); }); }; eventHooks.onBlur = function(dom) { addEvent(dom, "blurout", function(e) { addEvent.fire(dom, "blur"); }); }; }else{ eventHooks.onFocus = function(dom) { addEvent( dom, "focus", function(e) { addEvent.fire(dom, "focus"); }, true ); }; eventHooks.onBlur = function(dom) { addEvent( dom, "blur", function(e) { addEvent.fire(dom, "blur"); }, true ); }; }
低版本的oninput, onchange事件是一個麻煩,它們最多冒泡到form元素上。而且IE也沒有oninput,只有一個類似的onpropertychange事件。IE9,IE10的oninput其實也有許多BUG,但你們要求放低些,咱們也不用理會IE9,IE10的oninput事件。IE6-8的oninput事件,咱們是直接在元素上綁定onpropertychange事件,而後觸發一個datasetchanged 事件冒泡到document上,而且這個datasetchanged事件對象帶有一個__type__屬性,用來講明它原先冒充的事件。
function fixIEInput(dom, name) { addEvent(dom, "propertychange", function(e) { if (e.propertyName === "value") { addEvent.fire(dom, "input"); } }); } addEvent.fire = function dispatchIEEvent(dom, type, obj) { try { var hackEvent = document.createEventObject(); if (obj) { Object.assign(hackEvent, obj); } hackEvent.__type__ = type; //IE6-8觸發事件必須保證在DOM樹中,不然報"SCRIPT16389: 未指明的錯誤" dom.fireEvent("ondatasetchanged", hackEvent); } catch (e) {} }; function dispatchEvent(e) {//document上綁定的事件派發器 var __type__ = e.__type__ || e.type; e = new SyntheticEvent(e); var target = e.target; var paths = [];//獲取整個冒泡的路徑 do { var events = target.__events; if (events) { paths.push({ dom: target, props: events }); } } while ((target = target.parentNode) && target.nodeType === 1); // ...略 }
addEvent.fire這個方法在不一樣瀏覽器的實現是不同的,這裏顯示的IE6-8的版本,IE9及標準瀏覽器是使用document.createEvent, initEvent, dispatchEvent等API來建立事件對象與觸發事件。在IE6-8中,則須要用document.createEventObject建立事件對象,fireEvent來觸發事件。
ondatasetchanged事件是IE一個很是偏門的事件,由於IE的 fireEvent只能觸發它官網上列舉的幾十個事件,不能觸發自定義事件。而ondatasetchanged事件在IE9,chrome, firefox等瀏覽器中是當成一個自定義事件來對待,但那時它是使用elem.dispatchEvent來觸發了。ondatasetchanged是一個能冒泡的事件,只是充做信使,將咱們要修改的屬性帶到document上。
此是其一,onchange事件也要經過ondatasetchanged也冒充,由於IE下它也不能冒泡到document。onchange事件在IE仍是有許多BUG(或叫差別點)。checkbox, radio的onchange事件必須在失去焦點時才觸發,所以咱們在內部用onclick來觸發,而select元素在單選時候下,用戶選中了某個option, select.value會變成option的value值,但在IE6-8下它居然不會發生改變。最絕的是select元素也不讓你修改value值,後來我奠出修改HTMLSelectElement原型鏈的大招搞定它。
try { Object.defineProperty(HTMLSelectElement.prototype, "value", { set: function(v) { this._fixIEValue = v; }, get: function() { return this._fixIEValue; } }); } catch (e) {} function fixIEChange(dom, name) { //IE6-8, radio, checkbox的點擊事件必須在失去焦點時才觸發 var eventType = dom.type === "radio" || dom.type === "checkbox" ? "click" : "change"; addEvent(dom, eventType, function(e) { if (dom.type === "select-one") { var idx = dom.selectedIndex, option, attr; if (idx > -1) { //IE 下select.value不會改變 option = dom.options[idx]; attr = option.attributes.value; dom.value = attr && attr.specified ? option.value : option.text; } } addEvent.fire(dom, "change"); }); }
此外,滾動事件的兼容性也很是多,但在React官網中,統一你們用onWheel接口來調用,在內部實現則須要咱們根據瀏覽器分別用onmousewheel, onwheel, DOMMouseScroll來模擬了。
固然還有不少不少細節,這裏就不一一列舉了。爲了防止像React那樣代碼膨脹,針對舊版本的事件兼容,我都移到ieEvent.js文件中。而後基於它,打包了一個專門針對舊版本IE的ReactIE
https://github.com/RubyLouvre...
你們也能夠經過npm安裝,1.0.2就擁有這個文件
npm install anujs
下面經過一個示例介紹如何使用ReactIE.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <script src="./dist/polyfill.js"></script> <script src="./dist/ReactIE.js"></script> <script src="./dist/index9.js"></script> </head> <body> <div>這個默認會被清掉</div> <div id='example'></div> </body> </html>
首先創建一個頁面,裏面有三個JS,其實前兩個文件也能單獨打包的。
index.js的源碼是這樣的,業務線開發時是直接上JSX與es6,爲了兼容IE6-8,請不要在業務代碼上用Object.defineProperty與Proxy
class Select extends React.Component{ constructor() { super() this.state = { value: 'bbb' } this.onChange = this.onChange.bind(this) } onChange(e){ console.log(e.target.value) this.setState({ value: e.target.value }) } render() { return <div><select value={this.state.value} onChange={this.onChange}> <option value='aaa'>aaa</option> <option value='bbb'>bbb</option> <option value='ccc'>ccc</option> </select><p>{this.state.value}</p></div> } } class Input extends React.Component{ constructor() { super() this.state = { value: 'input' } this.onInput = this.onInput.bind(this) } onInput(e){ this.setState({ value: e.target.value }) } render() { return <div><input value={this.state.value} onInput={this.onInput} />{this.state.value}</div> } } class Radio extends React.Component{ constructor(props) { super(props) this.state = { value: this.props.value } this.onChange = this.onChange.bind(this) } onChange(e){ console.log(e.target.value) this.setState({ value: e.target.value }) } render() { return <span><input type='radio' name={this.props.name} value={this.props.value} onChange={this.onChange} />{this.state.value+''}</span> } } class Playground extends React.Component{ constructor(props) { super(props) this.state = { value: '請上下滾動鼠標滾輪' } this.onWheel = this.onWheel.bind(this) } onWheel(e){ this.setState({ value: e.wheelDelta }) } render() { return <div style={{width:300,height:300,backgroundColor:'red',display:'inline-block'}} onWheel={this.onWheel} >{this.state.value}</div> } } class MouseMove extends React.Component{ constructor(props) { super(props) this.state = { value: '請在綠色區域移動' } this.onMouseMove = this.onMouseMove.bind(this) } onMouseMove(e){ var v = e.pageX+' '+e.pageY; this.setState({ value: v }) } render() { return <div style={{width:300,height:300,backgroundColor:'#a9ea00',display:'inline-block'}} onMouseMove={this.onMouseMove} >{this.state.value}</div> } } class FocusEl extends React.Component{ constructor(props) { super(props) this.state = { value: '點我' } this.onFocus = this.onFocus.bind(this) } onFocus(e){ console.log(e.target.title) } render() { return <input title={this.props.title} onKeyUp={(e)=>{console.log(e.which)}} style={{width:100,height:50,backgroundColor:'green',display:'inline-block'}} onFocus={this.onFocus} /> } } window.onload = function(){ window.s = ReactDOM.render( <div><Select /><Input /><Radio name='sex' value="男" /><Radio name='sex' value='女'/> <p><Playground /> <MouseMove /><FocusEl title="aaa" /><FocusEl title="bbb" /></p> </div>, document.getElementById('example')) }
而後咱們建一個webpack.config.js,用的是webpack1
const webpack = require("webpack"); const path = require("path"); const fs = require("fs"); var es3ifyPlugin = require('es3ify-webpack-plugin'); module.exports = { context: __dirname, entry: { index9: "./src/index9.js" }, output: { path: __dirname + "/dist/", filename: "[name].js" }, plugins: [new es3ifyPlugin()], module: { loaders: [ { test: /\.jsx?$/, loader: "babel-loader", exclude: path.resolve(__dirname, "node_modules") } ] }, resolve: { //若是不使用anu,就能夠把這裏註釋掉 alias: { react: "anujs/dist/ReactIE.js", "react-dom": "anujs/dist/ReactIE.js" } } };
es3ify-webpack-plugin是專門將es5代碼轉換爲es3代碼,由於es5是容許用關鍵字,保留字做爲對象的方法與屬性,而es3不能。萬一碰上module.default,咱們就坑大了。es3ify是一個利器。
babel是經過.babelrc來配置,裏面用到一個
{ "presets": [ ["es2015", { "modules": false }], "react" ], "plugins": [ [ "transform-es2015-classes", { "loose": true } ] ] }
babel-plugin-transform-es2015-classes記使用loose模式。
babel-preset-es2015後面這樣設置是禁用生成 "use strict",也建議直接換成babel-preset-avalon,這是個preset生成的代碼兼容性更好。
若是你們用 uglify-js進行代碼上線,這也要注意一下,這裏有許多坑,它默認會把es3ify乾的活所有白作了。詳見 https://github.com/zuojj/fedl... 這篇文章
最後你們能夠經過加Q 79641290 聯繫我。