什么是CSS-in-JS?
顾名思义,CSS-in-JS就是可以使用JS来编写CSS样式,那么为什么要用JS来编写CSS呢?我写CSS写的好好的,干嘛非给自己找不自在呢?相信以前大家都听说过这么一个词:关注点分离,就算没听过这个词那么你肯定至少也听说过这么一句话:要把HTML、CSS和JS分开编写,不要写在一起形成耦合,不要写行内样式和行内脚本等,比如像这样👇
<p style="line-height: 20px" onclick="console.log('styled-components')">
CSS-in-JS
</p>
但是React的出现打破了这一原则,All in JS是它经典的开发理念,虽然这样违背了“关注点分离”这个原则,但是却有利于组件之间的隔离,使得组件间可以高度解耦,可复用性高。
Vue中为何很少见到类似的库
相信有过Vue开发经验的小伙伴们都知道,编写组件的时候是这样的👇
<template>
<h1>Vue</h1>
</template>
<script>
export default {
name: 'vue'
}
</script>
<style scoped>
h1 {
color: #999;
}
</style>
CSS直接就很完美的组件化了,不像React那样组件和css文件是分离的,而是直接集成在一个文件中,压根就用不到那些花里胡哨的东西。但是这样其实也是有一定的弊端的,比如CSS无法接受JS的传值👇
<template>
<div></div>
</template>
<script>
import img from '@/assets/img.png'
export default {
name: 'vue',
data () {
return {
img
}
}
}
</script>
<style scoped>
div {
width: 100vw;
height: 100vh;
}
</style>
如果想要div有个背景图应该怎么办?在<style>标签里应该怎么写?好像没办法把img这个变量给传进去……
通常做法是这样👇
<template>
<div :style="{background: `url(${img})`}"></div>
</template>
<script>
import img from '@/assets/img.png'
export default {
name: 'vue',
data () {
return {
img
}
}
}
</script>
<style scoped>
div {
width: 100vw;
height: 100vh;
}
</style>
但是这样的话编译过后就变成了嵌入在div标签里的行内样式,不利于维护。
还有就是有很多样式其实是公用的,比如flex👇
<template>
<div></div>
</template>
<script>
export default {
name: 'vue'
}
</script>
<style scoped>
div {
display: flex;
align-items: center;
justify-content: center;
}
</style>
如果在每一个组件中都写这么一段样式,不仅繁琐、还不利于维护,而且最关键的是每个用到这段样式的组件都会生成一份div[data-v-xxx]的样式,代码有很大的冗余不说,还会大幅度增大你的CSS文件体积,拖慢项目的运行速度。
那么聪明的你也许会想到:我直接定义一份全局CSS样式不就得了嘛?哪里需要就给哪里加个对应的class,像bootstrap那样,在对应的标签上加类名。
没错这确实是一种很好的解决方式,但是还记不记得用bootstrap的时候不仅仅是加个类名,你还需要安装人家官方定义的那种DOM结构来写你的<template>,还是会需要复制粘贴。而且最关键的是没用办法进行传参,虽然flex这个样式比较常用,但并不是所有的地方都是需要居中对齐,也需要动态来改变align-items和justify-content的话,那么就只能使用笨方法:写所有可能取值的类了。
也许你会说,那我写一个公共组件不就得了👇👇👇
<template>
<div :style="{alignItems: align, justifyContent: justify}">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'vue',
props: {
align: {
type: String,
default: 'center'
},
justify: {
type: String,
default: 'center'
},
}
}
</script>
<style scoped>
div {
display: flex;
}
</style>
但是这样又会带来一个问题,就是假如我需要一个组件变成这个样式怎么办?岂不是必须要被div给包裹住,于是乎你又会说:这还不简单?写成动态组件不就得了?
<template>
<component :style="{alignItems: align, justifyContent: justify}" :is="dom">
<slot></slot>
</component>
</template>
<script>
export default {
name: 'vue',
props: {
align: {
type: String,
default: 'center'
},
justify: {
type: String,
default: 'center'
},
dom: {
type: String,
default: 'div'
}
}
}
</script>
<style scoped>
div {
display: flex;
}
</style>
那假如我需要一堆组件共享一个样式呢?比如像Element-UI那样的嵌套写法,而不是像全局CSS那种很Low的办法,你可能会一拍胸口:没问题,看我这就给你给你封装一个出来👇
// Root.vue
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'root',
provide: {
color: 'yellow'
}
}
</script>
// Child1.vue
<template>
<h1 :style="{color}">
<slot></slot>
</h1>
</template>
<script>
export default {
name: 'child1',
inject: {
color: {
from: 'color',
default: 'blue'
}
}
}
</script>
// Child2.vue
<template>
<h2 :style="{color}">
<slot></slot>
</h2>
</template>
<script>
export default {
name: 'child2',
inject: {
color: {
from: 'color',
default: 'green'
}
}
}
</script>
用的时候只需这样即可:
// App.vue
<template>
<root>
<child1>Child1</child1>
<child2>Child2</child2>
</root>
</template>
<script>
import Root from '@/components/Root'
import Child1 from '@/components/Child1'
import Child2 from '@/components/Child2'
export default {
name: 'app',
components: {
Root,
Child1,
Child2
}
}
</script>
image
如果觉得这个颜色看不清,只需把Root组件中的provide里面的color: 'yellow'变成color: 'gray'然后再刷新一下页面即可👇
image真正意义上的实现了组件之间共享样式!
但是这样的话不觉得很麻烦吗?
不但多了一层不必要的DOM结构,而且只是为了个样式重用和样式组件化,就费了这么半天劲,甚至一些基础不是很牢固的小伙伴看到这里都晕了,其实市面上早就有封装好的库,我们这样就是在重复造轮子。
这时要是再提出几个需求的话就很难实现了,比如我想继承样式,当然你会说用Sass、Less或者Stylus这种预处理器可以做到,那如果我想继承的是默认标签的样式并进行扩展呢?我想继承a标签的默认样式(下划线、点击变红、点完变紫等)然后再加入一些字体大小、行高等样式,就只能傻傻的纯手写了。那么接下来我就为大家介绍一个写法优(zhuang)雅(bi)、成熟稳定并且在React生态圈大受欢迎但是在Vue生态圈却无人知晓的大名鼎鼎的CSS-in-JS库:styled-components!
vue-styled-components
顾名思义,styled-components就是样式化的组件,由于styled-components是专门为React量身定做的一个库,所以不太适合在Vue项目中使用,我知道你看到这里一定开始想骂人了:我特么津津有味的看了半天,结果你告诉我styled-components不适合在Vue项目中使用?
不要着急,虽然styled-components没法在Vue项目中使用,但是styled-components团队专门为Vue贴身打造了一个vue-styled-components,和React的styled-components用法非常相似,我们先从一个最简单的案例进行入门,首先要进行安装:
npm i -S vue-styled-components
或者
yarn add vue-styled-components
然后写组件的时候不能再写xxx.vue了,取而代之的xxx.js。你可以简单的理解为这是一个极简的组件,没有<template>也没有<script>,只专注于样式,写法如下👇
// Flex.js
import styled from 'vue-styled-components'
const Flex = styled.div`
display: flex;
align-items: center;
justify-content: center;
`
export {
Flex
}
可以看到没有了之前我们熟悉的<template><script><style>三大件,那么标签写在哪呢?就写在styled.的后面,你需要什么标签,你就styled.什么标签,比如styled.ul、styled.li、styled.input等等…
然后在它的前面需要一个变量来接收,起什么名字都可以,如:const Okay = styled.xxx
接下来是一个ES6的模板字符串语法``,不懂的话可以参考一下阮一峰老师的博客
然后就可以在你的字符串模板里面写你想要的任何CSS样式啦!不仅写法和CSS一模一样,甚至还支持类似于Sass、Less和Stylus的那种嵌套语法:
// Flex.js
import styled from 'vue-styled-components'
const Flex = styled.div`
display: flex;
align-items: center;
justify-content: center;
&:hover { background: gray; }
> :first-child { align-self: flex-start }
> :last-child { align-self: flex-start }
`
export {
Flex
}
平时你是怎么用xxx.vue组件的,你就怎么用这个xxx.js组件:
// App.vue
<template>
<flex>
<p>styled</p>
<span>components</span>
</flex>
</template>
<script>
import { Flex } from '@/components/Flex'
export default {
name: 'child1',
components: {
Flex
}
}
</script>
可以发现我们并没有定义<slot>却能插入正常位置当中,他会自动找到默认位置进行插入,因为每一个标签即是一个组件,不会存在比较复杂的DOM嵌套结构。接下来看看如何给CSS传参:
// Ok.js
import styled from 'vue-styled-components'
const Ok = styled('div', {
bg: {
type: String,
default: '#eee'
}
})`
width: 100px;
height: 100px;
background: ${ props => props.bg }
`
export { Ok }
现在styled后面不是不是直接跟一个点和字符串模板了,而是把点换成括号,第一个参数是一个字符串,代表了你想要什么DOM标签,第二个参数和你写Vue组件中的props一模一样,也可以偷懒写成数组形式:
// Ok.js
import styled from 'vue-styled-components'
const Ok = styled('div', ['bg'])`
width: 100px;
height: 100px;
background: ${ props => props.bg }
`
export { Ok }
${}里面可以写一个函数,这个函数返回的结果就会渲染成最后的CSS值,函数的第一个参数就是你的定义的属性的集合,定义过后接下来我们看看如何进行使用:
// App.vue
<template>
<ok bg="#333"/>
</template>
<script>
import { Ok } from '@/components/Ok'
export default {
name: 'app',
components: { Ok }
}
</script>
image
可以看到我们传进去的值完美生效,函数里面也可以写任何表达式:
// Ok.js
import styled from 'vue-styled-components'
const Ok = styled('div', ['bg'])`
width: 100px;
height: 100px;
background: ${props => {
alert(666)
return props.bg ? 'green' : 'blue'
}}
`
export { Ok }
image
可以看到你写的函数逻辑就会运行,我们还可以给<router-link>添加一个样式:
import styled from 'vue-styled-components'
// 不能直接引入router-link, 而是像这样取到router-link
const RouterLink = Vue.component('router-link')
const StyledLink = styled(RouterLink)`
color: #333;
font-size: 1em;
text-decoration: none;
`
export default StyledLink
用的时候只需把<router-link>换成<styled-link>即可:
<styled-link to="/">Custom Router Link</styled-link>
还提供了类似于Element-UI的共享数据写法:
import {ThemeProvider} from 'vue-styled-components'
new Vue({
// ...
components: {
'theme-provider': ThemeProvider
},
// ...
})
使用的时候<theme-provider>为根组件:
<theme-provider :theme="{
primary: 'black'
}">
<wrapper>
// ...
</wrapper>
</theme-provider>
子组件需要接收一下数据:
const Wrapper = styled.default.section`
padding: 4em;
background: ${props => props.theme.primary};
`
再来看一个继承的例子:
import StyledButton from './StyledButton'
const TomatoButton = StyledButton.extend`
color: tomato;
border-color: tomato;
`
export default TomatoButton
这样就可以非常完美的实现样式的扩展及复用,甚至还可以扩展原生标签:
const Button = styled.button`
background: green;
color: white;
`
const Link = Button.withComponent('a')
与Vue原生组件的对比
vue-styled-components编译过后会生成一个随机的类名:
image image而Vue原生组件写的样式会生成一个data-v-xxx的随机属性:
image image这样一来选择器的效率就会有着较大差距,vue-styled-components只有一个选择器,原生组件却是两个选择器合并,而且属性选择器的效率比较低,无形之中就拉开了差距。
传值方面原生组件给CSS传值只能通过这种方式:
image而vue-styled-components传值后属性不会显示在标签上,而是直接嵌入到CSS里:
image image而且vue-styled-components由于是JS,所以可以写JS代码,非常的方便。
还有更多有趣好玩的功能请戳:styled-components
快去拿这玩意重构一下你的项目吧!
网友评论