使用 React 动态加载样式表

IT技术 javascript stylesheet reactjs isomorphic-javascript
2021-03-02 14:10:09

我正在构建一个用于管理营销登陆页面的 CMS 系统。在“编辑登陆页面”视图中,我希望能够为用户正在编辑的任何登陆页面加载关联的样式表。我怎么能用 React 做这样的事情?

我的应用程序是完全 React、同构的,在Koa 上运行我的相关页面的基本组件层次结构如下所示:

App.jsx (has `<head>` tag)
└── Layout.jsx (dictates page structure, sidebars, etc.)
    └── EditLandingPage.jsx (shows the landing page in edit mode)

登录页面的数据(包括要加载的样式表的路径)在EditLandingPagein 中异步获取ComponentDidMount

如果您需要任何其他信息,请告诉我。很想弄清楚这一点!

额外奖励:我还想在离开页面时卸载样式表,我假设我可以做任何答案的相反操作ComponentWillUnmount,对吗?

6个回答

只需使用 react 的状态更新您想要动态加载的样式表的路径。

import * as React from 'react';

export default class MainPage extends React.Component{
    constructor(props){
        super(props);
        this.state = {stylePath: 'style1.css'};
    }

    handleButtonClick(){
        this.setState({stylePath: 'style2.css'});
    }

    render(){
        return (
            <div>
                <link rel="stylesheet" type="text/css" href={this.state.stylePath} />
                <button type="button" onClick={this.handleButtonClick.bind(this)}>Click to update stylesheet</button>
            </div>
        )
    }
};

此外,我已将其实现为react组件。你可以通过 npm install react-dynamic-style-loader 安装。
检查我的 github 存储库以检查:https :
//github.com/burakhanalkan/react-dynamic-style-loader

这很好地满足了我的需要。我使用 react-create-app 所以我不得不将 css 移动到公共文件夹。
2021-04-22 14:10:09
@cabaji99 您的链接元素的 href 是什么样的?你做了"%PUBLIC_URL%/stylesheet_name.css"还是什么?
2021-04-22 14:10:09
我不想加载整个样式表,尤其是因为我使用的是 Rails,所以我根据您的回答做了一些黑客攻击,但有条件地添加了样式标签gist.github.com/siakaramalegos/eafd1b114ddcbe8fac923edbc9f8a553
2021-04-26 14:10:09
这对我来说也很好用,请记住,如果您想public从 Javascript 代码中动态访问位于文件夹中的样式表,您应该使用PUBLIC_URL环境变量,如下所示: <link rel='stylesheet' type='text/css' href={ process.env.PUBLIC_URL + '/foo.css' }/>
2021-04-26 14:10:09
@MarcoPrins 您可以尝试 react-helmet 渲染到<head>元素中
2021-04-30 14:10:09

我认为 Burakhan 的答案是正确的,但加载<Link href = "" />到 body 标签中很奇怪这就是为什么我认为应该将其修改为以下内容 [我使用 React hooks]:

import * as React from 'react';
export default MainPage = (props) => {
  const [ stylePath, setStylePath ] = useState("style1.css");
    
  const handleButtonClick = () => {
    setStylePath({stylePath: 'style2.css'});
  }

  useEffect(() => {
    var head = document.head;
    var link = document.createElement("link");

    link.type = "text/css";
    link.rel = "stylesheet";
    link.href = stylePath;

    head.appendChild(link);

    return () => { head.removeChild(link); }

  }, [stylePath]);

  return (
    <div>
      <button type="button" onClick={handleButtonClick}>
        Click to update stylesheet
      </button>
    </div>
  );
};
我认为您需要return () => { head.removeChild(link); }head.appendChild节点正下方进行清理,否则您将在 stylePath 更改时继续添加节点。
2021-04-22 14:10:09

这是主要的 mixin teritority。首先,我们将定义一个帮助程序来管理样式表。

我们需要一个函数来加载一个样式表,并返回一个成功的Promise。样式表实际上非常疯狂地检测...

function loadStyleSheet(url){
  var sheet = document.createElement('link');
  sheet.rel = 'stylesheet';
  sheet.href = url;
  sheet.type = 'text/css';
  document.head.appendChild(sheet);
  var _timer;

  // TODO: handle failure
  return new Promise(function(resolve){
    sheet.onload = resolve;
    sheet.addEventListener('load', resolve);
    sheet.onreadystatechange = function(){
      if (sheet.readyState === 'loaded' || sheet.readyState === 'complete') {
        resolve();
      }
    };

    _timer = setInterval(function(){
      try {
        for (var i=0; i<document.styleSheets.length; i++) {
          if (document.styleSheets[i].href === sheet.href) resolve();
        } catch(e) { /* the stylesheet wasn't loaded */ }
      }
    }, 250);
  })
  .then(function(){ clearInterval(_timer); return link; });
}

好吧 $#!@... 我原以为只是在上面贴上一个加载项,但没有。这是未经测试的,所以如果有任何错误,请更新它——它是从几篇博客文章中编译而来的。

其余的相当简单:

  • 允许加载样式表
  • 可用时更新状态(以防止 FOUC)
  • 当组件卸载时卸载所有加载的样式表
  • 处理所有异步优点
var mixin = {
  componentWillMount: function(){
    this._stylesheetPromises = [];
  },
  loadStyleSheet: function(name, url){
    this._stylesheetPromises.push(loadStyleSheet(url))
    .then(function(link){
      var update = {};
      update[name] = true;
      this.setState(update);
    }.bind(this));
  },
  componentWillUnmount: function(){
    this._stylesheetPromises.forEach(function(p){
      // we use the promises because unmount before the download finishes is possible
      p.then(function(link){
        // guard against it being otherwise removed
        if (link.parentNode) link.parentNode.removeChild(link);
      });
    });
  }
};

同样,未经测试,如果有任何问题,请更新。

现在我们有了组件。

React.createClass({
  getInitialState: function(){
    return {foo: false};
  },
  componentDidMount: function(){
    this.loadStyleSheet('foo', '/css/views/foo.css');
  },
  render: function(){
    if (!this.state.foo) {
      return <div />
    }

    // return conent that depends on styles
  }
});

剩下的唯一要做的是在尝试加载之前检查样式表是否已经存在。希望这至少能让你走上正确的道路。

我不明白为什么人们不只是根据道具或状态更改项目内链接的 href 属性...
2021-04-26 14:10:09
将测试重点放在 Firefox 上,因为它在所有浏览器中的支持最差。
2021-05-07 14:10:09
哇,真棒!我一定会试一试,让你知道它是如何工作的。谢谢!
2021-05-10 14:10:09
这是有帮助的,但有一些错别字(错误)
2021-05-14 14:10:09

这就是我动态添加样式的方式:

import React, { Component } from "react";

class MyComponent extends Component {
    componentDidMount() {
        const cssUrl = "/public/assets/css/style.css";
        this.addStyle(cssUrl);
    }

    addStyle = url => {
        const style = document.createElement("link");
        style.href = url;
        style.rel = "stylesheet";
        style.async = true;

        document.head.appendChild(style);
    };

    render() {
        return <div> textInComponent </div>;
    }
}

export default MyComponent;
捆绑、缩小和缓存破坏呢?
2021-05-13 14:10:09

除了为样式表创建元素之外,您还可以尝试根据某些条件导入 css。ECMAScript 提供了一个启用动态module导入的提议,其工作原理如下:

if (condition) {
  import('your css path here').then((condition) => {});
}