React開発の基礎知識
Reactとは
Reactはユーザーインターフェースを構築するためのJavaScriptライブラリです。2013年にFacebookによってオープンソースとして公開され、現在では多くの大手企業で採用されています。
Reactの主な特徴:
- 宣言的プログラミング:UI = f(state) の考え方
- コンポーネントベース開発:複雑なUIを小さなコンポーネントに分割
- クロスプラットフォーム対応:Web、React Native(モバイル)、React VR(VR)など
Hello Reactの基本例
従来のJavaScript実装
<h2 id="text"></h2>
<button id="btn">テキスト変更</button>
<script>
// 命令型プログラミングの例
let message = 'こんにちは世界'
let textElement = document.getElementById("text")
let btnElement = document.getElementById("btn")
textElement.innerHTML = message
btnElement.onclick = function () {
message = 'こんにちはReact'
textElement.innerHTML = message
}
</script>
Reactを使用した実装
<div id="app"></div>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
let greeting = "こんにちは世界"
function changeText() {
greeting = 'こんにちはReact'
renderApp()
}
function renderApp() {
ReactDOM.render(
<div>
<h2>{greeting}</h2>
<button onClick={changeText}>テキスト変更</button>
</div>,
document.getElementById('app')
)
}
renderApp()
</script>
コンポーネントを使用した実装
<div id="app"></div>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
class WelcomeApp extends React.Component {
constructor() {
super();
this.state = {
message: "こんにちは世界"
}
}
render() {
return (
<div>
<h2>{this.state.message}</h2>
<button onClick={this.updateMessage.bind(this)}>テキスト変更</button>
</div>
)
}
updateMessage() {
this.setState({
message: 'こんにちはReact'
})
}
}
ReactDOM.render(<WelcomeApp/>, document.getElementById("app"))
</script>
JavaScriptコア構文
ES6のクラス
class User {
constructor(name, age) {
this.name = name
this.age = age
}
introduce() {
console.log(`名前:${this.name};年齢:${this.age}`)
}
}
const user = new User('田中太郎', 25);
user.introduce()
クラスの継承
class Person {
constructor(name) {
this.name = name
}
walk() {
console.log('親クラスのメソッド')
}
}
class Teacher extends Person {
constructor(name, subject) {
super(name);
this.subject = subject
}
teach() {
console.log(`${this.name}先生は${this.subject}を教えています`)
}
}
const teacher = new Teacher('佐藤花子', '数学');
teacher.walk()
teacher.teach()
JSXコア構文
映画リストの例
<div id="app"></div>
<script src="./js/react.development.js" crossorigin></script>
<script src="./js/react-dom.development.js" crossorigin></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class MovieList extends React.Component {
constructor() {
super();
this.state = {
title: "人気映画",
movies: ['千と千尋の神隠し', 'もののけ姫', '風の谷のナウシカ']
}
}
render() {
const movieItems = this.state.movies.reduce((list, film) => {
list.push(<li>{film}</li>)
return list
}, [])
return (
<div>
<div>
<h2>{this.state.title}リスト1</h2>
<ul>{movieItems}</ul>
</div>
<div>
<h2>{this.state.title}リスト2</h2>
<ul>{
this.state.movies.map(film => <li>{film}</li>)
}</ul>
</div>
</div>
)
}
}
ReactDOM.render(<MovieList/>, document.getElementById("app"));
</script>
カウンターアプリの例
<div id="app"></div>
<script src="./js/react.development.js" crossorigin></script>
<script src="./js/react-dom.development.js" crossorigin></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class CounterApp extends React.Component {
constructor() {
super();
this.state = {
count: 0
}
}
render() {
return (
<div>
<div>カウント: {this.state.count}</div>
<button onClick={this.increment.bind(this)}>増加</button>
<button onClick={this.decrement.bind(this)}>減少</button>
</div>
)
}
increment() {
this.setState({
count: this.state.count + 1
})
}
decrement() {
this.setState({
count: this.state.count - 1
})
}
}
ReactDOM.render(<CounterApp/>, document.getElementById("app"));
</script>
JSXの基本
JSXはJavaScriptの構文拡張であり、React.createElement()関数の糖衣構文です。JSXを使用すると、JavaScriptコード内にHTMLのような構造を記述できます。
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
const element = <div>こんにちは世界</div>
ReactDOM.render(element, document.getElementById("app"));
</script>
ReactがJSXを選択した理由
Reactは、レンダリングロジックが本質的に他のUIロジックと密接に結びついていると考えています(All in JS)。これらは不可分であるため、Reactはマークアップを別のファイルに分離するのではなく、コンポーネントとしてまとめています。
JSXの記述規則:
- 最上位にはルートタグが1つだけ必要(通常はdivまたはFragment)
- JSXを括弧()で囲むと可読性が向上し、改行も可能
- 自己終了タグは必ず/>で終わる
JSXでのコメント
<script type="text/babel">
/**
* ドキュメントコメント
*/
class CommentExample extends React.Component {
constructor() {
super();
/*
// 複数行コメント
this.state = {
}*/
}
// 単一行コメント
render() {
return (
<div>
{/* JSXのコメント */}
</div>
)
}
}
ReactDOM.render(<CommentExample/>, document.getElementById("app"));
</script>
JSXへのデータ埋め込み
<div id="app"></div>
<script src="./js/react.development.js" crossorigin></script>
<script src="./js/react-dom.development.js" crossorigin></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class DataDisplay extends React.Component {
constructor() {
super();
this.state = {
// 1、{}内で正常に表示できる内容
name: '山田太郎',// 文字列
age: 30,// 数値
hobbies: ['読書', '旅行', '料理'],// 配列
// 2、{}内で表示できない(無視される)内容
test1: null,// null
test2: undefined,// undefined
test3: true,// 真偽値
flag: true,
// 3、オブジェクトはJSXの子要素として使用できない
friend: {
name: '鈴木一郎',
age: 28
}
}
}
render() {
return (
<div>
<div>{this.state.name}</div>
<div>{this.state.age}</div>
<div>{this.state.hobbies}</div>
<div>{this.state.test1}</div>
<div>{String(this.state.test1)}</div>
<div>{this.state.test2}</div>
<div>{this.state.test2 + ''}</div>
<div>{this.state.test3}</div>
<div>{this.state.test3.toString()}</div>
<div>{this.state.flag ? 'ログイン中' : null}</div>
<div>{this.state.flag && 'ようこそ'}</div>
{/* <div>{this.state.friend}</div> */}
</div>
)
}
}
ReactDOM.render(<DataDisplay/>, document.getElementById("app"));
</script>
JSXへの式の埋め込み
<div id="app"></div>
<script src="./js/react.development.js" crossorigin></script>
<script src="./js/react-dom.development.js" crossorigin></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class ExpressionExample extends React.Component {
constructor() {
super();
this.state = {
firstName: '田中',
lastName: '太郎',
isLoggedIn: true
}
}
render() {
const {firstName, lastName, isLoggedIn} = this.state
return (
<div>
{/* 1、演算子式 */}
<div>{firstName + ' ' + lastName}</div>
<div>{20 * 50}</div>
{/* 2、三項演算子 */}
<div>{isLoggedIn ? 'お帰りなさい~' : 'ログインしてください~'}</div>
{/* 3、関数呼び出し */}
<div>{this.getFullName()}</div>
</div>
)
}
getFullName() {
return this.state.firstName + ' ' + this.state.lastName
}
}
ReactDOM.render(<ExpressionExample/>, document.getElementById("app"));
</script>
JSXでの属性バインディング
<div id="app"></div>
<script src="./js/react.development.js" crossorigin></script>
<script src="./js/react-dom.development.js" crossorigin></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
function getImageUrl(url, size) {
return url + `?param=${size}x${size}`
}
class AttributeBinding extends React.Component {
constructor() {
super();
this.state = {
title: "タイトル",
imageUrl: "https://example.com/image.jpg",
link: "https://www.google.com",
isActive: true
}
}
render() {
const {title, imageUrl, link, isActive} = this.state
return (
<div>
{/* 1、通常の属性バインディング */}
<h2 title={title}>見出し</h2>
<img src={getImageUrl(imageUrl, 140)} alt="画像"/>
<a href={link} target="_blank">Google検索</a>
{/* 2、クラス名のバインディング */}
<div className="box title">div要素</div>
<div className={"box title " + (isActive ? 'active' : "")}>div要素2</div>
<label htmlFor="inputId"></label>
{/* 3、スタイルのバインディング */}
<div style={{color: 'red', fontSize: '50px'}}>スタイル属性をバインドしたdiv</div>
</div>
)
}
}
ReactDOM.render(<AttributeBinding/>, document.getElementById("app"));
</script>
JSXでのイベントバインディングとthisの処理
<div id="app"></div>
<script src="./js/react.development.js" crossorigin></script>
<script src="./js/react-dom.development.js" crossorigin></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class EventHandling extends React.Component {
constructor() {
super();
this.state = {
message: "こんにちは",
counter: 100
}
this.handleClick = this.handleClick.bind(this)
}
render() {
return (
<div>
{/* 1、bindでthisをバインド(明示的バインディング) */}
<button onClick={this.handleClick}>ボタン1</button>
{/* 2、アロー関数を使用して関数を定義 */}
<button onClick={this.increment}>増加</button>
{/* 3、アロー関数を直接渡す(推奨) */}
<button onClick={() => {
this.decrement('田中')
}}>減少
</button>
</div>
)
}
handleClick() {
console.log(this.state.message)
}
// アロー関数はthisをバインドしない
// ES6のクラスフィールド構文
increment = () => {
console.log(this.state.counter)
}
decrement(name) {
console.log(this.state.counter, name)
}
}
ReactDOM.render(<EventHandling/>, document.getElementById("app"));
</script>
JSXでのイベントバインディング - パラメータ渡し
<div id="app"></div>
<script src="./js/react.development.js" crossorigin></script>
<script src="./js/react-dom.development.js" crossorigin></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class ParameterPassing extends React.Component {
constructor() {
super();
this.state = {
users: ['田中太郎', '佐藤花子', '山田次郎']
}
this.handleButtonClick = this.handleButtonClick.bind(this)
}
render() {
return (
<div>
<button onClick={this.handleButtonClick}>ボタン1</button>
<ul>{
this.state.users.map((user, index) => {
return (
<li
className="item"
title="リスト項目"
onClick={e => {
this.handleItemClick(user, index, e)
}}>
{user}
</li>
)
})
}</ul>
</div>
)
}
handleButtonClick(event) {
console.log('ボタンがクリックされました', event)
}
handleItemClick(user, index, event) {
console.log('リスト項目がクリックされました', user, index, event)
}
}
ReactDOM.render(<ParameterPassing/>, document.getElementById("app"));
</script>
条件付きレンダリング
<div id="app"></div>
<script src="./js/react.development.js" crossorigin></script>
<script src="./js/react-dom.development.js" crossorigin></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class ConditionalRendering extends React.Component {
constructor() {
super();
this.state = {
isLoggedIn: true
}
}
render() {
const {isLoggedIn} = this.state
// 1、if文による条件分岐:ロジックが複雑な場合
let welcomeMessage = null
if (isLoggedIn) {
welcomeMessage = <h2>お帰りなさい~</h2>
} else {
welcomeMessage = <h2>ログインしてください~</h2>
}
return (
<div>
{welcomeMessage}
{/* 2、三項演算子 */}
<button onClick={e => this.toggleLogin()}>{isLoggedIn ? 'ログアウト' : 'ログイン'}</button>
<hr/>
<h2>{isLoggedIn ? 'こんにちは、田中さん' : null}</h2>
{/* 3、論理積(&&)演算子 */}
<h2>{isLoggedIn && 'こんにちは、佐藤さん'}</h2>
{isLoggedIn && <h2>こんにちは、山田さん</h2>}
</div>
)
}
toggleLogin() {
this.setState({
isLoggedIn: !this.state.isLoggedIn
})
}
}
ReactDOM.render(<ConditionalRendering/>, document.getElementById("app"));
</script>
条件付きレンダリング - v-show効果
<div id="app"></div>
<script src="./js/react.development.js" crossorigin></script>
<script src="./js/react-dom.development.js" crossorigin></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class VisibilityToggle extends React.Component {
constructor() {
super();
this.state = {
isVisible: true
}
}
render() {
const {isVisible} = this.state
const displayStyle = isVisible ? 'block' : 'none'
return (
<div>
<button onClick={e => this.toggleVisibility()}>
{isVisible ? '非表示' : '表示'}
</button>
<h2 style={{display: displayStyle}}>こんにちは、田中さん</h2>
</div>
)
}
toggleVisibility() {
this.setState({
isVisible: !this.state.isVisible
})
}
}
ReactDOM.render(<VisibilityToggle/>, document.getElementById("app"));
</script>
リストレンダリング
<div id="app"></div>
<script src="./js/react.development.js" crossorigin></script>
<script src="./js/react-dom.development.js" crossorigin></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class ListRendering extends React.Component {
constructor() {
super();
this.state = {
names: ['田中太郎', '佐藤花子', '山田次郎'],
numbers: [5, 8, 3, 4, 9, 2, 7, 6, 1]
}
}
render() {
return (
<div>
<h2>名前リスト(マッピング)</h2>
<ul>{this.state.names.map(name => <li key={name}>{name}</li>)}</ul>
<h2>数字リスト(フィルタリング)</h2>
<ul>{this.state.numbers.filter(num => num >= 5).map(num => <li key={num}>{num}</li>)}</ul>
<h2>数字リスト(スライス)</h2>
<ul>{this.state.numbers.slice(0, 5).map(num => <li key={num}>{num}</li>)}</ul>
</div>
)
}
}
ReactDOM.render(<ListRendering/>, document.getElementById("app"));
</script>
JSXの本質
JSXの本質
<div id="app"></div>
<script src="./js/react.development.js" crossorigin></script>
<script src="./js/react-dom.development.js" crossorigin></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
/**
* 1、JSXはReact.createElement(component, props, ...children)関数の糖衣構文です
* - JSX -> babel -> React.createElement()
* 2、Babel公式サイトで変換プロセスを確認できます
* - https://babeljs.io/repl/#?presets=react
*/
const message1 = <h2>こんにちは世界</h2>
const message2 = React.createElement("h2", null, "こんにちは世界")
ReactDOM.render(message2, document.getElementById("app"));
</script>
仮想DOMの作成プロセス
<div id="app"></div>
<script src="./js/react.development.js" crossorigin></script>
<script src="./js/react-dom.development.js" crossorigin></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class VirtualDOMDemo extends React.Component {
constructor() {
super();
}
/**
* 1、React.createElementを通じて最終的にReactElementオブジェクトが作成されます
* 2、このReactElementオブジェクトの役割は何でしょうか?Reactがなぜこれを作成するのでしょうか?
* - ReactはReactElementオブジェクトを使ってJavaScriptのオブジェクトツリーを構成するため
* - このJavaScriptのオブジェクトツリーが仮想DOM(virtual dom)です
* 3、ReactElementのツリー構造をどのように確認するのでしょうか?
* - JSXの戻り値をコンソールに出力して確認できます
* 4、ReactElementが最終的に形成するツリー構造が仮想DOMです
* 5、jsx-仮想dom-実際のdom
* - jsxコード-ReactElementオブジェクト-実際のdom
* 6、なぜ仮想DOMを使用し、直接実際のDOMを操作しないのでしょうか?
* - 状態の変化を追跡するのが困難:従来の開発モデルでは、状態の変化を追跡するのが難しく、アプリケーションのデバッグが不便
* - 実際のDOM操作のパフォーマンスが低い:従来の開発モデルでは頻繁なDOM操作が行われ、これは非常にパフォーマンスが低い
* ~ document.createElement自体が非常に複雑なオブジェクトを作成する
* ~ DOM操作はブラウザのリフローと再描画を引き起こすため、開発では頻繁なDOM操作を避けるべき
* 7、宣言的プログラミング
* - 仮想DOMはプログラミング理念です
* ~ この理念では、UIは理想的または仮想化された方法でメモリに保持され、比較的単純なJavaScriptオブジェクトです
* ~ ReactDOM.renderを通じて仮想DOMと実際のDOMを同期させることができます。このプロセスを調整(Reconciliation)と呼びます
* - このプログラミング方法はReactに宣言的なAPIを提供します
* ~ UIをどのような状態にしたいかだけをReactに伝えればよい
* ~ ReactがDOMとこれらの状態が一致することを保証します
* ~ 手動でのDOM操作、属性操作、イベント処理から解放されます
*/
render() {
const virtualTree = (
<div>
<h2>東京</h2>
<h2>大阪</h2>
<h2>京都</h2>
</div>
)
console.log(virtualTree)
return virtualTree
}
}
ReactDOM.render(<VirtualDOMDemo/>, document.getElementById("app"));
</script>
実践例:ショッピングカート
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Reactショッピングカート</title>
<style>
table {
border-collapse: collapse;
width: 100%;
}
td, th {
border: 1px solid #cccccc;
padding: 10px 16px;
text-align: left;
}
th {
background: #f2f2f2;
}
.quantity {
margin: 0 6px;
}
button {
padding: 5px 10px;
margin: 0 5px;
}
</style>
</head>
<body>
<div id="app"></div>
<script src="./js/react.development.js" crossorigin></script>
<script src="./js/react-dom.development.js" crossorigin></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class ShoppingCart extends React.Component {
constructor() {
super();
this.state = {
products: [
{
id: 1,
name: 'JavaScript入門',
date: '2022-01',
price: 2980,
quantity: 1
},
{
id: 2,
name: 'React実践ガイド',
date: '2022-02',
price: 3580,
quantity: 1
},
{
id: 3,
name: 'CSSデザインブック',
date: '2022-03',
price: 2580,
quantity: 1
},
{
id: 4,
name: 'Node.js徹底解説',
date: '2022-04',
price: 4280,
quantity: 1
}
]
}
}
render() {
return this.state.products.length ? (
<div>
<table>
<thead>
<tr>
<th>ID</th>
<th>商品名</th>
<th>出版日</th>
<th>価格</th>
<th>数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{this.state.products.map((item, index) => {
return (
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.name}</td>
<td>{item.date}</td>
<td>¥{item.price.toLocaleString()}</td>
<td>
<button
disabled={item.quantity <= 1}
onClick={() => this.updateQuantity(index, -1)}
>-</button>
<span className="quantity">{item.quantity}</span>
<button onClick={() => this.updateQuantity(index, 1)}>+</button>
</td>
<td>
<button onClick={e => this.removeProduct(e, index)}>削除</button>
</td>
</tr>
)
})}
</tbody>
</table>
<h3>合計金額:¥{this.calculateTotal().toLocaleString()}</h3>
</div>
) : (
<h2>ショッピングカートは空です~</h2>
)
}
updateQuantity(index, change) {
const updatedProducts = [...this.state.products]
updatedProducts[index].quantity += change
this.setState({
products: updatedProducts
})
}
removeProduct(e, index) {
e.persist()
// Reactの設計原則:stateのデータは不変
this.setState({
products: this.state.products.filter((item, idx) => idx !== index)
})
}
calculateTotal() {
const totalPrice = this.state.products.reduce((total, item) => {
return total += item.price * item.quantity
}, 0)
return totalPrice
}
}
ReactDOM.render(<ShoppingCart/>, document.getElementById("app"));
</script>
</body>
</html>