インターラプト開発者ブログ

インターラプトのプロダクト・サービスに関する開発ブログ

React超入門 mapでリストアイテムを繰り返す

この記事ではTypeScriptを使います。

Reactでよく使う実装にArray.mapでのループがあります。 ループというのは繰り返しのことで一つのReactエレメントを繰り返してリスト表示させることができる機能です。

リストって何?

リストとは配列のことで、順番に要素が格納された長い箱のようなものです。

[1,2,3,4,5]

これは、1〜5までの数字が順番に格納された箱です。これがリスト(配列)です。

配列は、数字の他に文字列を入れることも出来ます。

['朝食','りんご','ヨーグルト']

文字列の他にオブジェクトも入ります。

[{key: 'value', 'キー': '値'}, {key: 'value', 'キー': '値'}]

オブジェクトは、キーと値がセットになったデータで、

data.key
data['キー']

とすると、値が取得できます。

Reactでは、この配列とオブジェクトを組み合わせてReactエレメントを繰り返して表示させることが多いです。

繰り返す元となる型を作ろう

型とは、データがどういうキーを持っているのか決めたものです。例えば人の名前と年齢を管理するデータであれば、

type Human = {
  name: string; // 名前
  age: number; //  年齢
}

となります。stringというのは文字列であるということを表します。numberは数値です。

Humanデータを作成する

リストアイテムを繰り返すために、このHumanを配列データにしてみます。

const humans: Human[] = [];

複数ある事がわかるように、humansのようにsをつけます。 その後ろにある Human[] が、Humanの配列型です。

今は空が代入されていますが、この配列の中にはHumanしか入れられないように型で縛っているわけです。

const humans: Human[] = [
  { name: '斎藤', age: 20},
  { name: '中島', age: 21},
  { name: '田中', age: 50},
];

先ほど定義した型定義に従って、データを作っていきます。 ちょっと、リストっぽくなってきたと思います。

Humanを繰り返す

ではいよいよ、このデータをReactエレメントで出力していきます。

const humans: Human[] = [
  { name: '斎藤', age: 20},
  { name: '中島', age: 21},
  { name: '田中', age: 50},
];

const HumanTag: React.FC = () => {
  return <>
    { humans.map( human => {
      <div key={human.name}>
         {human.name}さんは{human.age}歳です。
      </div>
    })}
  </>;
}

あとは.mapが配列の要素を一つずつ取り出してくれるので、そこにタグを入れるのみです。この時に必ずkeyを入れ忘れないようにしてください。

keyは必ず重複しないようにしてください。

NextJsでSSRに対応していないライブラリを使う

NextJsで読み込まれたライブラリは、サーバサイドでもレンダリングできるようになっていますが、稀にSSRに対応していないライブラリがあります。ベースがjQueryになっているようなライブラリだとよくあります。

原因は内部でwindowを呼んでいることで、windowが見つからないエラーとなるためです。

この場合は、dynamic import を使います。

import dynamic from 'next/dynamic';

let Mod;
if (typeof window !== 'undefined') {
  Mod = dynamic(() => import('@mod/module'), {
    ssr: false,
  });
}

見たとおりwindowが定義されていない時は、Modはnullとなります。

ライブラリがexport defaultで宣言されている場合は下記のようにします。

let Mod;
if (typeof window !== 'undefined') {
  Mod = import('mod').then((module) => module.default);
}

Reactで気づいたらすべてがuseになっていた話

ReactはHooksが導入される前から書いていました。同時はconstでコンポーネントを定義するのではなく、class構文で書いていました。今ネットで検索してもclass構文で、stateへのセットもthis.setStateでした。 パフォーマンス改善と利便性のために、constructorには大量の.bindが並ぶそんなコードでした。

class構文からconst定義の関数型コンポーネントになった

コンポーネントが、React.FC型によって関数型のコンポーネントになり、classを定義しなくてもconstで変数を定義するようにコンポーネントを実装出来るようになりました。

setStateがuseStateになった

class構文の時はstateをclassに定義し、初期化を行いthis.setStateでstateの値を変更し、stateを定義する場所が決まっています。setStateするときに、どのstateだっけと定義を見に行ったりしていました。

const [mikan, setMikan] = useState(null);

新しい書き方では、配列の分割代入を使う形になっていて、配列の1番目にはstateそのものの変数、二番目にはstateに代入するための関数が返ります。

初期化処理などはuseEffectになった

当時は初期化処理などは componentDidMount と componentDidUpdate を使っていました。しかし自分は直接renderメソッドに実装を書いてしまっていた記憶があります。非常に良くない書き方をしていました。そもそもメソッドが分かれていたので、双方の関数でデータをやり取りするにはthisを使う必要があり 変更のあったpropsを判別するにはifやswitchで判別する必要がありました。

そのため、非常に煩雑な実装になるため、できるだけreact-reduxのほうに書いたり、renderに書いたりしてしまっていました。

しかし、そんなReactの書き方はもうおしまい! useEffect でも描けるようになりました。

特に嬉しいのは使い勝手がrenderメソッドに直接書いているような感覚で使えることです。ロジックがあれば、それをuseEffectで包めばよいです。第二引数に[] を指定すると、componentDidMount、propsの一部のフィールドを配列で渡すと、componentDidUpdate相当になります。propsのフィールドが更新されたときにuseEffectの第一引数のコールバック関数が再実行されます。

また、useEffect内でreturnで関数を返せば componentWillUnmount相当の処理をしてくれます。

useMemoが便利

useMemoは、何度も繰り返されるライフサイクルの中で、第二引数の配列に渡されたpropsのフィールドの値が変わっていなければ、前回実行した値を使いまわす、キャッシュ機能を持つHookです。挙動としてはuseEffectと一緒で、ただreturnで値を返すことが違います。

便利だからとrender内でついついロジックを書いて変数に代入をしてしまっている場所があれば全てuseMemoに置き換えると良いです。

ただ注意が必要で、useMemoはコンポーネントさえもキャッシュにすることが出来ますが、明示的に再レンダリングさせたいと言った目的がなければ、単純に別コンポーネント化してしまったほうが良いです。コンポーネントの中身が単純なjQueryなどによるDOM相当を行っていて何度も再レンダリング行われてしまったりする場合などは例外的にコンポーネントにたいして使っても良いと思います。

onClickなどの関数はuseCallbackへ

class構文では、onClickに渡す関数はclassのconstructorでthisが使えるようにbindしていました。claasのメソッドなので、thisで定義されたpropsなstateしか呼び出せず、クロージャの変数が使えず、かといってrenderで書くと何度も関数が再定義されたり、クロージャの変数の扱いなどで何かとトラブルの原因になっていました。

それも直接useCallbackを使う事で、関数内に定義できるようになりました。useEffect同様関数自体をキャッシュしてくれるので、第二引数の変数が更新されるまでは中の関数の変数は不変なので、安心してクロージャの変数が使えます。

まとめ: 気づいたらuseで包まなくていい物がなくなった

useを適切に使うと全てがuse系に包まれて、リアクティブな状態になりました。プログラムは何かしたのイベントにより発動し、連鎖的につながって一つの処理が行われていきます。

もし手元に既存の手続き型のレガシーなコードがあったら、一つ一つのロジックを分解してuse系で包み込んでいくだけで、リアクティブなプログラムにリファクタリングすることが出来ます。

どこに書かないと行けないといったコーディングルールを理解する必要は無く、ドミノ倒しやマインクラフトの全自動マシンのように、連鎖反応を起こす仕組みさえ考えるだけでいいのです。

Nextjsでデータベースに接続する時に「Critical dependency: the request of a dependency is an expression」が出る

NextJsではWebpackを使用しているため、モジュールがWebpackに対応していないとCritical dependency: the request of a dependency is an expression というエラーが発生します。

NextJsでは、Sequelize や knex などでエラーが発生します。

これを回避するには、ウェブから読み込まれる部分とそうでない部分で明確にimportを分けると良いです。

importしたモジュールがWebpackに対応していない場合にそのページをWebから読み込んでしまうとエラーが発生するため、必ずWebpackがコンパイルせずにサーバサイドのみで実行するファイルで読み込みます。

DIを使っている場合は、明確にWebpack部分とバックエンド部分でDIコンテナを分けると良さそうです。

わかりやすいかも知れないReact.FCの使い方

前提

  • この記事ではTypeScriptを使用しています。
  • 17.0.1 のReactを使用しています。
  • Nextjs 10での動作確認をしています。

はじめに

ReactにはReact.FCという型定義があります。 こちらは、React.FunctionComponentが略された型で同じ型です。開発では殆どがReact.FCのほうが使われています。

昔のReactでは、React.SFC型が使われていましたが非推奨になっています。

React.FCとは

React.FCは、constによる型定義でコンポーネントを定義できる型です。コンポーネントというのはReact独自のタグで、オリジナルのタグを作成し、タグの中で他のタグをまとめて定義できるものです。

const MyTag: React.FC = () => {
  return <div>こんにちは</div>;
};

const Container: React.FC = () => {
  return <MyTag />;
};

コンポーネントの中は、returnで返ったタグがレンダリング(描画)されます。constで定義したタグはそのまま他のコンポーネントで呼び出して使う事ができます。

複数タグを描画する

基本的にはreturnで返るタグはひとつだけですが、<></>で囲むことで、複数タグをレンダリング出来ます。

 const MyTag: React.FC = () => {
  return <>
    <div>こんにちは</div>
    <div>こんにちは</div>
  </>;
};

こうすることで、親要素が消えて、子要素のみが直接描画されます。

また、親タグをひとつだけ用意しても同様のことが出来ますが、一階層余分な要素が増えます。CSSなどの定義で、階層によって描画が変わる場合に便利です。

 const MyTag: React.FC = () => {
  return <div>
    <div>こんにちは</div>
    <div>こんにちは</div>
  </div>;
};

タグに属性値を渡す

前項では、ただコンポーネントを呼び出しただけなのでコンポーネントの中身を書き換えることができません。コンポーネントに属性値を渡してその内容をレンダリングしてみます。

const MyTag: React.FC<{ title: string; }> = (props) => {
  return <div>{props.title}</div>;
};

const Container: React.FC = () => {
  return <MyTag title="こんばんは" />;
};

MyTagコンポーネントに、titleという属性を付けました。 TypeScriptなので、肩定義にもtitle: string;がついています。 これはReact.FCがジェネリクス型となっており、引数にどのような属性値が含まれるのか定義することができるようになっています。

const MyTag: React.FC<{ isAm: boolean; t1: string; t2: string; }> = (props) => {
  return <div>{props.isAm ? props.t1 : props.t2}</div>;
};

const Container: React.FC = () => {
  return <MyTag t1="おはよう" t2="こんにちは" isAm={true}/>;
};

属性値には複数の引数を定義することができます。 属性値の型にはstring以外にも様々な型を渡すことが可能で、どんな型でも入れることが出来ます。

文字列の場合は、""で定義しますが、変数やそれ以外のリテラル型やboolean型(true/falseの真偽値)は{}で囲います。

属性値はpropsという変数にオブジェクトとして格納されていて、ドットで呼び出すことができます。Reactでは慣習的にpropsという名前の変数が使われているので、合わせておきます。

この実装の中で下記のような部分があります。

props.isAm ? props.t1 : props.t2

こちらは三項演算子という条件式で、1項がtrue(真)だった場合は、2項を、偽だった場合は三項を返します。 isAmは午前中かどうかと言う意味の真偽値です。

propsを省略する

毎回propsを定義するのは使い勝手が悪いので、直接propsの中の属性値を呼び出せるようにします。

const MyTag: React.FC<{ title: string; }> = ({ title }) => {
  return <div>{title}</div>;
};

const Container: React.FC = () => {
  return <MyTag title="こんばんは" />;
};

propsが { title: string; } に置き換わりました。 こちらは、propsの中にあるtitleを取り出して定義することができる分割代入という方法で、Destructuring assignment 構文と言います。

分割代入は他にも様々なパターンがあります。

const [a, b] = [10, 20];
const { a, b } = { a: 10, b: 20 };

propsを何度も呼び出して使うと、見通しが悪くなるので、分割代入を使うようにすると良いです。

分割代入には別名をつけることも可能です。

const { a: newA, b } = { a: 10, b: 20 };

また定義されていなかった場合、初期値を設定することも可能です。

const { a = 30, b } = { a: 10, b: 20 };

コンポーネントのpropsをそのまま子コンポーネントに渡す

コンポーネントのレイアウト調整などで複数のコンポーネントに階層化させて実装した場合、コンポーネントを共通化させたい場合があります。

その場合は、...props と書くことで渡すことが出来ます。

const MyTag: React.FC<{ title: string; }> = ({ title }) => {
  return <div>{title}</div>;
};

const Container: React.FC<{ title: string; }> = (props) => {
  return <MyTag ...props />;
};

分割代入しているとpropsがなくてエラーが出るので、その場合は、下記のようにします。

const MyTag: React.FC<{ title: string; }> = ({ title }) => {
  return <div>{title}</div>;
};

const Container: React.FC<{ title: string; }> = (props) => {
  const { title } = props;
  console.log(title);
  return <MyTag ...props />;
};

引数内で分割代入すると使い勝手が悪いので、最初の行で分割代入をする場合もありますが、状況に応じて使い分けると良いです。

子要素を引数として受け渡す

HTMLタグでは他の要素をタグで包むことが出来ます。

<p>子要素</p>

Reactではこの子要素をpropsの中に引数として持ち、コンポーネントの内側で描画することができます。

子要素は、props.children としてアクセスすることが出来ます。

const MyTag: React.FC<PropsWithChildren<{ title: string; }>> = ({ title, children }) => {
  return <div>{title}: {children}</div>;
};

const Container: React.FC = () => {
  return <MyTag title="こんばんは"><strong>子要素</strong></>;
};

PropsWithChildren型は<{...}>で囲まれている属性値値の他に、childrenを含んだpropsである型を持ちます。

この型を使うことで、childrenを使うコンポーネントであることがわかりやすくなります。

React.VFC

こちらは逆でchildrenを含まないコンポーネントを定義するのに使用します。childrenを使用していないことを明示的に表したい場合は、こちらの型を使用します。

refの転送を行う

Reactコンポーネントでは、属性にrefを使うとタグのDOM要素を取得できます。直接DOMに対して何か処理を行いたい場合はrefを使います。

const ref = useRef(null);

const Container: React.FC = () => {
  return <div ref={ref}>node</div>;
};

このrefですが、Reactコンポーネントに対して使用すると、本来refを使用したい要素にうまく引き渡せない場合があります。

const ref = useRef(null);

const MyTag: React.FC<{ title: string; ref: any; }> = ({ title }) => {
  return <div ref={ref}>{title}</div>;
};

const Container: React.FC = () => {
  return <MyTag ref={ref}>node</div>;
};

上記の例ですと、refはうまく引き渡せずに、div要素のrefは取得できません。MyTagそのもののrefを取得してしまうからです。

この場合は、forwardRefを使うことで解決できます。

const MyTag = React.forwardRef((props, ref) => (
  <div ref={ref}>node</div>
));

fowardRefはその名の通りrefの転送を行ってくれます。 親要素でrefが指定された場合、親要素のrefを取得するのではなく、そのままコンポーネントの引数として渡すことが出来ます。

絶対に再レンダリングさせたくない場合のコンポーネント活用

TL;DR

絶対に再レンダリングさせたくない

jQueryなどで直接DOM操作しているライブラリをReactから呼び出して使うと、すこし困ったことがあります。Reactはコンポーネントが管理しているStateに変化があると、コンポーネントの持つHTML(JSX)を再レンダリングするのですが、jQueryなどでレンダリングした部分が消えてしまうためもう一度jQueryレンダリングを行う必要があります。

この際に、jQueryライブラリが内部で持っている状態(State)が消えてしまい、表示が初期状態に戻ってしまうことがあります。 renderを実装しているjQueryライブラリであれば、この問題を回避することが出来ますが、多くのライブラリではインスタンスを作成するときにしかレンダリングを提供していないことが多いと思います。

この場合出来るだけReact外のレンダリングを持つインスタンスの初期化を避けた方が良いです。

useMemoを使ってレンダリングされないElementを取得する

useMemoは、値をメモ化し不要な再計算を行わないように値を返してくれます。この中でjQueryライブラリ等を実行するだけのコンポーネントを呼び出します。( ※ コンポーネントの引数が無い場合でも同様の効果が得られますが、初回レンダリングで値を渡しレンダリングする必要があるため、useMemoを使った方が管理しやすいです)

const graph = useMemo( () => <Graph list={list} />, [] );

useMemoは第二引数に [] を指定すると初回のみレンダリングされます。list変数は初回の値のみメモ化されるため、listの値を変更してもGraphには新しいlistの中身が伝わりません。

これで再レンダリングを防ぐことが出来たので、あとはlistの更新方法を考えるだけです。もちろん useMemoの第二引数にlistを入れる方法は、listが更新されるたびにGraphが再レンダリングされるため使えません。

この場合Graph内でaddEventListener() でイベントの監視をして、親要素から値の変更を検知したらイベントを送るように実装します。

そのためは、まずjQueryなどで定義されたElementのrefを取得します。

refの転送を行う React.fowardRef

React.fowardRefは、コンポーネントにrefを指定すると、そのコンポーネントのrefではなく子コンポーネントのrefが参照されるようになります。

const Graph = React.forwardRef((props, ref) => (
  <div ref={ref} className="graph">
    {props.children}
  </div>
));

ref.currentの中身にはレンダリング済みのHTML DOMが入っており、このDOMに対して dispatchEvent() することで直接jQueryライブラリがレンダリングしたDOMに対してイベントを発火させることができます。

値を持たせたイベントを発火する

Eventを発火させるには、Eventを継承してカスタマイズしたものを利用しますが、もっと楽にカスタムイベントを作成できる機能が用意されています。

const event = new CustomEvent('updateList', { detail: list });

CustomEvent は 引数に { detail: any; } を持ち、任意のパラメータを渡せます。これによってlistを渡し、レンダリングを避けたい子コンポーネント側でイベントを購読し、ライブラリ内部による状態変更を行い、DOMを変更し、再レンダリングさせることなく状態変更出来ます。

補足

  • refのイベントを監視しなくても初めからライブラリが用意しているイベントを発火しても良いです。
  • Nextjsなのでホットリロードすると、二重でイベントを監視してしまうことがあるため、.removeEventListener してから.addEventListener します。

自己流記憶術

代表の高橋です。昔からやっている記憶術について話したいと思います。

自分は小学生の頃からプログラミングをやっているのですが、ノートを取るのが苦手で覚えたいことをメモ帳でまとめておくことが多かったです。

授業中、パソコンでメモできたら絶対頭良くなってたのにと思う日々でした。左利きな事もありとにかくノートにメモする事が苦手でした。

そこで開発したのがノートアプリでした。検索ができて、すぐにメモした内容にアクセスができるアプリです。まだEvernoteもない時代からパソコンでメモする習慣が出来ました。

プログラミングするときも、C言語について勉強したときも自前のメモアプリに記録していつでもコードを使えるようにしておく事で素早くプログラミングが出来る様になっていました。

後になってからそれを「スニペッドコード」と言うことを知りました。

次に活かせるナレッジ蓄積ルール

自分は知識を蓄積する時に、必ず意識していることがあります。

  • メモアプリは 1秒以内に起動できる事
  • メモアプリは 3秒以内に検索できる事
  • メモアプリは 5秒以内に目的の情報にたどれる事
  • クリップボードの情報はショートカットで貼り付けられて、先頭の行がタイトルになること
  • 必ずタイトルをつける事

小学生の頃からこのルールを守りつづけで、今では起業し順調に成長する事ができるようになってきました。

ナレッジは資産であり、多ければ多いほど良いのですが、1000を超えてくると重くなってくる物が多いです。そう言う時はファイルを分けて管理すると良いです。

  • Swift
  • Object-C
  • Ruby

のように関心事ごとにファイルを分けて管理します。

脳内にはインデックス「だけ」を作る

人は何か作業する時にワーキングメモリーに詰め込めるだけ詰め込んで作業の効率をあげますが、限界があります。ワークフロー全体を見るのか、ワークフローの一部分だけを見るのか分けて、ワーキングメモリーを切り替えて作業します。

それでも、ながら作業やかなり大きな範囲を一度にやると、ワーキングメモリーから溢れてしまい漏れが発生したり、無気力になったりします。

こう言う時は、覚えておきたいことをメモしておいて、中身については忘れてしまい、メモの「タイトル」だけを覚えておきます。必ずメモにタイトルをつける必要があるのは、記憶のインデックスを作るためです。

インデックスだけをワーキングメモリーに詰め込んでおくと、かなりの量を把握する事ができるようになります。実際には全部覚えているわけではなく、タイトルだけ思い出して、検索をかけます。タイトルから内容を見つけるまでに5秒以内で出来るように環境を構築しましょう。

ツリー構造を駆使しよう

メモアプリの中にはツリーで管理できるものがあります。物によっては2段までしかない物がありますが、何層でもツリーをネストできるものを使うと良いです。

知識は関連したものを集約することで、まとめてインデックスできるし、記憶の箱にアクセスする事ができます。ツリーの整理はそのまま記憶の整理になるのです。

逆引きを作ろう

メモアプリを使う際に、単純な項目別のメモをまとめるよりも「逆引き」としてメモした方が記憶の整理に繋がる場合があります。

逆引きというのはタイトルの付け方の工夫のことで、「スクロールバーの背景色を変える方法」といった付け方をします。この記憶の仕方の凄いところは、自分でこのようなタイトルを付けたことによる記憶が、ただコピペしただけよりも格段に強く記憶に焼き付けられることです。

内容はコピペでもいい場合がありますが、できる限りタイトルは手動で書いてみてください。忘れずに覚えておけます。

ワーキングメモリーが溢れたら

ワーキングメモリーが溢れると、一時的に無気力に襲われることがあります。こうなったら良くない状態が長く続くので、一度作業をやめて、今頭にあることを全部書き出して、寝たり遊んだり、美味しいものを食べたりしてください。

リフレッシュして、作業に取り掛かると良いです。もちろん忘れてしまっても、ちゃんとメモしてあるから安心です。忘れて良いと思える事が、ワーキングメモリが溢れた時の対処方法です。

絵文字を使おう

メモする時に、タイトルの先頭に絵文字をつかうだけです。 どうしても覚えておきたい場面で有効です。