<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
classNames
是一個簡單的且實用的JavaScript
應用程式,可以有條件的將多個類名組合在一起。它是一個非常有用的工具,可以用來動態的新增或者刪除類名。
倉庫地址:classNames
根據classNames
的README
,可以發現庫的作者對這個庫非常認真,檔案和測試用例都非常齊全,同時還有有不同環境的支援。
其他的就不多介紹了,因為庫的作者寫的很詳細,就直接上使用範例:
var classNames = require('classnames'); classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar' classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar' classNames({ foo: true, bar: true }); // => 'foo bar'
classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'
classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'
var arr = ['b', { c: true, d: false }]; classNames('a', arr); // => 'a b c'
let buttonType = 'primary'; classNames({ [`btn-${buttonType}`]: true });
還有其他的使用方式,包括在React
中的使用,可以去看看README
,接下里就開始閱讀原始碼。
先來直接來看看classNames
的原始碼,主要是index.js
檔案,程式碼量並不多:
/*! Copyright (c) 2018 Jed Watson. Licensed under the MIT License (MIT), see http://jedwatson.github.io/classnames */ /* global define */ (function () { 'use strict'; var hasOwn = {}.hasOwnProperty; function classNames() { var classes = []; for (var i = 0; i < arguments.length; i++) { var arg = arguments[i]; if (!arg) continue; var argType = typeof arg; if (argType === 'string' || argType === 'number') { classes.push(arg); } else if (Array.isArray(arg)) { if (arg.length) { var inner = classNames.apply(null, arg); if (inner) { classes.push(inner); } } } else if (argType === 'object') { if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes('[native code]')) { classes.push(arg.toString()); continue; } for (var key in arg) { if (hasOwn.call(arg, key) && arg[key]) { classes.push(key); } } } } return classes.join(' '); } if (typeof module !== 'undefined' && module.exports) { classNames.default = classNames; module.exports = classNames; } else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) { // register as 'classnames', consistent with npm package name define('classnames', [], function () { return classNames; }); } else { window.classNames = classNames; } }());
可以看到,classNames
的實現非常簡單,一共就是50
行左右的程式碼,其中有一些是註釋,有一些是相容性的程式碼,主要的程式碼邏輯就是classNames
函數,這個函數就是我們最終使用的函數,接下來就來看看這個函數的實現。
直接看最後的一段if
判斷,這些就是相容性的程式碼:
if (typeof module !== 'undefined' && module.exports) { classNames.default = classNames; module.exports = classNames; } else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) { // register as 'classnames', consistent with npm package name define('classnames', [], function () { return classNames; }); } else { window.classNames = classNames; }
可以看到這裡相容了CommonJS
、AMD
、window
三種方式,這樣就可以在不同的環境下使用了。
一下就看到了三種相容性方式的區別和特性了:
CommonJS
是Node.js
的模組規範,Node.js
中使用require
來引入模組,使用module.exports
來匯出模組;
所以這裡通過判斷module
是否存在來判斷是否是CommonJS
環境,如果是的話,就通過module.exports
來匯出模組。
AMD
是RequireJS
在推廣過程中對模組定義的規範化產出,AMD
也是一種模組規範,AMD
中使用define
來定義模組,使用require
來引入模組;
所以這裡通過判斷define
是否存在來判斷是否是AMD
環境,如果是的話,就通過define
來定義模組。
window
是瀏覽器中的全域性物件,這裡並沒有判斷,直接使用else
兜底,因為這個庫最終只會在瀏覽器中使用,所以這裡直接使用window
來定義模組。
接下來就來看看classNames
函數的實現了,先來看看他是怎麼處理多個引數的:
function classNames() { for (var i = 0; i < arguments.length; i++) { var arg = arguments[i]; if (!arg) continue; } }
這裡是直接使用arguments
來獲取引數,然後遍歷引數,如果引數不存在,就直接continue
;
參考:arguments
接下來就來看看引數型別的處理:
// ------ 省略其他程式碼 ------ var argType = typeof arg; if (argType === 'string' || argType === 'number') { // string or number classes.push(arg); } else if (Array.isArray(arg)) { // array } else if (argType === 'object') { // object }
這裡是通過typeof
來判斷引數的型別,只有三種分支結果:
string
或者number
,直接push
到classes
陣列中;array
,這裡是遞迴呼叫classNames
函數,將陣列中的每一項作為引數傳入;object
,這裡是遍歷物件的每一項,如果值為true
,則將key
作為類名push
到classes
陣列中;string
或者number
的處理比較簡單,就不多說了,接下來就來看看array
和object
的處理:
// ------ 省略其他程式碼 ------ if (arg.length) { var inner = classNames.apply(null, arg); if (inner) { classes.push(inner); } }
這裡的處理是先判斷陣列的長度,通過隱式轉換,如果陣列長度為0
,則不會進入if
分支;
然後就直接通過apply
來呼叫classNames
函數,將陣列作為引數傳入,這裡的null
是因為apply
的第一個引數是this
,這裡沒有this
,所以傳入null
;
然後獲取返回值,如果返回值存在,則將返回值push
到classes
陣列中;
參考:apply
toString
是否被重寫:// ------ 省略其他程式碼 ------ if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes('[native code]')) { classes.push(arg.toString()); continue; }
這裡的處理是先判斷arg
的toString
方法是否被重寫,如果被重寫了,則直接將arg
的toString
方法的返回值push
到classes
陣列中;
這一步可以說是很巧妙,第一個判斷是判斷arg
的toString
方法是否被重寫;
第二個判斷是判斷Object.prototype.toString
方法是否被重寫,如果被重寫了,則arg
的toString
方法的返回值一定不會包含[native code]
;
for (var key in arg) { if (hasOwn.call(arg, key) && arg[key]) { classes.push(key); } }
這裡使用for...in
來遍歷物件的每一項;
然後通過Object.prototype.hasOwnProperty.call
來判斷物件是否有某一項;
最後判斷物件的某一項的值是否為真值,並不是直接判斷arg[key]
是否為true
,這樣可以處理arg[key]
為不為boolean
的情況;
然後將物件的key
作為類名push
到classes
陣列中;
最後函數結束,通過join
將classes
陣列轉換為字串,返回;
在test
目錄下可以看到index.js
檔案,這裡是測試用例,可以通過npm run test
來執行測試用例;
這裡測試用例測試了很多邊界情況,通過測試用例上面的程式碼就可以看出來了:
it('keeps object keys with truthy values', function () { assert.equal(classNames({ a: true, b: false, c: 0, d: null, e: undefined, f: 1 }), 'a f'); });
it('joins arrays of class names and ignore falsy values', function () { assert.equal(classNames('a', 0, null, undefined, true, 1, 'b'), 'a 1 b'); });
這裡還傳遞了一個true
,因為是boolean
型別,在程式中是直接被忽略的,所以不會被保留;
it('supports heterogenous arguments', function () { assert.equal(classNames({a: true}, 'b', 0), 'a b'); });
it('should be trimmed', function () { assert.equal(classNames('', 'b', {}, ''), 'b'); });
it('returns an empty string for an empty configuration', function () { assert.equal(classNames({}), ''); });
it('supports an array of class names', function () { assert.equal(classNames(['a', 'b']), 'a b'); });
it('joins array arguments with string arguments', function () { assert.equal(classNames(['a', 'b'], 'c'), 'a b c'); assert.equal(classNames('c', ['a', 'b']), 'c a b'); });
it('handles multiple array arguments', function () { assert.equal(classNames(['a', 'b'], ['c', 'd']), 'a b c d'); });
it('handles arrays that include falsy and true values', function () { assert.equal(classNames(['a', 0, null, undefined, false, true, 'b']), 'a b'); });
it('handles arrays that include arrays', function () { assert.equal(classNames(['a', ['b', 'c']]), 'a b c'); });
it('handles arrays that include objects', function () { assert.equal(classNames(['a', {b: true, c: false}]), 'a b'); });
it('handles deep array recursion', function () { assert.equal(classNames(['a', ['b', ['c', {d: true}]]]), 'a b c d'); });
it('handles arrays that are empty', function () { assert.equal(classNames('a', []), 'a'); });
it('handles nested arrays that have empty nested arrays', function () { assert.equal(classNames('a', [[]]), 'a'); });
it('handles all types of truthy and falsy property values as expected', function () { assert.equal(classNames({ // falsy: null: null, emptyString: "", noNumber: NaN, zero: 0, negativeZero: -0, false: false, undefined: undefined, // truthy (literally anything else): nonEmptyString: "foobar", whitespace: ' ', function: Object.prototype.toString, emptyObject: {}, nonEmptyObject: {a: 1, b: 2}, emptyList: [], nonEmptyList: [1, 2, 3], greaterZero: 1 }), 'nonEmptyString whitespace function emptyObject nonEmptyObject emptyList nonEmptyList greaterZero'); });
toString
方法的物件it('handles toString() method defined on object', function () { assert.equal(classNames({ toString: function () { return 'classFromMethod'; } }), 'classFromMethod'); });
toString
方法it('handles toString() method defined inherited in object', function () { var Class1 = function () { }; var Class2 = function () { }; Class1.prototype.toString = function () { return 'classFromMethod'; } Class2.prototype = Object.create(Class1.prototype); assert.equal(classNames(new Class2()), 'classFromMethod'); });
it('handles objects in a VM', function () { var context = {classNames, output: undefined}; vm.createContext(context); var code = 'output = classNames({ a: true, b: true });'; vm.runInContext(code, context); assert.equal(context.output, 'a b'); });
Css-in-JS
是一種將Css
和JavaScript
結合在一起的方法,它允許你在JavaScript
中使用Css
,並且可以在執行時動態地生成Css
。
這種方法的優點是可以在JavaScript
中使用Css
的所有功能,包括變數、條件語句、迴圈等,而且可以在執行時動態地生成Css
,這樣就可以根據不同的狀態來生成不同的Css
,從而實現更加豐富的互動效果。
Css-in-JS
的缺點是會增加JavaScript
的體積,因為JavaScript
中的Css
是以字串的形式存在的,所以會增加JavaScript
的體積。
Css-in-JS
的實現方式有很多種,比如styled-components
、glamorous
、glamor
、aphrodite
、radium
等。
而這個庫就是一個將className
可以動態生成的庫,在庫的README
中有在React
中使用的例子,其實完全可以拋開React
,在任何需要的地方使用。
例如我在普通的HTML
中使用className
,例如有一個按鈕,我想根據按鈕的狀態來動態地生成className
,那麼可以這樣寫:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> .btn { width: 100px; height: 30px; background-color: #ccc; } .btn-size-large { width: 200px; height: 60px; } .btn-size-small { width: 50px; height: 15px; } .btn-type-primary { background-color: #f00; } .btn-type-secondary { background-color: #0f0; } </style> </head> <body> <button class="btn btn-size-large btn-type-primary" onclick="toggleSize(this)">切換大小</button> <button class="btn btn-size-large btn-type-primary" onclick="toggleType(this)">切換狀態</button> <script src="classnames.js"></script> <script> function toggleSize(el) { el.className = classNames('btn', { 'btn-size-large': el.className.indexOf('btn-size-large') === -1, 'btn-size-small': el.className.indexOf('btn-size-large') !== -1 }); } function toggleType(el) { el.className = classNames('btn', { 'btn-type-primary': el.className.indexOf('btn-type-primary') === -1, 'btn-type-secondary': el.className.indexOf('btn-type-primary') !== -1 }); } </script> </body> </html>
classnames
是一個非常簡單的庫,但是它的功能卻非常強大,它可以根據不同的條件來動態地生成className
,這樣就可以根據不同的狀態來動態地生成不同的className
,從而實現更加豐富的互動效果。
除了React
在使用Css-in-JS
,還有很多庫都在使用Css-in-JS
的方式來實現,這個庫程式碼量雖然少,但是帶來的概念卻是非常重要的,所以值得學習。
其實拋開Css-in-JS
的概念,這個庫的實現也很值得我們學習,例如對引數的處理,深層巢狀的資料結構的處理,已經測試用例的完善程度等等,都是值得我們學習的。
以上就是Css-In-Js實現classNames庫原始碼解讀的詳細內容,更多關於Css-In-Js實現classNames庫的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45