【问题标题】:Dynamically applying CSS to JSX objects将 CSS 动态应用到 JSX 对象
【发布时间】:2018-11-04 08:16:36
【问题描述】:

很高兴在参考了这个网站多年之后,我终于发布了我的第一篇文章。非常感谢这里的每一个人,我期待成为一名积极的成员并为社区做出积极贡献。

我正在尝试为以下问题寻找更多 react-js(ish) 解决方案: 我们有一个用 React 构建的小应用程序,它由一个简单的事件列表组成。要求之一是客户端能够为特定事件提供自定义样式。因此,当我们获取某个事件的数据时,该特定事件可能会关联一些特定的样式。这些是通过以文本形式返回的 API 调用来检索的。

为了让客户端尽可能简单和不繁琐,这些样式由简单的选择器组成,例如'.event-container'、'.event-date' 等...

在 vanilla JS 工作代码中,我解析了这些样式,并将事件 ID 添加到这些样式之前,以便它们最终成为 '#event-1234 .event-container'、'#event-1234 .event-date ' 等等...然后我在文档的头部创建一个样式节点并用生成的 CSS 填充它...

这很好用,但它不是“反应”的做事方式,我被问到是否可以找到一个更符合 React 机制的不同解决方案......这基本上意味着,我认为,那些风格会有直接应用于 JSX 对象。为了做到这一点,我可能需要一种方法来在 react 事件组件中运行类似 querySelector 的东西,该组件返回该 JSX 节点,然后将样式应用于 JSX 节点......

到目前为止,我认为这有点麻烦......并且会增加代码复杂性并降低可能应用的 CSS 的灵活性,但也许有人比我有更好的想法,因为我没有丰富的经验还没反应过来……

非常感谢大家对此提出的意见。

这是来自特定事件的 API 调用的一些 CSS:

.event-card {
    width: 100% !important;
    background-color:black;
    border-radius: 40px;
    padding: 20px;
    box-shadow: 2px 2px 2px 4px rgba(0, 0, 0, .4);      
}

/* Title */
.event-name {
    color:#79acd1; 
}

/* Type */
.event-type {
    color: white;
}

/* Progress: .event-status + .event-remaining */
.event-progress {
    color:white;
}

/* Comments */
.event-comments {
    color:white;
}

/* Description */
.event-description {
    color:white;
}

/* Status 'open'|'closed' */
.event-status {
    color:#57a5d0;
}

/* Remaining days */
.event-remaining {
    color:#57a5d0;
}

/* Progress Bar Progress */
.mdc-linear-progress__bar-inner {
    background-color:#57a5d0;
}

/* Progress Bar Buffer */
.mdc-linear-progress__buffer {
    background-color:#44516C;
}

/* Date */
.event-date {
    color:#89BBFE;
}

/* Load Button */
.mdc-button {
    background-color:#79acd1;
}

这是一个普通的 JS 对象,它负责解析选择器并预先添加相关事件 ID 并将它们附加到文档:

export default {
  applyStyle :  function(css, id) {
    let style = this.parseCSS(css, id);
    if (css) {
      this.createNode(style, id);
    }
  },
  parseCSS: function (css, id) {
    let _rules = css.trim().split('}');
    _rules.pop();
    let rules = [];
    for (var rule of _rules) {
      rule = rule.trim() + '}';
      rules.push(`#${id} ${rule}`);
    }
    if (!rules.length) {
      return;
    }
    return rules.join('\r\n');
  },
  createNode: function (css, id) {
    let styleId = id + '-style';
    if (document.getElementById(styleId)) {
      return;
    }
    let node = document.createElement('style');
    node.id = styleId;
    node.innerHTML = css;
    document.head.appendChild(node);
  }
};

这是我们的事件容器组件,也是我实现 vanilla JS 解决方案的地方(参见“applyStyle”):

import React, {Component} from 'react';
import PropTypes from 'prop-types';

import Loader from '../common/Loader.jsx';
import Error from '../common/Error.jsx';

import EventPanel from './EventPanel.jsx';
import {ERROR_FETCH_EVENT} from '../../constants/ErrorMessages';
import styles from '../../util/styles';

class Event extends Component {
  constructor() {

    super();
  }
  render() {
    const {event} = this.props;

    if (!event) {
      return (<Loader/>);
    }

    if (event.errorMessage) {
      return (
        <Error messageHeader={ERROR_FETCH_EVENT} error={this.props.event.errorMessage}/>
      );
    }

    this.htmlId = `event-${this.props.event.id}`;
    let css = this.props.event.css.data || '';
    styles.applyStyle(css, this.htmlId);
    return  (
      <div id={this.htmlId} className="oc-kse-content">
        <div className="oc-kse-events">
          <EventPanel event={this.props.event} onEventClick={this.props.onEventClick}/>
        </div>
      </div>
    );
  }
}

Event.propTypes = {
  event: PropTypes.object,
  onEventClick: PropTypes.func
};

export default Event;

最后是包含大部分 JSX 的事件面板组件:

import React, {Component} from 'react';
import PropTypes from 'prop-types';

import {Button} from 'rmwc/Button';
import {Card} from 'rmwc/Card';
import {LinearProgress} from 'rmwc/LinearProgress';

import format from 'date-fns/format';
import differenceInDays from 'date-fns/difference_in_days';

import config from '../../config/AppConfig';

class EventPanel extends Component {

  render() {
    const {event} = this.props;
    const daysRemaining = this.daysRemaining(event);
    const percentagePassed = this.percentagePassed(event);
    const isOpen = event.status && event.status === 'open';
    const startDate = this.formattedDate(event.startEvent);
    const endDate = this.formattedDate(event.endEvent);
    const startTime = this.formattedTime(event.startEvent);
    const endTime = this.formattedTime(event.endEvent);

    if (event) {
      return (
        <React.Fragment>
          <Card className="event-card">
            {
              event.imageFormats && <div className="media">
                  <img
                    src={`${config.IMAGE_HOST}${event.imageFormats.impexp.$ref}`}
                    alt={event.name}
                    className="media-image"/>
                </div>
            }
            <div className="content">
              <div className="detail">
                <div className="event-type">{event.eventType}</div>
                <div className="event-title">
                  <span className="event-name">{event.name}</span>
                  <span className="event-comments">
                    <i className="fa fa-comments fa-fw"></i>{
                      event.representationCount
                        ? event.representationCount
                        : 0
                    }
                  </span>
                </div>
                <div className="event-description">
                  {event.description}
                </div>
                <div className="event-progress">
                  <span className="event-status">{event.status}</span>
                  <span className="event-remaining">{daysRemaining >= 0 && daysRemaining + ' days left'}</span>
                </div>
                <div>
                  {!isNaN(percentagePassed) && isOpen && <LinearProgress progress={percentagePassed}></LinearProgress>}
                </div>
                <div className="event-dates">
                  <div className="event-date">
                    <span className="span-block space-right-small">{startDate}</span>
                    <span>{startTime}</span>
                  </div>
                  <div className="event-date">
                    <span className="span-block space-right-small">{endDate}</span>
                    <span>{endTime}</span>
                  </div>
                </div>
              </div>
              <div className="actions">
                <Button
                  raised={true}
                  className="primary"
                  onClick={() => this.props.onEventClick(event.id)}>LOAD</Button>
              </div>
            </div>
          </Card>
        </React.Fragment>
      );
    }
  }
}

EventPanel.propTypes = {
  event: PropTypes.object,
  onEventClick: PropTypes.func
};

export default EventPanel;

【问题讨论】:

  • 如果你放一些代码来给你输入会更好......
  • 大家好,在研究了一整天的 react 解决方案之后,我们认为 vanilla 方式是最有效和最简单的。谢谢

标签: javascript css reactjs jsx


【解决方案1】:

我不确定这是否是您的最佳选择,但将您的样式存储到这样的对象中可能会更好:

const styleOptions={
    eventStatus:{
       color:"white",
       /* other stuff in camelCase */
    },
    /* other options */

}

然后像这样应用你的样式:

render(){
    const style = styleOptions.eventStatus;
    return(
        <div style={style}>
          ...
        </div>
    )
}

这似乎是一种更 React 的组件样式方式,您无需创建节点,也无需解析 css 文件。

【讨论】:

  • 感谢您的回答。以这种方式想到的一个问题是能否拥有 CSS 伪元素、悬停、之前、之后等...
  • 您只需要以不同的方式管理它。使用 onMouseOver 之类的事件函数用于 :hover, 用于 :after 这里是一个例子 :stackoverflow.com/questions/28269669/….
  • 这就是为什么我认为这不是合适的方式。最好不要直接依赖 JavaScript 来完成仅使用纯 CSS 即可实现的事情......而且与仅在样式标签中手动应用 CSS 相比,实现会变得过于繁琐和过于复杂
  • 其实可以很简单。如果您使用“悬停”和“之前”、“之后”选项定义样式对象,则只需创建具有方法和默认行为的样式组件。然后你只需要给它样式对象作为道具,它应该与任何给定的样式完美配合。我认为,它比解析 css 文件和通过 dom 创建元素更可靠且资源消耗更少。在 React 中不推荐使用。
  • 感谢您的回答,但我不清楚如何与从 API 检索到的纯文本 CSS 分开。如果你能提供一个例子,我将不胜感激。谢谢
猜你喜欢
  • 2019-12-24
  • 2020-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-19
  • 2019-09-02
  • 1970-01-01
  • 2014-10-02
相关资源
最近更新 更多