React Hooksとは
コンポーネントの組み立て方
React らしくて一番きれいなコンポーネントの作り方としては まず関数コンポーネントで見た目だけを整えた Presentational Component を作る。それをインポー トして Hooks や HOC で必要な機能を追加していって、別途 Container Component を作る、というの がスマートなやり方。
基本的にReactは関数でコンポーネントを作るのが望ましく、 Hooks登場以前はHOC(Higher Order Component高階コンポーネント)やRender Propsと言うやり方があった。
- HOC(Higher Order Component高階コンポーネント) 高階関数が関数を引数に取るように、コンポーネントを引数にとり、戻り値としてコ ンポーネントを返す関数のこと。
Hooks
クラスコンポーネントを使わずに、関数コンポーネントに Local State やライフサイクルといった React の機能を 『接続する(hook into)』から Hooks。
HOCやRender Propsの弱点とされたもの、たとえばWrapper Hellになりやすい、可読性が低いといった点がほぼクリアされていて、コードが読みやすくシンプルになる。さらに state やライフサイクルを使うといったコンポーネントに付与したい機能をそこだけ切り出すことも簡単な ので、再利用しやすくテストしやすい。
StateとPropsのおさらい
- State(状態)とは
- Stateはそのコンポーネント自身が保持している。
- 他のコンポーネントに渡せない。
- 変更可能な変数。
State の変更で仮想DOMとの差分をとり、実際のDOMを更新し、コンポーネントを再描画するような用途に使う。
Props(プロパティ)とは
- 親コンポーネントから子コンポーネントに渡される情報。
- 変更不可能。
- デフォルト値の設定と検証が可能。
- 定義されたデフォルト値か、親コンポーネントから渡された値のどちらかを持つ。
State Hook
クラスコンポーネントの Local State に相当するものを関数コンポーネン トでも使えるようにする機能。 useStateを使う。
const [count, setCount] = useState(0); setCount(100); setCount(prevCount => prevCount + 1);
Effect Hook
Effect Hook (副作用フック)はクラスコンポーネントのライフサイクルメソッドcomponentDidMount(), componentDidUpdate(), componentWillUnmount() に相当する機能を実現す るもの。
ここで言うReactの副作用とは具体的には以下のものなどが該当する
- DOMを変更する
- APIとの通信
- console.log
- 変数への代入
コンポーネントのレンダリングの直前に実行させたい時(つまりcomponentDidMount()や componentDidUpdate()等を使うのと同じ)はuseEffectを使い以下のように書く。 例
useEffect(() => { doSomething(); return clearSomething(); }, [watchVar]);
useEffectに渡した関数の中身、ここでは doSomething() がコンポーネントのレンダリングの直前に実行される。 componentDidMount()や componentDidUpdate() といったメソッド内に書くのと同じ。
関数は必 ずしも戻り値を必要としないが、戻り値に関数を設定すると、それはコンポーネントのアンマウン ト直前に実行されることになる。 これは componentWillUnmount() に書くのと同じ。
useEffect(第一引数(引数なしの関数),第二引数(配列)) 第一引数にレンダリング直前に実行したい関数を入れる。 第二引数にその配列の中 に任意の変数を入れておくと、その値が前回のレンダリング時と変わらなければ第一引数で渡された 関数の中身の副作用処理実行がキャンセルされる。第二引数を省略するとレンダリング直前時に毎回第一引数が実行される。配列が空の場合は初回レンダリング直前時だけ実行される
逆にコンポーネントのアンマウン ト時に実行したい関数を描きたいときは第一引数で指定した関数の戻り値として関数を書く。
以下が例
useEffect(() => { レンダリング時に実行したい処理; return アンマウン ト時に実行したい関数; } ,第二引数(配列))
Custom Hook
StateHookやEffectHookを内部で呼び出して使う関数を作りたいときはCustomHookと言うものを作る。 と言うかHooksを呼べるのはcunsomHooknのみ。
関数名はuseを先頭につける。
React 関数コンポーネントとPresentational Component と Container Component
関数コンポーネント
Reactのコンポーネントはクラスでも関数でも書けるが Facebook の React 開発チームの公式的な見解としては関数を使うように勧めている。
過去の関数コンポーネントの欠点 クラスコンポーネントではそのままではクラスとして実装できていた、 Local State を持つ ことができない。そしてライフサイクルメソッドを備えられない。
Presentational Component と Container Component
役割による分類。 Presentational Component は、主に見た目を担うコンポーネント。 Container Componentは処理を担うコンポーネント。
関数コンポーネントはLocal State を持つ ことができず、そしてライフサイクルメソッドを備えられないので、そのままではContainer Componentとしては使えないが、HOC とか Render Props、Hooks といった機能を使ってContainer Componentとして使う。
【メモ】コンポーネント名は常に大文字。
React は小文字で始まるコンポーネントを DOM タグとして扱う。 例えば、 JSXでは
は HTML の div タグを表しますが、React コンポーネント
コンポーネントで大事な3要素
・ Props ・ Local State ・ ライフサイクル
Props
親コンポーネントから子コンポーネントのデータを受け渡す際に、親コンポーネントから受け取る値のこと。
Local State
コンポーネント内部の状態を規定するもの。 下は関数incrementで数値を足すカウンターのコードだが、AppStateやthis.stateがLocalStateに当たる。 AppStatは変数countで数値を保持する。
interface AppState { count: number; } class App extends Component<{}, AppState> { constructor(props: {}) { //コンストラクラクターでインスタンス生成時初期化 super(props); // this.state = { count: 0 }; //カウンター値を0にセット } increment() { this.setState(prevState => ({ //this.stateには直接数値がセットできないので、値の設定には必ず setState() メソッ ドを使う count: prevState.count + 1, })); } . . . render() { const { count } = this.state; return ( <Button color="green" onClick={() => this.increment()}> . . . ) }
ちなみにReactのデータは親コンポーネントから子コンポーネントに流れるので、 親コンポーネントクラスのAppが自身の状態を変更する関数を子コンポーネント(Button)に渡して、 ボタン入力時に発火されるイベントにその関数を仕込んでいる(onClick={() => this.increment())
コンポーネントのライフサイクル
コンポーネントのライフサイ クルとは、初期化されてマウントされレンダリングされ、何らかの処理が行われて再レンダリングさ れたりして、最後にアンマウントされるまでの過程。
- Mounting ...... コンポーネントが生成され DOM ノードに挿入されるフェーズ
- Updating ...... 変更を検知してコンポーネントが再レンダリングされるフェーズ
- Unmounting ...... コンポーネントが DOM ノードから削除されるフェーズ
- Error Handling ...... そのコンポーネント自身および子コンポーネントのエラーを捕捉す
これらのライフサイクルの各フェーズに介入して任意の処理を差し込むことができる メソッドが存在する。
【メモ】分割代入とは
オブジェクトの分割代入は、オブジェクトからプロパティを取り出して対応する変数に代入する構文。
let {a, b} = {a: 0, b: 1}; //a,bと言う独自の変数を作って、対応するプロパティの値を代入する。 console.log(a, b); // 0 1
Reactの基本思想
Reactの基本思想でよく取り上げらられるのは3つ
・ 仮想 DOM(Virtual DOM) ・ コンポーネント指向 ・ 単方向データフロー
仮想DOM
React では JavaScript オブジェクトで DOM と同じ構造のノードツリーを再現 しておき、一連の処理結果の最終的な差分だけを元の DOM に書き戻すようにしたことで、開発者が何も考えなくてもそれらのオーバーヘッドを最小限に抑えてくれるようになった。 これを仮想DOMと言う。
コンポーネント指向
Web アプリケーションを構築するための再利用可能なカ プセル化された独自の HTML タグを Web 標準の技術だけで作成できる技術のことね。レゴブロック を組み合わせるように、用意されたものやカスタマイズされた UI タグを HTML の中で組み合わせ ていくことでアプリケーションを作ることを目指す考え。
単方向データフロー
Vue や Angular はデータバインディングという方法を採用してる。 この方法はコンポーネントの階層がどんどん深くなっ ていくと、人間の頭では何が起こるか予測がつきづらくなってくる。 これを防ぐためにReact では、データは必ず親コンポーネントから子コンポーネン トへ一方通行で渡される。
React コンポーネントとJSX記法
ReactのJSX記法
create-react-appを少しいじったもの。
import React, { Component } from 'react'; import logo from './logo.svg'; import './App.css'; class App extends Component { render() { const logoOptions = { alt: "logo", className: "App-logo", src: logo }; const title="こんにちは React"; const targets = ["World", "Kanae", "Yukina"]; return ( <div className="App"> <header className="App-header"> { // コメントの書き方 } <img {...logoOptions} /> {title && <p>{title}</p>} {targets.map(target => ( <p>Hello, {target}!</p> ))} </header> </div> ); } } export default App;
下のコードはtitleがもし存在すれば、
{title}
を描くという意味 if条件を&&を使って表現している。{title && <p>{title}</p>}
下のコードはtarget配列の要素を繰り返し描画する書き方。
{targets.map(target => ( <p>Hello, {target}!</p> ))}
注意するべきは、JSX でタグを階層化して書くときは、ツリ ー階層のトップレベルはひとつにしないとエラーが出る。 上のコードの場合は
タグの階層がトップレベルなので、ここに並列して他のタグやなどを入れてはいけない。TypeScript 関数の型とモジュールの型定義
関数の型宣言
戻り値は型推論で省略可能だが、引 数は必ず指定する必要がある
関数の引数のかっこのすぐ後ろに型を書く
> const add = (n: number, m: number): number => n + m; > add(1, 3); 4 > function subtr(n: number, m: number): number { return n - m; } > subtr(5, 4); 1 > const hello = (): void => { console.log('Hello!') }; > hello(); Hello!
モジュールの型定義
TypeScriptでも拡張子 .d.ts の型定義ファイルさえ用意すれば TypeScript にインポートしてnpmのJavaScript製モジュールを使える。 モジュール自体にそういったtypescript用の定義ファイルが用意されている場合もある。
ない場合はDefinitlyTypedプロジェクトはそういったモジュールごとの型定義ファイルを有志のユーザーたちが作って公開している。
探す方法は npm公式サイトで お目当てのnpm のモジュール名の頭に @types/ をつけて検索すると出る。 例: @types/react-router
あるいは ターミナルで以下を実行しても出てくる。
yarn info @types/react-router
ただ、あくまで有志による第三者による非公式のものなので、動作 が完全には保証されてない。 またオリジナルのバージョン更新についていけて なくて、最新版に対応していないこともある。
TypeScript ジェネリクスと配列、オブジェクト
ジェネリクス(Generics)とは
データの型に束縛されず、型そのものをパラメータ化して扱うこと。
例えば以下のような二つの関数を一つにまとめたいとする。 しかし、型が違うので一緒にすることができない。
function a(x: string) { alert(x); } function b(x: number) { alert(x); } a("BELTLOGGER"); b(9);
そこでジェネリクスを使うとまとめられる。
function a<T>(x: T) { alert(x); } a<string>("BELTLOGGER"); a<number>(9);
配列の型
配列の書き方
> const arr1: number[] = [1, 2, 3]; //一般的にはこちらの書き方が多い [1,2,3] > const arr2: Array<number> = [1, 2, 3]; //ジェネリクスを使った書き方 [1,2,3]
オブジェクトの型
const john: { name: string, age: number } = { name: 'John', age: 25 }; //オブジェクト宣言時の型定義 interface User { //インターフェース文でも型定義できる name: string; age?: number; //ナンバー型。「?」を付けると省略可能なプロパティになる。 } const jane: User = { name: 'Jane', age: 27 }; const Jack: User = { name: 'Jack' }; type Person = User; /Type Aliasでインターフェース型を代入 const rick: Person = { name: 'Rick', age: 31 };
Type Aliasで型合成
interface Foo { hoge?: number, fuga: string }; interfaceBar{hoge:number }; interface Buz { piyo: string }; type FooBar1 = Foo & Bar; // { hoge: number, fuga: string } type FooBar2 = Foo | Bar; // { hoge?: number, fuga: string } or { hoge: number } type FooBuz1 = Foo & Buz; // { hoge?: number, fuga: string, piyo: string } type FooBuz2 = Foo | Buz; // { hoge?: number, fuga: string } or { piyo: string } type BarFooBuz = Bar & (Foo | Buz; // { hoge: number, fuga: string } or { hoge: number, piyo: string }
・ [&]交差型(Intersection Type) ...... 複数の型をひとつにまとめたもの。 『&』を使う。合成し た型のすべてのプロパティを備えるが、同じ名前のプロパティが省略可能と必須だと、必須 が優先される。 ・ [ | ]共用体型(Union Type) ...... 渡された複数の型のいずれかが適応される型。『|』を使う
イミュータブルな 配列とオブジェクト
constで定義すると変数自体の再代入とかはできないが、実は配列とオブジェクトの各要素の上書きや追加はできてしまう。
> arr[0] = 7; 7 > arr [7,2,3] >constobj={a:1,b:2}; >obj.b=5; 5 > obj {a:1,b:5}
オブジェクトや配列でもイミュータブルな変数を定義したいときは、TypeScript3.4以降ではReadOnlyな型が使用できるようになった。 書き方は以下。
> const arr1: ReadonlyArray<string> = ['foo', 'bar']; >constarr2:readonlystring[]=['foo','bar']; > arr1[0] = 'buz'; // error TS2542 > arr2[2] = 'buz'; // error TS2542 > const obj1: Readonly<{ foo: number } > = { foo: 2 }; > obj1.foo = 8; // error TS2540