這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
追憶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
標籤:其他
下一篇:返回列表

