ReactのDocsのMain Concepts部分を訳してみました。Versionは16.4.2。
Main concepts
Hello world
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
Introducing JSX (JSX入門)
JSXに式を埋め込む。
import React from 'react';
import ReactDOM from 'react-dom';
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: 'Harper',
lastName: 'Perez'
};
//// element ////
const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);
ReactDOM.render(
element,
document.getElementById('root')
);
Rendering Elements
Reactの要素(element)は不変(immutable)である。一度作成したら属性などを変更できない。ここまでの知識では,UIを更新するためには新しい要素を作ってReactDOM.render()
に渡す必要がある。
import React from 'react';
import ReactDOM from 'react-dom';
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(element, document.getElementById('root'));
}
setInterval(tick, 1000);
Components and Props
コンポーネントを定義するもっとも簡単な方法は,JavaScriptの関数を書くことである。
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
ES6クラスを使ってコンポーネントを定義することもできる。
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
functionを使った場合
import React from 'react';
import ReactDOM from 'react-dom';
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
classを使った場合
import React from 'react';
import ReactDOM from 'react-dom';
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
//// Passing a property here ////
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
上の例ではWelcome
コンポーネントに対して{name: 'Sara'}
をプロパティとして渡す。
コンポーネントから別のコンポーネントを参照することもできる。次の例では,Welcome
コンポーネントを複数回描画するApp
コンポーネントを作ることができる。
import React from 'react';
import ReactDOM from 'react-dom';
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
ReactDOM.render(
//// App ////
<App />,
document.getElementById('root')
);
State and Lifecycle (状態とライフサイクル)
import React from 'react';
import ReactDOM from 'react-dom';
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
上の例では,the fact that the Clock sets up a timer and updates the UI every second should be an implementation detail of the Clockという要件を満たしていない。
FunctionをClassに変換する
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
Classにlocal stateを追加する
import React from 'react';
import ReactDOM from 'react-dom';
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
Adding Lifecycle Methods to a Class
Clock
が最初にDOMを描画したら,タイマーをセットする。これをマウントする,という。
Clock
によって生成されたDOMが削除されたら,タイマーをクリアする。これをアンマウントする,という
import React from 'react';
import ReactDOM from 'react-dom';
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
//// componentDidMound ////
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
//// componentWillUnmount ////
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
//// update local state ////
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
Handling Events
- ReactイベントはcamelCaseで書く
Toggle
コンポーネントが"ON"/"OFF"を交互に描画する。
import React from 'react';
import ReactDOM from 'react-dom';
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isToggleOn: true };
// This binding is necessary to make `this` work in the callback
//// Binding ////
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
JavaScriptでは,デフォルトではクラスメソッドはバインディングされない。this.handleClick
とonClick
をバインドするのを忘れると,コールバックが呼ばれたとき,this
がundefined
になる。
Conditional Rendering
import React from 'react';
import ReactDOM from 'react-dom';
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
//// condition ////
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
ReactDOM.render(
<Greeting isLoggedIn={false} />,
document.getElementById('root')
);
要素変数
要素(element)に変数を使うことができる。これは条件に応じて一部だけ再描画するのを助ける。
import React from 'react';
import ReactDOM from 'react-dom';
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
//// Greeting(message) component ////
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
//// Login button component ////
function LoginButton(props) {
return (
<button onClick={props.onClick}>
Login
</button>
);
}
//// Logout button component ////
function LogoutButton(props) {
return (
<button onClick={props.onClick}>
Logout
</button>
);
}
class LoginControl extends React.Component {
constructor(props) {
super(props);
//// Binding ////
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
//// state ////
this.state = { isLoggedIn: false };
}
//// Handler sets state ////
handleLoginClick() {
this.setState({ isLoggedIn: true });
}
handleLogoutClick() {
this.setState({ isLoggedIn: false });
}
render() {
const isLoggedIn = this.state.isLoggedIn;
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
}
ReactDOM.render(
<LoginControl />,
document.getElementById('root')
);
インラインIf-Else
JSXに式を埋め込むことができる。
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
</div>
);
}
コンポーネントの描画を防ぐ
コンポーネントを描画したくない場合は,nullを返す。
function WarningBanner(props) {
//// Return null if warn is null ////
if (!props.warn) {
return null;
}
return (
<div className="warning">
Warning!
</div>
);
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {showWarning: true};
this.handleToggleClick = this.handleToggleClick.bind(this);
}
handleToggleClick() {
this.setState(prevState => ({
showWarning: !prevState.showWarning
}));
}
render() {
return (
<div>
//// Warning banner ////
<WarningBanner warn={this.state.showWarning} />
<button onClick={this.handleToggleClick}>
{this.state.showWarning ? 'Hide' : 'Show'}
</button>
</div>
);
}
}
ReactDOM.render(
<Page />,
document.getElementById('root')
);
Lists and Keys
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);
結果は
[2, 4, 6, 8, 10]
Basic List Component
import React from 'react';
import ReactDOM from 'react-dom';
function NumberList(props) {
const numbers = props.numbers;
//// build listItems ////
const listItems = numbers.map((number) =>
<li>{number}</li>
);
return (
//// return list ////
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
Keys
For example, if you extract a ListItem component, you should keep the key on the elements in the array rather than on the
element in the ListItem itself.import React from 'react';
import ReactDOM from 'react-dom';
function ListItem(props) {
// Correct! There is no need to specify the key here:
//// valueのみを返す ////
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
//// list Items ////
const listItems = numbers.map((number) =>
// Correct! Key should be specified inside the array.
<ListItem key={number.toString()} value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
Forms
import React from 'react';
import ReactDOM from 'react-dom';
class NameForm extends React.Component {
constructor(props) {
super(props);
//// value ////
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
//// Event handler for text, setting state ////
handleChange(event) {
this.setState({value: event.target.value});
}
//// Event handler for submit, show alert ////
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
//// Call event handler every keystoroke ////
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
ReactDOM.render(
<NameForm />,
document.getElementById('root')
);
Lifting State Up
複数のコンポーネント間で,同様の変更を反映したい場合がある。その場合は,共通の先祖(common ancestor)と状態(state)を共有することを推奨する。
1.与えられた温度で水が沸騰するか計算する
2.まず,摂氏をプロパティとして受け付けるコンポーネントを作る
3.次に,華氏を入力として受け付ける
4.温度を変化する関数を作る(摂氏を華氏に。華氏を摂氏に)
5.それぞれの入力を同期させる必要がある。摂氏を更新したら,華氏も反映される。
Reactでは共通の先祖に状態を持ち上げることで状態の共有を実現する。
これを"Lifting state up"と呼ぶ。
TemperatureInput
から得られるローカルステートを取り除き,代わりにCalculator
に移動させる。
import React from 'react';
import ReactDOM from 'react-dom';
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
//// 華氏to摂氏 ////
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
//// 摂氏to華氏 ////
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
//// 沸騰する/しない /////
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
//// 入力フォーム ////
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
//// プロパティで渡されたハンドラ
//// handleCelsiusChangeまたはhandleFahrenheitChangeを呼ぶ ////
handleChange(e) {
this.props.onTemperatureChange(e.target.value);
}
render() {
//// 渡された温度 ////
const temperature = this.props.temperature;
//// 渡された単位 ////
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature} onChange={this.handleChange} />
</fieldset>
);
}
}
/// 計算クラス
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'};
}
//// 上位のイベントハンドラー ////
handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature});
}
//// 上位のイベントハンドラー ////
handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature});
}
render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
//// プロパティとしてhandleCelsiusChangeを渡す ////
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />
//// プロパティとしてhandleFahrenheitChangeを渡す ////
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}
ReactDOM.render(
<Calculator />,
document.getElementById('root')
);
Composition vs Inheritance (コンポジットと継承)
Reactでは継承の代わりにコンポジション(composition)を使うことを推奨する。
Reactを初めて使う開発者は継承を求めるが,それをコンポジションでどのように解決するか示す。
-
child
プロパティを使って子エレメントを渡す
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
//// child: Welcome Thank you for.... ////
{props.children}
</div>
);
}
function WelcomeDialog() {
return (
<FancyBorder color="yellow">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
ReactDOM.render(
<WelcomeDialog />,
document.getElementById('root')
);
Thinking in React
Step1: UIをコンポーネント階層に分解する
- FilterableProductTable (全体を包含する)
- SearchBar (ユーザーの入力を受け付ける)
- ProductTable (ユーザー有力に基づきデータコレクションを表示する)
- ProductCategoryRow (各カテゴリーの先頭)
- ProductRow (各プロダクト)
Step2: 静的バージョンを作る
インタラクティブでないUIを作ってみる。この段階ではステートを使わないこと。
Step 3: Identify The Minimal (but complete) Representation Of UI State
何がステートか見ていこう。各データについて次の3つの質問に答えるだけ。
1. 親コンポーネントからプロパティを経由して渡されるか?もしそうであれば,それはステートではない。
2. 時間の経過にともない変化しないか?もしそうであれば,それはステートではない。
3. それは,ほかの状態の変化やプロパティの値から計算できるか?もしそうであれば,それはステートではない。