CSS
JavaScript
reactjs
css-modules
css-in-js

ReactコンポーネントのCSSをひとまとめにする

ReactのコンポーネントのCSSをもっとスマートにしたいというのが発端です。

CSS-in-JSやCSS Moduleを使ってCSSを管理していると<style>...</style>がたくさんできてしまってスマートでない。

<実装前>
ss 1.jpg

そこで、<style>タグひとつにまとめる方法を考えてみました。

<実装後>
ss 2.jpg

スマートになっていい感じ。

方法

ReactのcomponentDidMountもしくはcomponentWillMountでCSSを生成し、それを<style></style>タグの中へ入れる。

component.js
import { h, Component } from 'preact';
import { Link } from 'preact-router/match';
import Client from '../../../functions/client/index';
import Logo from '../../../src/assets/logo.svg'

export default class Header extends Component {
    constructor(props) {
        super(props);
        this.f = new Client;
        this.id = 'x' + this.f.sid();
    }

    componentDidMount() {
        let s = {};
        let q = {};
        let i = this.id;

        s['i'] = `
        position: relative;
        text-align: center;
        background: #000;
        `
        s['.l'] = `
        padding: 12px 0 11px;
        border-bottom: 1px solid #fff;
        display: inline-block
        `

        s['.l img'] = `
        width: 148px;
        height: auto
        `

        q[600] = [{
            '.l img' : `
            width: 111px;
            height: auto
            `
        },{
            '.l .test' : `
            width: 111px;
            height: auto
            `
        }];

        this.f.styles(s,i,q);
    }

    render() {
        return (
            <header class={this.id}>
                <a href="/" className="l">
                    <img src={Logo} alt="" />
                </a>
            </header>
        );
    }
}

なんども使うので、関数はClientという名前で別ファイルのclassからインポートして使っています。
実行はthis.f.styles(s,i,q)というところです。
クラスはランダムなidを生成してそれをクラス名としているので、ローディングの度に動的に作り出されます。

実行側の関数はこんな感じ。

client.js
// クライアント用の関数群

class Client {
    constructor() {
    }

    sid = () => {
        var firstPart = (Math.random() * 46656) | 0;
        var secondPart = (Math.random() * 46656) | 0;
        firstPart = ("000" + firstPart.toString(36)).slice(-3);
        secondPart = ("000" + secondPart.toString(36)).slice(-3);
        return firstPart + secondPart;
    }

    styles = (styles, id, mediaQueries) => {

        let val, style = document.getElementsByTagName('style');

        if (style.length === 0) {
            let elm = document.createElement('style'), h = document.getElementsByTagName('head');
            h[0].appendChild(elm);
        }

        let results = '';

        Object.keys(styles).forEach(function(key) {
            if (key === 'i') {
                let c = styles[key];
                c = c.replace( /\/\*(?:(?!\*\/)[\s\S])*\*\/|[\r\n\t]+/g, '' );
                c = c.replace( / {2,}/g, ' ' );
                c = c.replace( / ([{:}]) /g, '$1' );
                c = c.replace( /([;,]) /g, '$1' );
                c = c.replace( / !/g, '!' );
                results += '.'+ id + '{' + c + '}';
            } else {
                let c = styles[key];
                c = c.replace( /\/\*(?:(?!\*\/)[\s\S])*\*\/|[\r\n\t]+/g, '' );
                c = c.replace( / {2,}/g, ' ' );
                c = c.replace( / ([{:}]) /g, '$1' );
                c = c.replace( /([;,]) /g, '$1' );
                c = c.replace( / !/g, '!' );
                results += '.'+ id + ' ' +  key + '{' + c + '}';
            }
        })

        if (mediaQueries !== undefined) {
            var keys = Object.keys(mediaQueries);
            for (let i=keys.length; i--;) {
                var query = keys[i];
                var arr = mediaQueries[keys[i]];
                let set = '';
                arr.map(function(element, index, array) {
                    Object.keys(element).forEach((key) => {
                        let obj = element[key];
                        let c = obj;
                        c = c.replace( /\/\*(?:(?!\*\/)[\s\S])*\*\/|[\r\n\t]+/g, '' );
                        c = c.replace( / {2,}/g, ' ' );
                        c = c.replace( / ([{:}]) /g, '$1' );
                        c = c.replace( /([;,]) /g, '$1' );
                        c = c.replace( / !/g, '!' );
                        set += '.' + id + ' ' + key + '{' + c + '}'
                    })
                });
                results += '@media screen and (max-width:' + keys[i] + 'px){' + set + '}';
            }
        }

        style[0].innerText += results;
    }

}

export default Client; 

ブラッシュアップは必要だけど、とりあえず使うには問題ないと思います。

ポイントとメリット

  • CSSのスコープはコンポーネント単位になる
  • レスポンシブにも対応(指定時配列でpushするようにしています。)
  • <style>が一つに集約される
  • Object.keysのときにkeyの順序が昇順になるので、処理して大きいmedia queryから追加されるようにしています
  • ざっくりminifyされる
  • cssをjs用に書き換える必要がない