- 投稿日:2020-10-26T18:52:37+09:00
gatsby(react)でMaterial-UIのthemeにcustom colorを足す(色を追加する)
reactのMaterial-UIのthemeにカラーを追加する方法についてのメモです。
gatsby-browser.js
とgatsby-ssr.js
gatsby-browser.js
とgatsby-ssr.js
にimport { CssBaseline } from '@material-ui/core' import React from 'react' import { RecoilRoot } from 'recoil' //ここから3つをimport //{ ThemeProvider }と{ MuiThemeProvider }が //いい感じにMaterial UIでもthemeをいい感じにカスタムできるようにしてくれる import { ThemeProvider } from 'styled-components' import { MuiThemeProvider } from '@material-ui/core/styles' import { theme } from './src/utility/theme' export const wrapRootElement = ({ element }) => { return ( <RecoilRoot> <MuiThemeProvider theme={theme}> <ThemeProvider theme={theme}> <CssBaseline /> {element} </ThemeProvider> </MuiThemeProvider> </RecoilRoot> ) }これでthemeに設定した色が使えるようになります。
theme.js
下記のような形で、カスタムカラーを追加することが可能です。
ここではgrayfill: '#fff000'
を追加しました。import { createMuiTheme } from '@material-ui/core/styles' export const theme = createMuiTheme({ palette: { primary: { main: '#EF2D29', }, secondary: { main: '#00A7C1', }, grayfill: '#fff000', background: { default: '#fff', }, }, })利用
例えば、
Material Iconをgrayfill
で塗りたい時//色々必要なやつはimport(省略) //使いたいアイコンをインポート import PersonIcon from '@material-ui/icons/Person' //propsでthemeに追加したカラーを渡す const PersonGrayIcon = styled(PersonIcon)` color: ${props => props.theme.palette.grayfill}; ` const SearchPage = () => { return ( <PersonGrayIcon /> ) } export default SearchPage
- 投稿日:2020-10-26T16:41:19+09:00
reactのscssで自前のレスポンジブを設定する
sample.js.body { @include media-breakpoint-up(sm) { font-size: 16px; } }こんな感じで設定する。
breakpoint-down(lg)などとすると適応する画面のサイズを調整出来る。
- 投稿日:2020-10-26T15:09:02+09:00
Reactを使っていてGraphQLのApollo ClientでQuery/Mutation時のtimeout設定について
基本公式で完結するので、そこまで悩みポイントはないが、ちょっと注意点があったので参考程度にまとめておく。
環境
- React v16.13.x
- Apollo Client v3.2.x
- apollo-link-timeout https://github.com/drcallaway/apollo-link-timeout
実装
10秒でタイムアウトさせたい場合
imoprt ApolloLinkTimout from 'apollo-link-timeout'; const timeoutValue = 10000; // 10秒 const timeoutLink = new ApolloLinkTimeout(10000); const apolloClient = new ApolloClient({ link: timoutLink.concat( createHttpLink({ url: 'http://example.com' }), ), });注意点
timeoutをconcat引数に渡すと効かない
また、clientインスタンスを生成する時の処理を以下のようにするとtimeoutが効かないので注意
ハマリポイントではないが、どちらもconcat
メソッドがあるので逆でやってみたら効かなかったのでメモに残しておくが、基本的には、ドキュメント通りにやれば問題ない。
https://github.com/drcallaway/apollo-link-timeout#usageconst apolloClient = new ApolloClient({ link: createHttpLink({ url: 'http://example.com' }).concat(timoutLink), });Subscriptionは効かない
ドキュメントに書かれている
An Apollo Link that aborts requests that aren't completed within a specified timeout period. Note that timeouts are enforced for query and mutation operations only (not subscriptions).QueryとMutation処理にのみtimeoutが効く
この他、link周りで気になった点があれば、公式を読むと理解が深まる気がする。
https://www.apollographql.com/docs/link/
- 投稿日:2020-10-26T15:09:02+09:00
Reactを使っていてApollo ClientでQuery/Mutation時のtimeout設定について
基本公式で完結するので、そこまで悩みポイントはないが、ちょっと注意点があったので参考程度にまとめておく。
環境
- React v16.13.x
- Apollo Client v3.2.x
- apollo-link-timeout https://github.com/drcallaway/apollo-link-timeout
実装
10秒でタイムアウトさせたい場合
imoprt ApolloLinkTimout from 'apollo-link-timeout'; const timeoutValue = 10000; // 10秒 const timeoutLink = new ApolloLinkTimeout(10000); const apolloClient = new ApolloClient({ link: timoutLink.concat( createHttpLink({ url: 'http://example.com' }), ), });注意点
timeoutをconcat引数に渡すと効かない
また、clientインスタンスを生成する時の処理を以下のようにするとtimeoutが効かないので注意
ハマリポイントではないが、どちらもconcat
メソッドがあるので逆でやってみたら効かなかったのでメモに残しておくが、基本的には、ドキュメント通りにやれば問題ない。
https://github.com/drcallaway/apollo-link-timeout#usageconst apolloClient = new ApolloClient({ link: createHttpLink({ url: 'http://example.com' }).concat(timoutLink), });Subsriptionは効かない
ドキュメントに書かれている
An Apollo Link that aborts requests that aren't completed within a specified timeout period. Note that timeouts are enforced for query and mutation operations only (not subscriptions).QueryとMutation処理にのみtimeoutが効く
この他、link周りで気になった点があれば、公式を読むと理解が深まる気がする。
https://www.apollographql.com/docs/link/
- 投稿日:2020-10-26T13:03:05+09:00
ReactのContext + useState/useReducerでstateを管理する際の懸念点を改善するライブラリを作成した
概要
こんにちは。Webを先行しているkqitoです。
先日、
React
のContext
+useState
/useReducer
でstateを管理する際に懸念点を改善するライブラリを作成したので、
- その背景となった問題点
- 作成したライブラリについて
をまとめたいと思います。
作成したのはuse-global-contextというライブラリで、npmによれば週間3500DLほどされているようです。( 頑張って作成したので嬉しい... )
もし良ければリポジトリの方も閲覧して頂けると幸いです。
背景
ReactやRedux周りについて自分の意見を述べてみます。ここら辺に対して知識がある方は読み飛ばしていただいて結構です。
単方向データフローによる状態管理
Reactだけに止まらない話になると思いますが、仮想DOM機能を有したフレームワークを用いたWebフロントの開発には単方向データフローによる状態管理が主流となっていると思います。
このモデルを採用する理由としては
- 各Viewが複雑なイベントを持たない/やりとりしない
- 状態を分散させない
- 状態から一意なViewを作画する
という点があるという風に認識しています。
reactを用いたプロジェクトでは、この単方向データフローによる状態管理をしているのがほとんどだと思います(多分)
Reduxについて
上記で述べた単方向データフローの有名なライブラリの1つとして挙げられるのが
redux
です。redux
はreducer関数によって状態を管理するライブラリで、様々なプロジェクトで利用されています。また非同期処理はmiddlewareとして実行することができ、有名なライブラリとしては
redux-thunk
やredux-saga
などが代表的だと思います。コミュニティも活発で実績のあるライブラリですが、全てのプロジェクトにおいてこのreduxが採用できるわけでは無いと考えています。
適しているパターン
例えば、
- 大規模/長期間プロジェクト
- 複雑な状態管理
- storeの書き換えが頻繁に行われる事が想定される
のであれば
redux
はとても適していると思います。また、SSRにも対応しているのでパフォーマンス向上の一環として"事前にHTMLとして出力してCDNにキャッシュ"という手法も行う事が可能です。
適していないパターン
ずばり、小規模/短期間なプロジェクトだと私は考えています。
というのも、reduxでちゃんと実装するにはそれなりに"コスト"がかかると考えています。
- 実装コスト
- 非同期処理middleware(
redux-thunk
やredux-saga
など)の学習/実装コストこのようなコストに見返るほどの開発経験が得られるかどうかでreduxを採用するかを決めることが出来そうです。
また、reduxの素晴らしさを発揮出来ないと、ただ単にコードを冗長的にして、逆に可読性を落としているだけになってしまうパターンも考えられます。
じゃあreduxじゃないなら何が適しているかというと、後述する
Context API
だと思います。Context API + Hooks
以下の様にすることで簡易storeを作成することができます。
以下はuseReducerとuseContextとContextを組み合わせた簡易storeの一例です。
store.jsxexport const CounterStateContext = React.createContext(); export const CounterDispatchContext = React.createContext(); const Store = ({ children }) => { const [state, dispatch] = React.useReducer(reducer, initialState); return ( <CounterStateContext.Provider value={state}> <CounterDispatchContext.Provider value={dispatch}> {children} </CounterDispatchContext.Provider> </CounterStateContext.Provider> ); };以下の様にして利用します
Counter.jsxconst Counter = () => { // コンテキストを購読 const counter = React.useContext(CounterStateContext); return <p>{counter}</p>; };CounterButton.jsxconst CounterButton = () => { const counterDispatch = React.useContext(CounterDispatchContext); return ( <> <button onClick={() => counterDispatch(increment(1))}>+ 1</button> <button onClick={() => counterDispatch(decrement(1))}>- 1</button> </> ); };このようにシンプルな形でstoreを作成することが可能になります。
問題点
しかし、このstoreではいくつか懸念しなければならない点がいくつかあります。
不必要なレンダー
適切な手法で開発を行わないと、不必要なレンダーが発生する可能性があります。この点については様々な議論が行われていますが自分が最も理解しやすかったのは以下のissueのコメントです。
ここで述べられているのは主に2つの手法で
- Contextの分割
- Memo
を適切な粒度で行うというのものです。上記のリンクを参考にその問題点と解決策を述べてみます。
以下の様なオブジェクトをContextで管理しているとしましょう。
const state = { counter: 0, message: "", app: {} }そして、そのstateの
counter
プロパティをレンダーするCounterコンポーネントがあるとします。Counter.jsxconst Counter = () => { // コンテキスト全体からcounter部分を取得 const { counter } = React.useContext(CounterStateContext); return <p>{counter}</p>; };このCounterコンポーネントには問題があります。
useContext
は購読しているコンテキストが変更されたらそのコンポーネントの再レンダリングが走ります。(createContext
のcalculateChangedBits
などの話は無しとして)つまり、Counterコンポーネントにとって関係のない、
message
プロパティとapp
プロパティが変更した時にも再レンダリングが起こってしまうということです。理想は上記の
Counter
コンポーネントはcounter
プロパティが変更された場合にのみ再レンダリングが走って欲しいです。以下にその対策を記述します。
コンテキストの分割
先ほど、
useContext
は購読しているコンテキストが変更されたらそのコンポーネントの再レンダリングが走ります。と述べました。つまりコンテキストを適切な粒度で分割してしまえばその粒度でレンダリングが行われます。
イメージとしては以下の様な感じです。
store.jsx...省略 return ( <CounterStateContext.Provider value={counter}> <MessageStateContext.Provider value={message}> <AppStateContext.Provider value={app}> {children} </AppStateContext.Provider> </MessageStateContext.Provider> </CounterStateContext.Provider> );counter.jsxconst Counter = () => { const counter = React.useContext(CounterStateContext); return <p>{counter}</p>; };このようにコンテキストを分割すれば
message
プロパティとapp
プロパティが別コンテキストとして管理されている為、不必要なレンダリングが起こりません。が、ここで懸念したいのが、
- stateの追加/変更が起こった場合に適切な粒度を保てるのか
という点です。
ここは
redux
とよく対比される点(私が勝手に思い込んでる)で、redux
といっても実際にはcontext API
を利用しています。これは、ライブラリ名でいう所のreact-redux部分に該当します。
このようなcontextを分割する事とは対照的に、reduxはそのcontextが1つだけで構成されています。
比較してみると、
redux
利用者にとってコンテキスト部分が抽象化されていて開発する際にあまり気にしなくて良くなる点がredux
のメリットの1つとして数えることができるでしょう。話は逸れますが、
react-redux
がいわゆるreact
との仲介役なので、厳密に言えばreact
とredux
は疎結合でそれぞれが独立していると私は考えています。なのでreact
とredux
は独立して別々で管理する意識が大事だと思います。Memo
別のアプローチとしてmemoする手法が挙げられます。
以下の様に実装することで再レンダリングを防ぐことができます。
Counter.jsxconst Counter = () => { const counter = React.useContext(CounterStateContext); return React.useMemo(() => <p>{counter}</p>, [counter]); };上記の手法はコンポーネント自体は再計算されるので、useContext部分でmemoする手法もあります。
ここら辺の話は、言ってしまえば "やらなくても動く" 部分です。見落としがちだったりするので、このようなパフォーマンスの問題には担当エンジニア全員が意識を持って開発/検証/レビューしていきたいですね。
作成したライブラリについて
本題です。上記の様な問題を解決する為に以下のような事を目的としてuse-global-contextというライブラリを作成しました。
- 簡単にuseState/useReducerでstoreを作成できる
- 不必要なレンダーを防止
- SSR対応
使い方
import React from "react"; import { createUseReducerContext } from "use-global-context"; // ここでstoreを定義 const [useGlobalState, useGlobalDispatch, ContextProvider] = createUseReducerContext({ counter: { // counterステートの定義 counter.reducer, counter.initialState }, message: { // messageステートの定義 message.reducer, message.initialState }, app: { // appステートの定義 app.reducer, app.initialState } }); const App = () => ( <ContextProvider> <Counter /> <CounterButton /> </ContextProvider> ) const Counter = () => { // 必要なステートのみを取得 const counter = useGlobalState(state => state.counter); return <p>counter: {counter}</p>; }; const CounterButton = () => { // 必要なdispatchのみ取得 const counterDispatch = useGlobalDispatch(dispatch => dispatch.counter); return ( <> <button onClick={() => counterDispatch(increment(1))}>+ 1</button> <button onClick={() => counterDispatch(decrement(1))}>- 1</button> </> ); };このように利用することが出来ます。
上記の例は
useReducer
をよしなににしてくれるAPIですが、useState版も用意しています。ちなみに
useGlobalState
とuseGlobalDispatch
ですが、useSelector
ような機能が内部的にbindされています。const state = useGlobalState(); // { // counter: 0, // message: '', // app: { // name: 'use-global-context', // description: 'A easy global state management library' // } // } const app = useGlobalState(state => state.app); // { // name: "use-global-context", // description: "A easy global state management library", // } const dispatch = useGlobalDispatch() // { // counter: ƒ dispatchAction, // message: ƒ dispatchAction, // app: ƒ dispatchAction // } const counterDispatch = useGlobalDispatch(dispatch => dispatch.counter); // counter: ƒ dispatchAction,どのように問題を解決しているのか
ここからはライブラリの詳細部分を簡単に記述します。
このライブラリを作成する際にreduxの良い所はなるべく参考にしました。
まず
useGlobalState
とuseGlobalDispatch
ですが、react-redux
のuseSelector
をベースとして実装しています。
useSelector
は購読しているcontextが更新された場合、現在のstateの結果と更新後のstateの結果を比較し、selector後の差分がある場合には再レンダリングされる仕組みになっています。これにより
useContext
単体では問題となっていたstore購読部分が原因による不必要なレンダリングを防ぐことができます。参考: react-reduxのuseSelector
参考: use-global-contextのuseSelectorまた、それによりコンテキストの分割も意識しなくて良くなるのでreact初学者にとっても良い抽象化が出来ていると思います。
さいごに
Context
+useState
/useReducer
でstoreを作成する際に気をつけるべきこと・解決策をまとめました。上記のまとめた記事の内容や
use-global-context
でまだ懸念すべき点・実装するべき点などがあればご指摘いただけると幸いです。
- 投稿日:2020-10-26T06:01:42+09:00
Reactを学ぶII
■ はじめに
Reactについて2回目の記事にしました。
この記事で得る内容は以下の通りです。・JSX、トランスパイラ、React.create.Elementの理解
・Reactの基本文法の理解■ JSX
JavaScript内でHTMLを簡潔に記述する為の言語(JavaScriptの拡張言語)
Facebook社が開発し、Reactの公式ドキュメントはほぼJSXで記述されている(Reactの業界標準)→ JSXは、HTMLの様に書けるので、可読性を高める為に使われる
■ トランスパイラ
JSXの構文をブラウザが理解できる様に変換する翻訳の様な役割
○例
・JSX → JavaScript(ES6)
・JavaScript(ES6) → JavaScript(ES5)○代表的なトランスパイラ
・Babel、Coffee Script、Type Script...etc■ React.createElement
JSXが無い場合に使うが、HTMLを書いている感覚ではなくわかりにくい
JSXで記述した構文をトランスパイルし、出力したjsファイルを見ると、React.CreateElementを使った内容になるReact.createElementの例React.createElement( "h1", null, "Hello World!" );■ JSXの基本文法①
Reactパッケージのインストールが必須
sample.jsx// .jsxファイル内の先頭で宣言 import React from "react"■ JSXの基本文法②
HTMLとほぼ同じ記述(classはclassNameと記述する)
sample.jsxconst App = () => { return ( <div id="hoge" className="fuga"> <h1>Hello, World!</h1> </div> ); };■ JSXの基本文法③
{}内に変数や関数を埋め込める
5行目の{foo}が、JSXの基本文法②の4行目と同じになるsample.jsxconst foo = "<h1>Hello, World!</h1>" const App =() => { return ( <div id="hoge" className="fuga"> {foo} </div> ); };■ JSXの基本文法④
変数名などは全てキャメルケースにする
→キャメルケース:単語の区切りを大文字にする(ラクダのコブみたいなイメージ)
→ケバブケースは(JSX = JavaScriptなので)使用不可sample.jsxconst fooBar = "<h1>Hello, World!</h1>" const App =() => { return ( <div id="hoge" className="fuga"> {fooBar} </div> ); };■ JSXの基本文法⑤
空要素(例だとinput,imgタグ)は閉じる
sample.jsxconst App = () => { return ( <div id="hoge" className="fuga"> <input type="text" id="blankElement" /> <img src="/aseets/icon/foo.png" /> </div> ); };