20201026のReactに関する記事は6件です。

gatsby(react)でMaterial-UIのthemeにcustom colorを足す(色を追加する)

reactのMaterial-UIのthemeにカラーを追加する方法についてのメモです。

gatsby-browser.jsgatsby-ssr.js

gatsby-browser.jsgatsby-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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

reactのscssで自前のレスポンジブを設定する

sample.js
.body {

  @include media-breakpoint-up(sm) {
      font-size: 16px;
  }
}

こんな感じで設定する。
breakpoint-down(lg)などとすると適応する画面のサイズを調整出来る。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactを使っていてGraphQLのApollo ClientでQuery/Mutation時の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#usage

const 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/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactを使っていてApollo ClientでQuery/Mutation時の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#usage

const 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/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactのContext + useState/useReducerでstateを管理する際の懸念点を改善するライブラリを作成した

概要

こんにちは。Webを先行しているkqitoです。

先日、ReactContext + useState/useReducerでstateを管理する際に懸念点を改善するライブラリを作成したので、

  • その背景となった問題点
  • 作成したライブラリについて

をまとめたいと思います。

作成したのはuse-global-contextというライブラリで、npmによれば週間3500DLほどされているようです。( 頑張って作成したので嬉しい... )

Screen Shot 2020-10-26 at 11.48.55.png

もし良ければリポジトリの方も閲覧して頂けると幸いです。

背景

ReactやRedux周りについて自分の意見を述べてみます。ここら辺に対して知識がある方は読み飛ばしていただいて結構です。

単方向データフローによる状態管理

Reactだけに止まらない話になると思いますが、仮想DOM機能を有したフレームワークを用いたWebフロントの開発には単方向データフローによる状態管理が主流となっていると思います。

このモデルを採用する理由としては

  • 各Viewが複雑なイベントを持たない/やりとりしない
  • 状態を分散させない
  • 状態から一意なViewを作画する

という点があるという風に認識しています。

reactを用いたプロジェクトでは、この単方向データフローによる状態管理をしているのがほとんどだと思います(多分)

Reduxについて

上記で述べた単方向データフローの有名なライブラリの1つとして挙げられるのがreduxです。reduxはreducer関数によって状態を管理するライブラリで、様々なプロジェクトで利用されています。

また非同期処理はmiddlewareとして実行することができ、有名なライブラリとしてはredux-thunkredux-sagaなどが代表的だと思います。

コミュニティも活発で実績のあるライブラリですが、全てのプロジェクトにおいてこのreduxが採用できるわけでは無いと考えています。

適しているパターン

例えば、

  • 大規模/長期間プロジェクト
  • 複雑な状態管理
  • storeの書き換えが頻繁に行われる事が想定される

のであればreduxはとても適していると思います。

また、SSRにも対応しているのでパフォーマンス向上の一環として"事前にHTMLとして出力してCDNにキャッシュ"という手法も行う事が可能です。

適していないパターン

ずばり、小規模/短期間なプロジェクトだと私は考えています。

というのも、reduxでちゃんと実装するにはそれなりに"コスト"がかかると考えています。

  • 実装コスト
  • 非同期処理middleware(redux-thunkredux-sagaなど)の学習/実装コスト

このようなコストに見返るほどの開発経験が得られるかどうかでreduxを採用するかを決めることが出来そうです。

また、reduxの素晴らしさを発揮出来ないと、ただ単にコードを冗長的にして、逆に可読性を落としているだけになってしまうパターンも考えられます。

じゃあreduxじゃないなら何が適しているかというと、後述するContext APIだと思います。

Context API + Hooks

以下の様にすることで簡易storeを作成することができます。

以下はuseReduceruseContextContextを組み合わせた簡易storeの一例です。

store.jsx
export 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.jsx
const Counter = () => {
  // コンテキストを購読
  const counter = React.useContext(CounterStateContext);

  return <p>{counter}</p>;
};
CounterButton.jsx
const CounterButton = () => {
  const counterDispatch = React.useContext(CounterDispatchContext);

  return (
    <>
      <button onClick={() => counterDispatch(increment(1))}>+ 1</button>
      <button onClick={() => counterDispatch(decrement(1))}>- 1</button>
    </>
  );
};

このようにシンプルな形でstoreを作成することが可能になります。

問題点

しかし、このstoreではいくつか懸念しなければならない点がいくつかあります。

不必要なレンダー

適切な手法で開発を行わないと、不必要なレンダーが発生する可能性があります。この点については様々な議論が行われていますが自分が最も理解しやすかったのは以下のissueのコメントです。

参考URL

ここで述べられているのは主に2つの手法で

  • Contextの分割
  • Memo

を適切な粒度で行うというのものです。上記のリンクを参考にその問題点と解決策を述べてみます。

以下の様なオブジェクトをContextで管理しているとしましょう。

const state = {
  counter: 0,
  message: "",
  app: {}
}

そして、そのstateのcounterプロパティをレンダーするCounterコンポーネントがあるとします。

Counter.jsx
const Counter = () => {
  // コンテキスト全体からcounter部分を取得
  const { counter } = React.useContext(CounterStateContext);

  return <p>{counter}</p>;
};

このCounterコンポーネントには問題があります。

useContextは購読しているコンテキストが変更されたらそのコンポーネントの再レンダリングが走ります。(createContextcalculateChangedBitsなどの話は無しとして)

つまり、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.jsx
const 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との仲介役なので、厳密に言えばreactreduxは疎結合でそれぞれが独立していると私は考えています。なのでreactreduxは独立して別々で管理する意識が大事だと思います。

Memo

別のアプローチとしてmemoする手法が挙げられます。

以下の様に実装することで再レンダリングを防ぐことができます。

Counter.jsx
const 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版も用意しています。

ちなみにuseGlobalStateuseGlobalDispatchですが、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の良い所はなるべく参考にしました。

まずuseGlobalStateuseGlobalDispatchですが、react-reduxuseSelectorをベースとして実装しています。

useSelectorは購読しているcontextが更新された場合、現在のstateの結果と更新後のstateの結果を比較し、selector後の差分がある場合には再レンダリングされる仕組みになっています。

これによりuseContext単体では問題となっていたstore購読部分が原因による不必要なレンダリングを防ぐことができます。

参考: react-reduxのuseSelector
参考: use-global-contextのuseSelector

また、それによりコンテキストの分割も意識しなくて良くなるのでreact初学者にとっても良い抽象化が出来ていると思います。

さいごに

Context + useState/useReducerでstoreを作成する際に気をつけるべきこと・解決策をまとめました。

上記のまとめた記事の内容やuse-global-contextでまだ懸念すべき点・実装するべき点などがあればご指摘いただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.jsx
const App = () => {
  return (
    <div id="hoge" className="fuga">
      <h1>Hello, World!</h1>
    </div>
  );
};

■ JSXの基本文法③

{}内に変数や関数を埋め込める
5行目の{foo}が、JSXの基本文法②の4行目と同じになる

sample.jsx
const foo = "<h1>Hello, World!</h1>"
const App =() => {
  return (
    <div id="hoge" className="fuga">
      {foo}
    </div>
  );
};

■ JSXの基本文法④

変数名などは全てキャメルケースにする
→キャメルケース:単語の区切りを大文字にする(ラクダのコブみたいなイメージ)
→ケバブケースは(JSX = JavaScriptなので)使用不可

sample.jsx
const fooBar = "<h1>Hello, World!</h1>"
const App =() => {
  return (
    <div id="hoge" className="fuga">
      {fooBar}
    </div>
  );
};

■ JSXの基本文法⑤

空要素(例だとinput,imgタグ)は閉じる

sample.jsx
const App = () => {
  return (
    <div id="hoge" className="fuga">
      <input type="text" id="blankElement" />
      <img src="/aseets/icon/foo.png" />
    </div>
  );
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む