react は主に jsx で描かれる。この読み込み順は以下である
import React from "react";
const Name = () => {
//ここの型はChangeEventがdefaultでevent関連のobjectの型が入っており、TのシグネチャにInputElementが来ている
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e);
};
return (
//1つ目の{}は文字列以外のものが入るときに使う。2個目はobjectが入るので使っている。
<div style={{ padding: "16px", backgroundColor: "gray" }}>
<label htmlFor="name">name</label>
<input id="name" className="input-name" type="text" onChange={onChange} />
</div>
);
};
import React from "react";
type ContainerProps = {
title: string,
//親要素のDOMを子要素に入れるときはReact.ReactNodeで型を宣言しchildrenで入れる。
children: React.ReactNode
};
const Container = (props: ContainerProps) => {
const { title, children } = props;
return (
<div style={{ background: "red" }}>
<span>{title}</span>
<div> {children}</div>
</div>
);
};
const Parent = (): JSX.Element => {
return (
<Container title="title">
<p>ここの部分が子要素である</p>
</Container>
);
};
export default Parent;
children に関してはこちらを参照。FC は基本非推奨と考えて良さそう。props を引数に入れてしまえば同じ。
props の経由を楽にしてくれるもの。provider で宣言して consumer で受け取る
import React from "react";
//providerしたい子コンポーネントを定義
const TitleContext = React.createContext("");
const Title = () => {
return (
//タグでくくって親コンポーネントで渡したいpropsの変数を呼ぶことで利用できるようになる。
<TitleContext.Consumer>
{title => {
return <h1>{title}</h1>;
}}
</TitleContext.Consumer>
);
};
const Header = () => {
return (
<div>
<Title />
</div>
);
};
const Page = () => {
const title = "React";
return (
//宣言した以下の配下でtitleを利用できるようになる。
<TitleContext.Provider value={title}>
<Header />
</TitleContext.Provider>
);
};
export default Page;
関数コンポーネント中で状態やライフサイクルを扱うための機能。公式で提供しているものは 10 種類ある。これに加えて独自に組み合わせてできる独自フックがある。
useState は省略。
useReducer は状態を扱うためのもの。配列やオブジェクトなどの複数のデータをまとめたものを扱う。複雑性がある場合はこちらを使う。
import { useReducer } from "react";
type Action = "DECREMENT" | "INCREMENT" | "DOUBLE" | "RESET";
const reducer = (currentCount: number, action: Action) => {
switch (action) {
case "DECREMENT":
return currentCount - 1;
case "INCREMENT":
return currentCount + 1;
case "DOUBLE":
return currentCount * 2;
case "RESET":
return 0;
default:
return currentCount;
}
};
type CounterProps = {
initialValue: number
};
const Counter = (props: CounterProps) => {
const { initialValue } = props;
//dispatchとして受け付けて実行できるようになる。状態はcountが持つ
const [count, dispatch] = useReducer(reducer, initialValue);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch("DECREMENT")}>-</button>
<button onClick={() => dispatch("INCREMENT")}>+</button>
<button onClick={() => dispatch("DOUBLE")}>*2</button>
<button onClick={() => dispatch("RESET")}>Reset</button>
</div>
);
};
export default Counter;
注意すべき点:
useCallback と useMemo はメモ化用のフック(メモ化とは計算された値をメモリに保存し瞬時に返す最適化手法のことである)。これを使うために react が描画されるメソッドについておさらいしておく
上位コンポーネントが再描画されるとコンポーネントで再描画が発生する。以下 memo 化することで再描画を防ぐことができるようになる。
import { memo, useState } from "react";
type FizzProps = {
isFizz: boolean
};
const Fizz = (props: FizzProps) => {
const { isFizz } = props;
console.log(`Fizzが再描画されました, isFizz=${isFizz}`);
return <span>{isFizz ? "Fizz" : ""}</span>;
};
type BuzzProps = {
isBuzz: boolean
};
const Buzz =
memo <
BuzzProps >
(props => {
const { isBuzz } = props;
console.log(`Buzzが再描画されました, isBuzz=${isBuzz}`);
return <span>{isBuzz ? "Buzz" : ""}</span>;
});
const Parent = () => {
const [count, setCount] = useState(1);
const isFizz = count % 3 === 0;
const isBuzz = count % 5 === 0;
console.log(`Parentが再描画されました, count = ${count}`);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>+1</button>
<p>現在のカウント:{count}</p>
<p>
<Fizz isFizz={isFizz} />
<Buzz isBuzz={isBuzz} />
</p>
</div>
);
};
export default Parent;
// Parentが再描画されました, count = 1
// memoSample.tsx:8 Fizzが再描画されました, isFizz=false
// memoSample.tsx:17 Buzzが再描画されました, isBuzz=false
// memoSample.tsx:26 Parentが再描画されました, count = 2
// memoSample.tsx:8 Fizzが再描画されました, isFizz=false
// memoSample.tsx:26 Parentが再描画されました, count = 3
// memoSample.tsx:8 Fizzが再描画されました, isFizz=true
// memoSample.tsx:26 Parentが再描画されました, count = 4
// memoSample.tsx:8 Fizzが再描画されました, isFizz=false
// memoSample.tsx:26 Parentが再描画されました, count = 5
// memoSample.tsx:8 Fizzが再描画されました, isFizz=false
// memoSample.tsx:17 Buzzが再描画されました, isBuzz=true
ただし、上記では関数を渡したときや配列、オブジェクトを渡したときなどの値は再描画が発生する原因となる。そこで、useCallback や useMemo が利用される
import { useCallback, useState } from "react";
import React from "react";
type ButtonProps = {
onClick: () => void
};
//memo化しておくことで再描画されなくなる。
const DoubleButton = React.memo((props: ButtonProps) => {
const { onClick } = props;
console.log("DoubleButtonが再描画されました");
return <button onClick={onClick}>Double</button>;
});
const Parent = () => {
const [count, setCount] = useState(0);
//callbackを定義
const double = useCallback(() => {
setCount(c => c * 2);
}, []);
console.log("Parentが再描画されました");
return (
<div>
現在のcount: {count}
<button onClick={() => setCount(c => c + 1)}>+1</button>
<DoubleButton onClick={double} />
</div>
);
};
export default Parent;
useMemo は省略。
useEffect は useMemo や useCallback と同じで第一引数にコールバック関数、第二引数に更新処理が入る場合に値を入れる。useEffect は api 接続や最初の描画時にのみ処理されるものが記載される。
useEffect は以下の順番で実行される
という流れをとる。したがって、思い api 通信を行ったときは描画されたあとでデータが入る感じになる。それを防ぎたい場合はいくつかの方法があるが、Hooksh ではuseLayoutEffectが存在する。
これは「DOM が更新される」と「画面に描画される」前の間で実行されるもの。したがって、チラツキを消すことができる。
useContext は Context から値を参照するためのフック。useContext の引数に Context を渡すことで Context の値を取得できる
import React, { useContext } from "react";
type User = {
id: number,
name: string
};
const UserContext = (React.createContext < User) | (null > null);
const GrandChild = () => {
//親コンポーネントにproviderがある場合、useContextを利用することで値を取得することができる。hookを使わない場合はタグでconsumerを使って囲む必要がある。
const user = useContext(UserContext);
return user !== null ? <p>Hello, {user.name}</p> : null;
};
const Child = () => {
const now = new Date();
return (
<div>
<p>Current: {now.toLocaleDateString()}</p>
<GrandChild />
</div>
);
};
const Parent = () => {
const user: User = {
id: 1,
name: "HogeUser"
};
return (
<UserContext.Provider value={user}>
<Child />
</UserContext.Provider>
);
};
ref には大きく分けて 2 つの使い方がある
データの保持は useState や useReducer が存在するが、これらとの違いは再描画が発生するかどうかの違いがある。useState の方で値が変わる場合は DOM が更新される。useRef はされない。
DOM を参照することができ、<input>要素などにマウントするとその DOM を所持することができるようになる
import React, { useState, useRef } from "react";
const ImageUploader = () => {
const inputImageRef = (useRef < HTMLInputElement) | (null > null);
const fileRef = (useRef < File) | (null > null);
const [message, setMessage] = (useState < string) | (null > "");
const onClickText = () => {
if (inputImageRef.current !== null) {
inputImageRef.current.click();
}
};
const onChangeImage = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = e.target.files;
console.log(files);
if (files !== null && files.length > 0) {
fileRef.current = files[0];
}
};
return (
<div>
// ここではp要素でクリックしてinputのonChange処理を実行している。
<p style={{ textDecoration: "underline" }} onClick={onClickText}>
画像をuploadする
</p>
<input
type="file"
ref={inputImageRef}
accept="image/*"
onChange={onChangeImage}
style={{ visibility: "hidden" }}
/>
</div>
);
};
export default ImageUploader;
useImperativeHandle は親要素から小要素のイベントを明示的に呼び出すことができるもの。ただ、あまり使われることはない。親要素と小要素が密な結合となるため使われるのがそこまで多くない
カスタムフックはこれまで上げてきた hook をつなぎ合わせたものを作ることができるもの。メソッドとして定義する。
import React, { useState, useCallback, useDebugValue } from "react";
//カスタムフック
const useInput = (defaultText: string = "") => {
const [state, setState] = useState(defaultText);
const onChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setState(e.target.value);
}, []);
useDebugValue(`Input: ${state}`);
return [state, onChange] as const;
};
const Input = () => {
const [text, onChangeText] = useInput();
return (
<div>
<input type="text" value={text} onChange={onChangeText} />
</div>
);
};
これを利用することで、各コンポーネントでメソッドを記載する必要がなくなった。useDebugValueはデバッグ用の hook である。react developer tools というブラウザの拡張機能を利用することで中身を見ることができるようになる。