Kindle本「Reactではじめるフロントエンド開発入門」のまとめ。
点数
83点
感想
とてもわかりやすかった。
create-react-appを使わずに、webpackの設定を手動で行っている点が勉強になった。
npmのパッケージ名に古いものがあったが、それ以外は最新の内容(webpackバージョン4についての記述もあり)になっていたのがよかった。
Jestの説明がかなり雑に書かれていたのが残念。
基本
プロジェクト生成
以下の3つの方法があるが、仕組みを理解するためには1か2がよい。
書籍内では2のyarnが使われていた。
yarnはnpmと同じpackage.jsonが使えるため、同一プロジェクトでnpm or yarnで固定する必要はない。
- npmを使う
- yarnを使う
- create-react-appを使う
yarnでプロジェクト生成
brew install yarn
mkdir FirstApp && cd FirstApp
yarn init
yarn add babel-loader @babel/core @babel/preset-env @babel/preset-react --dev
# 書籍ではbabel-core, babel-preser-es2015, babel-preset-reactになっているが古い
# @babel/preset-reactはJSXをトランスパイルするのに必要
yarn add webpack webpack-cli react react-dom --dev
# yarnの--dev(または-D)はnpmの--save-devと同じ意味
mkdir src && cd src && touch app.js
cd ../ && mkdir dist && cd dist && touch index.html
cd ../ && touch webpack.config.js
# npx webpackで実行
# またはpackage.jsonのscriptに"build": "webpack"を追加してyarn buildとする
<!doctype html>
<html>
<head>
<meta charset="UTF8"/>
</head>
<body>
<div id="app"></div>
</body>
<script src="app.js"></script>
</html>
const webpack = require('webpack');
const path = require('path');
const config = {
entry: './src/app.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'app.js'
},
module: {
rules:[{
test: /\.js$/,
exclude: path.resolve(__dirname, 'node_modules'),
loader: 'babel-loader',
query:{
presets: ['@babel/preset-env', '@babel/preset-react']
}
}]
}
};
module.exports = config;
import React from "react";
import ReactDOM from "react-dom";
export default class App extends React.Component {
render(){
return(
<h1>Hello World</h1>
)
}
}
ReactDOM.render(
<App />,
document.getElementById("app")
);
CSSをバンドルする
- npmのcss-loaderとstyle-loaderが必要なのでインストール。
yarn add css-loader style-loader --dev
cd src && mkdir css && cd css && touch app.css
- webpack.config.jsのmodule.rulesにルールを追加
{
test: /.css$/,
exclude: path.resolve(__dirname, 'node_modules'),
loader: ['style-loader', 'css-loader']
}
※ style-loaderとcss-loaderの順番も重要。
(後ろのcss-loaderから実行され、css-loaderの処理後にstyle-loaderが実行される必要があるため)
※ loaderではなくuseとする書き方もある。
https://qiita.com/soarflat/items/28bf799f7e0335b68186
- app.jsにimport文を追加
import styles from "./css/app.css";
- index.htmlにidやclassName属性を追加し、app.cssにスタイルを定義
<h2 className="title">おはよう</h2>
※ classやforはJavaScriptの予約語なのでclassName, htmlForとする
※ tabindexやreadonlyはキャメルケースにしてtabIndex, readOnlyとする
webpack-dev-serverの導入
- インストール
yarn add webpack-dev-server --dev
- webpack.config.jsにdevServerを追加
devServer: {
contentBase: './dist',
port: 8080
}
npx webpack-dev-server
で実行
(またはpackage.jsonのscriptに”start”: “webpack-dev-server”を追加してyarn startとする)- 、http://localhost:8080で確認
コンポーネント
コンポーネントの最小単位
Reactは1つ以上のJSXを返す関数をコンポーネントと認識する。
const Welcome = (props) => {
return <h1>Hello, {props.name}</h1>;
};
クラスで定義することもでき、こちらの方が一般的である。
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
イベントハンドラの定義
以下のようにメソッドを定義し、this.メソッド名
でイベントハンドラを登録する。
constructor() {
super();
this.handleHogeClick = this.handleHogeClick.bind(this);
}
handleHogeClick(e) {
console.log(e.target.textContent);
}
render() {
return <button onClick={this.handleHogeClick}>hoge</button>
}
this.handleHogeClick = this.handleHogeClick.bind(this);
はthisの束縛のために必要。
bindしないと、イベントハンドラ内でthisはイベントソースを指してしまう。
クラス内のメンバにthisでアクセスできるようにするためには、コンストラクタでイベントハンドラに対してbind(this)を実行しておく必要がある。
ステート
リアクティブ にしたい変数はstateオブジェクトのプロパティとして定義する必要がある。
値の設定はthis.setState()
で行う。this.setState()
は指定したプロパティだけが更新される。
constructor() {
super();
this.state = {
num: 0,
}
this.handleIncrementClick = this.handleIncrementClick.bind(this);
}
handleIncrementClick() {
this.setState({num: this.state.num + 1})
}
render() {
return (
<div>
<h2>{this.state.num}</h2>
<button onClick={this.handleIncrementClick}>Increment</button>
</div>
);
}
入力値の受け取り
inputやtextareaなどの入力値はステートで管理する必要がある。
(イベントハンドラ内でthis.setState()
を使ってevent.target.value
を保存する)
そのため、入力値を取得したい要素全てに対して、対応するstateのプロパティを用意しなければならない。
配列の表示
mapメソッドのコールバック関数でJSXをreturnする。
ユニークとなるkey属性をつけることが推奨されている。
{this.state.todos.map((todo)=>{
return <p key={todo}>{todo}</p>
})}
ステートの配列への値の追加はconcatメソッドを使う。this.setState({todos: this.state.todos.concat(this.state.input)});
※ pushの戻り値は要素数、concatの戻り値は配列、なのでconcatを使う
ライフサイクル
componentDidMountがよく使われる。
(アプリ起動時の処理、ネットワークやローカルストレージからのデータ取得処理など)
ex)
componentDidMount内でsetInterval()
を使ってタイマーを設定
componentWillUnmount内でclearInterval()
を使ってタイマーを解除
※ ReactDOM.unmountComponentAtNode(document.getElementById('app'));
で手動でアンマウントすることができる
ステートの受け渡し
Reactでは親コンポーネントのstateを子コンポーネントのpropsとして渡すことで、子コンポーネントを自動更新させることが多い。
親コンポーネントのstateだけ管理しておけば、子コンポーネントの状態は考えなくてよい。UIの状態を1箇所で管理できるというのが、Reactの魅力の1つである。
また、メソッドをpropsとして渡すことで、子コンポーネントから親コンポーネントのメソッドを呼び出すことが可能である。
stateを更新するメソッドをpropsと渡して、子から親のStateを更新することがよく行われる。
親コンポーネント
import React from "react";
import ReactDOM from "react-dom";
import Todo from "./todo";
import styles from "./css/app.css";
class App extends React.Component {
constructor() {
super();
this.state = {
input: '',
todos: []
};
this.handleAddButtonClick = this.handleAddButtonClick.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.removeTodo = this.removeTodo.bind(this);
}
handleAddButtonClick() {
this.setState({todos: this.state.todos.concat(this.state.input), input: ''});
}
removeTodo(i) {
let todos = this.state.todos;
todos.splice(i, 1);
this.setState({todos: todos});
}
handleInputChange(e) {
this.setState({input: e.target.value})
}
render() {
return (
<div>
<input type="text" onChange={this.handleInputChange} value={this.state.input}/>
<button onClick={this.handleAddButtonClick}>追加</button>
<Todo todos={this.state.todos} removeTodo={this.removeTodo}/>
</div>
);
}
}
ReactDOM.render(
<div>
<App name={"hoge"}/>
</div>,
document.getElementById("app")
);
<Todo todos={this.state.todos} removeTodo={this.removeTodo}/>
で親コンポーネントのstateを子コンポーネントのpropsとして渡している。
子コンポーネント
import React from "react";
export default class Todo extends React.Component {
render() {
return (
<ul>
{this.props.todos.map((todo, i) => {
<li ket="{i}" onClick={() => this.props.removeTodo(i)}>{todo}</li>
})}
</ul>
)
}
}
onClick={() => this.props.removeTodo(i)}
で、親コンポーネントからpropsとして渡されたメソッドをコールしている。
コメント