ラベル Javascipt の投稿を表示しています。 すべての投稿を表示
ラベル Javascipt の投稿を表示しています。 すべての投稿を表示

2021年9月1日水曜日

React:React + TypeScript

1.関数コンポーネント

関数コンポーネントです。普通の関数と同じく、引数を型づけしてください。
JSXの要素を返せば戻り値は推論されます。

type AppProps = { message: string }; // interfaceでもよい

const App = ({ message }: AppProps) => <div>{message}</div>;

戻り値を型づけする場合には、JSX.Elementです。
正しい値が返されなければ、エラーが示されます。

const App = ({ message }: AppProps): JSX.Element => <div>{message}</div>;

型エイリアスやインタフェースは定めずに、型注釈を直接書き加えても構いません。
型をほかで使わないときは、この方が簡単です。

const App = ({ message }: { message: string }) =>

<div>{message}</div>;

引数と戻り値をそれぞれ定めるのでなく、関数そのものに型づけすることもできます。

  • コンポーネントの関数をReact.FunctionComponentまたはReact.FCで型づけするのは、TypeScriptの基本テンプレートから除かれるからです。
  • React 18ではReact.FunctionComponentが、デフォルトではchildrenを暗黙で受け取らないように改められるので、将来的にはVoidFunctionComponentは推奨されなくなります。

2.フック

2.1.useState

useStateフックには、可能なかぎり初期値を与えましょう。そうすれば、状態変数の型は推論されるからです。

const [text, setText] = useState('');  // 初期値を与える

初期値を与えなかったとき、「React: フックで値を定めた要素に制御されたコンポーネントを使うよう警告が出る ー Warning: Changing uncontrolled input」という問題も確認されています。

型を組み合わせたいという場合には、useStateにユニオン型をジェネリックで定めればよいでしょう。

const [id, setId] = useState<string | number>(0);

あるいは、型が定めてあっても、初期値があとの初期化処理で決まるとか、値がない場合としてnullを含めたいということもあるかもしれません。そのときも、ユニオン型でnullを加えてください。

const [user, setUser] = React.useState<IUser | null>(null);

// 初期化処理など

setUser(newUser);

######################################

Genericsの簡単な具体例(関数編)

// number型

function test(arg: number): number {

  return arg;

}

// string型

function test2(arg: string): string {

  return arg;

}

test(1); //=> 1

test2("文字列"); //=> 文字列


これをGenericsを使用する事で下記のように書く事が可能です。

test<string>の引数はstring型だけ、またtest<number>の引数はnumber型だけが許されるようになります。

function test<T>(arg: T): T {

  return arg;

}

test<number>(1); //=> 1

test<string>("文字列"); //=> 文字列

//※ Genericsでも型推論ができるので、引数から型が明示的にわかる場合は省略が可能

test("文字列2"); //=> "文字列2"

######################################

nullは型に含めず、useStateの初期値が決まらないという場合には、型アサーションで逃げる手もあります。

const [user, setUser] = React.useState<IUser>({} as IUser);

// 型 'null' の引数を型 'SetStateAction<IUser>' のパラメーターに割り当てることはできません。

// setUser(null);  // nullは認められない

setUser(newUser);  // 型に合致した値のみ設定できる

ただし、型アサーションはTypeScriptに値({})の型を偽っているだけだ、ということにご注意ください。状態変数(user)の値を正しく扱うことは、コードの書き手に委ねられるのです。誤ればランタイムエラーになってしまうかもしれません。

2.2.useReducer

useReducerフックを使うには、リデューサの関数が定められていなければなりません。関数に型づけするのは、リデューサが受け取る状態(state)とアクション(action)のふたつの引数です。戻り値の型は推論されます。

type STORE = { count: number };

type ACTIONTYPE =

    | { type: 'increment' }

    | { type: 'decrement' };

const reducer = (state: STORE, action: ACTIONTYPE) => {

    switch (action.type) {

        case 'increment':

            return { count: state.count + 1 };

        case 'decrement':

            return { count: state.count - 1 };

        default:

            return state;

    }

};


export default reducer;

すると、useReducerフックを使う側では、とくに型の定めはなくて構いません。

import { useReducer } from 'react';

import reducer from './reducer';


const initialState = { count: 0 };

const Counter = () => {

    const [state, dispatch] = useReducer(reducer, initialState);


};

リデューサ関数そのものを、React.Reducerで型づけることもできます。状態(state)とアクション(action)を受け取って、戻り値は状態です。引数の型はジェネリックで与えてください。

import React from 'react';


// const reducer = (state: STORE, action: ACTIONTYPE) => {

const reducer: React.Reducer<STORE, ACTIONTYPE> = (state, action) => {


};

なお、「React + TypeScript: useReducerを使ったカウンターのアプリケーションに型づけする」は、簡単なカウンターの作例をもとに、さらに詳しく解説しました。ご興味がありましたらお読みください。


2.3.判別可能なユニオン型

判別に用いるプロパティが備わった型を複数結ぶと、判別可能なユニオン型(discriminated union)と呼ばれます。

type ACTIONTYPE =

    | { type: 'increment' }

    | { type: 'decrement' };

判別用のプロパティ値が限定されるため、それ以外の値を弾くことができるのです。

const reducer = (state: STORE, action: ACTIONTYPE) => {

    switch (action.type) {

        case 'increment':

            return { count: state.count + 1 };

        case 'decrement':

            return { count: state.count - 1 };

        // 型 '"reset"' は型 '"increment" | "decrement"' と比較できません。

        /* case 'reset':  // エラー

            return { count: 0 }; */

        default:

            return state;

    }

};


2.4.useEffect

useEffectフックでは、はっきりした型づけより、推論される戻り値の型にご注意ください。たとえば、つぎの例です。

useEffect(

     () =>

          window.setTimeout(() => console.log('timeout'), 1000)

     , []

);

アロー関数式=>の本体に波括弧{}なしに1行で書いた文は、そのまま戻り値になります(「アロー関数」の「関数の本体」参照)。ところが、useEffectのコールバック関数は、戻り値はなし(undefined)にするか、「エフェクトのクリーンアップ」関数でなければなりません。そのため、つぎのようなエラーが示されてしまうのです。

Type 'number' is not assignable to type 'void | Destructor'.


クリーンアップ関数がないときは、useEffectのコールバックから値を返さないように注意してください(本体が1行のときは波かっこ{}で括ります)。

useEffect(

    () => {

        window.setTimeout(() => console.log('timeout'), 1000)

    }

    , []

);


2.5.useRef

useRefフックについて、「React+TypeScript Cheatsheets」には型の定めと初期値の与え方で、3つの例が示されています。currentプロパティが書き替えられるか、null値の代入を認めるかどうかが違いです。

// 読み取り専用。

const nullRef = useRef<number>(null);

// 指定した型の値で書き替えられる。nullは不可。

const nonNullRef = useRef<number>(null!);

// 型指定にnullを含める。

const nullableRef = useRef<number | null>(null);


3.イベントハンドラ

イベントハンドラのもっとも簡単な定め方は、要素のイベント属性(onChange)に直に書き加えることです。引数のイベント(event)の型は推論されますので、注釈が要りません。

import React, { useState } from 'react';


function App() {

    const [text, setText] = useState('');

    return (

        <div>

            <input type="text"

                value={text}

                onChange={(event) => setText(event.currentTarget.value)}

            />

        </div>

    );

}


ハンドラ関数(handleChange)を分けて定めると、通常の関数と同じように引数と戻り値を型づけすることになるでしょう(戻り値は推論させても構いません)。

function App() {


    const handleChange = (event: React.FormEvent<HTMLInputElement>): void => {

        setText(event.currentTarget.value);

    };

    return (

        <div>

            <input type="text"

                value={text}

                // onChange={(event) => setText(event.currentTarget.value)}

                onChange={handleChange}

            />

        </div>

    );

}

さらに、ハンドラ関数そのものを(この場合FormEventHandlerで)型づけることもできます。定められるのは、前の例と同じく引数と戻り値の型です。けれど、それぞれをアラカルトで決めるのでなく、セットにした関数の型で示す方がより明確になります。

function App() {


    // const handleChange = (event: React.FormEvent<HTMLInputElement>): void => {

    const handleChange: React.FormEventHandler<HTMLInputElement> = (event) => {


}


2021年8月29日日曜日

React:コンポーネントのライフサイクル

1.Reactのコンポーネント

ReactではUIを構成する要素のことをコンポーネント(Component)と呼びます。

具体的にはボタンやヘッダー、コンテナなどのことです。Reactで開発する利点としてはそれらのUIパーツをそれぞれ細かくコンポーネントに分けておくことで、再描画が必要なものだけに更新を走らせてパフォーマンスをチューニングができます。

2.コンポーネントのライフサイクル

ライフサイクルにはまず、大きく分けて3つの期間があります。

それぞれ順に

Mounting、Updating、Unmountingと呼ばれます。

2.1.Mounting

UIにコンポーネントが描画されるまでの準備期間。

使用できるメソッド:

  • constructor()
  • getDerivedStateFromProps()
  • render()
  • componentDidMount()

2.2.Updating

UIにコンポーネントが表示されていて、基本的にユーザーが操作できる期間。

使用できるメソッド:

  • getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

2.3.Unmounting

他のコンポーネントに切り替える前に現在のコンポーネントを破棄するための期間。

使用できるメソッド:

  • componentWillUnmount()

3.ライフサイクルメソッド(基本編)

3.1.constructor()

Mounting時

JSXおよびTSXのフォーマットではまず見かけません。JavaScriptでReactを書く場合にstateの初期化やactionのバインドのために使用します。


3.2.componentDidMount()

Mounting時

1度目のrenderが呼ばれた後に1度だけ呼ばれるメソッドです。この時点ではまだUIに表示されていません。データをフェッチしたり、アニメーションやタイマーをセットする場合はここで行います。このメソッドからはDOMが作成されていますが、直接のDOM操作などライフサイクルを外れる処理は原則避けましょう。


3.3.render()

Mounting/Updating時

コンポーネントの根幹となる、一番呼ばれるかつ必須のメソッドです。ここに書いてあるコードが実際にUIに現れるものになります。

renderはpropsやstateが更新されるたびに呼ばれるため、ここで直接propsやstateを操作する処理を書いてはいけません。新しい関数を定義するのも避けましょう。そしてAPIのように、propsやstateの値が変わっても結果は冪等であるべきです。

※冪等:べきとうは、大雑把に言って、ある操作を1回行っても複数回行っても結果が同じであることをいう概念です


3.4.componentDidUpdate()

Updating時

第一引数に1つ前のprops、第二引数に1つ前のstate、第三引数にsnapshot(後述のgetSnapshotBeforeUpdateの返り値)が入ってきますが、必要なければ省略可能です。これもよく呼ばれるメソッドなため、パフォーマンスを低下させるpropsやstateを更新する処理は最低限にしましょう。また、if文や後述のshouldComponentUpdateで無駄な処理を避けることができます。


3.5.componentWillUnmount()

Unmounting時

現在のコンポーネントを破棄する直前に呼ばれるメソッドで、アニメーションやタイマーを設定していた場合はここで破棄します。そうしないと新しいコンポーネントのサイクルが始まった後も、その分のメモリが開放されないままになってしまいます。ちなみにもうrenderが呼ばれることはないので、ここでpropsやstateを変更しても意味がありません。

4.ライフサイクルメソッド(上級編)

4.1.getDerivedStateFromProps()

Mounting時

これだけはstaticです。第一引数に次のprops、第二引数に前のstateが入ってきます。propsの値によってstateの値を更新したい場合、propsが更新されてrender()が呼ばれる前にstateの更新が必要かどうかをチェックするメソッドです。更新があれば更新後のstate、なければnullを返します。


4.2.shouldComponentUpdate()

Updating時

第一引数に次のprops、第二引数に次のstateを持っており、前のpropsやstateの値やアドレスと比較し、変更がなければfalseを、あればtrueを返します。falseが返るとこのあとのrender()は呼ばれず再描画は行われません。PureComponentを使うと自動的にこの比較を行ってくれるため、自分で書くケースは稀ですが要のメソッドです。


4.3.getSnapshotBeforeUpdate()

Updating時

このメソッドの返り値はcomponentDidUpdateの第三引数に渡ります。具体例に挙げられているようなスクロール位置など、引き継ぎたい値がある場合に使います。


また上記に上げた以外で、エラーをハンドリングしたい場合はcomponentDidCatch()というメソッドが用意されています。

5.使用しないライフサイクルメソッド

補足として、React v16.2までで使用されていて、現在は使用を控えたほうがいいメソッドもここで上げておきます。古い資料ではこれらの記述がありますが、見かけても読み飛ばしてよいです。


5.1.getDefalutProps()

一度だけ呼ばれ、propsが渡されなかった場合に使う値を指定できます。constructorや、TypeScriptではデフォルト値を指定することで解決可能。


5.2.getInitialState()

Mounting時にまず呼ばれ、描画前にstateを変更するためのメソッド。TypeScriptではstateに初期値を指定できるので特に必要性を感じることはありません。


5.3.componentWillMount()

v16.3以降ではUNSAFE_componentWillMount()に改名されており、v17で完全に削除予定。1回目のrenderが呼ばれる直前に実行されます。


5.4.componentWillUpdate()

v16.3以降ではUNSAFE_componentWillUpdate()になっており、これもv17から削除予定です。使いたいと思ったら代わりにcomponentDidUpdate()やgetSnapshotBeforeUpdate()を使用すべき。


5.5.componentWillReceiveProps()

v16.3以降ではUNSAFE_componentWillReceiveProps()で、v17から削除予定。componentDidUpdate()またはgetDerivedStateFromProps()で代用できます。