這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
追憶Scoped
偶然想起了一次面試,二面整體都聊完了,該做的演算法題都做出來了,該背的八股文也背的差不多了,面試官頻頻點頭,似乎對我的基礎和專案經驗都很是滿意,嗯,我內心os本次面試應該十拿九穩了,
突然,面試官說:「我的主技術堆疊是React,Vue寫的很少,對Vue中style樣式中的scoped有點興趣,你知道vue中為什么有這個么?」
我不假思索:「哦, 這個主要是為了做樣式隔離,避免組件間和父子組件間的樣式覆寫問題,有點類似React中使用的StyleModule,也是可以避免不同組件間樣式覆寫問題,」
回答完之后我又開始暗自得意,回答的多么巧妙,既回答了問題,又表明自己對React也是有一點了解的,
可能面試官看出了我的得意之色,點點頭之后又問出了一個問題:「知道是怎么實作的么?」
我先茫然的盯著面試官的臉看了兩秒鐘,然后在我已有的知識庫中搜索,搜索一遍又一遍,發現這可能是我的一個盲區,我確實不太清楚實作原理啊!!
面試官可能看出了我對于此的知識匱乏,很和善的說「我就是感興趣,隨便問問」,
啊,事已至此,我只能對面試官露出一個尷尬卻不失禮貌的微笑說「這塊我確實沒有仔細思考過,我下來會詳細研究一下這款,具體是如何現在scoped的,」
「好,那本次面試就到這里吧,回去等通知吧!」面試官和藹的說,
雖然最后面試順利通過,但是這個問題我覺得還是有必要記錄下:”Vue中Style中的Scoped屬性是如何實作樣式隔離的?“
初見Scoped
我們初次見到scoped
應該是在Vue Loader中的Scoped Css
檔案中,
子組件的根元素
使用 scoped
后,父組件的樣式將不會滲透到子組件中,
深度作用選擇器
如果你希望 scoped
樣式中的一個選擇器能夠作用得“更深”,例如影響子組件,你可以使用 >>> 運算子:
<style scoped> .a >>> .b { /* ... */ } </style>上述代碼會編譯成:
.a[data-v-f3f3eg9] .b { /* ... */ }
注意:像Sass之類的前處理器無法正確決議>>>
,這種情況下可以使用/deep/
或::v-deep
運算子取而代之,兩者都是>>>
的別名,同樣可以正常作業,
實戰Scoped
style
標簽無
scoped標識
<style lang="less" > .demo { a { color: red; } } </style>
編譯之后
.demo a { color: red; }
style
表現中有
scoped標識
<style lang="less" scoped> .demo { a { color: red; } } </style>
編譯之后
.demo a[data-v-219e4e87] { color: red; }
父子組件中同時修改a
標簽樣式
// 子組件 <style scoped> a { color: green; } </style> // 父組件 <style lang="less" scoped> .demo { a { color: red; } } </style>
編譯完之后,父組件樣式對子組件樣式沒有影響
/* 子組件 a 標簽樣式 */ a[data-v-458323f2] { color: green; } /* 父組件 a 標簽樣式 */ .demo a[data-v-219e4e87] { color: red; }
如果想父組件對子組件的樣式產生影響,就需要使用更深級的選擇器 >>>
或 /deep/
或 ::v-deep
使父組件的樣式對子組件產生影響,
<style lang="less" scoped> .demo { /deep/ a { color: red; } } </style>
編譯完之后
a[data-v-458323f2] { color: green; } .demo[data-v-ca3944e4] a { color: red; }
我們可以看到 編譯后的 /deep/ a
被替換成了 a
標簽,實作了父組件對子組件樣式的修改,
解密Scoped實作
回顧初見Scoped
,我們是在vue-loader
的說明檔案中了解到的scoped
的用法,所以我們從vue-loader
包入手,發現compiler.ts
中:
try { // Vue 3.2.13+ ships the SFC compiler directly under the `vue` package // making it no longer necessary to have @vue/compiler-sfc separately installed. compiler = require('vue/compiler-sfc') } catch (e) { try { compiler = require('@vue/compiler-sfc') } catch (e) { } }可以看到compiler的參考在
@vue/compiler-sfc
包中,@vue/compiler-sfc
包的compileStyle.ts
檔案中有一個doCompileStyle()
函式,然后我們大致看下這個函式的作用:export function doCompileStyle( options: SFCAsyncStyleCompileOptions ): SFCStyleCompileResults { // 只保留了部分主要流程代碼 const plugins = (postcssPlugins || []).slice() plugins.unshift(cssVarsPlugin({ id: id.replace(/^data-v-/, ''), isProd })) if (trim) { plugins.push(trimPlugin()) } if (scoped) { // 引入了scoped插件 plugins.push(scopedPlugin(id)) } try { // 呼叫postcss result = postcss(plugins).process(source, postCSSOptions) } catch (e) { } }
doCompileStyle()
主要做了一件事,就是按需引入postcss需要的插件,其中就有scoped
的插件,這個scoped
插件應該就是Scoped Css
的核心了,
我們看下scopedPlugin
插件都做了什么
const scopedPlugin = () => { return { postcssPlugin: 'vue-sfc-scoped', Rule(rule) { processRule(id, rule) } } function processRule(id: string, rule: Rule) { /* import selectorParser from 'postcss-selector-parser' * 通過 postcss-selector-parser 獲取css AST */ rule.selector = selectorParser(selectorRoot => { selectorRoot.each(selector => { rewriteSelector(id, selector, selectorRoot) }) }).processSync(rule.selector) } function rewriteSelector( id: string, selector: selectorParser.Selector, selectorRoot: selectorParser.Root ) { let node: selectorParser.Node | null = null let shouldInject = true // find the last child node to insert attribute selector selector.each(n => { // DEPRECATED ">>>" and "/deep/" combinator if ( n.type === 'combinator' && (n.value =https://www.cnblogs.com/smileZAZ/archive/2023/07/12/=='>>>' || n.value =https://www.cnblogs.com/smileZAZ/archive/2023/07/12/=='/deep/') ) { n.valuehttps://www.cnblogs.com/smileZAZ/archive/2023/07/12/= ' ' n.spaces.before = n.spaces.after = '' // warn( // `the >>> and /deep/ combinators have been deprecated. ` + // `Use :deep() instead.` // ) // 可以結束本次回圈 return false } if (n.type === 'pseudo') { const { value } = n // deep: inject [id] attribute at the node before the ::v-deep // combinator. if (value =https://www.cnblogs.com/smileZAZ/archive/2023/07/12/==':deep' || value =https://www.cnblogs.com/smileZAZ/archive/2023/07/12/=='::v-deep') { if (n.nodes.length) { // .foo ::v-deep(.bar) -> .foo[xxxxxxx] .bar // replace the current node with ::v-deep's inner selector let last: selectorParser.Selector['nodes'][0] = n n.nodes[0].each(ss => { selector.insertAfter(last, ss) last = ss }) // insert a space combinator before if it doesn't already have one const prev = selector.at(selector.index(n) - 1) if (!prev || !isSpaceCombinator(prev)) { selector.insertAfter( n, selectorParser.combinator({ value: ' ' }) ) } selector.removeChild(n) } else { // DEPRECATED usage in v3 // .foo ::v-deep .bar -> .foo[xxxxxxx] .bar // warn( // `::v-deep usage as a combinator has ` + // `been deprecated. Use :deep(<inner-selector>) instead.` // ) const prev = selector.at(selector.index(n) - 1) if (prev && isSpaceCombinator(prev)) { selector.removeChild(prev) } selector.removeChild(n) } return false } } if (n.type !== 'pseudo' && n.type !== 'combinator') { node = n } }) if (node) { ;(node as selectorParser.Node).spaces.after = '' } else { // For deep selectors & standalone pseudo selectors, // the attribute selectors are prepended rather than appended. // So all leading spaces must be eliminated to avoid problems. selector.first.spaces.before = '' } if (shouldInject) { // 給seletor的node節點添加屬性 id selector.insertAfter( // If node is null it means we need to inject [id] at the start // insertAfter can handle `null` here node as any, selectorParser.attribute({ attribute: id, value: id, raws: {}, quoteMark: `"` }) ) } }
上述是保留了主要流程的插件代碼,至此,我們可以得出scoped
的實作方案就是通過postcss
插件這種形式實作,
大家如果沒有理解上述插件的原理,下面我提供個簡單的插件代碼,方便大家在node平臺上運行理解,
簡易流程:
const postcss = require('postcss'); // 決議Css AST const selectorParser = require('postcss-selector-parser'); postcss([ { postcssPlugin: 'post-test-plugin', Rule(rule) { console.log(rule.selector, 'rule.selector'); rule.selector = selectorParser(selectorRoot => { selectorRoot.each(selector => { let node = null; selector.each(n => { if(n.type === 'combinator' && n.value =https://www.cnblogs.com/smileZAZ/archive/2023/07/12/=='/deep/') { n.valuehttps://www.cnblogs.com/smileZAZ/archive/2023/07/12/= ' '; return false; } if(n.type !=='pseudo' && n.type !=='combinator') { node= n; } }) selector.insertAfter( node, selectorParser.attribute({ attribute: '123456', }) ) }) }).processSync(rule.selector) console.log(rule.selector, 'after ruleSelector'); } } ]).process(`/deep/ a { color: red }; b:hover{ color: blue }`).then(res =>{ console.log(res.css); // [123456] a { color: red }; b[123456]:hover{ color: blue } });
關于Debug的一個小技巧
上述解密部分有的朋友可能會疑惑,怎么就能剛好定位到這些檔案呢?這里給大家分享一個debug的小技巧,主要適用于vscode
編輯器,以本次scoped分析
為例:
通過原始碼我們大概分析出可能
是compiler-sfc
包中的插件進行的scoped操作,那么我們直接在猜測位置打下斷點如圖所示:
然后打開package.json
檔案,在scripts
命令列上有除錯按鈕,點擊除錯選擇build命令:
然后自動開始執行npm run build
,定位到我們剛才打的斷點那里:
左側有呼叫堆疊和當前變數以及除錯按鈕,然后就可以一步步進行除錯啦,
至此,Vue的Scoped Css
對你來說應該不再陌生了吧,如果還是有疑惑,可以按照上述步驟自行除錯解惑哦~
本文轉載于:
https://juejin.cn/post/7254083731488849957
如果對您有所幫助,歡迎您點個關注,我會定時更新技術檔案,大家一起討論學習,一起進步,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/557156.html
標籤:其他
下一篇:返回列表