[译]

如果你不曾了解 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 成为了第二个传入参数。

可以看到,差别在于当我们使用标签模板字符串调用 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前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值