AlloyTeam:致我們終將組件化的 Web (多圖)
這篇文章將從兩年前的一次技術爭論開始。爭論的聚焦就是下圖的兩個目錄分層結構。我說按模塊劃分好,他說你傻逼啊,當然是按資源劃分。
”按模塊劃分“目錄結構,把當前模塊下的所有邏輯和資源都放一起了,這對于多人獨自開發和維護個人模塊不是很好嗎?當然了,那爭論的結果是我乖乖地改回主流的”按資源劃分“的目錄結構。因為,沒有做到JS模塊化和資源模塊化,僅僅物理位置上的模塊劃分是沒有意義的,只會增加構建的成本而已。
雖然他說得好有道理我無言以對,但是我心不甘,等待他日前端組件化成熟了,再來一戰!
而今天就是我重申正義的日子!只是當年那個跟你撕逼的人不在。
## 模塊化的不足
模塊一般指能夠獨立拆分且通用的代碼單元。由于JavaScript語言本身沒有內置的模塊機制(ES6有了!!),我們一般會使用CMD或ADM建 立起模塊機制。現在大部分稍微大型一點的項目,都會使用requirejs或者seajs來實現JS的模塊化。多人分工合作開發,其各自定義依賴和暴露接 口,維護功能模塊間獨立性,對于項目的開發效率和項目后期擴展和維護,都是是有很大的幫助作用。
但,麻煩大家稍微略讀一下下面的代碼
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 。 方法之間線性跳轉,你大概也能感受這樣代碼弊端。隨著頁面邏輯越來越復雜,這條”過程線“也會越來越長,并且越來越繞。加之缺少規范約束,其他項目成員根 據各自需要,在”過程線“加插各自邏輯,最終這個頁面的邏輯變得難以維護。
開發需要小心翼翼,生怕影響“過程線”后面正常邏輯。并且每一次加插或修改都是bug泛濫,無不令產品相關人員個個提心吊膽。
## 頁面結構模塊化
基于上面的面向過程的問題,行業內也有不少解決方案,而我們團隊也總結出一套成熟的解決方案:Abstractjs, 頁面結構模塊化。我們可以把我們的頁面想象為一個樂高機器人,需要不同零件組裝,如下圖,假設頁面劃分為 tabContainer,listContainer和imgsContainer三個模塊。最終把這些模塊add到最終的pageModel里面,最 終使用rock方法讓頁面啟動起來。
(原過程線示例圖)
(頁面結構化示例圖)
下面是偽代碼的實現
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: "<li soda-repeat='item in data.tabs'>{{item}}</li>", 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是什么,綁定了什么事件,做了什么上報,出錯怎么處理。新增的代碼就應該放置在相 應的模塊上相應的狀態方法(preload,process,event,complete…),杜絕了以往的無規則亂增代碼的行文。并且,根據不同業務 邏輯封裝不同類型的Model,如列表滾動的ScrollModel,滑塊功能的SliderModel等等,可以進行高度封裝,集中優化。
現在基于Model的頁面結構開發,已經帶有一點”組件化“的味道。每個Model都帶有各自的數據,模板,邏輯。已經算是一個完整的功能單元。但距離真正的WebComponent還是有一段距離,至少滿足不了我的”理想目錄結構“。
## WebComponents 標準
我們回顧一下使用一個datapicker的jquery的插件,所需要的步奏:
1. 引入插件js
2. 引入插件所需的css(如果有)
3. copy 組件的所需的html片段
4. 添加代碼觸發組件啟動
現階段的“組件”基本上只能達到是某個功能單元上的集合。他的資源都是松散地分散在三種資源文件中,而且組件作用域暴露在全局作用域下,缺乏內聚性很容易就會跟其他組件產生沖突,如最簡單的css命名沖突。對于這種“組件”,還不如上面的頁面結構模塊化。
于是W3C按耐不住了,制定一個WebComponents標準,為組件化的未來指引了明路。
下面以較為簡潔的方式介紹這份標準,力求大家能夠快速了解實現組件化的內容。(對這部分了解的同學,可以跳過這一小節)
1. <template>模板能力
模板這東西大家最熟悉不過了,前些年見的較多的模板性能大戰artTemplate,juicer,tmpl,underscoretemplate等等。而現在又有mustachejs無邏輯模板引擎等新入選手。可是大家有沒有想過,這么基礎的能力,原生HTML5是不支持的(T_T)。
而今天WebComponent將要提供原生的模板能力
<template id="datapcikerTmpl"> <div>我是原生的模板</div> </template>
template標簽內定義了myTmpl的模板,需要使用的時候就要innerHTML= document.querySelector('#myTmpl').content
;可以看出這個原生的模板夠原始,模板占位符等功能都沒有,對于動態數據渲染模板能力只能自力更新。
2. ShadowDom 封裝組件獨立的內部結構
ShadowDom可以理解為一份有獨立作用域的html片段。這些html片段的CSS環境和主文檔隔離的,各自保持內部的獨立性。也正是ShadowDom的獨立特性,使得組件化成為了可能。
在具體dom節點上使用createShadowRoot方法即可生成其ShadowDom。就像在整份Html的屋子里面,新建了一個shadow的房間。房間外的人都不知道房間內有什么,保持shadowDom的獨立性。
var wrap = document.querySelector('#wrap'); var shadow = wrap.createShadowRoot(); shadow.innerHTML = '<p>you can not see me </p>'
3. 自定義原生標簽
初次接觸Angularjs的directive指令功能,設定好組件的邏輯后,一個<Datepicker />就能引入整個組件。如此狂炫酷炸碉堡天的功能,實在令人拍手稱快,躍地三尺。
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。當解析器發現我們在文檔中標記它將檢 查是否一個名為createdCallback的方法。如果找到這個方法它將立即運行它,所以我們把克隆模板的內容來創建的ShadowDom。
最后,registerElement的方法傳遞我們的prototype來注冊自定義標簽。
上面的代碼開始略顯復雜了,把前面兩個能力“模板”“shadowDom”結合,形成組件的內部邏輯。最后通過registerElement的方式注冊組件。之后可以愉快地<datapicker></datapicker>的使用。
4. imports解決組件間的依賴
<link rel="import" href="datapciker.html">
這個類php最常用的html導入功能,HTML原生也能支持了。
WebComponents標準內容大概到這里,是的,我這里沒有什么Demo,也沒有實踐經驗分享。由于webComponents新特性,基本上除了 高版本的Chrome支持外,其他瀏覽器的支持度甚少。雖然有polymer幫忙推動webcompoents的庫存在,但是polymer自身的要求版 本也是非常高(IE10+)。所以今天的主角并不是他。
我們簡單來回顧一下WebCompoents的四部分功能:
1 .<template>定義組件的HTML模板能力
2. Shadow Dom封裝組件的內部結構,并且保持其獨立性
3. Custom Element 對外提供組件的標簽,實現自定義標簽
4. import解決組件結合和依賴加載
## 組件化實踐方案
官方的標準看完了,我們思考一下。一份真正成熟可靠的組件化方案,需要具備的能力。
“資源高內聚”—— 組件資源內部高內聚,組件資源由自身加載控制
“作用域獨立”—— 內部結構密封,不與全局或其他組件產生影響
“自定義標簽”—— 定義組件的使用方式
“可相互組合”—— 組件正在強大的地方,組件間組裝整合
“接口規范化”—— 組件接口有統一規范,或者是生命周期的管理
個人認為,模板能力是基礎能力,跟是否組件化沒有強聯系,所以沒有提出一個大點。
既然是實踐,現階段WebComponent的支持度還不成熟,不能作為方案的手段。而另外一套以高性能虛擬Dom為切入點的組件框架React,在 非死book的造勢下,社區得到了大力發展。另外一名主角Webpack,負責解決組件資源內聚,同時跟React極度切合形成互補。
所以【Webpack】+【React】將會是這套方案的核心技術。
不知道你現在是“又是react+webpack”感到失望,還是“太好了是react+webpack”不用再學一次新框架的高興
。無論如何下面的內容不會讓你失望的。
### 一,組件生命周期
React天生就是強制性組件化的,所以可以從根本性上解決面向過程代碼所帶來的麻煩。React組件自身有生命周期方法,能夠滿足“接口規范化”能力 點。并且跟“頁面結構模塊化”的所封裝抽離的幾個方法能一一對應。另外react的jsx自帶模板功能,把html頁面片直接寫在render方法內,組 件內聚性更加緊密。
由于React編寫的JSX是會先生成虛擬Dom的,需要時機才真正插入到Dom樹。使用React必須要清楚組件的生命周期,其生命周期三個狀態:
Mount
: 插入Dom
Update
: 更新Dom
Unmount
: 拔出Dom
mount這單詞翻譯增加,嵌入等。我倒是建議“插入”更好理解。插入!拔出!插入!拔出!默念三次,懂了沒?別少看黃段子的力量,
組件狀態就是: 插入-> 更新 ->拔出。
然后每個組件狀態會有兩種處理函數,一前一后,will函數和did函數。
componentWillMount()
準備插入前
componentDidlMount()
插入后
componentWillUpdate()
準備更新前
componentDidUpdate()
更新后
componentWillUnmount()
準備拔出前
因為拔出后基本都是賢者形態(我說的是組件),所以沒有DidUnmount這個方法。
另外React另外一個核心:數據模型props和state,對應著也有自個狀態方法
getInitialState()
獲取初始化state。
getDefaultProps()
獲取默認props。對于那些沒有父組件傳遞的props,通過該方法設置默認的props
componentWillReceiveProps()
已插入的組件收到新的props時調用
還有一個特殊狀態的處理函數,用于優化處理
shouldComponentUpdate()
:判斷組件是否需要update調用
加上最重要的render方法,React自身帶的方法剛剛好10個。對于初學者來說是比較難以消化。但其實getInitialState
,componentDidMount
,render
三個狀態方法都能完成大部分組件,不必望而卻步。
回到組件化的主題。
一個頁面結構模塊化的組件,能獨立封裝整個組件的過程線
我們換算成React生命周期方法:
組件的狀態方法流中,有兩點需要特殊說明:
1,二次渲染:
由于React的虛擬Dom特性,組件的render函數不需自己觸發,根據props和state的改變自個通過差異算法,得出最優的渲染。
請求CGI一般都是異步,所以必定帶來二次渲染。只是空數據渲染的時候,有可能會被React優化掉。當數據回來,通過setState,觸發二次render
2,componentWiillMount與componentDidMount的差別
和大多數React的教程文章不一樣,ajax請求我建議在WillMount的方法內執行,而不是組件初始化成功之后的DidMount。這樣能在“空數據渲染”階段之前請求數據,盡早地減少二次渲染的時間。
willMount
只會執行一次,非常適合做init的事情。
didMount
也只會執行一次,并且這時候真實的Dom已經形成,非常適合事件綁定和complete類的邏輯。
### 二,JSX很丑,但是組件內聚的關鍵!
WebComponents的標準之一,需要模板能力。本是以為是我們熟悉的模板能力,但React中的JSX這樣的怪胎還是令人議論紛紛。React還 沒有火起來的時候,大家就已經在微博上狠狠地吐槽了“JSX寫的代碼這TM的丑”。這其實只是Demo階段JSX,等到實戰的大型項目中的JSX,包含多 狀態多數據多事件的時候,你會發現………….JSX寫的代碼還是很丑。
(即使用sublime-babel等插件高亮,邏輯和渲染耦合一起,閱讀性還是略差)
為什么我們會覺得丑?因為我們早已經對“視圖-樣式-邏輯”分離的做法潛移默化。
基于維護性和可讀性,甚至性能,我們都不建議直接在Dom上面綁定事件或者直接寫style屬性。我們會在JS寫事件代理,在CSS上寫上 classname,html上的就是清晰的Dom結構。我們很好地維護著MVC的設計模式,一切安好。直到JSX把他們都糅合在一起,所守護的技術棧受 到侵略,難免有所抵制。
但是從組件化的目的來看,這種高內聚的做法未嘗不可。
下面的代碼,之前的“邏輯視圖分離”模式,我們需要去找相應的js文件,相應的event函數體內,找到td-info的class所綁定的事件。
對比起JSX的高度內聚,所有事件邏輯就是在本身jsx文件內,綁定的就是自身的showInfo方法。組件化的特性能立馬體現出來。
<p className="td-info" onClick={this.showInfo}>{obj.info}</p>
(注意:雖然寫法上我們好像是HTML的內聯事件處理器,但是在React底層并沒有實際賦值類似onClick屬性,內層還是使用類似事件代理的方式,高效地維護著事件處理器)
再來看一段style的jsx。其實jsx沒有對樣式有硬性規定,我們完全可遵循之前的定義class的邏輯。任何一段樣式都應該用class來定義。在 jsx你也完全可以這樣做。但是出于組件的獨立性,我建議一些只有“一次性”的樣式直接使用style賦值更好。減少冗余的class。
<div className="list" style={{background: "#ddd"}}> {list_html} </div>
或許JSX內部有負責繁瑣的邏輯樣式,可JSX的自定義標簽能力,組件的黑盒性立馬能體驗出來,是不是瞬間美好了很多。
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%) 可組合,但缺乏有效的加載方式
“接口規范化”—— (100%)組件生命周期方法
### Webpack 資源組件化
對于組件化的資源獨立性,一般的模塊加載工具和構建流程視乎變得吃力。組件化的構建工程化,不再是之前我們常見的,css合二,js合三,而是體驗在組件 間的依賴于加載關系。webpack正好符合需求點,一方面填補組件化能力點,另一方幫助我們完善組件化的整體構建環境。
首先要申明一點是,webpack是一個模塊加載打包工具,用于管理你的模塊資源依賴打包問題。這跟我們熟悉的requirejs模塊加載工具,和grunt/gulp構建工具的概念,多多少少有些出入又有些雷同。
首先webpak對于CommonJS與AMD同時支持,滿足我們模塊/組件的加載方式。
require("module"); require("../file.js"); exports.doStuff = function() {}; module.exports = someValue;
define("mymodule", ["dep1", "dep2"], function(d1, d2) { return someExportedValue; });
當然最強大的,最突出的,當然是模塊打包功能。這正是這一功能,補充了組件化資源依賴,以及整體工程化的能力
根據webpack的設計理念,所有資源都是“模塊”,webpack內部實現了一套資源加載機制,可以把想css,圖片等資源等有依賴關系的“模塊”加載。這跟我們使用requirejs這種僅僅處理js大大不同。而這套加載機制,通過一個個loader來實現。
// 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的正則為匹配文件規則,loader的為匹配到文件將由什么加載器處理,多個處理器之間用!
分隔,處理順序從右到左。
如style!css
,css文件通過css-loader(處理css),再到style-loader(inline到html)的加工處理流。
jsx文件通過jsx-loader編譯,‘?’開啟加載參數,harmony支持ES6的語法。
圖片資源通過url-loader加載器,配置參數limit,控制少于10KB的圖片將會base64化。
#### 資源文件如何被require?
// 加載組件自身css require('./slider.css'); // 加載組件依賴的模塊 var Clip = require('./clipitem.js'); // 加載圖片資源 var spinnerImg = require('./loading.png');
在webpack的js文件中我們除了require我們正常的js文件,css和png等靜態文件也可以被require進來。我們通過webpack命令,編譯之后,看看輸出結果如何:
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體。最終會以內聯的方式插入到html中。
圖片文件在編號6,可以看出exports出base64化的圖片。
#### 組件一體輸出
// 加載組件自身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啊傻逼!”。可是項目組件化之后,組件的內部封裝已經很好了,其內部dom結構和css趨向簡單,獨立,甚至是破碎的。 LESS和SASS的一體式樣式框架的設計,他的嵌套,變量,include,函數等豐富的功能對于整體大型項目的樣式管理非常有效。但對于一個功能單一 組件內部樣式,視乎就變的有點格格不入。“不能為了框架而框架,合適才是最好的”。視乎原生的css能力已經滿足組件的樣式需求,唯獨就是上面的css作 用域問題。
這里我給出思考的方案: classname隨便寫,保持原生的方式。編譯階段,根據組件在項目路徑的唯一性,由【組件classname+組件唯一路徑】打成md5,生成全局唯 一性classname。正當我要寫一個loader實現我的想法的時候,發現歪果仁已經早在先走一步了。。。。
這里具體方案參考我之前博客的譯文:http://www.alloyteam.com/2015/10/8536/
之前我們討論過JS的模塊。現在通過Webpack被加載的CSS資源叫做“CSS模塊”?我覺得還是有問題的。現在style-loader插 件的實現本質上只是創建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即可
//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)輸出。當然可以在最后輸出的版本為了節省提交,僅僅使用hash值即可。另外在react中的用 法大概如下。
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那樣需要變量來設置,身為一線開發者的我們大概能夠感受到:設計師們改版UI,絕對不是簡單的換個色或改個間距,而是面目全非的全新UI,這絕對不是一個變量所能解決的”維護性“。
反而項目實戰過程中,真正要解決的是:在版本迭代過程中那些淘汰掉的過期CSS,大量地堆積在項目當中。我們像極了家中的歐巴醬不舍得丟掉沒用的東西,因為這可是我們使用sass或less編寫出具有高度的可維護性的,肯定有復用的一天。
這些堆積的過期CSS(or sass)之間又有部分依賴,一部分過期沒用了,一部分又被新的樣式復用了,導致沒人敢動那些歷史樣式。結果現網項目迭代還帶著大量兩年前沒用的樣式文件。
組件化之后,css的格局同樣被革新了。可能postcss才是你現在手上最適合的工具,而不在是sass。
到這里,我們終于把組件化最后一個問題也解決了。
“作用域獨立”—— (100%) 如同shadowDom作用域獨立
到這里,我們可以開一瓶82年的雪碧,好好慶祝一下。不是嗎?
### 組件化之路還在繼續
webpack和react還有很多新非常重要的特性和功能,介于本文僅僅圍繞著組件化的為核心,沒有一一闡述。另外,配搭gulp/grunt補充webpack構建能力,webpack的codeSplitting,react的組件通信問題,開發與生產環境配置等等,都是整套大型項目方案的所必須的,限于篇幅問題。可以等等我更新下篇,或大家可以自行查閱。
但是,不得不再安利一下react-hotloader神器。熱加載的開發模式絕對是下一代前端開發必備。嚴格說,如果沒有了熱加載,我會很果斷地放棄這套方案,即使這套方案再怎么優秀,我都討厭react需要5~6s的編譯時間。但是hotloader可以在我不刷新頁面的情況下,動態修改代碼,而且不單單是樣式,連邏輯也是即時生效。
如上在form表單內。使用熱加載,表單不需要重新填寫,修改submit的邏輯立刻生效。這樣的開發效率真不是提高僅僅一個檔次。必須安利一下。
或許你發現,使用組件化方案之后,整個技術棧都被更新了一番。學習成本也不少,并且可以預知到,基于組件化的前端還會很多不足的問題,例如性能優化方案需 要重新思考,甚至最基本的組件可復用性不一定高。后面很長一段時間,需要我們不斷磨練與優化,探求最優的前端組件化之道。
至少我們可以想象,不再擔心自己寫的代碼跟某個誰誰沖突,不再為找某段邏輯在多個文件和方法間穿梭,不再copy一片片邏輯然后改改。我們每次編寫都是可重用,可組合,獨立且內聚的組件。而每個頁面將會由一個個嵌套組合的組件,相互獨立卻相互作用。
對于這樣的前端未來,有所期待,不是很好嗎
至此,感謝你的閱讀。
轉載自 AlloyTeam:http://www.alloyteam.com/2015/11/we-will-be-componentized-web-long-text/