原文出處: AlloyTeam 歡迎分享原創(chuàng)到伯樂頭條
《=》”按模塊劃分“目錄結構,把當前模塊下的所有邏輯和資源都放一起了,這對于多人獨自開發(fā)和維護個人模塊不是很好嗎?當然了,那爭論的結果是我乖乖地改回主流的”按資源劃分“的目錄結構。因為,沒有做到JS模塊化和資源模塊化,僅僅物理位置上的模塊劃分是沒有意義的,只會增加構建的成本而已。
雖然他說得好有道理我無言以對,但是我心不甘,等待他日前端組件化成熟了,再來一戰(zhàn)!
而今天就是我重申正義的日子!只是當年那個跟你撕逼的人不在。
模塊化的不足
模塊一般指能夠獨立拆分且通用的代碼單元。由于JavaScript語言本身沒有內置的模塊機制(ES6有了!?。覀円话銜褂肅MD或ADM建立起模塊機制?,F在大部分稍微大型一點的項目,都會使用requirejs或者seajs來實現JS的模塊化。多人分工合作開發(fā),其各自定義依賴和暴露接口,維護功能模塊間獨立性,對于項目的開發(fā)效率和項目后期擴展和維護,都是是有很大的幫助作用。
但,麻煩大家稍微略讀一下下面的代碼
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | require([ 'Tmpl!../tmpl/list.html','lib/qqapi','module/position','module/refresh','module/page','module/net' ], function(listTmpl, QQapi, Position, Refresh, Page, NET){ var foo = '', bar = []; QQapi.report(); Position.getLocaiton(function(data){ //... }); var init = function(){ bind(); NET.get('/cgi-bin/xxx/xxx',function(data){ renderA(data.banner); renderB(data.list); }); }; var processData = function(){ }; var bind = function(){ }; var renderA = function(){ }; var renderB = function(data){ listTmpl.render('#listContent',processData(data)); }; var refresh = function(){ Page.refresh(); }; // app start init(); }); |
上面是具體某個頁面的主js,已經封裝了像Position,NET,Refresh等功能模塊,但頁面的主邏輯依舊是”面向過程“的代碼結構。所謂面向過程,是指根據頁面的渲染過程來編寫代碼結構。像:init -> getData -> processData -> bindevent -> report -> xxx 。 方法之間線性跳轉,你大概也能感受這樣代碼弊端。隨著頁面邏輯越來越復雜,這條”過程線“也會越來越長,并且越來越繞。加之缺少規(guī)范約束,其他項目成員根據各自需要,在”過程線“加插各自邏輯,最終這個頁面的邏輯變得難以維護。
開發(fā)需要小心翼翼,生怕影響“過程線”后面正常邏輯。并且每一次加插或修改都是bug泛濫,無不令產品相關人員個個提心吊膽。
頁面結構模塊化
基于上面的面向過程的問題,行業(yè)內也有不少解決方案,而我們團隊也總結出一套成熟的解決方案:Abstractjs,頁面結構模塊化。我們可以把我們的頁面想象為一個樂高機器人,需要不同零件組裝,如下圖,假設頁面劃分為tabContainer,listContainer和imgsContainer三個模塊。最終把這些模塊add到最終的pageModel里面,最終使用rock方法讓頁面啟動起來。
下面是偽代碼的實現
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | require([ 'Tmpl!../tmpl/list.html','Tmpl!../tmpl/imgs.html','lib/qqapi','module/refresh','module/page' ], function(listTmpl, imgsTmpl, QQapi, Refresh, Page ){ var tabContainer = new RenderModel({ renderContainer: '#tabWrap', data: {}, renderTmpl: ' event: function(){ // tab's event } }); var listContainer = new ScrollModel({ scrollEl: $.os.ios ? $('#Page') : window, renderContainer: '#listWrap', renderTmpl: listTmpl, cgiName: '/cgi-bin/index-list?num=1', processData: function(data) { //... }, event: function(){ // listElement's event }, error: function(data) { Page.show('數據返回異常[' + data.retcode + ']'); } }); var imgsContainer = new renderModel({ renderContainer: '#imgsWrap', renderTmpl: listTmpl, cgiName: '/cgi-bin/getPics', processData: function(data) { //... }, event: function(){ // imgsElement's event }, complete: function(data) { QQapi.report(); } }); var page = new PageModel(); page.add([tabContainer,listContainer,imgsContainer]); page.rock(); }); |
我們把這些常用的請求CGI,處理數據,事件綁定,上報,容錯處理等一系列邏輯方法,以頁面塊為單位封裝成一個Model模塊。
這樣的一個抽象層Model,我們可以清晰地看到該頁面塊,請求的CGI是什么,綁定了什么事件,做了什么上報,出錯怎么處理。新增的代碼就應該放置在相應的模塊上相應的狀態(tài)方法(preload,process,event,complete…),杜絕了以往的無規(guī)則亂增代碼的行文。并且,根據不同業(yè)務邏輯封裝不同類型的Model,如列表滾動的ScrollModel,滑塊功能的SliderModel等等,可以進行高度封裝,集中優(yōu)化。
現在基于Model的頁面結構開發(fā),已經帶有一點”組件化“的味道。每個Model都帶有各自的數據,模板,邏輯。已經算是一個完整的功能單元。但距離真正的WebComponent還是有一段距離,至少滿足不了我的”理想目錄結構“。
WebComponents 標準
我們回顧一下使用一個datapicker的jquery的插件,所需要的步奏:
1. 引入插件js
2. 引入插件所需的css(如果有)
3. copy 組件的所需的html片段
4. 添加代碼觸發(fā)組件啟動
現階段的“組件”基本上只能達到是某個功能單元上的集合。他的資源都是松散地分散在三種資源文件中,而且組件作用域暴露在全局作用域下,缺乏內聚性很容易就會跟其他組件產生沖突,如最簡單的css命名沖突。對于這種“組件”,還不如上面的頁面結構模塊化。
于是W3C按耐不住了,制定一個WebComponents標準,為組件化的未來指引了明路。
下面以較為簡潔的方式介紹這份標準,力求大家能夠快速了解實現組件化的內容。(對這部分了解的同學,可以跳過這一小節(jié))
1. 模板能力
模板這東西大家最熟悉不過了,前些年見的較多的模板性能大戰(zhàn)artTemplate,juicer,tmpl,underscoretemplate等等。而現在又有mustachejs無邏輯模板引擎等新入選手。可是大家有沒有想過,這么基礎的能力,原生HTML5是不支持的(T_T)。
而今天WebComponent將要提供原生的模板能力
XHTML
1 2 3 | id='datapcikerTmpl'> 我是原生的模板 |
template標簽內定義了myTmpl的模板,需要使用的時候就要innerHTML= document.querySelector('#myTmpl').content
;可以看出這個原生的模板夠原始,模板占位符等功能都沒有,對于動態(tài)數據渲染模板能力只能自力更新。
2. ShadowDom 封裝組件獨立的內部結構
ShadowDom可以理解為一份有獨立作用域的html片段。這些html片段的CSS環(huán)境和主文檔隔離的,各自保持內部的獨立性。也正是ShadowDom的獨立特性,使得組件化成為了可能。
JavaScript
1 2 3 | var wrap = document.querySelector('#wrap'); var shadow = wrap.createShadowRoot(); shadow.innerHTML = ' you can not see me |
在具體dom節(jié)點上使用createShadowRoot方法即可生成其ShadowDom。就像在整份Html的屋子里面,新建了一個shadow的房間。房間外的人都不知道房間內有什么,保持shadowDom的獨立性。
3. 自定義原生標簽
初次接觸Angularjs的directive指令功能,設定好組件的邏輯后,一個
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 | var tmpl = document.querySelector('#datapickerTmpl'); var datapickerProto = Object.create(HTMLElement.prototype); // 設置把我們模板內容我們的shadowDom datapickerProto.createdCallback = function() { var root = this.createShadowRoot(); root.appendChild(document.importNode(tmpl.content, true)); }; var datapicker = docuemnt.registerElement('datapicker',{ prototype: datapickerProto }); |
Object.create方式繼承HTMLElement.prototype,得到一個新的prototype。當解析器發(fā)現我們在文檔中標記它將檢查是否一個名為createdCallback的方法。如果找到這個方法它將立即運行它,所以我們把克隆模板的內容來創(chuàng)建的ShadowDom。
最后,registerElement的方法傳遞我們的prototype來注冊自定義標簽。
上面的代碼開始略顯復雜了,把前面兩個能力“模板”“shadowDom”結合,形成組件的內部邏輯。最后通過registerElement的方式注冊組件。之后可以愉快地
4. imports解決組件間的依賴
XHTML
1 | rel='import' href='datapciker.html'> |
這個類php最常用的html導入功能,HTML原生也能支持了。
WebComponents標準內容大概到這里,是的,我這里沒有什么Demo,也沒有實踐經驗分享。由于webComponents新特性,基本上除了高版本的Chrome支持外,其他瀏覽器的支持度甚少。雖然有polymer幫忙推動webcompoents的庫存在,但是polymer自身的要求版本也是非常高(IE10+)。所以今天的主角并不是他。
我們簡單來回顧一下WebCompoents的四部分功能:
1 .定義組件的HTML模板能力 2. Shadow Dom封裝組件的內部結構,并且保持其獨立性 3. Custom Element 對外提供組件的標簽,實現自定義標簽 4. import解決組件結合和依賴加載 組件化實踐方案 官方的標準看完了,我們思考一下。一份真正成熟可靠的組件化方案,需要具備的能力。 “資源高內聚”—— 組件資源內部高內聚,組件資源由自身加載控制 “作用域獨立”—— 內部結構密封,不與全局或其他組件產生影響 “自定義標簽”—— 定義組件的使用方式 “可相互組合”—— 組件正在強大的地方,組件間組裝整合 “接口規(guī)范化”—— 組件接口有統(tǒng)一規(guī)范,或者是生命周期的管理 個人認為,模板能力是基礎能力,跟是否組件化沒有強聯(lián)系,所以沒有提出一個大點。 既然是實踐,現階段WebComponent的支持度還不成熟,不能作為方案的手段。而另外一套以高性能虛擬Dom為切入點的組件框架React,在facebook的造勢下,社區(qū)得到了大力發(fā)展。另外一名主角Webpack,負責解決組件資源內聚,同時跟React極度切合形成互補。 所以【Webpack】+【React】將會是這套方案的核心技術。 不知道你現在是“又是react+webpack”感到失望
一,組件生命周期
React天生就是強制性組件化的,所以可以從根本性上解決面向過程代碼所帶來的麻煩。React組件自身有生命周期方法,能夠滿足“接口規(guī)范化”能力點。并且跟“頁面結構模塊化”的所封裝抽離的幾個方法能一一對應。另外react的jsx自帶模板功能,把html頁面片直接寫在render方法內,組件內聚性更加緊密。
由于React編寫的JSX是會先生成虛擬Dom的,需要時機才真正插入到Dom樹。使用React必須要清楚組件的生命周期,其生命周期三個狀態(tài):
Mount
: 插入Dom
Update
: 更新Dom
Unmount
: 拔出Dom
mount這單詞翻譯增加,嵌入等。我倒是建議“插入”更好理解。插入!拔出!插入!拔出!默念三次,懂了沒?別少看黃段子的力量,
組件狀態(tài)就是: 插入-> 更新 ->拔出。
然后每個組件狀態(tài)會有兩種處理函數,一前一后,will函數和did函數。
componentWillMount()
準備插入前
componentDidlMount()
插入后
componentWillUpdate()
準備更新前
componentDidUpdate()
更新后
componentWillUnmount()
準備拔出前
因為拔出后基本都是賢者形態(tài)(我說的是組件),所以沒有DidUnmount這個方法。
另外React另外一個核心:數據模型props和state,對應著也有自個狀態(tài)方法
getInitialState()
獲取初始化state。
getDefaultProps()
獲取默認props。對于那些沒有父組件傳遞的props,通過該方法設置默認的props
componentWillReceiveProps()
已插入的組件收到新的props時調用
還有一個特殊狀態(tài)的處理函數,用于優(yōu)化處理
shouldComponentUpdate()
:判斷組件是否需要update調用
加上最重要的render方法,React自身帶的方法剛剛好10個。對于初學者來說是比較難以消化。但其實getInitialState
,componentDidMount
,render
三個狀態(tài)方法都能完成大部分組件,不必望而卻步。
回到組件化的主題。
一個頁面結構模塊化的組件,能獨立封裝整個組件的過程線
我們換算成React生命周期方法:
組件的狀態(tài)方法流中,有兩點需要特殊說明:
1,二次渲染:
由于React的虛擬Dom特性,組件的render函數不需自己觸發(fā),根據props和state的改變自個通過差異算法,得出最優(yōu)的渲染。
請求CGI一般都是異步,所以必定帶來二次渲染。只是空數據渲染的時候,有可能會被React優(yōu)化掉。當數據回來,通過setState,觸發(fā)二次render
2,componentWiillMount與componentDidMount的差別
和大多數React的教程文章不一樣,ajax請求我建議在WillMount的方法內執(zhí)行,而不是組件初始化成功之后的DidMount。這樣能在“空數據渲染”階段之前請求數據,盡早地減少二次渲染的時間。
willMount
只會執(zhí)行一次,非常適合做init的事情。
didMount
也只會執(zhí)行一次,并且這時候真實的Dom已經形成,非常適合事件綁定和complete類的邏輯。
二,JSX很丑,但是組件內聚的關鍵!
WebComponents的標準之一,需要模板能力。本是以為是我們熟悉的模板能力,但React中的JSX這樣的怪胎還是令人議論紛紛。React還沒有火起來的時候,大家就已經在微博上狠狠地吐槽了“JSX寫的代碼這TM的丑”。這其實只是Demo階段JSX,等到實戰(zhàn)的大型項目中的JSX,包含多狀態(tài)多數據多事件的時候,你會發(fā)現………….JSX寫的代碼還是很丑。
為什么我們會覺得丑?因為我們早已經對“視圖-樣式-邏輯”分離的做法潛移默化。
基于維護性和可讀性,甚至性能,我們都不建議直接在Dom上面綁定事件或者直接寫style屬性。我們會在JS寫事件代理,在CSS上寫上classname,html上的就是清晰的Dom結構。我們很好地維護著MVC的設計模式,一切安好。直到JSX把他們都糅合在一起,所守護的技術棧受到侵略,難免有所抵制。
但是從組件化的目的來看,這種高內聚的做法未嘗不可。
下面的代碼,之前的“邏輯視圖分離”模式,我們需要去找相應的js文件,相應的event函數體內,找到td-info的class所綁定的事件。
對比起JSX的高度內聚,所有事件邏輯就是在本身jsx文件內,綁定的就是自身的showInfo方法。組件化的特性能立馬體現出來。
(注意:雖然寫法上我們好像是HTML的內聯(lián)事件處理器,但是在React底層并沒有實際賦值類似onClick屬性,內層還是使用類似事件代理的方式,高效地維護著事件處理器)
再來看一段style的jsx。其實jsx沒有對樣式有硬性規(guī)定,我們完全可遵循之前的定義class的邏輯。任何一段樣式都應該用class來定義。在jsx你也完全可以這樣做。但是出于組件的獨立性,我建議一些只有“一次性”的樣式直接使用style賦值更好。減少冗余的class。
XHTML
1 2 3 | className='list' style={{background: '#ddd'}}> {list_html} |
或許JSX內部有負責繁瑣的邏輯樣式,可JSX的自定義標簽能力,組件的黑盒性立馬能體驗出來,是不是瞬間美好了很多。
JavaScript
1 2 3 4 5 6 7 8 | render: function(){ return ( div> Menus bannerNums={this.state.list.length}>/Menus> TableList data={this.state.list}>/TableList> /div> ); } |
雖然JSX本質上是為了虛擬Dom而準備的,但這種邏輯和視圖高度合一對于組件化未嘗不是一件好事。
學習完React這個組件化框架后,看看組件化能力點的完成情況
“資源高內聚”—— (33%) html與js內聚
“作用域獨立”—— (50%) js的作用域獨立
“自定義標簽”—— (100%)jsx
“可相互組合”—— (50%) 可組合,但缺乏有效的加載方式
“接口規(guī)范化”—— (100%)組件生命周期方法
Webpack 資源組件化
對于組件化的資源獨立性,一般的模塊加載工具和構建流程視乎變得吃力。組件化的構建工程化,不再是之前我們常見的,css合二,js合三,而是體驗在組件間的依賴于加載關系。webpack正好符合需求點,一方面填補組件化能力點,另一方幫助我們完善組件化的整體構建環(huán)境。
首先要申明一點是,webpack是一個模塊加載打包工具,用于管理你的模塊資源依賴打包問題。這跟我們熟悉的requirejs模塊加載工具,和grunt/gulp構建工具的概念,多多少少有些出入又有些雷同。
首先webpak對于CommonJS與AMD同時支持,滿足我們模塊/組件的加載方式。
JavaScript
1 2 3 4 | require('module'); require('../file.js'); exports.doStuff = function() {}; module.exports = someValue; |
JavaScript
1 2 3 | define('mymodule', ['dep1', 'dep2'], function(d1, d2) { return someExportedValue; }); |
當然最強大的,最突出的,當然是模塊打包功能。這正是這一功能,補充了組件化資源依賴,以及整體工程化的能力
根據webpack的設計理念,所有資源都是“模塊”,webpack內部實現了一套資源加載機制,可以把想css,圖片等資源等有依賴關系的“模塊”加載。這跟我們使用requirejs這種僅僅處理js大大不同。而這套加載機制,通過一個個loader來實現。
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // webpack.config.js module.exports = { entry: { entry: './index.jsx', }, output: { path: __dirname, filename: '[name].min.js' }, module: { loaders: [ {test: /\.css$/, loader: 'style!css' }, {test: /\.(jsx|js)?$/, loader: 'jsx?harmony', exclude: /node_modules/}, {test: /\.(png|jpg|jpeg)$/, loader: 'url-loader?limit=10240'} ] } }; |
上面一份簡單的webpack配置文件,留意loaders的配置,數組內一個object配置為一種模塊資源的加載機制。test的正則為匹配文件規(guī)則,loader的為匹配到文件將由什么加載器處理,多個處理器之間用!
分隔,處理順序從右到左。
如style!css
,css文件通過css-loader(處理css),再到style-loader(inline到html)的加工處理流。
jsx文件通過jsx-loader編譯,‘?’開啟加載參數,harmony支持ES6的語法。
圖片資源通過url-loader加載器,配置參數limit,控制少于10KB的圖片將會base64化。
資源文件如何被require?
JavaScript
1 2 3 4 5 6 | // 加載組件自身css require('./slider.css'); // 加載組件依賴的模塊 var Clip = require('./clipitem.js'); // 加載圖片資源 var spinnerImg = require('./loading.png'); |
在webpack的js文件中我們除了require我們正常的js文件,css和png等靜態(tài)文件也可以被require進來。我們通過webpack命令,編譯之后,看看輸出結果如何:
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | webpackJsonp([0], { /* 0 */ /***/ function(module, exports, __webpack_require__) { // 加載組件自身css __webpack_require__(1); // 加載組件依賴的模塊 var Clip = __webpack_require__(5); // 加載圖片資源 var spinnerImg = __webpack_require__(6); /***/ }, /* 1 */ /***/ function(module, exports, __webpack_require__) { /***/ }, /* 2 */ /***/ function(module, exports, __webpack_require__) { exports = module.exports = __webpack_require__(3)(); exports.push([module.id, '.slider-wrap{\r\n position: relative;\r\n width: 100%;\r\n margin: 50px;\r\n background: #fff;\r\n}\r\n\r\n.slider-wrap li{\r\n text-align: center;\r\n line-height: 20px;\r\n}', '']); /***/ }, /* 3 */ /***/ function(module, exports) { /***/ }, /* 4 */ /***/ function(module, exports, __webpack_require__) { /***/ }, /* 5 */ /***/ function(module, exports) { console.log('hello, here is clipitem.js') ; /***/ }, /* 6 */ /***/ function(module, exports) { module.exports = 'data:image/png;base64,iVBORw0KGg......' /***/ } ]); |
webpack編譯之后,輸出文件視乎亂糟糟的,但其實每一個資源都被封裝在一個函數體內,并且以編號的形式標記(注釋)。這些模塊,由webpack的__webpack_require__內部方法加載。入口文件為編號0的函數index.js,可以看到__webpack_require__加載其他編號的模塊。
css文件在編號1,由于使用css-loader和style-loader,編號1-4都是處理css。其中編號2我們可以看我們的css的string體。最終會以內聯(lián)的方式插入到html中。
圖片文件在編號6,可以看出exports出base64化的圖片。
組件一體輸出
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // 加載組件自身css require('./slider.css'); // 加載組件依賴的模塊 var React = require('react'); var Clip = require('../ui/clipitem.jsx'); // 加載圖片資源 var spinnerImg = require('./loading.png'); var Slider = React.createClass({ getInitialState: function() { // ... }, componentDidMount: function(){ // ... }, render: function() { return ( div> Clip data={this.props.imgs} /> img className='loading' src={spinnerImg} /> /div> ); } }); module.exports = Slider; |
如果說,react使到html和js合為一體。
那么加上webpack,兩者結合一起的話。js,css,png(base64),html 所有web資源都能合成一個JS文件。這正是這套方案的核心所在:組件獨立一體化。如果要引用一個組件,僅僅require('./slider.js')
即可完成。
加入webpack的模塊加載器之后,我們組件的加載問題,內聚問題也都成功地解決掉
“資源高內聚”—— (100%) 所有資源可以一js輸出
“可相互組合”—— (100%) 可組合可依賴加載
CSS模塊化實踐
很高興,你能閱讀到這里。目前我們的組件完成度非常的高,資源內聚,易于組合,作用域獨立互不污染。。。。等等
,視乎CSS模塊的完成度有欠缺。那么目前組件完成度來看,CSS作用域其實是全局性的,并非組件內部獨立。下一步,我們要做得就是如何讓我們組件內部的CSS作用域獨立。
這時可能有人立馬跳出,大喊一句“德瑪西亞!”,哦不,應該是“用sass啊傻逼!”??墒?strong>項目組件化之后,組件的內部封裝已經很好了,其內部dom結構和css趨向簡單,獨立,甚至是破碎的。LESS和SASS的一體式樣式框架的設計,他的嵌套,變量,include,函數等豐富的功能對于整體大型項目的樣式管理非常有效。但對于一個功能單一組件內部樣式,視乎就變的有點格格不入?!安荒転榱丝蚣芏蚣埽线m才是最好的”。視乎原生的css能力已經滿足組件的樣式需求,唯獨就是上面的css作用域問題。
這里我給出思考的方案: classname隨便寫,保持原生的方式。編譯階段,根據組件在項目路徑的唯一性,由【組件classname+組件唯一路徑】打成md5,生成全局唯一性classname。正當我要寫一個loader實現我的想法的時候,發(fā)現歪果仁已經早在先走一步了。。。。
這里具體方案參考我之前博客的譯文:http://www.alloyteam.com/2015/10/8536/
之前我們討論過JS的模塊。現在通過Webpack被加載的CSS資源叫做“CSS模塊”?我覺得還是有問題的?,F在style-loader插件的實現本質上只是創(chuàng)建link[rel=stylesheet]元素插入到document中。這種行為和通常引入JS模塊非常不同。引入另一個JS模塊是調用它所提供的接口,但引入一個CSS卻并不“調用”CSS。所以引入CSS本身對于JS程序來說并不存在“模塊化”意義,純粹只是表達了一種資源依賴——即該組件所要完成的功能還需要某些asset。
因此,那位歪果仁還擴展了“CSS模塊化”的概念,除了上面的我們需要局部作用域外,還有很多功能,這里不詳述。具體參考原文 http://glenmaddern.com/articles/css-modules
非常贊的一點,就是cssmodules已經被css-loader收納。所以我們不需要依賴額外的loader,基本的css-loader開啟參數modules即可
JavaScript
1 2 3 4 5 6 7 8 | //webpack.config.js ... module: { loaders: [ {test: /\.css$/, loader: 'style!css?modules&localIdentName=[local]__[name]_[hash:base64:5]' }, ] } .... |
modules參數代表開啟css-modules功能,loaclIdentName為設置我們編譯后的css名字,為了方便debug,我們把classname(local)和組件名字(name)輸出。當然可以在最后輸出的版本為了節(jié)省提交,僅僅使用hash值即可。另外在react中的用法大概如下。
JavaScript
1 2 3 4 5 6 7 8 9 10 11 | var styles = require('./banner.css'); var Banner = new React.createClass({ ... render: function(){ return ( div> div className={styles.classA}>/div> /div> ) } }); |
最后這里關于出于對CSS一些思考,
關于css-modules的其它功能,我并不打算使用。在內部分享【我們竭盡所能地讓CSS變得復雜】中提及:
我們項目中大部分的CSS都不會像boostrap那樣需要變量來設置,身為一線開發(fā)者的我們大概能夠感受到:設計師們改版UI,絕對不是簡單的換個色或改個間距,而是面目全非的全新UI,這絕對不是一個變量所能解決的”維護性“。
反而項目實戰(zhàn)過程中,真正要解決的是:在版本迭代過程中那些淘汰掉的過期CSS,大量地堆積在項目當中。我們像極了家中的歐巴醬不舍得丟掉沒用的東西,因為這可是我們使用sass或less編寫出具有高度的可維護性的,肯定有復用的一天。
這些堆積的過期CSS(or sass)之間又有部分依賴,一部分過期沒用了,一部分又被新的樣式復用了,導致沒人敢動那些歷史樣式。結果現網項目迭代還帶著大量兩年前沒用的樣式文件。
組件化之后,css的格局同樣被革新了。可能postcss才是你現在手上最適合的工具,而不在是sass。
到這里,我們終于把組件化最后一個問題也解決了。
“作用域獨立”—— (100%) 如同shadowDom作用域獨立
到這里,我們可以開一瓶82年的雪碧,好好慶祝一下。不是嗎?
組件化之路還在繼續(xù)
webpack和react還有很多新非常重要的特性和功能,介于本文僅僅圍繞著組件化的為核心,沒有一一闡述。另外,配搭gulp/grunt補充webpack構建能力,webpack的codeSplitting,react的組件通信問題,開發(fā)與生產環(huán)境配置等等,都是整套大型項目方案的所必須的,限于篇幅問題??梢缘鹊任腋孪缕?,或大家可以自行查閱。
但是,不得不再安利一下react-hotloader神器。熱加載的開發(fā)模式絕對是下一代前端開發(fā)必備。嚴格說,如果沒有了熱加載,我會很果斷地放棄這套方案,即使這套方案再怎么優(yōu)秀,我都討厭react需要5~6s的編譯時間。但是hotloader可以在我不刷新頁面的情況下,動態(tài)修改代碼,而且不單單是樣式,連邏輯也是即時生效。
如上在form表單內。使用熱加載,表單不需要重新填寫,修改submit的邏輯立刻生效。這樣的開發(fā)效率真不是提高僅僅一個檔次。必須安利一下。
或許你發(fā)現,使用組件化方案之后,整個技術棧都被更新了一番。學習成本也不少,并且可以預知到,基于組件化的前端還會很多不足的問題,例如性能優(yōu)化方案需要重新思考,甚至最基本的組件可復用性不一定高。后面很長一段時間,需要我們不斷磨練與優(yōu)化,探求最優(yōu)的前端組件化之道。
至少我們可以想象,不再擔心自己寫的代碼跟某個誰誰沖突,不再為找某段邏輯在多個文件和方法間穿梭,不再copy一片片邏輯然后改改。我們每次編寫都是可重用,可組合,獨立且內聚的組件。而每個頁面將會由一個個嵌套組合的組件,相互獨立卻相互作用。
對于這樣的前端未來,有所期待,不是很好嗎
至此,感謝你的閱讀。
點擊下面 “閱讀原文” 查看詳情!
聯(lián)系客服