この記事は「株式会社オープンストリーム "小ネタ" Advent Calendar 2020」の18日目の記事です。
はじめに
Reactを学習して次はHooksを導入しよう…と少しハマったので、「どの手順で導入するか」「ハマった点」をまとめました。
環境
$ node --version
v14.15.0
{
...
"dependencies": {
"next": "10.0.3",
"react": "17.0.1",
"react-dom": "17.0.1"
},
"devDependencies": {
"@types/node": "^14.14.10",
"@types/react": "^17.0.0",
"typescript": "^4.1.2"
}
}
次のクラスコンポーネントで書いたReactのTSXを使います。元ネタはこちらです
import styles from "../styles/Home.module.css";
import todoStyles from "../styles/Todo.module.css";
import React, { Component } from "react";
type Props = {};
type State = {
todo: TodoItem[];
currentText: string;
};
type TodoItem = {
title: String;
};
class App extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
todo: [
{ title: "JavaScript覚える" },
{ title: "jQuery覚える" },
{ title: "ES2015覚える" },
{ title: "React覚える" },
],
currentText: "",
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event: React.ChangeEvent<HTMLInputElement>) {
this.setState({
currentText: event.target.value,
});
}
addTodo =()=> {
this.state.todo.push({
title: this.state.currentText
})
this.setState({
todo: this.state.todo,
currentText: ''+
})
}
deleteTodo = (i: number)=> {
this.state.todo.splice(i, 1)
this.setState({
todo: this.state.todo
})
}
render() {
return (
<div>
<h1>TODOアプリ</h1>
<nav>
<ul>{this.state.todo.map( (todo, i) => {
return <li key={i}><input type="button" value="☓" onClick={() => this.deleteTodo(i)}/> {todo.title}</li>
})}</ul>
<input type="text" value={this.state.currentText} onChange={this.handleChange}/> <input type="button" value="追加" onClick={this.addTodo} />
</div>
);
}
useState
からHooksをはじめる
次のステップでクラスで書いたコンポーネントをHooks使用した操作に書き換えることができます。
- クラスコンポーネントから関数コンポーネントにする
- Reactで使いたいStateを
useState
で宣言する
関数コンポーネントはアロー関数でも書くことができます。
export default function Home() {
...
};
const Home = () => {
...
};
export default Home;
次に、Stateを useState
で宣言します。 useState
をimportした後に 関数コンポーネントの中で 次のように記述します。
import React, { useState } from "react";
const Home = () => {
const [todoList, setTodoList] = useState(initialItems);
};
export default Home;
-
todoList
: 管理したいStateの名前 -
setTodoList
: Stateをセットするために新しく作る関数名 -
initialItems
: Stateの初期値
Reactのドキュメントでは プリミティブで簡単な形を初期値として与えていますが、Objectがたくさん入ったArrayでも対応します。
TypeScriptで独自の型を定義しても動作します。
import React, { useState } from "react";
type TodoItem = {
title: String;
};
const Home = () => {
const initialItems: TodoItem[] = [
{ title: "JavaScript覚える" },
{ title: "jQuery覚える" },
{ title: "ES2015覚える" },
{ title: "React覚える" },
];
const [todoList, setTodoList] = useState(initialItems);
// Reqctのドキュメントに載っているプリミティブな例
const [count, setCount] = useState(0);
...
値の読み取り
クラスコンポーネントは this.state.hoge
でStateの値を使うことができますが、Hooks(関数コンポーネント) になると this.state
を取り除いて使うことができます。
リストのStateを繰り返し処理する例でも this.state
を取り除いて使います。
{this.state.todoList.map((todo, i) => {
return (
...
);
})
}
{todoList.map((todo, i) => {
return (
...
);
})
}
値のセット
クラスコンポーネントは this.state.hoge
でStateを操作していましたが、Hooks(関数コンポーネント) では useState
で定義したセット用の関数を用います。
例えば、先ほど定義した操作用関数を onClick
に仕込むと次のようになります。
<input type="button" value="全消去" onClick={() => setTodoList([])} />
ただし、Array.splice()
など配列を操作する場合はこの方法だとうまくいきません。
Hooksで扱うStateは Array.splice()
などの変更が取れない
Hooksで提供されているState更新用関数では、操作によって値が異なった場合に画面を再描画して反映させています。
この値が異なっているかどうかの判定( Object.is
)の仕様により、配列そのものを操作する場合は 別の配列にコピーしてそれを操作、setする方法などで変更後のものは別のオブジェクトであると認識せる必要があります。
「Stateで使っている配列を操作して画面に反映させたい」といったことを考える場合は留意するポイントです。
const Home = () => {
const deleteTodo = (index: number) => {
const newTodoList = [...todoList];
newTodoList.splice(index, 1);
setTodoList(newTodoList);
};
return (
...
<input type="button" value="上から2番目を消す" onClick={() => deleteTodo(1)} />
);
};
export default Home;
次のように同じ配列に対して操作してsetしても、データ上では反映されますが再描画が発生せず正しく画面に反映されません(他のStateを操作すると反映される場合があります)
const deleteTodo = (index: number) => {
todoList.splice(index, 1);
setTodoList(todoList);
};
参考
(コードの元ネタ)
React で作る TODO アプリ前編 – React 入門 - to-R Media
https://www.to-r.net/media/react-tutorial13/
フックの導入 – React
https://ja.reactjs.org/docs/hooks-intro.html
5分でわかるReact Hooks - Qiita
https://qiita.com/Mitsuzara/items/98d1bc4a83265a764084
React Hooksで配列の要素を削除したあとの再描画について
https://www.ravness.com/posts/reactsplice
フック API リファレンス – React (state 更新の回避)
https://ja.reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update