27

我正在构建一个用于管理营销登陆页面的 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,对吧?

4

7 回答 7

35

只需使用 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>
        )
    }
};

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

于 2016-01-17T10:15:21.500 回答
10

我认为 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>
  );
};
于 2020-03-04T19:44:21.343 回答
8

这是主要的混合领域。首先,我们将定义一个帮助器来管理样式表。

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

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; });
}

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

其余的相当简单:

  • 允许加载样式表
  • 可用时更新状态(以防止 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
  }
});

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

于 2015-02-07T19:57:27.887 回答
0

在我的方法中,我使用这个:

const TenantSelector = ({ children }) => {
  // imagine its value from a json config
  const config = {
      custom_style: 'css/tenant.css' 
  }
  require(`./assets/${config.custom_style}`)
  return (
    <>
      <React.Suspense fallback={<></>}>
      </React.Suspense>
      {children}
    </>
  )
}

ReactDOM.render(
  <TenantSelector>
   <YourApp>
  </TenantSelector>,
  document.getElementById("root")
)
于 2021-08-26T06:59:22.310 回答
0

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

if (condition) {
  import('your css path here').then((condition) => {});
}
于 2020-08-19T05:30:28.503 回答
0

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

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;
于 2020-03-20T13:57:31.817 回答
0

我在渲染功能中使用 react-helmet....

{inject ? 
    <Helmet>
        <link rel="stylesheet" href="css/style.css" />
    </Helmet> : null}
于 2021-03-30T08:36:41.957 回答