了解前端理論:rscss和rsjs
在前端開(kāi)發(fā)中,我們會(huì)嘗試去定一些規(guī)則和約定,來(lái)讓項(xiàng)目質(zhì)量更高,更易于維護(hù)。而對(duì)于這些規(guī)則和約定,我們也會(huì)希望它內(nèi)容簡(jiǎn)單,容易理解。
rscss和rsjs是一套比較新,也比較小巧的前端開(kāi)發(fā)規(guī)則和約定,其中rs代表Reasonable System,所以可以理解為,追求“合理”的css和js。本文除了介紹它們,還會(huì)有一點(diǎn)補(bǔ)充以及我自己的看法,也推薦你點(diǎn)擊鏈接閱讀原作者給出的完整內(nèi)容。
從css的疑問(wèn)開(kāi)始
rscss希望有效地改善寫css中的這樣幾個(gè)常見(jiàn)問(wèn)題(css哲學(xué)三問(wèn)):
- 這個(gè)class到底什么意思?
- 這個(gè)class還有地方用到嗎?
- 我新寫的這個(gè)class,會(huì)有沖突嗎?
組件原則
rscss首先推崇的是以組件(Components)為基礎(chǔ)的思考方式。在各類前端框架中,幾乎都可以看到組件,如Bootstrap和Materialize:
一個(gè)組件是這樣的感覺(jué):
小到一個(gè)按鈕,大到整個(gè)web應(yīng)用,可見(jiàn)的視覺(jué)元素都可以這樣當(dāng)做一個(gè)組件。
組件的命名
rscss推薦組件至少使用兩個(gè)單詞的命名,中間用短橫線(-
)連接:
.search-form { /* ... */ } .article-card { /* ... */ }
組件的元素
組件內(nèi)部的更細(xì)小的部分,當(dāng)做組件的元素(Elements)。
元素的命名
為了和前面的組件區(qū)分開(kāi)來(lái),元素的命名只使用一個(gè)單詞。
顯然,只有一個(gè)單詞是很容易沖突的,因此rscss建議以關(guān)系選擇符把元素和組件關(guān)聯(lián)起來(lái):
.search-form > .field { /* ... */ } .search-form > .action { /* ... */ }
推薦子選擇符 > 而不是包含選擇符 (空格),以更好地避免沖突:
.article-card .title { /* okay */ } .article-card > .author { /* ✓ better */ }
如果確實(shí)需要用到多個(gè)單詞,直接連接它們(不使用短橫線等分隔符),以體現(xiàn)區(qū)別:
.profile-box > .firstname { /* ... */ }
為每一個(gè)組件的元素使用class名,不要使用標(biāo)簽選擇符。有名字的元素會(huì)更有語(yǔ)義。
多種屬性或狀態(tài)
無(wú)論是組件還是元素,都可以有多種屬性或狀態(tài)(Variants,也可以叫變體):
屬性或狀態(tài)的命名
使用短橫線(-)開(kāi)頭來(lái)命名表示屬性或狀態(tài)的class。
/* component variants */ .like-button.-wide { /* ... */ } .like-button.-disabled { /* ... */ } /* element variants */ .shopping-card > .title.-small { /* ... */ }
對(duì)命名方式的解釋
rscss推薦的短橫線作為前綴的class名可能會(huì)讓你有一點(diǎn)驚訝,可以這樣寫的嗎?答案是的確可以,而且搭配得還相當(dāng)巧妙。為什么這么說(shuō)呢?請(qǐng)看w3c對(duì)css標(biāo)識(shí)符的解釋:
In CSS, identifiers (including element names, classes, and IDs in selectors) can contain only the characters [a-zA-Z0-9] and ISO 10646 characters U+0080 and higher, plus the hyphen (-) and the underscore (_); they cannot start with a digit, two hyphens, or a hyphen followed by a digit.
其中ISO 10646等同于Unicode??梢钥吹?,w3c特意在css標(biāo)識(shí)符一般使用的英文字母、數(shù)字以及一部分Unicode字符(U+0080以上)之外,提到了短橫線(-)和下劃線(_)也是可用的。
以短橫線作為前綴的class名相當(dāng)于有了一個(gè)特殊的標(biāo)記,一眼就可以提醒你這是一個(gè)表示屬性或狀態(tài)的class。
組件嵌套
組件是可以嵌套的。
對(duì)應(yīng)html類似這樣:
<div class="article-link"> <div class="vote-box"> ... </div> <h3 class="title">...</h3> <p class="meta">...</p> </div>
嵌套中的屬性或狀態(tài)
當(dāng)一個(gè)組件位于另一個(gè)組件內(nèi)部的時(shí)候,可能會(huì)想要這個(gè)組件表現(xiàn)得特別一點(diǎn)。這個(gè)時(shí)候,建議不要使用關(guān)系選擇符把它們耦合在一起:
.article-header > .vote-box > .up { /* ✗ avoid this */ }
建議的做法是為組件增加一個(gè)屬性或狀態(tài)class:
<div class="article-header"> <div class="vote-box -highlight"> ... </div> ... </div>
然后以這個(gè)class為基礎(chǔ)來(lái)定義特別的樣式:
.vote-box.-highlight > .up { /* ... */ }
這樣做的目的是讓一個(gè)組件的樣式不依賴其所處的位置。OOCSS的原則之一,Separate container and content,也是這樣的理念。
布局思想
rscss推薦除一些具有固定寬高的特定元素(如頭像,logo)外,組件本身不定義任何影響布局位置的屬性:
- 定位(
position
、top
、left
、right
、bottom
) - 浮動(dòng)(
float
、clear
) - 外邊距(
margin
) - 尺寸(
width
、height
)
這樣做的意思是說(shuō),如果把組件看做一個(gè)整體,它應(yīng)該是自適應(yīng)的。
需要定義布局位置屬性的情況
如果要定義組件的影響布局位置的屬性,建議使用關(guān)系選擇符把組件和它所處的環(huán)境關(guān)聯(lián)起來(lái):
.article-list > .article-card { width: 33.3%; float: left; } .article-card { /* ... */ } .article-card > .image { /* ... */ } .article-card > .title { /* ... */ } .article-card > .category { /* ... */ }
在上面這段代碼可以注意到,“組件本身的外觀”與“組件在某一環(huán)境中的位置”被明確地分離了。
輔助類
rscss推薦輔助類(Helpers)單獨(dú)存放一個(gè)文件,且class名以下劃線(_
)開(kāi)頭。輔助類也常會(huì)用到!important
,對(duì)應(yīng)的,應(yīng)盡可能少使用輔助類。
._pull-left { float: left !important; } ._pull-right { float: right !important; }
下劃線(_)作為前綴的class名,如前文已經(jīng)解釋過(guò)的那樣,也是作為一個(gè)特殊的標(biāo)記提醒你這是一個(gè)輔助類,請(qǐng)謹(jǐn)慎使用它。
輔助類在前端框架中也很常見(jiàn)。
rscss與其他css理論的比較
rscss的組件(Component),元素(Element)等概念,在BEM、SMACSS這些css理論中也有類似的存在。它們比較起來(lái)是這樣的:
以上就是rscss的主要內(nèi)容了,下面來(lái)看看rsjs。
關(guān)注傳統(tǒng)web應(yīng)用的rsjs
rsjs關(guān)注的是非單頁(yè)應(yīng)用(non-SPA web application),也就是我們通常理解的有很多頁(yè),主要使用jQuery,而且每個(gè)頁(yè)都可以有自己的.js文件的傳統(tǒng)網(wǎng)站。
現(xiàn)在已經(jīng)有了可遵循的JavaScript代碼本身的風(fēng)格指南,因此,rsjs只對(duì)一些其他的要點(diǎn)提出建議,如命名空間,文件組織方式。
行為原則
rsjs推薦把由JavaScript實(shí)現(xiàn)的交互功能當(dāng)做一次只影響一個(gè)組件(Component)的行為(Behavior)。下面是一個(gè)參考示例:
<div class="main-navbar" data-js-collapsible-nav> <button class="expand" data-js-expand>Expand</button> <a href="/">Home</a> <ul>...</ul> </div> /* Behavior - behaviors/collapsible-nav.js */ $(function () { var $nav = $("[data-js-collapsible-nav]"); if (!$nav.length) return; $nav .on("click", "[data-js-expand]", function () { $nav.addClass("-expanded"); }) .on("mouseout", function () { $nav.removeClass("-expanded"); }); });
這其中包含了多項(xiàng)建議。
使用data屬性
建議使用html5的data自定義屬性data-js-___
來(lái)標(biāo)記和一個(gè)行為有關(guān)的DOM元素。
相比用ID和class來(lái)選取元素,這種data屬性的形式一方面更具有明確的意義,提醒你這是一個(gè)和交互行為有關(guān)的元素,另一方面更易于復(fù)用,在任何DOM結(jié)構(gòu)里添加這樣的data屬性即可獲得對(duì)應(yīng)的行為。
為每個(gè)行為單獨(dú)建立文件
建議每一個(gè)行為對(duì)應(yīng)的JavaScript代碼都分離到單獨(dú)的文件里,并以文件名明示。文件名可以參照data-js-___
這個(gè)屬性名里的對(duì)應(yīng)名稱,這樣,根據(jù)屬性名就很容易找到對(duì)應(yīng)的JavaScript代碼。
一個(gè)可能的文件目錄結(jié)構(gòu):
└── javascripts/ └── behaviors/ ├── collapsible-nav.js ├── avatar-hover.js ├── popup-dialog.js └── notification.js
不使用行內(nèi)JavaScript
在html中不要以<script>...</script>
或onclick=""
等形式添加行內(nèi)JavaScript代碼。通過(guò)保持行為的邏輯代碼獨(dú)立于html,可以使代碼更易于維護(hù)。
從rsjs的內(nèi)容來(lái)看,在已有React、Vue等庫(kù)的今天,“行為獨(dú)立于內(nèi)容”的約定仍然對(duì)傳統(tǒng)的以jQuery為主的Web應(yīng)用有一定意義。
初始數(shù)據(jù)的獲取方式
傳統(tǒng)Web站點(diǎn)的一個(gè)常見(jiàn)的場(chǎng)景是,后端語(yǔ)言在頁(yè)面中預(yù)先輸出某些數(shù)據(jù),然后JavaScript會(huì)取用它們。你可能見(jiàn)到過(guò)下面這樣<script>
標(biāo)簽的實(shí)現(xiàn)方式,但顯然,根據(jù)上一條建議,這是應(yīng)避免的。
<script> window.UserData = { email: "john@gmail.com", id: 9283 } </script>
rsjs建議的方案是,如果這些數(shù)據(jù)只需要一個(gè)組件使用,可以利用之前提到的data屬性(保存為值),由行為的JavaScript代碼來(lái)自行取出。
<!-- ✓ Used by the user-info behavior --> <div class="user-info" data-js-user-info='{"email":"john@gmail.com","id":9283}'>
如果是多個(gè)組件使用的數(shù)據(jù),可以使用<head>
里的meta標(biāo)簽。
<head> ... <!-- option 1 --> <meta property="app:user_data" content='{"email":"john@gmail.com","id":9283}'> <!-- option 2 --> <meta property="app:user_data:email" content="john@gmail.com"> <meta property="app:user_data:id" content="9283">
命名空間
rsjs建議使用盡可能少的全局變量。共用的類,函數(shù),放到單個(gè)Object里,比如叫App
:
if (!window.App) window.App = {}; App.Editor = function() { // ... };
在多個(gè)行為之間可復(fù)用的幫助方法,可以單獨(dú)建立Object,并將它們分文件保存在helpers/
:
/* helpers/format_error.js */ if (!window.Helpers) window.Helpers = {}; Helpers.formatError = function (err) { return "" + err.project_id + " error: " + err.message; };
第三方庫(kù)的處理
rsjs建議如果引入第三方庫(kù),也做成組件行為的形式。比如,Select2的功能,可以只影響帶有屬性data-js-select2
的元素。
// select2.js -- affects `[data-js-select2]` $(function () { $("[data-js-select2]").select2(); });
所有第三方庫(kù)的代碼可以集中到一個(gè)類似vendor.js
的文件,并和站點(diǎn)本身的代碼各自獨(dú)立。這樣,當(dāng)站點(diǎn)更新代碼的時(shí)候,用戶可以直接利用緩存,而并不需要再次獲取這些第三方庫(kù)代碼。
rsjs對(duì)自己的歸納
rsjs認(rèn)為自身的內(nèi)容更偏向于對(duì)開(kāi)發(fā)者友好,也就是更易于維護(hù),而在性能上(對(duì)用戶友好)可能沒(méi)有做到最好。以上提到的各項(xiàng)建議,也是有利有弊,rsjs只是在權(quán)衡了利弊的基礎(chǔ)上得到的更利于長(zhǎng)期維護(hù)的結(jié)論。
rsjs不是萬(wàn)金油,它不適用于單頁(yè)應(yīng)用(SPA)等前端功能很復(fù)雜的情況。它關(guān)注的是的那種多個(gè)網(wǎng)頁(yè),每個(gè)網(wǎng)頁(yè)一點(diǎn)JavaScript交互的傳統(tǒng)網(wǎng)站。
結(jié)語(yǔ)
rscss和rsjs所用的“合理”是一個(gè)很取巧的表述,不是完美,不是最好,也不是出色,它只是在說(shuō)希望代碼能“合乎道理”。rscss和rsjs大概就是這樣,以簡(jiǎn)約的風(fēng)格,不長(zhǎng)的篇幅,追求著“小而合理”。
目前rsjs還在更新中(work-in-progress),rscss則已經(jīng)比較成熟。很推薦試試其中你也認(rèn)為合理的建議!
相關(guān)文章
Websocket協(xié)議詳解及簡(jiǎn)單實(shí)例代碼
這篇文章主要介紹了Websocket協(xié)議詳解及簡(jiǎn)單實(shí)例代碼的相關(guān)資料,這里對(duì)websocket 協(xié)議進(jìn)行詳細(xì)介紹,并附簡(jiǎn)單實(shí)例代碼,需要的朋友可以參考下2016-12-12深入理解JavaScript系列(42):設(shè)計(jì)模式之原型模式詳解
這篇文章主要介紹了深入理解JavaScript系列(42):設(shè)計(jì)模式之原型模式詳解,原型模式(prototype)是指用原型實(shí)例指向創(chuàng)建對(duì)象的種類,并且通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象,需要的朋友可以參考下2015-03-03溫故知新——JavaScript中的字符串連接問(wèn)題最全總結(jié)(推薦)
這篇文章主要介紹了JS中的字符串連接問(wèn)題,ECMAScript 中最常見(jiàn)的一個(gè)問(wèn)題是字符串連接的性能。在調(diào)用 join() 方法時(shí)才會(huì)發(fā)生連接操作,具體操作步驟大家可查看下文的詳細(xì)講解,感興趣的小伙伴們可以參考一下。2017-08-08javascript 數(shù)組的定義和數(shù)組的長(zhǎng)度
本文主要介紹javascript 數(shù)組的定義和數(shù)組的長(zhǎng)度,比較簡(jiǎn)單,希望能給大家做一個(gè)參考。2016-06-06