React メモ
code: メモ
補足: コンポーネント名は常に大文字で始めてください。
React は小文字で始まるコンポーネントを DOM タグとして扱います。例えば、<div /> は HTML の div タグを表しますが、<Welcome /> はコンポーネントを表しており、スコープ内に Welcome が存在する必要があります。
この規約の背後にある理由については JSX を深く理解するを参照してください。
code: state を直接変更しないこと
// state はユーザ操作や時間経過などで動的に変化するデータを扱うために確保されている機能です。
// Wrong ... コンポーネントは再レンダーされません
this.state.comment = 'Hello';
// Correct
this.setState({comment: 'Hello'});
code:state の更新は非同期に行われる可能性がある
this.props と this.state は非同期に更新されるため、
次の state を求める際に、それらの値に依存するべきではありません。
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
これを修正するために、オブジェクトではなく関数を受け取る setState() の 2 つ目の形を使用します。
その関数は前の state を最初の引数として受け取り、更新が適用される時点での props を第 2 引数として受け取ります
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
// Correct (こちらでもOK)
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
code: イベント処理
React でのイベント処理は DOM 要素のイベントの処理と非常に似ています。いくつかの文法的な違いがあります:
React のイベントは小文字ではなく camelCase で名付けられています。
JSX ではイベントハンドラとして文字列ではなく関数を渡します。
例えば、以下の HTML:
<button onclick="activateLasers()">
Activate Lasers
</button>
// React
<button onClick={activateLasers}>
Activate Lasers
</button>
code: 「新しいページを開く」というリンクのデフォルト動作を抑止する
React では false を返してもデフォルトの動作を抑止することができません。明示的に preventDefault を
呼び出す必要があります。例えば、プレーンなHTML では、「新しいページを開く」というリンクのデフォルト
動作を抑止するために次のように書くことができます。
// プレーン HTML
<a href="#" onclick="console.log('The link was clicked.'); return false">
Click me
</a>
// React
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
ここで、e は合成 (synthetic) イベントです。React はこれらの合成イベントを W3C の仕様に則って定義しているので、
ブラウザ間の互換性を心配する必要はありません。React のイベントはネイティブのイベントと全く同様に動作する
わけではありません。詳細については、SyntheticEvent のリファレンスガイドを参照してください。
code: トグルボタン例
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make this work in the callback
this.handleClick = this.handleClick.bind(this);
}
// state は stateaaa とか prevState とかでもいいぽいので、state という文字列が入っていればよさそう
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
code: イベントハンドラに引数を渡す
ループ内では、イベントハンドラに追加のパラメータを渡したくなることがよくあります。
例えば、id という行の ID がある場合、以下のどちらでも動作します
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
上記の 2 行は等価であり、上側ではアロー関数が、下側では Function.prototype.bind が使われています。
どちらの場合でも、React イベントを表す e という引数は ID の次の 2 番目の引数として渡されることになります。
アロー関数では e を明示的に渡す必要がありますが、bind の場合には id 以降の追加の引数は自動的に転送されます。
code: 条件つきレンダリング
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
// ユーザがログインしているかどうかによって、これらのコンポーネントの一方だけを表示する Greeting コンポーネント
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
ReactDOM.render(
// Try changing to isLoggedIn={true}:
<Greeting isLoggedIn={false} />,
document.getElementById('root')
);
code: 要素変数
function LoginButton(props) {
return (
<button onClick={props.onClick}>
Login
</button>
);
}
function LogoutButton(props) {
return (
<button onClick={props.onClick}>
Logout
</button>
);
}
LoginControl は現在の state によって <LoginButton /> もしくは <LogoutButton /> の一方をレンダーします。
加えて、前の例の <Greeting /> もレンダーします
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}
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')
);
code: 論理 && 演算子によるインライン If
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}
ReactDOM.render(
<Mailbox unreadMessages={messages} />,
document.getElementById('root')
);
code: 条件演算子によるインライン If-Else
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
</div>
);
}
// より大きな式にも適用する
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn
? <LogoutButton onClick={this.handleLogoutClick} />
: <LoginButton onClick={this.handleLoginClick} />
}
</div>
);
}
code:コンポーネントのレンダーを防ぐ
稀なケースですが、他のコンポーネントによってレンダーされているにも関わらず、コンポーネントが自分のことを隠したい、
ということがあるかもしれません。その場合はレンダー出力の代わりに null を返すようにしてください。
function WarningBanner(props) {
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(state => ({
showWarning: !state.showWarning
}));
}
render() {
return (
<div>
<WarningBanner warn={this.state.showWarning} />
<button onClick={this.handleToggleClick}>
{this.state.showWarning ? 'Hide' : 'Show'}
</button>
</div>
);
}
}
ReactDOM.render(
<Page />,
document.getElementById('root')
);
code: 複数のコンポーネントをレンダリングする
JavaScript の map() 関数を利用して、numbers という配列に対して反復処理を行っています。
それぞれの整数に対して <li> 要素を返しています。最後に、結果として得られる要素の配列を
listItems に格納しています
const listItems = numbers.map((number) =>
<li>{number}</li>
);
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
code:Key
Key は、どの要素が変更、追加もしくは削除されたのかを React が識別するのに役立ちます。
配列内の項目に安定した識別性を与えるため、それぞれの項目に key を与えるべきです。
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
兄弟間でその項目を一意に特定できるような文字列を key として選ぶのが最良の方法です。
多くの場合、あなたのデータ内にある ID を key として使うことになるでしょう
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
レンダリングされる要素に安定した ID がない場合、最終手段として項目のインデックスを使うことができます
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
要素の並び順が変更される可能性がある場合、インデックスを key として使用することはお勧めしません。
パフォーマンスに悪い影響を与え、コンポーネントの状態に問題を起こす可能性があります。Robin Pokorny による、
key としてインデックスを用いる際の悪影響についての詳しい解説をご覧ください。より詳しく学びたい場合は、
key が何故必要なのかについての詳しい解説を参照してください。
code:key のあるコンポーネントの抽出
key が意味を持つのは、それをとり囲んでいる配列の側の文脈です。
例えば、ListItem コンポーネントを抽出する際には、key は ListItem 自体の <li> 要素に書くのではなく、
配列内の <ListItem /> 要素に残しておくべきです。
// 例: 不適切な key の使用法
function ListItem(props) {
const value = props.value;
return (
// Wrong! There is no need to specify the key here:
<li key={value.toString()}>
{value}
</li>
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Wrong! The key should have been specified here:
<ListItem value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
// 例: 正しい key の使用法
// 基本ルールとしては、map() 呼び出しの中に現れる要素に key が必要
function ListItem(props) {
// Correct! There is no need to specify the key here:
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Correct! Key should be specified inside the array.
<ListItem key={number.toString()} value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
code:key は兄弟要素の中で一意であればよい
配列内で使われる key はその兄弟要素の中で一意である必要があります。
しかし全体でユニークである必要はありません。2 つの異なる配列を作る場合は、同一の key が使われても構いません
function Blog(props) {
const sidebar = (
<ul>
{props.posts.map((post) =>
<li key={post.id}>
{post.title}
</li>
)}
</ul>
);
const content = props.posts.map((post) =>
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
);
return (
<div>
{sidebar}
<hr />
{content}
</div>
);
}
const posts = [
{id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
{id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
ReactDOM.render(
<Blog posts={posts} />,
document.getElementById('root')
);
code:map() を JSX に埋め込む
// 上記の例では listItems 変数を別途宣言して、それを JSX に含めました
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
// JSX では任意の式を埋め込むことができますので、map() の結果をインライン化することもできます
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
);
}
時としてこの結果はよりすっきりしたコードとなりますが、この記法は乱用されることもあります。
普通の JavaScript でそうであるように、読みやすさのために変数を抽出する価値があるかどうか決めるのはあなたです。
map() の中身がネストされすぎている場合は、コンポーネントに抽出する良いタイミングかもしれない、
ということにも留意してください。
code: 制御されたコンポーネント
フォーム送信時に名前をログに残すようにしたい場合、フォームを制御されたコンポーネントとして書くことができます
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
// textarea タグ
class EssayForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Please write an essay about your favorite DOM element.'
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('An essay was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Essay:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
// select タグ
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('Your favorite flavor is: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Pick your favorite flavor:
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
// 補足
value 属性に配列を渡すことで、select タグ内の複数のオプションを選択することができます
<select multiple={true} value={'B', 'C'}> code:複数の入力の処理
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
});
}
render() {
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
code:state のリフトアップ
// コンポーネントで同期したい共通の値は親に集約させる
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
return (
<fieldset>
<legend>Enter temperature in Celsius:</legend>
<input
value={temperature}
onChange={this.handleChange} />
<BoilingVerdict
celsius={parseFloat(temperature)} />
</fieldset>
);
}
}
// after
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
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);
}
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 {scaleNamesscale}:</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>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}
ReactDOM.render(
<Calculator />,
document.getElementById('root')
);
code: 子要素の出力 (Containment)
function Contacts() {
return <div className="Contacts" />;
}
function Chat() {
return <div className="Chat" />;
}
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={
<Contacts />c
}
right={
<Chat />
} />
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);