一开始看到styled-components的写法的时候, 我也是一脸懵逼,这是什么鬼?!!
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
这又是什么语法, js? jsx?还是其他什么自定义的语法?需要babel支持么?需要webpack配置插件么?最后发现它居然就是正宗的Javascript!!!就是正宗的Javascript!!!就是正宗的Javascript!!!
原来是ES6的一个新特性,Tagged Template Literals!
Template literals
模板字符串大家应该都熟悉了吧,就是
模板字面量/Template literals 是允许嵌入表达式的字符串字面量。你可以使用多行字符串和字符串插值功能。它们在ES2015规范的先前版本中被称为“模板字符串/template strings”。 比如:
`普通的字符串就ok,只需要把字符串的单/双引号改为反引号就可以啦`// 普通的字符串就ok,只需要把字符串的单/双引号改为反引号就可以啦 `里面也可以插入表达式${1 + 1}, ${Date.now()}, ${true ? 'true!' : 'false!'...` // 里面也可以插入表达式2, 1501229051899, true!...`
Tagged Template Literals
template string既然就是字符串,那就可以作为参数传递给函数,而ES6对将template strings传递给函数做了新的语法支持。我们先来定义一个函数:
const f = (...args) => console.log(...args);
这个函数只是简单地输出传入的参数,大家可以在自己的chrome console里面执行一下, 然后自己跑一下代码:
f(1, 2) // 1 2
如果我们传入template strings呢?
f(``) // ""
f(`12`) // "12"
f`` // [""]
f`12` // ["12"]
对比发现,如果是把template string放在括号里面,那其实就是把template string执行结果的字符串传给函数,没什么稀奇的。 但是如果直接把template string放在函数后面, 没有括号的话, 得到的居然是包含字符串的数组?!
我们前面说了template string里面是可以有“插值(interpolations)”的,比如abc${1 + 1}
,如果我们把这个传递给函数f会得到什么呢?
f(`abc${1 + 1}edf, now=${Date.now()}.`) // abc2edf, now=1501230160188.
f`abc${1 + 1}edf, now=${Date.now()}.` // ["abc", "edf, now=", "."] 2 1501230160188
可以看出,将带interpolations的template string用括号包起来传入函数f,则得到的参数就是一个执行过的string,没有什么稀奇的。但是如果直接把template string放在函数后面, 没有括号的话, 得到的第一个参数是是包含字符串的数组,并且这些字符串是由interpolations分隔开的字符串,而后面的第2个参数是第1个interpolations的value,第3个参数是第2个interpolations的value......
然而这又有什么用呢?我们刚说了,“后面的参数是某个interpolations的value”,关键就在这value上。在js里面,value可以是数字123,可以是string"abc",可以是数组[1, 2, 3], 可以是对象{a: 1}, 也可以是函数(a, b) => {return a + b;}啊!so,如果我们的template string里面有某个函数又会怎样呢?
g(`test ${() => console.log('test function')}`) // test () => console.log('test function')
g`test ${() => console.log('test function')}` // ["test", ""] () => console.log('test function')
肉眼上可能很难看出来,但是第一个调用里面,输出的是函数toString()转为字符串后的结果,也就是并非真正的函数,所以不能执行。而第二个调用,输出的是真正的函数,那就可以执行这个函数!
我们再定义一个函数:
const execArgs = (...args) => args.forEach(arg => {
if (typeof arg === 'function') {
arg()
}
})
然后调用一下:
execArgs(1, 2) // undefined
execArgs(() => {console.log('a function!')}) // a function!
execArgs(1, 'a', () => {console.log('another function!')}) // another function!
试试template string呢?
execArgs(`template string, ${() => {console.log('template string!')}}`) // undefined
可以看到,传递过去的确实已经不是function了。
execArgs`template string, ${() => {console.log('template string!')}}` // template string!
再次证明,传递过去的确实是函数本身!
现在我们可以转到styled-components上来了。
styled.h1`
font-size: 1.5em;
`
就等于如下
styled.h1('font-size: 1.5em;')
so, 其实styled.h1, styled.div等就是一个函数, 他们接受Tagged Template Literals这样的参数。
而为了实现根据传入的props显示不同样式,我们可以这样定义:
const MyDiv = styled.div`
color: ${props => props.color};
font-size: ${props => props.bigger ? '2em' : '1em'};
`
然后我们在使用的时候就可以这样使用:
<MyDiv color="green" bigger>anything in div...</MyDiv>
然后styled-components在render的时候会把props传递给template string里面的interpolations函数,这些函数就能根据props的值来显示不同的样式了。