「Reactではじめるフロントエンド開発入門」の感想・備忘録1

スポンサーリンク

Kindle本「Reactではじめるフロントエンド開発入門」のまとめ。

点数

83点

感想

とてもわかりやすかった。
create-react-appを使わずに、webpackの設定を手動で行っている点が勉強になった。
npmのパッケージ名に古いものがあったが、それ以外は最新の内容(webpackバージョン4についての記述もあり)になっていたのがよかった。
Jestの説明がかなり雑に書かれていたのが残念。

基本

プロジェクト生成

以下の3つの方法があるが、仕組みを理解するためには1か2がよい。
書籍内では2のyarnが使われていた。
yarnはnpmと同じpackage.jsonが使えるため、同一プロジェクトでnpm or yarnで固定する必要はない。

  1. npmを使う
  2. yarnを使う
  3. 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をバンドルする

  1. npmのcss-loaderとstyle-loaderが必要なのでインストール。
yarn add css-loader style-loader --dev
cd src && mkdir css && cd css && touch app.css
  1. 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

  1. app.jsにimport文を追加

import styles from "./css/app.css";

  1. index.htmlにidやclassName属性を追加し、app.cssにスタイルを定義

<h2 className="title">おはよう</h2>

classやforはJavaScriptの予約語なのでclassName, htmlForとする
tabindexやreadonlyはキャメルケースにしてtabIndex, readOnlyとする

webpack-dev-serverの導入

  1. インストール
    yarn add webpack-dev-server --dev
  2. webpack.config.jsにdevServerを追加
    devServer: {
      contentBase: './dist',
      port: 8080
    }
  3. npx webpack-dev-serverで実行
    (またはpackage.jsonのscriptに”start”: “webpack-dev-server”を追加してyarn startとする)
  4. 、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として渡されたメソッドをコールしている。

コメント