Open Yuya-Furusawa opened 3 years ago
// titleおよびcolorはstringもしくはundefinedとなる
type ButtonProps = {
title?: string
color?: string
}
?
をつけるとそのプロパティは省略可能になる
全部に?
を付けたい場合はPartial<>
を使った方が便利
type ButtonProps = Partial<{
title: string
color: string
}>
const Summary: React.FC<Props, State> = (props) => {
hogehoge;
};
React.FC<>
について、第一引数にpropsの型、第二引数にstateの型を指定する
指定しない場合やstateが無い場合は、<Props, {}>
、<Props>
、<{}, State>
という形にする
const [count, setCount] = useState<Type>(0);
setCount()
にType
以外の型を入れようとするとエラーになる
booleanとか使う時に便利
const [isHoshimiya, setIsHoshimiya] = useState<boolean>(true);
だが基本的にTypeScriptでは型推論してくれるのであんまり型指定しなくても大丈夫
// countの型はnumberになる
// setCount()にnumber以外を入れるとエラーになる
const [count, setCount] = useState(0);
参照
例
type Foo = number;
type Hoge = string;
type FooHoge = Foo | Hoge; //独自の型
//関数のPropsの型を定義するときはinterface
interface Props {
contents: FooHoge;
};
const Card: React.FC<Props> = (contents) => {
hogehoge
};
参照
propsを渡すときにコード量を減らせる
(props)
ではなく({ hogehoge })
と書けば記述量を減らせる
修正前
const App = () => {
const greeting = 'Hello Function Component!';
return <Headline value={greeting} />;
}
const Headline = (props) => {
return <h1>{props.value}</h1>;
}
修正後
const App = () => {
const greeting = 'Hello Function Component!';
return <Headline value={greeting} />;
}
const Headline = ({ value }) => {
return <h1>{value}</h1>;
}
こんな風に、propsが2つ以上ある時、
const Button = ({ children, onClick }) => {
return <button onClick={onClick}>{children}</button>
}
スプレッド構文を使ってpropsの記述を減らせる
const Button = (props) => {
return <button {...props} />
}
ただしこの場合、可読性が下がるのであまりやるべきでは無い!!
参照
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined
用途
ReactNode
だけに決まっている(要するにchildrenの型を指定する時は、ReactNode
で書いた方が良い)JSX.Element
との違い
JSX.Element
は ReactElement
である( ReactElement
を継承している )
interface Element extends React.ReactElement<any, any> { }
という定義ReactNode
で困らないし、後々 string
なり null
なり渡したい時に便利JSX.Element
より React.ReactElement
を使っていった方が齟齬がない参照
useEffect
に渡された関数はレンダーの結果が画面に反映された後に動作する
componentDidMount
componentDidUpdate
componentWillUnmount
// useEffectの場合
useEffect(() => {
document.title =`${count}回クリックされました`
})
// クラスコンポーネントの場合
componentDidMount(){
document.title =`${this.state.count}回クリックされました`
}
componentDidUpdate(){
document.title =`${this.state.count}回クリックされました`
}
第2引数に配列を渡すと、「これの値が変わった時だけコンポーネントを再レンダーする!」みたいなことができる
// useEffectを使った場合
useEffect(() => {
elm.addEventListener('click', () => {})
return () => {
elm.removeEventListener('click', () => {})
}
}, [])
// countに変化があった時だけ関数を実行
useEffect(() => {
document.title =`${count}回クリックされました`
},[count])
第2引数に空の配列を渡すと、初回のレンダー時のみ関数を実行する(= componentDidMount
と同じになる)
// 初回だけ関数を実行
useEffect(() => {
document.title =`${count}回クリックされました`
},[])
//これと同じ
componentDidMount(){
document.title =`${this.state.count}回クリックされました`
}
クリーンアップ関数をuseEfffect
内でreturnすると2度目以降のレンダリング時に前回の副作用を消してしまうことができる。
イベントリスナの削除やタイマーのキャンセルなどを行うことができる。
useEffect(() => {
elm.addEventListener('click', () => {})
// returned function will be called on component unmount
return () => {
elm.removeEventListener('click', () => {})
}
}, [])
useEffectの第一引数として非同期関数を設定できない というのも、第一引数として設定する関数の戻り値はundefinedもしくはcleanup関数でなくてはならない、ため
//これはエラー
//戻り値がPromiseインスタンスになっている
useEffect(async () => {
const response = await fetch("https://www.googleapis.com/books/v1/volumes?q=AWS");
const data = await response.json();
console.log(data);
},[]);
正しくは以下の通り
//これはOK
useEffect(() => {
const data = async() => {
const response = await fetch("https://www.googleapis.com/books/v1/volumes?q=AWS");
const data = await response.json();
alert(data.totalItems);
}
data()
}, []);
参照
propsにboolean型を渡すことがもちろんできる
true
を渡す時render() {
return (
<Movie released={true} />
)
}
簡略化可能
render() {
return (
<Movie released /> //何を指定しなくてもtrueになる
)
}
false
を渡す時render() {
return (
<Movie released={false} />
)
}
undefined
が渡されるrender() {
return (
<Movie /> // propsを指定しなかったらundefinedを渡してるのと同じ、真偽値判定はfalseになる
)
}
参照
exportの仕方には、"named export"と"default export"の2種類がある。
//named export
export const sqrt = Math.sqrt;
import { sqrt } from "./hoge"; // { }を使う
//default export
export default const sqrt = Math.sqrt;
import sqrt from "./hoge"; // { }はいらない
default exportは1つのファイルにつき1つしか指定できない。
さらに、named exportはimportする時に、対応するオブジェクトと同じ名前を使用しなくてはいけない。 しかし、default exportはimport側で任意の名前で呼び出すことができる(ただしこれにより後々リファクタリングがめんどくさくなる可能性も)。
// test.js
let k; export default k = 12;
// 他のファイル
import m from './test'; // k がdefault exportなのでimportする k の代わりに m を使用することができる
console.log(m);
参照
例
const Foo = () => {
useEffect(() => {
// ここがコールバック関数
console.log("Fooがマウントされました!");
// ↓これがクリーンアップ関数
return () => {
console.log("Fooがアンマウントされる!");
};
}, []);
return <p>I am foo</p>;
};
useEffect
におけるクリーンアップ関数の実行タイミング再レンダリング時
アンマウント時
参照
基本的な構文(Functional Componentの場合)
const FlavorForm = () => {
const [flavor, setFlavor] = useState('coconut');
handleSubmit(event) {
alert('Your favorite flavor is: ' + flavor);
event.preventDefault();
}
return (
<form onSubmit={handleSubmit}>
<label>
Pick your favorite flavor:
<select value={flavor} onChange={(e) => setFlavor(e.target.value)}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}
selected
属性の代わりにReactではselectタグのvalue属性を使用する参照
参照
使い方
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch, Link } from 'react-router-dom';
import { Home, About, Dashboard } from './component';
const App = () => {
return (
<BrowserRouter>
<div>{document.title}</div>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/about' component={About} />
<Route exact path='/dashboard' component><Dashboard /></Route>
</Switch>
<Link to='/'>Back To Home</Link>
</BrowserRouter>
)
}
BrowserRouter
の中にRoute
を入れるRoute
の中でpathとそれに対応して表示するコンポーネントを指定するexact
の指定が重要
path='/'
にexact
を指定しないと/about
でも/dashboard
でもHome
が表示されてしまうreact-routerとreact-router-domの違い
Link
やNavLink
が使えるので基本こっちを使う参照
Contextを使うと、propsのバケツリレーを防ぐことができる
// まずはコンテクストを作成
// 引数はdefault value
const ResourceContext = React.createContext("");
export const RootComponent = () => {
const resource = getResource(); // 何らかのデータを取ってくる
return (
// Providerコンポーネントで包むことによって、Provider配下のコンテキストを決定する
// このvalueが今までバケツリレーしていたprops
<ResourceContext.Provider value={resource.name}>
<NavigationComponent />
<BodyComponent />
</ResourceContext.Provider>
);
};
export const NavigationComponent = (props) => {
// useContextのhooksを使う
// 値はResourceContext.Providerのvalueで決定される
const resourceName = React.useContext(ResourceContext);
return (
<header>
<TitleComponent title={resourceName} />
<NannkanoButtonComponent />
<NannkanoMenuComponent />
</header>
);
};
export const TitleComponent = (props) => (
<div>{props.title}</div>
);
export const BodyComponent = (props) => {
// ココも!
const resourceName = React.useContext(ResourceContext);
return (
<main>
<ResourceTitleComponent name={resourceName} />
...
</main>
);
};
export const ResourceTitleComponent = (props) => (
<article>{props.name}</article>
);
使い方としては、
createContext
でコンテクストを作成するMyContext.Provider
でpropsを渡したいコンポーネントを包むuseContext
でpropsを取り出して使う
みたいな感じ参照
import React, { useReducer } from 'react';
function Counter() {
// useReducerの第1引数はstateとactionを引数に持つ関数、第2引数はstateの初期値
// sumはstate
const [sum, dispatch] = useReducer((state, action) => {
return state + action;
}, 0);
return (
<>
{sum}
// dispatchはuseReducerに渡した関数のこと
// dispatchにわたす引数はその関数でいうaction
<button onClick={() => dispatch(1)}>
Add 1
</button>
</>
);
}
typeとかを使ってReduxっぽく応用することが多いっぽい?
import React, {useReducer} from 'react ';
const [state, dispatch] = useReducer(authReducer, initialState);
function authReducer(state, action){
// typeによって処理を変える
switch (action.type) {
case 'LOGIN':
return {
...state,
user: action.payload
};
case 'LOGOUT':
return {
...state,
user: null
};
default:
return state;
}
}
function login(userData){
// typeは'LOGIN'としてdispatch(=authReducer)関数を呼び出す
dispatch({
type: 'LOGIN',
payload: userData
});
}
function logout(){
// typeは'LOGIN'としてdispatch(=authReducer)関数を呼び出す
dispatch({ type: 'LOGOUT' });
}
参照
まずは動的なルートを定義する
import { BrowserRouter as Router, Route } from 'react-router-dom';
<Router>
<Route path='/posts/:postId' component={SinglePost} />
</Router>
このとき、以下のように取得できる
const SinglePost = (props) => {
const postId = props.match.params.postId;
}
ちなみに型定義する場合は、
import { RouteComponentProps } from 'react-router-dom';
type PostProps = RouteComponentProps<{
// パラメータの型を指定する
postId: string;
}>;
const SinglePost: FC<PostProps> = (props) => {
const postId = props.match.params.postId;
}
参照
そもそもRef
はあまり使うべきでは無いらしい、以下のときに使う(公式より)
useRef
を用いると要素への参照を行うことができる
//100は初期値
const number = useRef(100);
//current属性で値を取得
console.log(number.current); // 100
DOMの参照で使われることが多い模様
const inputElement = useRef(null)
//例: inputElement.currentで <input type="text" /> を参照
<input ref={inputElement} type="text" />
console.log(inputElement.current); // <input type="text" />
useRef
(というかRef
)を用いるとコンポーネントの再描画はせずに、内部の値だけ更新する、みたいなことができる
参照
public/index.html
<body>
(...省略)
<div id="modal"></div>
<div id="root"></div>
</body>
src/ModalPortal.js
import ReactDOM from 'react-dom';
const ModalPortal = ({ children }) => {
const el = document.getElementById('modal');
// id="modal"に対してPortalを作成する
return ReactDOM.createPortal(children, el);
};
export default ModalPortal;
src/App.js
import "./App.css";
import Modal from "./Modal";
import ModalPortal from "./ModalPortal";
export default function App() {
return (
<div className="App">
<h1>Hello World</h1>
// Portalで囲った部分(Modal)が別のDOMにレンダリングされる
<ModalPortal>
<Modal />
</ModalPortal>
</div>
);
}
参照
propsの分割代入を行うときにどうやって型定義を行うか
例
// 型を定義
type AppProps = {
message: string;
};
// 分割代入をしない場合
const App = (props: AppProps) => <div>{message}</div>;
// 定義した型を使って型定義
const App = ({ message }: AppProps) => <div>{message}</div>;
// 返り値の型を指定する場合
const App = ({ message }: AppProps): JSX.Element => <div>{message}</div>;
// 定義した型を使わない場合、ただし可読性は下がる
const App = ({ message }: { message: string }) => <div>{message}</div>;
参照
イベントハンドラの渡し方に注意
function showMessage(){
console.log('message);
}
// ボタンクリック時に関数が実行される
<button onClick={showMessage}>ボタン</button>
// 無名関数を使ってもOK
<button onClick={() => showMessage()}>ボタン</button>
// 引数がある場合
// 関数の方に引数を入れる
<button onClick={() => showMessage(mes)}>ボタン</button>
以下のパターンはNG JSXが評価されるタイミングで実行されてします
<button onClick={showMessage()}>ボタン</button>
// 引数あるときも注意
<button onClick={showMessage(mes)}>ボタン</button>
参照
string
型とString
型についてTypeScriptを使うときにstring
型とString
型を混在して使っていたので整理
string
型const str1 = "string";
console.log(typeof str1); // string
String
型const str2 = String("string");
console.log(typeof str2); // String
string
を用いる
参照
基本的には
にレンダリングが行われる。 しかし、3番目により、意識しないと無駄なレンダリングにつながってしまう。 その結果、UIの描写に時間がかかったりしてしまう。
レンダリングをスキップするためにはmemo
やuseCallback
、useMemo
を使ってメモ化を行う。
(だが、実際にはmemoなどを使うよりはdiv
タグを減らすなどのほうがパフォーマンスは上がるみたい)
(メモ化:同じ結果を返す処理について、初回のみ処理を実行記録しておき、値が必要となった2回目以降は、前回の処理結果を計算することなく呼び出し値を得られるようにすること)
memo
React.memo
はコンポーネントをラップする。propsが同じだったら親コンポーネントが更新されても再描画せずに、以前の結果を描画する。
stateが更新されたときは新しくレンダーする。
useCallback
コンポーネントを再描写するとそのコンポーネントの中で定義されている関数も新たに作り直される。そしてその関数をpropsとして子コンポーネントに渡すと、関数自体は変わって無くてもpropsが変更されたことになるので、子コンポーネントが再描画されてしまい、不必要な再描画を招く。
useCallback
は関数自体をメモ化する。依存する引数が変化しなければ、以前と同じ関数を返す。
useMemo
useCallback
は関数自体をメモ化するが、useMemo
は関数の結果をメモ化する。
「計算負荷は高いが、何回やっても結果は変わらない」みたいなときに使う。
(関係のない部分が変更されてコンポーネントが再描写されるたびにコストの高い計算が行われることを防ぐ)
参照
例えば、あるモジュールをimportしたときに
import hoge from 'fugafuga';
こんなエラーが出る時がある
Could not find a declaration file for module 'fugafuga'.
Try npm install @types/fugafuga if it exists or add a new declaration (.d.ts) file containing declare module 'fugafuga';
これはモジュールの型定義ファイルが無くTypeScriptに対応できていないことによる
メジャーなパッケージなら@types/fugafuga
という型定義ファイルがあるのでそれをインストールすればOK
@types
がなければ自分で型定義ファイルを自作する必要がある
.d.ts
ファイルを作成する(.ts
ファイルでもOK)
ファイルの拡張子が.d.ts
の場合、各ルートレベルの定義にはdeclare
というキーワードを前に付ける必要がある
// fugafuga.d.ts
declare module 'fugafuga';
こうすればエラーは解消される
が、moduleをimportすると型は暗黙的にany
になる
ちゃんと型付けを行う時はこんな感じ
// fugafuga.d.ts
declare module 'fugafuga' {
export function getRandomNumber(): number
}
import { getRandomNumber } from 'fugafuga';
const x = getRandomNumber(); // x is inferred as number
自分が使うオブジェクトに対してだけ型付けを行えば十分
declare module
をした(これをアンビエントモジュール宣言という)モジュールはどこに配置しても大丈夫
アンビエントモジュール宣言はプロジェクト全体に適用される
しかしあくまでもTSのコンパイルが通る必要があるため、tsconfig.json
で設定したコンパイルの箇所には配置する必要あり
型定義ファイルの名前は何でもよい(中身が重要)が、実務的には宣言するモジュールごとにファイルを分け、src/@types/
以下に配置することが多い
面倒な場合はglobal.d.ts
みたいにすることもある
※注意1
トップレベルにimport
してしまうとアンビエントモジュール宣言にならずグローバルにならない
他のファイルからexportしたものを宣言に使うときはdeclare
ブロックの中でimport
する必要あり
参照:Import class in definition file (*d.ts) - stackoverflow
※注意2
独自に.d.ts
ファイルや.ts
ファイルを作りアンビエントモジュール宣言を行わなくても型定義をすることはできる
しかしdeclare module
が一番優先されるので、基本的にはこれを使うのが良さそう
アンビエントモジュール宣言を用いない場合は、ファイルの配置箇所・ファイル名には気をつける必要あり
参照:TypeScript の型定義ファイルの探索アルゴリズム
参照
TypeScriptではclass内のオブジェクト(メンバーや関数など)にprivate
、protected
、public
の修飾子を付けてアクセス制御ができる
private
:class以外では参照することができない(子クラス内やインスタンス化したときに参照できない)protected
:子クラス内では参照できるがインスタンス化したら参照できないpublic
:いつでもどこからでも参照できる(何もしなくてもそうなるので、コメント的な意味合いになる)class SmallDog {
private secretPlace: string; //アクセス制御
dig(): string {
return this.secretPlace;
}
bury(treasure: string) {
this.secretPlace = treasure;
}
}
const miniatureDachshund = new SmallDog();
miniatureDachshund.bury("骨");
// インスタンスからはアクセスできない
// error TS2341: Property 'secretPlace' is private and only accessible within class 'SmallDog'.
miniatureDachshund.secretPlace;
console.log(miniatureDachshund.dig()); // 骨
ちなみにコンストラクタの引数としてプロパティを宣言できる
class SmallDog {
constructor(private secretPlace: string) {
}
...
}
参照
TypeScriptでは、クラスを定義すると同時に同名の型も定義される。
class Foo {
method(): void {
console.log('Hello, world!');
}
}
const obj: Foo = new Foo();
クラスFoo
が定義されたことで型Foo
も同時に定義される
最後のobj: Foo
のFoo
は型のFoo
(new Foo()
のFoo
はクラスFoo
の実体)
ちなみにここの型Foo
は以下と同じ
interface MyFoo {
method: ()=> void;
}
const obj: MyFoo = new Foo(); //エラーは出ない
参照
JSONをparseした結果はany
型として扱われてしまう。
そのため予期しない型のデータが取得された場合、予期しないところでエラーが発生する。
型推論をさせてしまうのもダメ。
回避するためにはデータの取得段階でvalidationを掛ける必要がある。
validationに使えるのは@mojotech/json-type-validationというライブラリ
参照
この図が分かりやすい
なので、useState
の初期値はマウント時にしか読み込まれない(constructorのstateなので)
コンポーネントがレンダリングされるたびにstateが初期値に戻ったりしない
初期値を頻繁に使いたいならpropsとして扱うのがベター
コンポーネント内の関数はレンダリングの度に実行されるので注意
副作用のあるような関数はレンダリング時ではなくマウント時に実行させるのが良い
マウント時のみ実行させたいならuseEffect
を使う
ちゃんと型付けする場合はこんな感じ
function lengthOrDefault(str: string | null, defaultLength: number): number {
return str != null ? str.length : defaultLength;
}
戻り値の型付けを省略した場合はreturn
されている式から型推論される
// foo は (num: number) => "hoge" | "fuga" 型
function foo(num: number) {
if (num >= 0) {
return "hoge";
} else {
return "fuga";
}
}
return
文が無いときはvoid
型が推論される。
return
文はあるけどreturn
せずに終了する場合があるときはundefined
になる。
// fooの返り値は "hoge" | undefined 型
function foo(num: number) {
if (num >= 0) {
return "hoge";
}
}
undefined
にするのではなくエラーにしたい場合はtsconfig.js
の設定を変更すればOK
「変数の型は宣言時に決まる」ため、関数が使われる際に推論は行われない。 型注釈がない場合、any型として推論される。
// 引数はany型として扱われてしまう。
// 設定によってはimplicit anyということでエラー
function foo(num) {
if (num >= 0) {
return "hoge";
}
}
なので、基本的には引数は型注釈を与える必要がある。 ただし、contextual typeがあるときは引数に型注釈を与える必要はない。
type Func = (arg: number) => number;
const double: Func = function(num) {
// 引数 num の型は number 型と推論されている
return num * 2;
};
参照
ReactでSSRをする際に使う。すでに存在するmarkupにevent listenerなどをアタッチする。 (hydrate = 水を与える、event listenerの無い干からびたHTMLにevent listenerという水を与えるイメージ)
レンダーされる内容がサーバーとクライアントで一致していなくてはならない。
なのでReactDOMServer
などを用いてマークアップを用意する必要がある
参照
参照した記事
React
TypeScript
React + TypeScript
TypeScriptをなぜ使うのか?