CSS自定义属性在组件开发中的使用

发布于 大漠

图解CSS系列的《CSS自定义属性》一文中,对CSS的自定义属性做过深入的阐述。如果你阅读过这篇文章,应该对CSS自定义属性有所了解,也能体会到该特性的强大之处。今天,CSS自定义属性可以用于Web开发中的很多地方,能让开发者变得更为便捷灵活,比如最近流行的Dark Mode开发。而今天我们就来和大家聊聊CSS自定义属性在组件开发中的运用。

为什么要在组件开发中使用CSS自定义属性

对于Web开发者而言,组件开发已经有很多种模式,从传统的HTML和CSS模式到现代JavaScript框架(比如React,Vue)。不管是哪一种方式,都离不开CSS,而且只是CSS的使用方式不同而以。

如果你还在使用传统的组件开发模式,可能你更趋向于将CSS放在一个独立的.css文件中,并且将文件和组件放在一起:

而使用JavaScript框架开发时,可能会将CSS和模板都放在同一个.js(或.vue)文件中:

特别是使用React框架开发时,很多开发都都热衷于CSS-in-JS的模式。

在React中写样式有很多种方式,前段时间《React中编写CSS的姿势》一文有专门介绍过,感兴趣的同学可以看看。

不管是哪种方式,都有其利弊,这里不会和大家深聊他们之间的差异,主要和大家一起探讨CSS自定义属性如何使我们的工作流得到进一步的改善。并且探讨自定义属性在React和Vue框架构建组件时怎么使用。

很多Web开发者都这么认为:

不需要CSS中的变量(自定义属性),因为可以使用JavaScript做完所有事情

事实上,在使用框架开发组件时,使用CSS自定义属性有两个主要原因:

  • 人体工程学很好
  • 它开启了新的可能性,可以使用CSS自定义属性做一些JavaScript做不到的事情

既然如此,那我们就开始吧。

快速回顾CSS自定义属性

CSS自定义更深入的介绍,请移步这里阅读

这里我们只是快速回顾CSS自定义属性的基础知识。在使用CSS自定义属性时,一般会在:root中先声明CSS自定义属性:

:root {
    --color-text: black;
    --color-background: lightgray;
    --color-primary: rebeccapurple;
    --gutter: 16px;
}

然后在需要使用的地方,通过var()函数来调用已声明的自定义属性,通过var()函数调用的自定义属性会作为别的CSS属性的值,比如:

.element {
    color: var(--color-text);
    margin-bottom: var(--gutter);
}

.primary {
    color: var(--color-primary);
}

它们看上去就像属性,事实上也是如此。通过--前缀声明的被称为属性,即CSS自定义属性;被var()函数调用的自定义属性(即--前缀声明的自定义属性)又被称为是CSS的变量。也可以说:

CSS自定义属性也被称为CSS变量。换句话说,它既是属性也是变量

在CSS中,CSS自定义属性可以像其他CSS属性一样,可以运用于任何HTML元素上。也同样遵从CSS的使用规则。

使用CSS自定义属性构建Web组件

我想你对CSS自定义属性有一个基本的认识了,即使没有也不必着急,接下来我将一步一步带大家进入CSS自定义的事件。

下面我将拿一个常见的组件为例,向大家介绍CSS自定义属性在组件构建中的使用。比如我们要构建下面这样的一个卡片组件:

上面的Demo根据@Alan在Codepen上的案例改来的

这个Demo并不复杂,上面示例也是我们传统的一种做法,一看代码便知道怎么回事。接下来,我将会使用CSS自定义属性来改造上面的卡片,为了节约时间,只将颜色部分换成CSS自定义属性描述。

正如上图所示,简单的分析卡片上各个元素使用到的颜色,大约有九个地方,共用了七种颜色,这样一来,我们显式的在:root中声明七个有关于颜色的自定义属性:

:root {
    --primary-color: #ffffff;                /* 主色 */ 
    --body-bg-color: #f1f1f1;                /* body背景颜色 */
    --card-bg-color: var(--primary-color);   /* card背景颜色 */
    --card-box-shadow-color: #405070;        /* card盒子阴影颜色 */
    --btn-bg-color: #28c3f5;                 /* button背景颜色 */
    --btn-color: var(--primary-color);       /* button文本颜色 */
    --paragraph-color: gray;                 /* 段落文本颜色 */
    --card-object-bg-color: #eaeff8;         /* card顶部背景颜色 */
    --avatar-bg-color: var(--primary-color); /* 头像背景颜色 */
    --title-color: #101c34;                  /* 标题2文本颜色 */
}

注意,自定义属性的名称可以根据自己的喜好来定义,但建议使用具有语义化的名称

这样一来,我们上面的案例可以换成自定义属性,并且在运用到颜色的属性换成var()函数,引用已声明的自定义属性:

body {
    background-color: var(--body-bg-color);
}

.card {
    background: var(--card-bg-color);
    box-shadow: 0px 1px 10px 1px var(--card-box-shadow-color);
}

.card__object {
    background-color: var(--card-object-bg-color);
}

.card__avatar {
    background-color: var(--avatar-bg-color);
}

.card__body {
    background: var(--card-bg-color);
}

.card__body h4 {
    color: var(--title-color);
}

.card__body p {
    color: var(--paragraph-color);
}

.card__body .btn {
    background: var(--btn-bg-color);
    color: var(--btn-color);
}

.card__body .btn:hover {
    color: var(--btn-bg-color);
}

你可能已经发现了,上面我们有的自定义属性嵌套了另一个自定义属性。在CSS自定义属性中,是不推荐这么用的。正如上例所示,在用到#ffffff颜色的地方,统一使用一个自定义属性,比如--primary-color

就上面而言,你或许还没有发现CSS自定义的优势。下面这个示例,可以向你展示它的优势。我们在上例的基础上,复制另一个卡片出来,但我们期望的主题色不一样:

就上面的示例而言,我们没有做太多的调整,只是将卡片放在不同的两个容器中:

<!-- HTML -->
<div class="wrapper light">
    <div class="card">...</div>
</div>

<div class="wrapper dark">
    <div class="card">...</div>
</div>

.dark卡片上,只是调整重新定义一份自定义属性的值:

.dark {
    --color: #fff;
    --primary-color: #1a1515;
    --body-bg-color: #1a1818;
    --card-box-shadow-color: #6a716e;
    --btn-bg-color: #ff5722;
    --paragraph-color: #c7c1c1;
    --card-object-bg-color: #282035;
    --title-color: #ffffff;
    --avatar-bg-color: #673ab7;
}

是不是觉得好香。如果你接触过Dark Mode的话,你可能已经感觉到了,我们可以让同一个卡片,根据用户系统的喜好设置来显示卡片的风格。这个时候将会用到CSS的最新媒体查询prefers-color-scheme,即,将.drak块中的自定义属性放到该媒体查询中:

@media (prefers-color-scheme: dark) {
    :root {
        /* Dark Card */
        --color: #fff;
        --primary-color: #1a1515;
        --body-bg-color: #1a1818;
        --card-box-shadow-color: #6a716e;
        --btn-bg-color: #ff5722;
        --paragraph-color: #c7c1c1;
        --card-object-bg-color: #282035; 
        --title-color: #ffffff;
        --avatar-bg-color: #673ab7;
    } 
}

这个时候,你看到的效果会是像下面这样:

你可以尝试着切换系统设置:

就可以看到卡片亮色和暗色的切换。你也可以直接通过Chrome的开发者调试工具对两种模式进行切换:

darklight两者之间切换,你就能看到Dark Mode两种模式下卡片的效果:

我们还可以在此基础上做得更好一些,比如说添加一个切换按钮,让用户可以直接在页面中选择自己喜欢的主题:

上面我们看到的是最简单的方式。如果你对颜色系统熟悉的话,那么还可以使用CSS自定义属性CSS的颜色系统(比如HSL)和CSS的calc()函数结合起来调整颜色的色相饱和度亮度来改变颜色,从而达到我们给组件换肤的效果。如果你对这方面感兴趣的话,可以阅读@Dieter Raber的新博文《Creating Color Themes With Custom Properties, HSL, and a Little calc()》。文章就详细介绍了,如何使用自定义属性来实现换肤效果

如果你对颜色相关的知识感兴趣的话,可以花点时间阅读下面几篇文章:

有了这个基础之后,我们就可以进入JavaScript框架构建组件怎么使用CSS自定义属性了。我们先来看React中怎么使用CSS自定义属性。

React组件中使用CSS自定义属性

在这里不会和大家聊怎么用React构建组件和如何在React中使用CSS。我想你在这方面应该有一定的基础,甚至比我强。如果你和我一样是React的初学者,建议你先花一点点时间阅读下面几篇文章:

其实在去年整理过一篇怎么在React构建组件时使用CSS自定义属性

在接下来的内容中,将会和大家探讨不同的使用方式。注意,接下来构建React组件都是基于Create React App方式来构建。

我们继续吧。请在命令终端先执行下面的命令来构建一个React项目:

❯ npx create-react-app react-css-custom-property

在该项目中,创建一个名为Card组件,并且将上面Demo中卡片移动到React中来。

Card组件中,我们采用的还是样式和模板分离的方式,为了更好的管理CSS自定义属性,在这个Demo中将所有声明的CSS自定义属性放置在/src/cssVar.css中:

// src/cssVar.css
:root {
    --color: #333;
    --primary-color: #ffffff; /* 主色 */
    --body-bg-color: #f1f1f1; /* body背景颜色 */
    --card-box-shadow-color: #405070; /* card盒子阴影颜色 */
    --btn-bg-color: #28c3f5; /* button背景颜色 */
    --paragraph-color: gray; /* 段落文本颜色 */
    --card-object-bg-color: #eaeff8; /* card顶部背景颜色 */
    --title-color: #101c34; /* 标题2文本颜色 */
    --avatar-bg-color: #fff;
}

@media (prefers-color-scheme: dark) {
    :root {
        --color: #fff;
        --primary-color: #1a1515; /* 主色 */
        --body-bg-color: #1a1818; /* body背景颜色 */
        --card-box-shadow-color: #6a716e; /* card盒子阴影颜色 */
        --btn-bg-color: #ff5722; /* button背景颜色 */
        --paragraph-color: #c7c1c1; /* 段落文本颜色 */
        --card-object-bg-color: #282035; /* card顶部背景颜色 */
        --title-color: #ffffff; /* 标题2文本颜色 */
        --avatar-bg-color: #673ab7;
    }
}

看到的效果将是我们预期想要的:

在我们实际的开发中,同一个组件可能会用于不同的页面或者说同一页面上不同位置,并且在样式上有一定的差异化。为了向大家演示这样的一个场景,我们在同一文件中引用两个相同的Card组件,只是让另一个Card组件在样式上有差异。首先尝试着在新的Card组件中改变已声明的自定义属性的值:

// /src/App.css
.wrapper.aside {
    --body-bg-color: #cdcaca;
    --color: #03a9f4;
}

.aside .card {
    --primary-color: #ffffff;
    --card-box-shadow-color: #151817;
}

.aside .card__object {
    --card-object-bg-color: #9e9e9e;
}

.aside .card__avatar {
    --avatar-bg-color: #2196f3;
}

.aside .card__body h4 {
    --title-color: #000000;
}

.aside .card__body p {
    --paragraph-color: #7a7575;
}

.aside .card__body .btn {
    --btn-bg-color: #03a9f4;
    --primary-color: #ffffff;
}

.aside .card__body .btn:hover {
    --btn-bg-color: #03a9f4;
}

引用在.asideCard组件在UI上已经达到我们想要的效果:

但很多React开发者并不太喜欢上面的开发模式,因为上面的开发模式并不能解决CSS存在的问题,比如说样式的冲突。为了避免无必要的烦恼,因此会采用CSS-in-JS的开发模式。

在使用CSS-in-JS模式的时候,有可能会依赖于一些优秀的库,比如说styled-components。因此,就先从这种方式开始。

CSS自定义属性和styled-components的结合

styled-components是一个非常优秀的库

如果要使用该库来构建React组件,首先需要先安装它:

❯ npm install --save styled-components

同样拿Card组件为例,为了更好的区分前面的示例,我们重新创建一个名为CardWithStyledComponents的组件:

// src/components/CardWithStyledComponents
import React from 'react';
import styled from 'styled-components';

interface CardProps {
    title: string;
    userName: string;
    avatarUrl: string;
    des: string;
    buttonText: string;
}

const Card = styled.div`
    width: 30vw;
    min-height: 60vh;
    border-radius: 8px;
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
`;

const CardObject = styled.div`
    height: 15vh;
    width: 100%;
    border-radius: 8px 8px 0 0;
    display: flex;
    justify-content: center;
`;

const CardAvatar = styled.div`
    width: 80px;
    height: 80px;
    border-radius: 50%;
    padding: 5px;
    transform: translateY(calc(15vh - 40px));
`;

const CardAvatarImg = styled.img`
    width: 80px;
    height: 80px;
    border-radius: 50%;
`;

const CardBody = styled.div`
    min-height: 28vh;
    padding: 50px 20px 40px;
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    border-radius: 0 0 8px 8px;
`;

const UserName = styled.h3`
    box-sizing: border-box;
    line-height: 1.6;
    font-weight: bold;
`;

const CardTitle = styled.h4`
    box-sizing: border-box;
    line-height: 1.6;
    font-weight: bold;
    opacity: 0.4;
`;

const CardDes = styled.p`
    font-size: 16px;
    margin-bottom: 30px;
`;

const CardLink = styled.a`
    padding: 12px 20px;
    border: none;
    border-radius: 32px;
    font-size: 12px;
    text-decoration: none;
    font-weight: bold;
    transition: all 0.1s ease-in;

    &:hover {
        background: transparent;
        border: 1px solid currentColor;
    }
`;

const CardWithStyledComponents = (props: CardProps) => {
    const {title, userName, avatarUrl, des, buttonText, ...rest} = props
    return (
        <Card {...rest}>
            <CardObject>
                <CardAvatar>
                    <CardAvatarImg src={avatarUrl} alt={userName} />
                </CardAvatar>
            </CardObject>
            <CardBody>
                <UserName>{userName}</UserName>
                <CardTitle>{title}</CardTitle>
                <CardDes>{des}</CardDes>
                <div className="action">
                    <CardLink href="https://www.fedev.cn">{buttonText}</CardLink>
                </div>
            </CardBody>
        </Card>
    )
}

export default CardWithStyledComponents;

并在App.tsx中引用CardWithStyledComponents组件:

<CardWithStyledComponent
    title="Digital Product Designer" 
    avatarUrl="https://media-exp1.licdn.com/dms/image/C5603AQG9JCcSU89gTg/profile-displayphoto-shrink_200_200/0?e=1592438400&v=beta&t=HIWU0K4mFvIm5eAXPvb7wps7qKDY4MoUV1S6wPeZl30" 
    userName="Alan Dong" 
    des="Build it in a scalable way, we moved slow to move fast." 
    buttonText="View Profile"
/>

在浏览器中,看到的卡片只有布局的效果,但并没有颜色方面的样式:

现在我们要做的是将颜色添加到卡片的元素上。为了能更好管理颜色,我们在src/目录下创建一个constants.ts文件,声明一个COLORS对象,把卡片组件要用到的颜色都放在这里:

// src/constants.ts
export const COLORS = {
    color: "#333",
    primaryColor: "#ffffff",
    bodyBgColor: "#f1f1f1",
    cardBoxShadowColor: "#405070",
    btnBgColor: "#28c3f5",
    paragraphColor: "gray",
    cardObjectBgColor: "#eaeff8",
    titleColor: "#101c34",
    avatarBgColor: "#fff",
};

将管理颜色的文件在卡片组件中引用,并且将颜色赋值到对应的CSS属性之上:

// src/components/CardWithStyledComponents/index.tsx

import { COLORS } from '../../constants';

const Card = styled.div`
    // ...
    background: ${COLORS.primaryColor};
    box-shadow: 0px 1px 10px 1px ${COLORS.cardBoxShadowColor};
`;

const CardObject = styled.div`
    // ...
    background-color: ${COLORS.cardObjectBgColor};
`;

const CardAvatar = styled.div`
    // ...
    background-color: ${COLORS.avatarBgColor};
`;

const UserName = styled.h3`
    color: ${COLORS.color};
`;

const CardTitle = styled.h4`
    // ...
    color: ${COLORS.titleColor};
`;

const CardDes = styled.p`
    // ...
    color: ${COLORS.paragraphColor};
`;

const CardLink = styled.a`
    // ...
    background: ${COLORS.btnBgColor};
    color: ${COLORS.primaryColor};

    &:hover {
        // ...
        color: ${COLORS.btnBgColor}
    }
`;

如果不出意外,你在浏览器看到的效果会像下面这样:

我们还可以使用styled-componentsThemeProvider特性将theme(props)透传给子组件。将CardWithStyledComponents复制一份并粘贴到src/目录下,然后将其命名为CardWithStyledComponentsDark,向大家演示ThemeProvider改变组件UI,实现一个暗色系卡片。在这个新组件中,要改变的地方很少:

// src/components/CardWithStyledComponentsDark/index.tsx

const Card = styled.div`
    background: ${props => props.theme.colors.primaryColor};
    box-shadow: 0px 1px 10px 1px ${props => props.theme.colors.cardBoxShadowColor};
`;

const CardObject = styled.div`
    background-color: ${props => props.theme.colors.cardObjectBgColor};
`;

const CardAvatar = styled.div`
    background-color: ${props => props.theme.colors.avatarBgColor};
`;

const UserName = styled.h3`

    color: ${props => props.theme.colors.color};
`;

const CardTitle = styled.h4`
    
    color: ${props => props.theme.colors.titleColor};
`;

const CardDes = styled.p`
    
    color: ${props => props.theme.colors.paragraphColor};
`;

const CardLink = styled.a`
    
    background: ${props => props.theme.colors.btnBgColor};
    color: ${props => props.theme.colors.primaryColor};

    &:hover {
        
        color: ${props => props.theme.colors.btnBgColor}
    }
`;

并在App.tsx中引入ThemeProvider和黑色系卡片要的颜色对象DARK_COLORS

// src/App.tsx
import CardWithStyledComponentsDark from './components/CardWithStyledComponentsDark'
import { ThemeProvider } from 'styled-components';
import { DARK_COLORS } from './constants';

<ThemeProvider theme={{colors: DARK_COLORS}}>
    <CardWithStyledComponentsDark
        title="Web UI Developer" 
        avatarUrl="https://avatars0.githubusercontent.com/u/368462?s=460&v=4" 
        userName="Airen Liao" 
        des="When others mention you, assign you, or request your review, GitHub will let them know that you have limited availability." 
        buttonText="Edit profile"
    />
</ThemeProvider>

效果如下:

你可能已经发现了,上面的几个效果并没有使用CSS自定义属性。嗯,没错。不过不用着急,接下来这个示例就来向大家演示在styled-components中如何使用CSS的自定义属性。这里将会使用到styled-componentscreateGlobalStyle特性。为了更好的演示效果,先复制CardWithStyledComponentsDark组件,并将其重新命名为CardWithStyledComponentsVars。并且在组件中使用到UI颜色的属性值换成CSS自定义属性:

// src/components/CardWithStyledComponentsVars/index.tsx
const Card = styled.div`
    background: var(--primary-color);
    box-shadow: 0px 1px 10px 1px var(--card-box-shadow-color);
`;

const CardObject = styled.div`
    background-color: var(--card-object-bg-color);
`;

const CardAvatar = styled.div`
    transform: translateY(calc(15vh - 40px));
    background-color: var(--avatar-bg-color);
`;

const UserName = styled.h3`
    color: var(--color);
`;

const CardTitle = styled.h4`
    color: var(--title-color);
`;

const CardDes = styled.p`
    color: var(--paragraph-color);
`;

const CardLink = styled.a`
    background: var(--btn-bg-color);
    color: var(--primary-color);

    &:hover {
        color: var(--btn-bg-color)
    }
`;

// src/App.tsx
import CardWithStyledComponentsVars from './components/CardWithStyledComponentsVars'
import { createGlobalStyle } from 'styled-components';

const GlobalStyles = createGlobalStyle`
    html {
        --color: #333;
        --primary-color: #ffffff;
        --body-bg-color: #f1f1f1;
        --card-box-shadow-color: #405070; 
        --btn-bg-color: #28c3f5;
        --paragraph-color: gray;
        --card-object-bg-color: #eaeff8; 
        --title-color: #101c34;
        --avatar-bg-color: #fff;
    }
`

function App() {
    return (
        <div className="app">
            <GlobalStyles />
            <div className="wrapper">
                <CardWithStyledComponentsVars
                    title="Digital Product Designer" 
                    avatarUrl="https://media-exp1.licdn.com/dms/image/C5603AQG9JCcSU89gTg/profile-displayphoto-shrink_200_200/0?e=1592438400&v=beta&t=HIWU0K4mFvIm5eAXPvb7wps7qKDY4MoUV1S6wPeZl30" 
                    userName="Alan Dong" 
                    des="Build it in a scalable way, we moved slow to move fast." 
                    buttonText="View Profile"
                />
            </div>
        </div>
    );
}

这个时候你看到的示例就有CSS自定义属性的影子了:

在此基础上,将媒体查询特性@media (prefers-color-scheme: dark)一起在GlobalStyles中声明,就可以轻松的实现Dark Mode的效果:

const GlobalStyles = createGlobalStyle`
    html {
        --color: #333;
        --primary-color: #ffffff;
        --body-bg-color: #f1f1f1;
        --card-box-shadow-color: #405070; 
        --btn-bg-color: #28c3f5;
        --paragraph-color: gray;
        --card-object-bg-color: #eaeff8; 
        --title-color: #101c34;
        --avatar-bg-color: #fff;
    }

    @media (prefers-color-scheme: dark) {
        :root {
        --color: #fff;
        --primary-color: #1a1515;
        --body-bg-color: #1a1818;
        --card-box-shadow-color: #6a716e;
        --btn-bg-color: #ff5722;
        --paragraph-color: #c7c1c1;
        --card-object-bg-color: #282035;
        --title-color: #ffffff;
        --avatar-bg-color: #673ab7;
        }
    }  
`

同样的,我们也可以将constants.ts中的COLORSDARK_COLORS赋值给相应的自定义属性中:

// src/App.tsx
import { COLORS, DARK_COLORS } from './constants';

const GlobalStyles = createGlobalStyle`
    html {
        --color: ${COLORS.color};
        --primary-color: ${COLORS.primaryColor};
        --body-bg-color: ${COLORS.bodyBgColor};
        --card-box-shadow-color: ${COLORS.cardBoxShadowColor}; 
        --btn-bg-color: ${COLORS.btnBgColor};
        --paragraph-color: ${COLORS.paragraphColor};
        --card-object-bg-color: ${COLORS.cardObjectBgColor}; 
        --title-color: ${COLORS.titleColor};
        --avatar-bg-color: ${COLORS.avatarBgColor};
    }

    @media (prefers-color-scheme: dark) {
        :root {
        --color: ${DARK_COLORS.color};
        --primary-color: ${DARK_COLORS.primaryColor};
        --body-bg-color: ${DARK_COLORS.bodyBgColor};
        --card-box-shadow-color: ${DARK_COLORS.cardBoxShadowColor};
        --btn-bg-color: ${DARK_COLORS.btnBgColor};
        --paragraph-color: ${DARK_COLORS.paragraphColor};
        --card-object-bg-color: ${DARK_COLORS.cardObjectBgColor};
        --title-color: ${DARK_COLORS.titleColor};
        --avatar-bg-color: ${DARK_COLORS.avatarBgColor};
        }
    }  
`

有关于styled-components更多的介绍,还可以阅读:

CSS自定义属性和React Context API的结合

React Context API提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。如果你对React Context API未接触过的话,建议你花点时间阅读《初探React Context API》一文。

或许有些开发者不太喜欢在项目中使用第三方的库,比如上面提到的styled-components。在React开发组件的时候,不使用styled-components也可以在组件的开发中使用CSS自定义属性。简单地说,可以使用React Context API相关的特性让CSS自定义属性在React组件中使用。

我们同样用上面提到的卡片组件为例。

首先,在src/目录下创建一个ThemeContext.js文件,创建一个ThemeContext

// src/ThemeContext.js
import React from 'react'

const ThemeContext = React.createContext('light')

export default ThemeContext

复制Card组件,并且重新命名为CardThemeContext。同时我们在App.js中引用,而用将ThemeContext导入进来。这里做一个简单的判断,如果themelight引用的是COLORS对象,如果是dark则引用DARK_COLORS

const theme = useContext(ThemeContext)
const currentTheme = theme === 'light' ? COLORS : DARK_COLORS

另外,需要创建一个函数,将对象中的键值转换相应的自定义属性:

const setCSSVariables = theme => {
    for (const value in theme) {
        document.documentElement.style.setProperty(`--${value}`, theme[value]);
    }
};

这样我们在App.js中就可以通过ThemeContext.Provider来调用CardThemeContext组件:

// src/App.js
import React, { useContext, useEffect } from 'react';
import CardThemeContext from './components/CardThemeContext'
import {COLORS, DARK_COLORS} from '../src/constants'

import ThemeContext from './ThemeContext'

import './App.css';

const App = () => {
    const theme = useContext(ThemeContext)
    const currentTheme = theme === 'light' ? COLORS : DARK_COLORS

    const setCSSVariables = theme => {
        for (const value in theme) {
        document.documentElement.style.setProperty(`--${value}`, theme[value]);
        }
    };

    useEffect(()=>{
        setCSSVariables(currentTheme)
    })

    
    return <ThemeContext.Provider value={theme}>
        <CardThemeContext 
            title="Digital Product Designer" 
            avatarUrl="https://media-exp1.licdn.com/dms/image/C5603AQG9JCcSU89gTg/profile-displayphoto-shrink_200_200/0?e=1592438400&v=beta&t=HIWU0K4mFvIm5eAXPvb7wps7qKDY4MoUV1S6wPeZl30" 
            userName="Alan Dong" 
            des="Build it in a scalable way, we moved slow to move fast." 
            buttonText="View Profile"
        />
    </ThemeContext.Provider>
}

export default App;

这个时候我们可以看到light主题的样式风格:

如果把ThemeContext的值设置为dark

// src/ThemeContext.js
import React from 'react'

const ThemeContext = React.createContext('light')

export default ThemeContext

那么卡片就会切换到dark主题风格:

在上面的基础上,我们稍加改良,添加一个ToggleButton组件,来做主题的切换:

// src/components/ToggleButton/index.js
import React from 'react'
import './index.css'

const ToggleButton = (props) => {
    return <div className="bulb__container" onClick={props.clickHandler}>
    <button className="bulb">
    <div className="bulb__image">
        <img src="https://static.fedev.cn/sites/default/files/blogs/2020/2002/bulb-on.png" alt="Swining Light Bulb" />
    </div>
    </button>
</div>
}

export default ToggleButton

这个时候,ThemeContext也要做一点调整:

// src/ThemeContext.js
import React from 'react'

const ThemeContext = React.createContext(['light', () => {}])

export default ThemeContext

App.js添加click事件,以及使用useState()钩子函数来对theme状态切换:

// src/App.js
import React, { useContext, useEffect, useState, useMemo } from 'react';
import CardThemeContext from './components/CardThemeContext'
import ToggleButton from './components/ToggleButton'
import {COLORS, DARK_COLORS} from '../src/constants'
import ThemeContext from './ThemeContext'
import './App.css';

const setCSSVariables = theme => {
    for (const value in theme) {
        document.documentElement.style.setProperty(`--${value}`, theme[value]);
    }
};

const App = () => {
    const [theme, setTheme] = useState(useContext(ThemeContext)[0])

    const value = useMemo(() => ({
        theme,
        setTheme
    }),[theme])
    
    const clickHandler = () => {
        setTheme(theme === 'light' ? 'dark' : 'light')
    }

    const currentTheme = theme === 'light' ? COLORS :  DARK_COLORS

    useEffect(()=>{
        setCSSVariables(currentTheme)
    })

    return <ThemeContext.Provider theme={value}>
        <CardThemeContext 
            title="Digital Product Designer" 
            avatarUrl="https://media-exp1.licdn.com/dms/image/C5603AQG9JCcSU89gTg/profile-displayphoto-shrink_200_200/0?e=1592438400&v=beta&t=HIWU0K4mFvIm5eAXPvb7wps7qKDY4MoUV1S6wPeZl30" 
            userName="Alan Dong" 
            des="Build it in a scalable way, we moved slow to move fast." 
            buttonText="View Profile"
        />

        <ToggleButton clickHandler={clickHandler} />
    </ThemeContext.Provider>
}

export default App;

这个时候就可以通过切换按钮让lightdark主题之间切换:

示例源码可以点击这里下载

Vue中使用CSS自定义属性

在使用Vue框架来构建组件时,也可以在组件中使用CSS自定义属性,我们来看一个@Dave Follett在他的《A New Vue On JavaScript30 - 03 CSS Variables》教程中写的示例:

具体的就不展开阐述了,如果你感兴趣的话,可以阅读下面几篇文章:

小结

CSS自定义属性(CSS变量)到今天为止算得止是成熟的技术了,在Web中使用的场景也越来越多。今天这篇文章主要阐述了CSS自定义属性在构建组件中是怎么使用的。最后希望这篇文章对你有所帮助。