美文网首页
教你在Vue项目中使用React超火的CSS-in-JS库: s

教你在Vue项目中使用React超火的CSS-in-JS库: s

作者: 交互动效 | 来源:发表于2019-12-24 18:57 被阅读0次

    什么是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的模板字符串语法``,不懂的话可以参考一下阮一峰老师的博客

    image

    然后就可以在你的字符串模板里面写你想要的任何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

    快去拿这玩意重构一下你的项目吧!

    相关文章

      网友评论

          本文标题:教你在Vue项目中使用React超火的CSS-in-JS库: s

          本文链接:https://www.haomeiwen.com/subject/tpbfoctx.html