はじめに
React Hooksの基礎について学んだことを2回に分けて記事にしました。
今回はReact Hooks(導入、Props、useState、useReducer、useEffect、axios)
次回、React Hooks(useContext、useMemo、useCallback)
開発環境
React: 17.0.2
Node.js: 14.16.0
Visual Studio Code: 1.57.1
Function Component と Class Component
本稿では先にHooksについての説明をすることを前提に進めていくので、Function Componentでの説明をします。その前提知識としてFunction Component と Class Componentついて少しだけお話しします。
フック(hook)が React 16.8 で追加される前までは、component間でのデータの扱いを柔軟に行うためにはClassComponentで書く方法しかありませんでした。ClassComponentはJavaScript特有のクラスの概念の理解が必要な上に、状態管理の一元管理ができずライフサククル別の処理が必要になり、そういったことがエラーの原因になっていました。フック(hook)の導入でFunctionComponentによる多様なcomponent間での状態の共有を可能にするなど様々な機能により、見通しが良く、少ない作業での実装が可能になりました。
creat-react-app
まずファイルを生成
create-react-app example
フォルダー構造
.
├── node_modules
├── README.md
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.css
│ ├── App.test.js
│ ├── App.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── reportWebVitals.js
│ └── setupTests.ts
├── tsconfig.json
└── yarn.lock
public/index.html が一番上の階層のhtmlファイル。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<!-- -->
<div id="root"></div>
<!-- -->
</body>
</html>
下記のReactDOM.render()
からid="root"
と紐づいて、上記の<div id="root"></div>
へApp.js
が出力されている。
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
reportWebVitals();
component生成
src直下にcomponents
というフォルダを作成してその中にBasic.js
というファイルを作成。
ちなみに、VSCodeの拡張機能ES7 React/Redux/GraphQL/React-Native snippets
をインストールすると、componentの中でrafce
と入力するだけで、補完機能を使用してfunctional component
の雛形を作成できる。
import React from 'react'
const Basic = () => {
return (
<div> // 必ずdivタグやReact.Fragmentなどで囲わないといけない
</div>
)
}
export default Basic
React.Fragmentの省略
React.fragmentはDOMに余分なノードを追加することなく子要素をまとめることができるようになります。
またReact.fragmentには省略記法があります。
<React.Fragment></React.Fragment>
// 以下のように省略できます
<></>
Basic.jsを表示させる
Basic.js
を以下のように書き換えて、App.jsに渡して表示させる。
import React from "react";
const Basic = () => {
return (
<>
<h1>Hello World!</h1>
</>
);
};
export default Basic;
App.jsの中身は初期状態からいらない要素を削除してBasic.jsのインポートとBasicタグを追加。
import "./App.css";
import Basic from "./components/Basic";
function App() {
return (
<div className="App">
<Basic />
</div>
);
}
export default App;
Props
Basic.jsの表示内容をApp.jsから変更する。
App.jsの<Basic />
に変数と値を指定する。
import "./App.css";
import Basic from "./components/Basic";
function App() {
return (
<div className="App">
{/* titleという変数を持たせて"React!!"を代入 */}
<Basic title="React!!" />
</div>
);
}
export default App;
App.jsで指定した、title
をBasic.jsで受け取る。
propsというオブジェクトを受け取るとその中にtitleが入っている。
import React from "react";
const Basic = (props) => { // propsを受け取る
return (
<>
{/* propsから中身を取り出す */}
<h1>Hello {props.title}</h1>
</>
);
};
export default Basic;
clickイベント
クリックしたらコンソールログを表示させるという簡単な関数を作成し、buttonタグに指定する。
import React from "react";
const Basic = (props) => {
const log = () => { // logという関数を作成
console.log("クリック!");
};
return (
<>
<h1>Hello {props.title}</h1>
{/* onClickに作成したlog関数を指定 */}
<button onClick={log}>クリック</button>
</>
);
};
export default Basic;
useStateを使ってクリックするとブラウザーで表示されているカウントが1ずつカウントする関数を作ります。
ブラウザーでクリックボタンをクリックするとコンソールにクリック!と表示されることができる。
useState
useStateは定義した変数の状態の保持し、変数の更新するための関数を返します。
下記はコードはボタンを押すたびにカウントが増えていくという簡単な例。
import React, { useState } from "react";
const Basic = () => {
// countという変数を定義し、useState(0)でcountの初期値に0を設定
// setCountはcountを更新するための関数名です。
// ちなみにこのuseState左辺の第二引数の関数名は(set変数名)とするのが慣習のようです。
const [count, setCount] = useState(0);
return (
<>
<button
onClick={() => {
// countをaとして受け取り、+1して返している
setCount((a) => a + 1);
}}
>
カウント {count}
</button>
</>
);
};
export default Basic;
これでボタンを押すごとにカウントされる。
useStateでオブジェクト扱う
import React, { useState } from "react";
const Basic = () => {
const [User, setUser] = useState({ name: "", age: "" });
return (
<>
<form>
<input
type="text"
value={User.name}
// onChangeは一文字入力するごとに値が更新され、ブラウザーに動的に表示される。
// ...Userはオブジェクトを分解して、nameの値の更新と、残りのUser要素もそのまま返す必要があるため。
onChange={(user) => setUser({ ...User, name: user.target.value })}
/>
<input
type="text"
value={User.price}
onChange={(user) => setUser({ ...User, age: user.target.value })}
/>
</form>
<h1>名前: {User.name}</h1>
<h1>年齢: {User.age}</h1>
</>
);
};
export default Basic;
useStateで配列を扱う
オブジェクトとほとんど扱い方は同じ。
import React, { useState } from "react";
const Basic = () => {
const [users, setUsers] = useState([]);
const newUsers = () => {
setUsers([
...users,
{
id: users.length,
name: "name",
},
]);
};
return (
<>
<button onClick={newUsers}>add</button>
<ul>
{/* users配列から一つずつuserを取り出して加工 */}
{users.map((user) => (
<li key={user.id}>
{user.name} id: {user.id}
</li>
))}
</ul>
</>
);
};
export default Basic;
useReducer
useReducerはuseStateにはないreducerという機能を使用して、より複雑な処理をしたり、コンポーネントをまたいでの状態の更新などができます。
// reducerはstateを更新するための関数で、dispatchは、reducerを実行するための呼び出し関数です
// initialArgはstateの初期値、initは初期化関数を持たせることができる。
const [state, dispatch] = useReducer(reducer, initialArg, init);
次の例でまず、buttonタグ内でtypeを持ったdispatchが設定され、dispatchが起動するとreducerが呼び出され、reducerがactionに格納されたtypeによって条件毎に異なるstateを返すことがわかります。
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}. //dispatch()の引数がactionに入る
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
useEffect
useEffect に渡された関数はレンダーの結果が画面に反映された後に動作します。
デフォルトでは渡された関数はレンダーが終了した後に毎回動作しますが、特定の値が変化した時のみ動作させるようにすることもできます。
// レンダー終了後毎回走る
useEffect(() => {
console.log('Hi!')
})
// 最初のレンダー終了後だけ走る
useEffect(() => {
console.log("Hi");
}, []);
// fooに変更がある度に走る
useEffect(() => {
console.log("Hi");
}, [foo]);
useEffectへ渡す関数がsetIntervalのような永続的な場合はメモリーリークを無くすため、下記のようにコンポーネントが消えるタイミングで関数を終了させる。
import React, { useState, useEffect } from "react";
function Basic() {
const [count, setCount] = useState(0);
const time = () => {
setCount((value) => value + 1);
};
useEffect(() => {
const interval = setInterval(time, 1000);
return () => {
clearInterval(interval); // component破棄時に走る
};
}, []);
return <>{count}</>;
}
export default Basic;
axiosとFetchAPI
JSONPlaceholderというテスト開発用に無料でデータを提供しているAPIサービスを使用して、axiosを使った方法とJavaScriptメソッドのFetchAPIの2通りのAPIの取得方法です。
axios
import React, { useState, useEffect } from "react";
import axios from "axios";
const Basic = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
axios.get("https://jsonplaceholder.typicode.com/Users").then((res) => {
setUsers(res.data);
});
}, []);
return (
<>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
};
export default Basic;
import React, { useState, useEffect } from "react";
const Basic = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch(`https://jsonplaceholder.typicode.com/Users`)
.then((res) => res.json())
.then((data) => {
setUsers(data);
});
}, []);
return (
<>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
};
export default Basic;
asios
JSONPlaceholderというテスト開発用に無料でデータを提供しているAPIサービスから、axiosを使ったAPIデータを取得します。
axiosで受け取ったデータをUsersにsetして、そのUsersをmapで一覧表示します。
import React, { useState, useEffect } from "react";
import axios from "axios";
const Basic = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
axios.get("https://jsonplaceholder.typicode.com/Users").then((res) => {
setUsers(res.data);
});
}, []);
return (
<>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
};
export default Basic;
次はJavaScriptメソッドのFetchAPIを使用した方法です。
axiosとは違い、HTMLの形式でデータが取得されるのでjsonメソッドで変換しています。
import React, { useState, useEffect } from "react";
const Basic = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch(`https://jsonplaceholder.typicode.com/Users`)
.then((res) => res.json())
.then((data) => {
setUsers(data);
});
}, []);
return (
<>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
};
export default Basic;