- Published on
React基礎
- Authors
- Name
- Kikusan
- Refs
- shell
- eslint
- VS Code拡張
- チュートリアル
- レンダーとJSX
- コンポーネントとprops
- 関数コンポーネント
- クラスコンポーネント
- Component Lifecycle
- if
- for
- 組み込みコンポーネント
- Reack Hooks
- useState
- useEffect
- useMemo
- useRef
- useContext
- useReducer
- 独自フック
- React Router
- Redux
Refs
- チュートリアル: https://ja.reactjs.org/tutorial/tutorial.html
- ドキュメント: https://ja.reactjs.org/docs/getting-started.html
shell
npx create-react-app hello-world --template=typescript
cd hello-world
# eslint グローバルインストールでパスを通す 他にプロジェクトがあれば -> https://qiita.com/mysticatea/items/6bd56ff691d3a1577321
npm install -g eslint
npm i -D eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react
eslint --init # 質問に答える .eslintrc.[js/yaml]が生成されるのを書き換えて自分好みにする
eslint src # コードチェック
eslint
読み込みファイルを限定するため.eslintrc.js
のparseOptions
にproject: "./tsconfig.eslint.json"
を設定し、ファイルを以下の内容で作成する。
{
"extends": "./tsconfig.json",
"include": [
"src/**/*.js",
"src/**/*.jsx",
"src/**/*.ts",
"src/**/*.tsx"
],
"exclude": [ "node_modules" ]
}
VS Code拡張
- ESLint: 静的コード解析ツールLintの統合. https://qiita.com/Mount/items/5f8196b891444575b7db
- Prettier: コード整形ツールPrettierの統合.
CMD + Shift + P -> Format Document
https://qiita.com/takeshisakuma/items/bbb2cd2f1c65de70e363
チュートリアル
github: https://github.com/kikusan-16/react-tutorial
このチュートリアルはクラスコンポーネントを利用したstateを使用している。
この管理方法ではstateを子孫にpropsとして渡しているが、
state管理の問題としてpropがバケツリレーされるprop drilling問題があり、これを解決するためにreduxがあった。
ただ、今はReact関数コンポーネントにReact Hooksが導入され、状態管理を簡潔に行えるようになった。
今は関数コンポーネントを基本的に使用して、できないことがある場合にクラスコンポーネントをつかうといい。
レンダーとJSX
React要素はrender
によりDOMにレンダリングされる。
JSXの特性
- コンパイルによって
React.createElement()
関数に変換される。(帰ってくるのはObject=React要素
) - タグを生成できる。
{}
で変数を代入できる。const element = <h1>Hello, {name}</h1>;
- デフォルトでXSSを防ぐ。
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
// strictモードは本番ビルドには影響を与えない
<React.StrictMode>
<App />
</React.StrictMode>
);
ReactDOMは要素とその子要素に必要な分だけDOMの更新を行う。
コードでJSX全体を塗り替えるように書いていても、差分がある部分だけ更新される。
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
const tick = () => {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2> {/* 更新されるのはここだけ*/}
</div>
);
root.render(element);
}
setInterval(tick, 1000);
import React from 'react';
はJSXがコンパイルされた後必要になるので必ず記載が必要だったが、
TypeScriptではtsconfig.json
のcompilerOptions.jsx=react-jsx
とするとimportしなくても動作するようになる。
コンポーネントとprops
関数コンポーネント
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
// propsの型定義
type Props = {name: String, children?: React.ReactNode};
// TypeScriptでジェネリクス使用
const Welcome: React.FunctionComponent<Props> = (props: Props) => {
// 分割代入
const { name, children } = props;
return <>
<h1>Hello, {name}</h1>
{/* childrenは暗黙的に呼び出し時の中身のタグを受け取れる */}
{children}
</>;
}
root.render(
// propsに属性を渡せる
<Welcome name="Kikusan">
{/* childrenに子要素を渡せる */}
<p>this is children</p>
</Welcome>
);
クラスコンポーネント
クラスコンポーネントではstateとライフサイクルメソッドを使用できる。
Hooksがある今は率先して使う必要はない。
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
type Props = {date?: Date};
type State = {date?: Date};
// <props, state>で型指定
class Clock extends React.Component<Props, State> {
timerID?: number;
// stateの更新はコンストラクタで
constructor(props: Props) {
super(props);
this.state = {date: new Date()};
}
// レンダリングはrenderメソッドで
render() {
return <h2>It is {this.state.date && this.state.date.toLocaleTimeString()}.</h2>
}
// マウント時一度だけ呼ばれる。データロードはcomponentDidMountで
componentDidMount = () => {
this.timerID = window.setInterval(
() => this.tick(),
1000
);
}
// 更新があると呼ばれる。stateとpropsの変更はここで。
componentDidUpdate = () => {
console.log(this.state);
}
// 削除時処理はcomponentWillUnmountで
componentWillUnmount = () => {
clearInterval(this.timerID);
}
tick = () => {
// stateの更新はsetStateで行う
this.setState({
date: new Date()
});
};
}
root.render(<Clock />);
Component Lifecycle
- constructor: 1度だけの初期化
- render: JSXの返却以外は行わない
- (表示される)
- componentDidMount: マウントされた時1度だけ呼ばれる。データロードにちょうどいい
- (更新を待つ)
- componentDidUpdate: 親か自分の要素に変更があった時に呼ばれる。state/propsの変更をするのにちょうどいい
- (コンポーネントが表示されなくなるのを待つ)
- componentWillUnmount: 通常は使用せずなにかcleanupが必要な場合使用する
if
JavaScriptのifを使用するか、jsxでは{}
で真偽値を使用する。
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
const n = Math.floor(Math.random()*10);
root.render(
<>
{/* 偽の場合はレンダリングされない */}
{n > 5 && <p>{n} is larger than 5 </p>}
<p>{n} is {n % 2 === 0 ? 'even' : 'odd'}</p>
</>
);
for
配列を使用する。
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
const numbers = [1, 2, 3, 4, 5];
root.render(
<>
<ul>
{/* keyはReactの識別性のために必要。並び替えが変わる場合を考慮して、index以外を使用すること */}
{numbers.map((number) =>
<li key={number.toString()}>{number}</li>
)}
</ul>
</>
);
組み込みコンポーネント
予め用意されたタグがJSXでは使える。
基本的にhtmlと同じだが、JavaScriptの命名規則に合わせて属性名が変わっている。
(ユーザ定義コンポーネントでも同じだが)boolean値は値を省略するとtrue
になる。
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L3140
Reack Hooks
関数コンポーネントでのみ、使える。
ループや条件分岐内ではなく、React関数内トップレベルで使用する。
Hooksにおけるpropsやstateの値はレンダリング毎に固定される。
useState
オブジェクトの状態を保つ。
import React, { useState } from 'react'
import ReactDOM from 'react-dom/client'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
)
const Example = (): React.ReactElement => {
// count=変数
// setCount=関数, setCount(処理)
// useState(初期値)
const [count, setCount] = useState(0)
// hooks関数には関数を渡せる。引数にはstateが入る
// count+1でも書けるが、hooksではstateの値が常に最新とは限らないので関数を渡す
const increment = (): void => setCount((c) => c + 1)
return (
<div>
<p>You clicked { count } times</p>
<button onClick={ increment }>
Click me
</button>
</div>
)
}
root.render(<Example />)
useEffect
副作用がある場合のオブジェクトの書き換え。componentDidMount and componentDidUpdateに似ている。
import React, { useState, useEffect } from 'react'
import ReactDOM from 'react-dom/client'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
)
const Example = (): React.ReactElement => {
const [count, setCount] = useState(0)
/* useEffect
* 第二引数は依存配列といい、配列内の変数に差分があると第一引数が実行される。
* 省略するとレンダリング毎に第一引数が実行される。
* [] のときはcomponentDidMountと同じように実行される。
* [count] のときはcomponentDidUpdateと同じように実行される。
*
* 正確にはcomponentDidMountは描画前に実行されるが、useEffectは先に初期値で描画される
* (これを利用してロード中を簡単に表せる)
*/
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`
// return () => clearSomething() 戻り値に関数を入れるとunMount時に実行
}, [count])
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
)
}
root.render(<Example />)
useMemo
計算量の多い処理を分けておき、依存配列に変化があった場合だけ再計算するようにできる。
import React, { useState, useMemo } from 'react'
import ReactDOM from 'react-dom/client'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
)
const Example = (): React.ReactElement => {
const calc = (n: number): number => {
return n ** 2
}
const [count, setCount] = useState(0)
// 依存配列の中身に変更があった場合だけ再計算
const memoizedValue = useMemo(() => calc(count), [count])
return (
<div>
<p>{`You clicked ${count} times => ${memoizedValue}`}</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
)
}
root.render(<Example />)
useRef
Refはレンダリングではなくて要素の変更を行いたい場合に使用する。
リアルDOMの要素を参照できる。
以下の場合に使用できる。
- フォーカス、テキストの選択およびメディアの再生の管理
- アニメーションの発火
- サードパーティの DOM ライブラリとの統合
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
const TextInput: React.FunctionComponent = () => {
// HooksのuseRef(初期化値)
const inputRef = React.useRef<HTMLInputElement>(null);
const onButtonClick = (): void => {
// current はrefが渡されたリアルDOMオブジェクトになる
if (inputRef.current) inputRef.current.focus();
};
return (
<div>
{/* refを渡す */}
<input type="text" ref={inputRef} />
<input type="button" value="Focus" onClick={onButtonClick} />
</div>
);
};
root.render(<TextInput />);
useContext
コンポーネントへの値の受け渡しを可能にする。useContext
呼び出しオブジェクトから、直近の<ContextName.Provider>
が記載された値を取り出す。
import React, { useContext } from 'react'
import ReactDOM from 'react-dom/client'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
)
const themes = {
light: {
foreground: '#000000',
background: '#eeeeee'
},
dark: {
foreground: '#ffffff',
background: '#222222'
}
}
// createContext(デフォルト値)でcontextを作成する
const ThemeContext = React.createContext(themes.light)
const App = (): React.ReactElement => {
return (
// タグにcontext.Provider value= を使用してツリーの子孫に値を差し替えられる。
// 子供は直近の親のcontext.Providerに作用される
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
)
}
const Toolbar = (): React.ReactElement => {
return (
<div>
<ThemedButton />
</div>
)
}
const ThemedButton = (): React.ReactElement => {
// useContextでcontextを使用する。
const theme = useContext(ThemeContext)
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
)
}
root.render(<App />)
useReducer
React Hooksで個別のコンポーネントにおいてReduxの機能を再現したもの。 useStateの代替品として使用され、複数の値にまたがる複雑なstateロジックがある時に使う。
import React, { useReducer } from 'react'
import ReactDOM from 'react-dom/client'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
)
const initialState = { count: 0 }
const CounterActionType = {
increment: 'counter/increment',
decrement: 'counter/decrement'
} as const // as const で全ての値をreadonlyにする
type ValueOf<T> = T[keyof T] // Tのvalueのユニオン型
type CounterAction = {
type: ValueOf<typeof CounterActionType> // counter/increment | counter/decrement
}
type CounterState = { count: number }
// reducer (state, action): newState
const reducer = (state: CounterState, action: CounterAction): CounterState => {
switch (action.type) {
case 'counter/increment':
return { count: state.count + 1 }
case 'counter/decrement':
return { count: state.count - 1 }
default:
throw new Error()
}
}
const Counter = (): React.ReactElement => {
// 現在のstateとreducerを呼ぶためのdispatcherを受け取る。
const [state, dispatch] = useReducer(reducer, initialState)
return (
<>
Count: { state.count }
<button onClick={() => dispatch({ type: 'counter/decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'counter/increment' })}>+</button>
</>
)
}
root.render(
<Counter />
)
独自フック
再利用可能な処理を切り出して定義しておくこと。
名前はuse~
をつける。
やってることは関数分離だが、呼び出し元のstateやeffectを使用できる
。
import React, { useState } from 'react'
import ReactDOM from 'react-dom/client'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
)
const useCount = (): Array<number | (() => void)> => {
const [count, setCount] = useState(0)
const increment = (): void => setCount((c) => c + 1)
return [count, increment]
}
const Example = (): React.ReactElement => {
const [count, increment] = useCount()
return (
<div>
<p>You clicked { count as number } times</p>
<button onClick={ increment as () => void }>
Click me
</button>
</div>
)
}
root.render(
<>
<Example />
<Example />
</>
)
React Router
公式: https://reactrouter.com/en/main
- install
npm install react-router-dom
チュートリアルから動作を見ることができる。
- Routerが履歴管理をしてくれることで戻る動作などに対応できる(aでなくLinkタグを使うのもそのため)
- HooksAPIを使用することで履歴やURLパラメータなどを使用できる
- ReactGAライブラリを使用してSPAでもGoogleAnalyticsの機能を使える
- ReactHelmetライブラリを使用することでSPAでもHeader情報を書き換えることができる
Redux
Fluxアーキテクチャを採用した状態管理ライブラリ FluxではMVCでのmodel ⇄ view の複雑な関係性をよしとせず、単方向データフローを提供する。
- Store: アプリケーションの状態データの集合
- Action: イベントそのもの
- Dispatcher: Actionに応じてStoreの更新処理を行うもの
- View: Reactなど
reduxではここにreducer: 唯一storeのデータを変更できる純粋関数
を追加する。
参考画像: https://redux.js.org/tutorials/essentials/part-1-overview-concepts#redux-application-data-flow
Viewからは状態データの変更は行わず、Actionを起動する。
- install
npm install redux react-redux