[譯] ? styled-components 背後的魔法

leto發表於2019-02-20

如果你不曾瞭解 styled-components,下面是 styled component 中定義 React 元件的形式:

const Button = styled.button`
  background-color: papayawhip;
  border-radius: 3px;
  color: palevioletred;
`
複製程式碼

你可以用 Button 變數來渲染元件,就如同其他任何 React 元件一樣。

<Button>Hi Dad!</Button>
複製程式碼

所以原理是什麼?你覺得是哪種 webpack、babel 之類的神奇轉譯器能做到這樣?

標籤模板字串(Tagged Template Literals)

實際上,styled.button`` 這種古怪的宣告,是 JavaScript 語法的一部分!這是一種叫做“標籤模板字串”的特性,在 ES6 中引入。

本質上來說,呼叫函式 styled.button() 和使用 styled.button`` 幾乎是一回事!但是當你傳入引數時就會看到不同之處了。

我們先建立一個簡單的函式用於探索:

const logArgs = (...args) => console.log(...args)
複製程式碼

這個函式會輸出呼叫時傳入的引數,別的什麼都不做。

你可以在(任何現代瀏覽器)控制檯中,貼上上面的函式,然後執行接下來的程式碼,來跟隨我的分析。

一個簡單的使用例子:

logArgs('a', 'b')
// -> a b
複製程式碼

-> 在本文中表示輸出內容

現在,試著用標籤模板字串來呼叫它:

logArgs``
// -> [""]
複製程式碼

只列印出來一個陣列,裡面有且僅有一個空字串。有趣!當傳入一個簡單的字串進去又會發生什麼呢?

logArgs`I like pizza`
// -> ["I like pizza"]
複製程式碼

好吧,所以這個陣列的第一個元素正是傳入的字串,不管裡面是什麼內容。那為什麼還要搞個陣列出來呢?

插值

模板字串可以進行插值,類似於:`I like ${favoriteFood}`。讓我們將一個模板字串作為引數,使用小括號呼叫 logArgs

const favoriteFood = 'pizza'

logArgs(`I like ${favoriteFood}.`)
// -> I like pizza.
複製程式碼

如你所見,JavaScript 繼續執行,將插入字串的值放入字串,然後傳遞給了函式。那麼我們直接使用模板字串來呼叫 logArgs 呢?

const favoriteFood = 'pizza'

logArgs`I like ${favoriteFood}.`
// -> ["I like ", "."] "pizza"
複製程式碼

開始有趣起來了:可以看到,我們不再僅僅是得到了一個內容為 "I like pizza" 的字串(像我們使用小括號呼叫的時候)。

傳入引數的第一位仍然是陣列,不過現在有了 2 個元素:位於插值左側的 I like,作為陣列第一個元素;位於插值的右側的 .,是陣列第二個元素。插值內容 favoriteFoor 成為了第二個傳入引數。

[譯] ? styled-components 背後的魔法

可以看到,差別在於當我們使用標籤模板字串呼叫 logArgs 時,模板字串被分解了,首先是原始文字組成的陣列,然後是插值。

如果我們插入不止一個變數呢,你能猜到嗎?

const favoriteFood = 'pizza'
const favoriteDrink = 'obi'

logArgs`I like ${favoriteFood} and ${favoriteDrink}.`
// -> ["I like ", " and ", "."] "pizza" "obi"
複製程式碼

每個插入的變數,都成為了呼叫函式傳入的下個引數。你儘可以插入新的變數,會一直向後繼續!

與通常呼叫函式的方法比較一下:

const favoriteFood = 'pizza'
const favoriteDrink = 'obi'

logArgs(`I like ${favoriteFood} and ${favoriteDrink}.`)
// -> I like pizza and obi.
複製程式碼

我們僅僅得到了一個長字串,所有東西都揉在一起了。

為什麼這很有用?

哎呦不錯哦,這樣我們就能用用重音符(`)呼叫函式了,而且傳參也別具一格,哇哦 —— 不過這又有什麼了不起的?

好吧,事實證明可以用它進行一些很酷的探索。我們將 styled-components 作為案例,分析一下。

對於 React 元件,你希望使用 props 值調整他們的樣式。比如我們通過傳入一個 primary 的 prop 值,讓 <Button /> 元件變大一些,像這樣:<Button primary />

當你使用 styled-components 傳入一個插值函式,我們其實就向元件傳入了一個 props,使用它就可以進行元件樣式調整。

const Button = styled.button`
  font-size: ${props => props.primary ? '2em' : '1em'};
`
複製程式碼

現在如果 Button 是個基本按鈕(primary),就有 2em 大小的字型,否則為 1em。

// font-size: 2em;
<Button primary />

// font-size: 1em;
<Button />
複製程式碼

回頭看一眼 logArgs 函式。我們嘗試使用插值函式呼叫它,就像上面 styled.button 一樣,只不過我們沒有使用插值模板字串。我們傳入什麼呢?

logArgs(`Test ${() => console.log('test')}`)
// -> Test () => console.log('test')
複製程式碼

函式被 toString 轉化了,logArgs 獲取到一個字串,看上去就是:"Test () => console.log('test')"。(注意現在只是一個字串,不是真的函式

比較一下直接使用插值模板字串呼叫:

logArgs`Test ${() => console.log('test')}`
// -> ["Test", ""] () => console.log('test')
複製程式碼

我知道上面的文字現在還是不明顯,但是我們拿到的第二個傳入引數確實是個函式了!(不僅是函式宣告時的字串)在你的控制檯多試幾次,仔細觀察,來更好地感受它。

這表示我們現在能夠拿到函式了,也能直接執行它!為了深入測試,讓我們來建立一個稍有不同的函式,它可以執行所有傳入引數中的函式:

const execFuncArgs = (...args) => args.forEach(arg => {
  if (typeof arg === 'function') {
    arg()
  }
})
複製程式碼

當呼叫這個函式時,它會忽略所有不是函式的引數,但是如果傳入引數是函式,它就會執行這個函式:

execFuncArgs('a', 'b')
// -> undefined

execFuncArgs(() => { console.log('this is a function') })
// -> "this is a function"

execFuncArgs('a', () => { console.log('another one') })
// -> "another one"
複製程式碼

讓我們試著用小括號包裹著模板字串來再呼叫一次:

execFuncArgs(`Hi, ${() => { console.log('Executed!') }}`)
// -> undefined
複製程式碼

什麼都沒發生,因為 execFuncArgs 沒有被傳入函式。它不過得到了一個字串:"Hi, () => { console.log('I got executed!') }"

現在看一下,當我們使用標籤模板字串呼叫函式會發生什麼:

execFuncArgs`Hi, ${() => { console.log('Executed!') }}`
// -> "Executed!"
複製程式碼

與之前相比,execFuncArgs 獲得的第二個引數是一個真正的函式,並且執行了它。

styled-components 底層就是這麼做的!在渲染時,我們向所有插值函式中傳入 props,以便使用者可以基於 props 修改樣式。

標籤模板字串使得 styled-components API 得以實現,沒有這個特性 styled-compnents 就不可能出現。期待大家能以不同的方式利用標籤模板字串!

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章