React项目中全局修改css的几个方法

一. 需求

最近项目针对客户需求,希望在尽量少修改源代码的情况下开发一套展示系统,只能对数据进行查看,而不能对数据进行操作,比如增删改。

项目满足条件:

进行增删改操作的主要方式为按钮和链接。并且由于项目依赖组件库,这些元素拥有相同的类名。因此最直接的思路就是通过类名找到这些元素,从css样式入手,简单来说就是样式覆盖。

二. 解决方案

目前一共三种方案:入口处增加内联样式、 挂载外部脚本、 自开发Webpack Loader
三种方案均有优缺点,综合考察之后采用第三种方案

方案一:入口处增加内联样式

React的项目入口文件:index.html

<!-- index.html部分代码片段 -->
<head>
        <style type="text/css">
		#systemEnter[system-mode=read] .exampleClass {
			/* 类exampleClass样式,例如禁止点击事件 */
			pointer-events: none;
			cursor: not-allowed;
		}
	</style>
</head>
<body>
	<div id="systemEnter" system-mode="read"></div>
</body>
复制代码

优点

简单直接,能够很快的支撑需求

缺点

需要覆盖的样式较多时对入口文件改动较大

方案二:挂载外部脚本

在外部脚本中操作DOM实现样式覆盖,同时监听路径变化,在每一次监听触发后执行脚本中的内容

第一步:入口处引入脚本

<!-- index.html部分码片段 -->
<body>
</body>
<script src = "./disabled.js">
复制代码

第二步:编写脚本

监听地址改变,即每次路由变化都重新获取DOM节点并对其进行操作
本项目使用history路由,监听较为复杂,方法如下:
juejin.cn/post/684490…
使用hash路由监听较为简单,

/** 脚本完整代码(history路由) */
class Dep {                  // 订阅池
    constructor(name) {
        this.id = new Date() //这里简单的运用时间戳做订阅池的ID
        this.subs = []       //该事件下被订阅对象的集合
    }
    defined() {              // 添加订阅者
        Dep.watch.add(this);
    }
    notify() {              //通知订阅者有变化
        this.subs.forEach((e, i) => {
            if (typeof e.update === 'function') {
                try {
                    e.update.apply(e)  //触发订阅者更新函数
                } catch (err) {
                    console.warr(err)
                }
            }
        })
    }
}
Dep.watch = null;
class Watch {
    constructor(name, fn) {
        this.name = name;       //订阅消息的名称
        this.id = new Date();   //这里简单的运用时间戳做订阅者的ID
        this.callBack = fn;     //订阅消息发送改变时->订阅者执行的回调函数     
    }
    add(dep) {                  //将订阅者放入dep订阅池
        dep.subs.push(this);
    }
    update() {                  //将订阅者更新方法
        var cb = this.callBack; //赋值为了不改变函数内调用的this
        cb(this.name);
    }
}
var addHistoryMethod = (function () {
    var historyDep = new Dep();
    return function (name) {
        if (name === 'historychange') {
            return function (name, fn) {
                var event = new Watch(name, fn)
                Dep.watch = event;
                historyDep.defined();
                Dep.watch = null;       //置空供下一个订阅者使用
            }
        } else if (name === 'pushState' || name === 'replaceState') {
            var method = history[name];
            return function () {
                method.apply(history, arguments);
                historyDep.notify();
            }
        }
    }
}())
window.addHistoryListener = addHistoryMethod('historychange');
history.pushState = addHistoryMethod('pushState');
history.replaceState = addHistoryMethod('replaceState');

/**  绑定事件,操作DOM */
window.addHistoryListener('history', function () {
    setTimeout(() => {   //使用定时器,避免页面未加载无法获取DOM
        // 禁用链接(业务相关)
        var disableLabel = document.querySelectorAll('.xxx');
        disableLabel.forEach((item) => {
            item.setAttribute("style", "pointer-events:none")
        })
        // 禁用除检索之外的按钮(业务相关)
        var disableButton = document.querySelectorAll('.yyy');
        disableButton[0].childNodes.forEach((item) => {
            item.setAttribute("disabled", "")
        })
    }, 500) //时间根据页面加载速度而定
})
复制代码

优点

实现了外部挂载,对源码的改动较方案一更小

缺点

  • 页面加载速度不确定,有时候脚本在定时器范围内找不到DOM,从而导致脚本失效
  • 每次路由变化时执行脚本,若脚本变大会影响页面渲染速度

方案三:自定义Loader

截屏2021-06-06 上午2.04.24.png

第一步:配置自定义loader

在webpack中,loader从下往上执行,因此必须将自定义loader写在sass/less-loader之后

/** webpack.js部分代码片段 */
'sass-loader',
     {
      loader: path.resolve(__dirname, './loaders/merge-css.js'), //自定义loader路径
      options: {
          style: path.resolve(__dirname, './disabled.css'), //自定义css文件路径
      },
     }
复制代码

第二步:实现自定义loader功能

将第三步编写的css样式文件读入,合并到原始文件并传给下一个loader

/** loader-css.js代码 */
const { getOptions } = require('loader-utils');
const fs = require('fs');
module.exports = function (source) {
    const options = getOptions(this);
    const { style } = options;
    // 读取样式文件,返回字符串
    const string = fs.readFileSync(style);
    // 合并到原始文件,返回给下一个loader
    source += string;
    return source;
};
复制代码

第三步:

在disabled.css中编写css样式即可完成样式覆盖

优点

  • 不影响源代码,配置webpack即可
  • 扩展性强,可适用于其他需求

缺点

编写loader学习成本较大,需理解webpack相关知识

三. 最终的使用方式

考虑到后续的可扩展性,使用第三种方式

四. 实际效果展示

业务相关,仅部分展示,实际效果为全局生效
WechatIMG145.jpeg

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享