20200820のReactに関する記事は3件です。

Material-UIのTextFieldコンポーネントで、Inputフォームにスタイルを適用する

問題

  • Material-UIのTextFieldコンポーネントを使用してフォームを作成する
  • この入力フォームやPlaceholderのスタイルを変更しようと、コンポーネントStyleを当てようとしても、反映されない

原因

  • TextFieldコンポーネントは、input要素を含んだdivのため、直接スタイルを反映することができない

解決法

  • inputフォームを変更したい場合は、TextFieldコンポーネントのinputPropsを、 - placeholderを変更したい場合には、同様のInputLabelPropsを変更する
  • どちらもpropsとして渡す要素・方法は同じ
  • これらはinput要素、label要素のattributesを変更するためのprops
  • 公式ドキュメントのComponentAPIに書いてあります

makeStylesを使用している場合

  • makeStylesで作成したclassNameを、inputPropsに渡す
  • これによって、input要素がmakeStylesで作成したclass名を持つことになり、スタイルが適用される
sample.js
import { makeStyles, TextField } from '@material-ui/core'

const useStyles = makeStyles(() => ({
  title: {
    fontSize: 30,
  },

export default function Home() {
  const classes = useStyles();

  return(
      <form>
        <TextField inputProps={{className: classes.title }} InputLabelProps={{ className: classes.title }} label="タイトル" name="title"/>
      </form>
  )

makeStylesを使用していない場合

sample.js
import { TextField } from '@material-ui/core'

export default function Home() {

  return(
      <form>
        <TextField inputProps={{style: {fontSize: 140} }}  InputLabelProps={{ style: {fontSize: '30px', color: 'red'}}} label="タイトル" name="title"/>
      </form>
  )
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Router v6 の Object-based Routes をキメると、ルーティング実装が気持ちよくなる(かもしれない)ので紹介させてほしい

React Router v6(20200820時点では6.0.0-beta.0でpre-release) には新機能として Object-based Routes が追加される。試しに使ってみたところ気に入ってしまったのでぜひ紹介させてほしい。コンポーネントとルーティングをいい感じに分離できてなかなか気持ちが良かった。

概要

  • 「ルーティング用のオブジェクトを作成し、それをuseRoutes hooksでコンポーネントに読み込む」という形でルーティングを実現。
  • 「ルーティング用のオブジェクト」でchildrenを設定することで、ネストの深いルーティングも1つのオブジェクトで表現可能。
  • childrenは親コンポーネント内で<Outlet />コンポーネントとして呼び出し可能で、他のコンポーネントと組み合わせての利用も可能。

コード例

上の説明だけだと伝わる気がしないのでコード例を示す。
特に「親コンポーネント」「子コンポーネント」に、<Route><Switch>(v6では<Routes>)といった「ルーティングに関わるコード」がほとんど存在しない点に注目してほしい。

詳細は公式サイトを参照のこと。
https://reacttraining.com/blog/react-router-v6-pre/#object-based-routes

なお以下のコードは必要な箇所を部分的に抜き出したものであり、そのままでは動作しない。(例えばReact Routerに関係ないimportは全て省略している)
動作するコードは以下リポジトリ参照。(本記事と関係のないコードも多数含まれる点に注意)
https://github.com/takahiromasui001/zaik/tree/00ed024c4f36218a004280adc25316bbc55dbdab

ルーティング用のオブジェクト

ルーティングの情報をコンポーネントからほぼ完全に分離できる。別ファイルにすると更に爽快感が増す。

routes.tsx
import { Navigate } from 'react-router-dom'

export const rootPath = [
  { path: '/login', element: <Login /> },
  {
    path: '/*',
    element: <Private />,
    children: [
      {
        path: 'stocks/*',
        element: <Stock />,
        children: [
          { path: '/:id', element: <StockDetail /> },
          { path: '/', element: <StockList /> },
        ],
      },
      { path: '/', element: <Navigate to="/stocks" /> },
    ],
  },
]

なおこの例ではchildrenを利用することで、ネスト構造を1つのオブジェクトにまとめているが、オブジェクトに分けて必要なコンポーネントに個別に読み込ませることもできる。(その場合は後ほど紹介する<Outlet>を用いず、次に紹介する「親コンポーネント」の実装のみでネスト構造のルーティングを実現することも可能)

親コンポーネント

「ルーティング用のオブジェクト」をimportしてuseRoutesに渡すと、ルーティング機能を持つelement(ReactElement)を作成してくれる。elementをreturnに埋め込むだけでルーティングが実現できる。

App.tsx
import { rootPath } from './routes'

function App() {
  const element = useRoutes(rootPath)

  return  (
    {element}
  )
}

子コンポーネント

「ルーティング用のオブジェクト」においてchildrenとして設定したルーティングは、親コンポーネント内で<Outlet />を呼び出すことで利用可能。<Outlet />はコンポーネントとして扱えるので、他のコンポーネント(下の例で言うとHeader)と組み合わせて利用出来てとても便利。

Private.tsx
import { Outlet } from 'react-router-dom'

const Private: React.FC = () => {
  return (
    <>
      <Header />
      <Outlet />
    </>
  )
}

デメリット

機能を知らないと、最初に各コンポーネントで<Outlet />を見た際にルーティングのことだとは全く分からないし、コンポーネントだけを上から辿っても(routes.tsxを見ない限り)ルーティングの情報に行き着けない。コードの可読性が別の観点で落ちているかもしれない。

最後に

最初にも書いたがまだβ版なので注意して利用のこと。
また、ドキュメントを見ながらサラッと実装して動いて喜んでいるだけなので、おかしな点があったら指摘してほしい。
あとNext.js(触ったことない)のpagesを用いた自動生成(?)ルーティングと使い勝手を比較したい。

参考ページ

https://reacttraining.com/blog/react-router-v6-pre/#object-based-routes

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

React v17 何が変わった?

先日、React v17がリリースされましたね!

前回のメジャーリリースからなんと2.5年もかかったようです。
ほとんどが公式の意訳になりますが、何が変わったのか重要そうなところをピックアップして自分メモとしてまとめておきます。
全てを網羅しているわけではないので、より詳しく知りたい方は下記の公式リリースノートを参照ください。
公式リリースノート

新しい機能はないよ

React開発者は現在新しい機能追加に向けて取り組んでいますが、今回のv17には実は新機能は追加されていません。
v17は言わば、これからリリース予定の大規模アップデートのための踏み石になっています。

段階的アップデート

今までのアップデートには必ず破壊的変更が含まれていました。v15 -> v16の破壊的変更は皆さんの記憶にも新しいかと思います。そのような破壊的変更は、メンテされてないコードは特に、アップデート作業は困難を極めます。
1つのページに複数のバージョンのReactを置く、という対処方法もありますが、元来のReactではその方法ではイベントがうまく起動しないという問題がありました。

v17ではそのような問題が解消されています!?
これがreact v17の一番の目玉となっています。
これからのアップデートでも、各react毎にアップデートし、古い部分は残す、という作業が可能になるように設計されていくそうです。

先ほど新しい機能の追加はない、と言いましたが、破壊的な変更がないわけではありません。
上記のイベント問題を解消するために、特にイベント部分が今までと違う実装となっています。

実際にはFacebookではアップデートのために変更しなければならなかったコンポーネントは
100,000以上のコンポーネントのなかで20個ほどしかなかったので、ほとんどのアプリでは大きなトラブルはないだろう、とのことです。

段階的アップグレードをlazy-loadでどのようにするか、のdemoを公式が用意してくれています。

イベントの委任に関する変更

v17でのイベントの内部変更について見てみましょう。
下記はよくあるReactでのイベント登録です。

<button onClick={handleClick}>

これをバニラJSに直すと下記になります。

myButton.addEventListener('click', handleClick);

上記では、reactでボタンに登録したclickイベントが、そのDOMのイベントとして登録されています。
しかしReactのイベントの中には、登録すると宣言したDOM以外にイベントが登録される場合があります。
どこに登録するかというと、documentに一括で登録されます。
これはイベントの委任(Event Delegation)と呼ばれる一般的なテクニックです。
イベントの委任について詳しくはこちら。
イベントの委任をすることでパフォーマンス改善にもなり、イベントの再生を行うことができるなど、色々な機能が使えるようになります。

しかし、この仕組みは段階的アップデートを行う際問題となってしまいます。
もし1ページに複数の異なるバージョンのReactを設置しようとすると、どちらのイベントもdocumentに登録されてしまうため、e. stopPropagation()が上手く動かなくなります。

ちなみにイベントの伝播について復習したい方は、下記の記事がおすすめです。
バブリング と キャプチャリング

上記問題は実際にissueに上がっているので、理解を進めるためにリリースノートを一旦離れ、issueの内容を見てみます。
Attach event listeners at the root of the tree instead of document #8117

To understand how this solves the problem, let's assume that we have OuterComponent which is running React version A and InnerComponent that is running version 2. The inner component attaches the event listener at the top of the inner tree using bubble phase and the outer component at the root of the outer component.

When there's a click event on the innerComponent, the inner version of React will be notified first because it's deeper in the tree, which will dispatch the event through the innerComponent hierarchy and eventually something will call React e.stopPropagation(), which will call the DOM e.stopPropagation(), so that the outer version of React will never be notified.

意訳: この問題を理解するために、OuterComponentというreactのバージョンその1と、InnterComponentというreactのバージョンその2があると仮定します。InnterComponentはバブルフェーズでinnterのトップにイベントを付与し、OuterComponentも同様にバブルフェーズでouterのトップにイベントを付与します。

innterComponentをクリックした際、ツリーの奥側にあるinnerのバージョンその2のreactがouterより先にそれを感知し、イベントをディスパッチします。そしてe.stopPropagation()が呼ばれます。
その為、外側のバージョンその1のreactはイベントを感知することができません。

この問題を解消する為v17ではdocumentにイベントを委任せず、reactがrenderされるroot DOMにイベントを委任するようになりました。

const rootNode = document.getElementById('root');
ReactDOM.render(<App />, rootNode);
// 上記の場合、 id=root のDOMにイベントが委任される

image.png

※公式リリースノートから拝借した画像です。

この変更はreactの段階的アップデートのみでなく、他の技術と組み合わせてreactを使う際にも有効です。
外側のコードをjqueryで書き、内側でreactを使う場合でも、e.stopPropagation()に悩まされずに済みます。

アップデートの際気をつけること

document.addEventListener(...) を記述している際は要注意です。
上記イベントを登録し、reactのイベント内でe.stopPropagation()を呼び出していても今までのreactバージョンではdocumentのカスタムイベントを走らせることができました。
しかし、v17ではe.stopPropagation()により伝播が止まってしまいます。リクエスト通りになるということですね。

document.addEventListener('click', function() {
  // reactのコンポーネントが e.stopPropagation()を読んでいたらここは実行されない
});

下記のようにキャプチャリングをtrueにすることで調整しましょう。
これでバブリング時ではなくキャプチャリング時に発火するので、e.stopPropagation()の影響を受けずにすみます。

document.addEventListener('click', function() {

}, { capture: true });

その他破壊的変更

イベントのシステムに関して若干の変更があります。

  • onScroll がバブリングしなくなりました。
  • onFocusonBlur はネイティブのfocusinfocusoutを内部で使用するようになりました。つまり、JS本来の動きに近くなったということですね。
  • onClickCaptureといったキャプチャフレーズイベントは、実際のブラウザのキャプチャフレーズのリスナーとなりました。

上記で注意すべきなのは、onFocusは内部でfocusを使用していたものをfocusinに変更していますが、依然としてバブリングするという点です。

Effect クリーンアップのタイミング

useEffectのクリーンアップのタイミングに一貫性を持たせたとのことです。

useEffect(() => {

  return () => {
    // クリーンアップ
  };
});

通常、クリーンアップ関数を走らせる場合、画面遷移を遅らせる必要はないので、reactでは画面更新後すぐに非同期処理としてクリーンアップ関数を走らせます。(同期処理が必要な場合はuseLayoutEffectを使うべきです。)
しかし、v16のeffectのクリーンアップ関数では同期的に実行されていました。
これは大きなアプリでの画面遷移ではパフォーマンス面に置いて致命的です。
v17のuseEffectのクリーンアップは非同期処理として実行されます。コンポーネントがアンマウントされ、画面が再描画された後に実行されます。

加えて、react17では、クリーンアップ関数の実行順が違うことがありましたが、
ツリー内の順番通りに実行されるとのことです。

潜在的な問題

useEffect(() => {
  someRef.current.someSetupMethod();
  return () => {
    someRef.current.someCleanupMethod();
  };
});

上記のsomeRef.currentはimmutableなので、クリーンアップ実行時にはnullになっている場合があります。

useEffect(() => {
  const instance = someRef.current;
  instance.someSetupMethod();
  return () => {
    instance.someCleanupMethod();
  };
});

上記のように変数に割り当てておきましょう。

undefined返却で常にエラーとなるようになった

reactではクラスと関数のコンポーネントに関してundefinedとなっていないかのチェックをしています。

function Button() {
  return // エラー
}

これは値を返し忘れていることを防止する意図があります。

function Button() {
  // return書き忘れでundefinedが返却値になってしまっている
  <button />;
}

しかし、v16ではforwardRefmemoといった、同じチェックを必要とするコンポーネントに上記のエラーチェックが含まれていませんでした。
v17ではforwardRef memoでもundefinedが返されるとエラーが出るように変更されています。

(意図的に何も返したくないときはnullを返してね!)

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