色々詰め込んだタイトルになりましたが、chroma.jsとReactで、「CSSのカラーコードを簡単に作れるアプリを作成できれば面白そう」という理由で始めたものです。
作るうちに見栄え。状態管理、JavaScriptファイルの統合やらを考え始めると、タイトルのツールが必要だったので加えました。
勉強目的という趣味でしていることですので、内容は参考程度にお願いします(私の勉強メモを兼ねています)。
できたもの
↓が作成したものです。
実際に動くので、触ってみて下さい。
JavaScript(bundle.js)のファイルサイズは、約340KBになりました(mode: productionで作成した場合)。
package.json
{
"name": "chromajs",
"version": "1.0.0",
"main": "src/index.js",
"license": "MIT",
"devDependencies": {
"react-scripts": "^4.0.1",
"redux-devtools": "^3.7.0",
"webpack-cli": "^4.2.0",
"webpack-dev-server": "^3.11.0"
},
"dependencies": {
"@material-ui/core": "^4.11.2",
"babel-loader": "8.1.0",
"chroma-js": "^2.1.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-redux": "^7.2.2",
"redux": "^4.0.5"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"eject": "react-scripts eject",
"test": "react-scripts test",
"wpbuild": "webpack",
"wpstart": "webpack-dev-server --hot --inline",
"wptest": "jest --watch"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
各ライブラリのこと
React
言わずもがなですが、SPA(Single Page Application)作成で用いられる有名なJavaScriptライブラリですね。詳しくは、ググって頂いたほうが詳しい解説が見つかります。
Redux
状態(state)管理をするJavaScriptのコンテナで、React特有のものではないようですが、Reactと一緒に使用されることが多いようです。なお、Reactで使う場合はReact Reduxを使用します。
Reactは個々のコンポーネントごとにstateを持つため、プログラムが複雑になると標準の状態では状態管理が難しくなります。Reduxは各コンポーネントに依存しない状態の保管庫を持ち(Storeと呼ぶ)、また、状態の値の呼び出し、書き込みの方法を提供します。後者はreducerとactionと呼ばれる方法で実装します。各コンポーネントReact Reduxのconnectを使用して結合できます。
補足ですが、Chromeの拡張機能でReduxのストアをデバッグ用に確認できるツールがあります(Redux DevTools)。これが結構便利でした。ストアの中身がちゃんと書き換わっているのかを確認できます。
使用するにはコードを追記する必要があります(公式の説明はこちらのGitHubページ)。私は以下のようにしてします。参考までに。
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
//import store from './redux/store';
//↓テスト用コード
import { createStore, compose } from 'redux'
import rootReducer from "./redux/reducers";
const store = createStore(
rootReducer,
compose(
process.env.NODE_ENV === 'development' && window.devToolsExtension ? window.devToolsExtension() : f => f
)
)
//↑テスト用コード
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
chroma.js
chroma.jsは、CSSで使用するカラーコード(#FFFFFFとか)、RGB、CMYK、など色彩コードの操作が簡単にできるライブラリです。カラーコードはX11 Colorsにも対応しています(whiteとかredとかazureとか、ウェブでよく使用される色の名称)。自力で各コードを変換することもできなくはなさそうですが、chroma.jsは簡単にできるので便利です。
注意点というほどのものではないですが、RGBやCMYKやHSVの戻り値は配列で返されます。chorma('#fff').css()
のように.css()
プロパティを使用するとRGB(255,255,255)
のようなCSS用の値を返してくれます。.name()
で色名称か#fff
のような値を返します。
面白いのは、グラデーションを作成できるところで、色々便利な使い方ができそうです。
Material-UI
Material-UIは、React用のUIコンポーネントライブラリで、マテリアルデザインと呼ばれるGoogleが提唱したデザインシステムを実装しているもの。ライセンスは、MIT。
導入実績がすごいですね…。NASA、J.P.Morgan、NETFLIX、amazon、UNIQLOもあります。
今回はスライドバーを使用したかったので、これを使用しました(他にもありますが、きれいだったので)。
webpack
例えば、yarn build
とかでプロジェクトファイル一式をビルドすると、.jsとか.mapファイルとか複数の静的ファイルが作成されますが、webpackを使用するとそれら全てを1つのファイルにまとめてくれます。複数ファイルになるよりはHTTPのトラフィックも減るし、1つのアプリのファイルを1つのファイルにまとめることができます。ファイルサイズも削減できるみたいですね(副次的な効果なのかは分かりませんが)。
作成ではまったこと、注意点など
構成が複雑な時はHooksよりReduxのほうが構成が整理できる
HooksはReact 16.8から追加された機能で、クラスを書かなくてもstateが利用できるものです。状態管理(stateのこと)の作り込みが簡単にできるのはメリットなのですが、Storeの概念がなく、コンポーネントの数が増加してシステム全体として1つの場所(ストア)で状態管理をしたい場合、Hooksだと難があるというデメリットがあります(歴史的経緯は詳しく知りませんが、それまでは全部Reduxでやっていたのかも)。
上に載せた例だとまだ動作は軽いですが、例えば下の例のように色一覧のリスト(これもコンポーネント)を予め作成しておき、クリックすると上のバーと色見本に反映させる場合、Hooksだと値が変わると下部の色一覧のコンポーネントも全て再描画される作りにしかできないので、結果として、動作がだいぶカクついてしまいます。
Hooks、あまり融通が利いた作りにすることができないようです。
実は最初Hooksで作成していたのですが、以上の問題があったので、React Reduxで作り直すことにしました。
Redux、最初は覚えるのが難しい
Redux(とかReact Redux)の解説はネットで探せば沢山見つかりますし、英語に抵抗がなければ本家サイトのBasic Tutorialが詳しいです。それほど難しい英語ではないし、CodeSandBoxにもサンプルコードがあるのでハンズオン的にも学べます。
書籍だと『React.js & Next.js 超入門』(掌田津耶乃著、秀和システム、2019)を見ましたが、こちらも丁寧に解説されていますが、ただconnectあたりの説明がもっと欲しかったので少しもの足りなかったです(結構重要な箇所なので)。
ざっくりしたポイントとしては、
- 一番上の親要素を
Provider
で囲む - ストア(store)作成
- reducer作成
- action作成
- connectでコンポーネントを紐付ける
という感じでしょうか。
親要素をProviderで囲む
こんな感じ。これをしないと、connectも使えないし各々の子要素で状態が取得・セットできません。
(他のimport省略)
import store from './redux/store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
store作成
ストアはstateの値を一元的に管理するもの。
reducer作成
状態(state)は直接書き換えることはせず、reducerを通してセットする。
条件分岐をしているのは、追加や更新など、各々の処理を定義しているため。action.type
で判断(分岐)する。引数として元のstate、action(ここに更新後の値が来る)を取る。
const init = {
value: '#fff'
};
//Reducer
export default function (state = init, action){
switch (action.type){
case SET_DATA:
const {value} = action.payload;
return {...state, value: value};
default:
return state;
}
}
本筋に関係ない話ですが、JavaScriptにあまり慣れていないこともあり、連想配列、アロー関数の書き方、スプレッド構文、コールバック関数の書き方などで結構手間取った(一番時間を取ったかも)。
action作成
reducerに対する処理(更新・追加・削除など)を予め定義しておくもの。type
は必ず必要。
import { SET_DATA } from "./actionTypes";
export const setData = value => ({
type: SET_DATA,
payload: {value: value}
});
connectでコンポーネントを紐付ける
connectの理解が結構大変でした。日本語の情報源でもいいですが、React ReduxのBasic Tutorialが参考になると思います(上でも挙げましたが)。
下は、コンポーネントの中でstateを参照し、かつ更新もする場合の書き方。
class CustomSlider extends React.Component{
render(){
(・・・中略・・・)
}
};
const mapStateToProps = state => {
const { color } = state;
const RGB = Chroma(color.value).rgb();
return {RGB:RGB};
};
export default connect(mapStateToProps, { setData })(CustomSlider);
これ(↓)は、stateの取得をする時だけの書き方。withStyles(useStyles)
があるのはMaterial UIのためです(下で別途解説します)。
class ConfigColor extends Component {
render() {
const { classes } = this.props;
return (
(・・・中略・・・)
);
}
}
const mapStateToProps = state => {
const { color } = state;
const RGB = Chroma(color.value).rgb();
return {RGB: RGB};
}
export default connect(mapStateToProps)((withStyles(useStyles)(ConfigColor)));
こちらはstateのセットをする時だけの書き方。actionを指定します。
import {setData} from '../redux/actions';
(・・・中略・・・)
class ColorList extends Component {
render() {
const { classes } = this.props;
return (
(・・・中略・・・)
);
}
}
export default connect(null, { setData })((withStyles(useStyles)(ColorList)));
mapStateToPropsは state => state
という書き方もできます。難しいと感じるのは、React Reduxが柔軟な実装方法に対応しているためだと思うのですが、慣れてみると(多少慣れた程度だけど)Hooksよりは使いやすい気がします。
Material-UIのstylesを使う場合
@material-ui/stylesを使用する場合、一工夫しないと動作しません。具体的には、↓のコードで「★ポイント」と書いた場所を追記・修正する必要があります(①を追記、②を修正)。
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Paper from "@material-ui/core/Paper";
import { withStyles, makeStyles } from '@material-ui/core/styles';
import {setData} from '../redux/actions';
const useStyles = theme => ({
root: {
(・・・中略・・・)
}
});
class Palette extends Component {
render() {
const { classes } = this.props; //★ポイント①
return (
<Paper className={classes.root}>
(・・・中略・・・)
);
}
}
const mapStateToProps = state => {
(・・・中略・・・)
}
export default connect(mapStateToProps,{setData})(withStyles(useStyles)(Palette)); //★ポイント②
もう少し、すっきり書く方法があるかもしれませんが。
webpack実行でハマったこと
webpack.config.js
を作成する必要があります。私は以下のように作成しました。public
フォルダにファイル一式が作成されます(index.html
は自分で作らないといけないよ)。
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'public')
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: 'babel-loader',
},
]
}
};
あと、babel.config.js
を作成してないと、ちゃんと動作してくれませんでした。
module.exports = {
"presets": ["@babel/preset-env", "@babel/preset-react"]
};
mode
がdevelopment
だとbundle.js
のファイルサイズが大きくなります(数MBとか)。mode: 'production'
でリリース版が作成できます。
他の所感
開発環境には、Visual Studio Codeを使用しています。
有名かもしれませんが、ESLint便利ですね。コードの不備をチェックしてくれるので(今回は使用していないけどカスタマイズもできる)。あと、上では書きませんでしたが、Live Server拡張機能とか、上でも挙げたRedux DevToolsはデバッグに便利でした。
今まで.NET開発がメインで、JavaScriptは開発環境周りが不便なイメージがあったのですが(逆にVisual Studioが便利すぎるのだろうけど)、拡張機能を揃えるとだいぶ開発は楽にできるようでした。
あと、Google検索で「colorpicker」と検索するとGoogleのツールが使用できますので、RGBとかの色要素をただ探したいだけなら、そちらのほうが便利かもしれません(CSS用のコードは直接は生成しないけど)。
以上です。