前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >丢人啊,工作 10 年了,才知道用这种方式实现 CSS 换肤

丢人啊,工作 10 年了,才知道用这种方式实现 CSS 换肤

作者头像
用户6901603
发布2024-05-17 19:19:58
700
发布2024-05-17 19:19:58
举报
文章被收录于专栏:不知非攻不知非攻
  • 一、 React19 中 context 的具体改动
  • 二、 use(context) 基础介绍
  • 三、 简单粗暴样式替换实现换肤
  • 四、 利用 css 变量实现换肤

本文共 3219 字,阅读预计使用 5 分钟

1、改动

与之前的版本相比,在 React19 中,context 有一些细微的变化。主要体现在如下三个方面。

!一、删除 旧版 Context 旧版本的 Context 在 2018 年 10 月(v16.6.0)被废弃。但是为了保证平滑的升级,旧版代码一直沿用到了现在。在 React 19 中,这些代码会正式被删除。旧版本的 Context 仅在使用 contextTypesgetChildContext API 的类组件中可用。因此它的删除对现在的项目应该只会造成很小的影响。

如果你在项目中仍然使用了旧版 Context,你可以参考下面新旧版本的对比写法进行调整升级。

代码语言:javascript
复制
// 之前
import PropTypes from 'prop-types';

class Parent extends React.Component {
  static childContextTypes = {
    foo: PropTypes.string.isRequired,
  };

  getChildContext() {
    return { foo: 'bar' };
  }

  render() {
    return <Child />;
  }
}

class Child extends React.Component {
  static contextTypes = {
    foo: PropTypes.string.isRequired,
  };

  render() {
    return <div>{this.context.foo}</div>;
  }
}
代码语言:javascript
复制
// 之后
const FooContext = React.createContext();

class Parent extends React.Component {
  render() {
    return (
      <FooContext value='bar'>
        <Child />
      </FooContext>
    );
  }
}

class Child extends React.Component {
  static contextType = FooContext;

  render() {
    return <div>{this.context}</div>;
  }
}

二、简化 Provider 的使用

代码语言:javascript
复制
const Context = createContext({})

在以前的使用中,我们需要使用 Context.Provider 来包裹子组件。

代码语言:javascript
复制
<Context.Provider value={value}>
  {props.children}
</Context.Provider>

在 React19 中,我们可以直接使用 Context 来代替 Provider,从而让代表变得更加简洁。

代码语言:javascript
复制
<Context value={value}>
  {props.children}
</Context>

三、可以使用 use 获取 context

以前的版本中,在组件内部我们使用 useContext 来获取 context 中的状态。

代码语言:javascript
复制
// before
import { useContext } from 'react';

function MyComponent() {
  const theme = useContext(ThemeContext);
  // ...

在 React19 中,我们可以直接使用 use 来获取

代码语言:javascript
复制
// after
import { use } from 'react';

function MyComponent() {
  const theme = use(ThemeContext);
  // ...

2、重学一次 context

在 React 中,props 能够帮助我们将数据层层往下传递。但是,当数据传递太多层之后,不仅代码上比较繁琐,理解上也容易混乱不清。因此,我们需要一种能够跨越组件层级让直达子组件的数据传递方式,这就是 context.

context 表示组件实例在运行期间能够直接读取的状态和内容。他记录了内存中的活跃数据。我们可以将这些数据使用 useState 来定义。那么,context 中的数据更改,就会驱动所有使用到该数据的 UI 发生变化。

context 的学习主要分为如下三个部分 一、 如何创建 context 二、 顶层组件中如何传递数据 三、 子组件中如何获取数据

一、如何创建 context

我们可以使用 createContext 来创建 context.

代码语言:javascript
复制
const SomeContext = createContext(defaultValue)

defaultValue 表示默认值。他可以作为数据的兜底结果。当你无法从 value 中读取具体的值时,则会使用 defaultValue 中的值。在代码运行过程中,默认值始终保持不变。如果没有默认值,我们至少需要传入一个 null

createContext 执行之后的返回值,就是我们需要的 context

二、如何传递 context

返回的 context 通常是一系列组件的顶层父组件。因此,在使用时,我们通常会首先定义该顶层父组件。

代码语言:javascript
复制
function Provider(props) {
  const value = {...}
  return (
    <SomeContext value={value}>
      {props.children}
    </SomeContext>
  )
}
export default Provider

在该顶层父组件中,我们使用刚才创建的 context 作为父级标签,把子组件包起来。并作为渲染内容返回。

代码语言:javascript
复制
<SomeContext value={value}>
  {props.children}
</SomeContext>

此处的 value 表示我们在上下文中定义好的值。我们可以自己随意定义你想要传递给子组件的所有数据/方法。

i这些数据/方法通常被多个不同的子组件共同使用。否则我们没必要将数据/方法存储在 context 中。

代码语言:javascript
复制
import { createContext } from 'react';

const ThemeContext = createContext('light');

function App() {
  const [theme, setTheme] = useState('light');
  // ...
  return (
    <ThemeContext value={theme}>
      <Page />
    </ThemeContext>
  );
}

此时,案例中 Page 组件的所有后代子组件,都可以直接获取 context 中的值,不管层级有多深。

value 可以是任何类型,你可以根据自己的项目需要设计合适的数据类型。

三、如何获取 context 中的值

在任意被包裹的子组件中,我们可以使用 use 来获取 context 中的值。

代码语言:javascript
复制
function Button() {
  // ✅ Recommended way
  const theme = use(ThemeContext);
  return <button className={theme} />;
}

获取方法非常简单,接下来,我们使用具体的实践案例来分享 context 的使用。

i需要注意,多个 Context 可以嵌套使用,只是在实践中,这种场景不算多见。

3、换肤方案一

先来看一眼我们实现案例的演示效果。我们实现了部分 UI 的皮肤切换,并且记录了切换次数。

在文件中,为了验证 context 的作用,我们将组件结构设计为如下

代码语言:javascript
复制
+ App
  - index.jsx
  - Card.jsx
  - Setting.jsx
  - context.jsx

context.jsx 中,我们会创建好 context,并组织好要传递给子组件的 value,完整代码如下

代码语言:javascript
复制
import {createContext, useState} from 'react'

export const Context = createContext({theme: 'dark'})

export default function Provider(props) {
  const [theme, setTheme] = useState('light')
  const [counter, setCounter] = useState(0)

  const value = {
    theme,
    setTheme,
    counter,
    setCounter
  }

  return (
    <Context value={value}>
      {props.children}
    </Context>
  )
}

i需要稍微注意观察的是,我们这里使用 useState 创建了数据,并将操作数据的方法一并集成在了 value 中,这样做的目的是为了确保数据的变动能触发 UI 的更新

index.jsx 中,我们使用刚才创建好的 context 组件将所有子组件包裹起来。

代码语言:javascript
复制
import Card from './Card'
import Setting from './Setting'
import Provider from './context'
import './index.css'

function Index() {
  return (
    <Provider>
      <div id='tips'>切换主题,并记录切换次数</div>
      <Card />
      <Card />
      <Setting />
    </Provider>
  )
}

export default Index

然后,分别在子组件中,使用 use 获取到组件需要的状态与方法即可。

代码语言:javascript
复制
// Card.jsx
import {use} from 'react'
import {Context} from './context'

export default function Card() {
  const {theme} = use(Context)

  const __cls = `_06_card ${theme}`
  return (
    <>
      <div className={__cls}>
        <div className="title">Canary</div>
        <p>The use API is currently only available in React’s Canary and experimental channels. Learn more about React’s release channels here.</p>
      </div>
    </>
  )
}
代码语言:javascript
复制
// Setting.jsx
import {use} from 'react'
import {Context} from './context'

export default function Card() {
  const {theme, counter, setTheme, setCounter} = use(Context)

  const __switch = () => {
    setTheme(theme == 'light' ? 'dark' : 'light')
    setCounter(counter + 1)
  }
  return (
    <div className='_06_setting'>
      <button onClick={__switch}>点击切换到{theme === 'light' ? '暗黑' : '明亮'}主题</button>
      <div id='tips'>已切换:{counter || 0} 次</div>
    </div>
  )
}

此时,我们用于切换皮肤的方法,是将分别代码不同皮肤的 className 写入到每一个需要使用的元素中。虽然实现了功能,但是在真实项目中,必定会造成大量的工作量。因此这并不是一种好的思路。

代码语言:javascript
复制
._06_card {
  margin: 20px 0;
  padding: 20px;
  border-radius: 10px;
  transition: all 0.2s;
}

._06_card.light {
  background-color: rgba(0, 0, 0, 0.02);
  border: 1px solid rgba(0, 0, 0, 0.1);
  color: rgba(0, 0, 0, 0.88);
}

._06_card.dark {
  background-color: rgba(0, 0, 0, 0.8);
  border: 1px solid rgba(0, 0, 0, 1);
  color: rgba(255, 255, 255, 0.8);
}

._06_card .title {
  font-weight: bold;
  font-size: 20px;
}

._06_setting {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

!除了这种方式,包括暴力重写并覆盖所有样式的方式来切换皮肤,都属于工作量很大的方案。这仅仅适合在项目设计之初没有考虑换肤功能的项目。并不推荐

4、换肤方案二

我们可以换一种高级一点的用法来完成皮肤的切换功能。那就是利用 CSS 变量

CSS 变量又称之为自定义属性。他已经在主流浏览器中被普遍支持,我们可以在许多项目中使用该特性。我们熟知的 antd 中就大量运用了自定义属性。

声明一个自定义属性,需要以 -- 开头,属性值可以是任何有效的 CSS 值。

代码语言:javascript
复制
element {
  --main-bg-color: brown;
}

i注意理解这句话:自定义属性和其他属性一样,是写在规则集之内的。 因此,它的改变,也能触发 transition 动画的执行

并且要注意的是,规则集所指定的选择器定义了自定义属性的可见作用域。通常的最佳实践是定义在根伪类 :root 下,这样就可以在 HTML 文档的任何地方访问到它了

代码语言:javascript
复制
:root {
  --main-bg-color: brown;
}

当然,我也应该根据实践运用灵活选择作用域。

在本案例中,我们把不同的主题定义在如下属性中。

代码语言:javascript
复制
[data-theme=dark] {
  --font-color: rgba(255, 255, 255, 0.8);
  --background-color:  rgba(0, 0, 0, 0.8);
  --border-color: rgba(0, 0, 0, 0.1);
}
[data-theme=light] {
  --font-color: rgba(0, 0, 0, 0.88);
  --background-color:  rgba(0, 0, 0, 0.02);;
  --border-color: rgba(0, 0, 0, 0.1);
}

然后在页面元素样式的运用中,我们使用 var() 获取自定义属性对应的值。而不直接使用值。

代码语言:javascript
复制
._06_card {
  background-color: var(--background-color);
  border: 1px solid var(--border-color);
  color: var(--font-color);
}

这样,我们只需要通过修改父级元素的 data-theme 的值,就可以简单做到主题切换。

代码语言:javascript
复制
const __switch = () => {
  let _theme = theme == 'light' ? 'dark' : 'light'
  document.documentElement.setAttribute('data-theme', _theme)
  setTheme(_theme)
  setCounter(counter + 1)
}

这样,一个简单易用,可维护性强的主题方案就搞定了。前端码易

本文将会收录至:前端码易 要成为 React 高手,推荐阅读 React 哲学

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-05-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 这波能反杀 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、改动
  • 2、重学一次 context
  • 3、换肤方案一
  • 4、换肤方案二
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档