こちらのページで環境構築の手順を把握した React について、開発を行うために必要な基本事項について記載します。
ReactDOM.render(element, container) の第一引数に指定する React element は JSX で記述できます。
const element = <h1>Hello, world!</h1>;
React element は関数から返すことができます。タグは入れ子にすることができます。{}
で値を埋め込むこともできます。その場合、属性値はダブルクォーテーション等で囲わないようにします。
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
function getGreeting(user) {
if (user) {
return <h1 id={user.id}>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
const user = {
firstName: 'Harper',
lastName: 'Perez',
id: 'myid'
};
const element = (
<div>
{getGreeting(user)}
<h2>Good to see you here.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
以下のような仮想 DOM が生成されます。
<div id="root">
<div>
<h1 id="myid">Hello, Harper Perez!</h1>
<h2>Good to see you here.</h2>
</div>
</div>
const
で定義していることからも分かるとおり React element はイミュータブルです。再描画する場合は新規 React element を構築して ReactDOM.render()
に再度渡します。ReactDOM.render()
は変更のあった仮想 DOM だけを更新します。このことを、以下のコードでは setInterval を利用して確認しています。
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);
React element を返すものは React component とよばれます。React component は function として記述することもできますが、実際には後述の state などが利用できる class として記述されます。
function として定義された React component
function App(props) {
return <h1>Hello, {props.name}</h1>;
}
ReactDOM.render(
<App name="myname" />,
document.getElementById('root')
);
class として定義された React component
class App extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
ReactDOM.render(
<App name="myname" />,
document.getElementById('root')
);
慣習として、アプリケーション全体を React で記述するときは App
という名称のコンポーネントを root に render します。Rails テンプレートエンジン内で利用する場合などは複数箇所で render するためその限りではありません。
class で記述した React component は、状態 state
や componentDidMount
, componentWillUnmount
といったライフサイクルフックを持つことができます。
class Clock extends React.Component {
constructor(props) {
super(props);
// コンストラクタ内では setState を
// 利用せず state に直接代入します。
this.state = {
date: new Date(),
cnt: 0
};
}
// render 後に実行されるライフサイクルフックです。
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
// 以下のようにしても同じ意味です。
// const self = this;
// this.timerID = setInterval(
// function(){ self.tick() },
// 1000
// );
}
// 仮想 DOM から本 component が削除される前に実行される
// ライフサイクルフックです。
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
// コンストラクタ外では setState を利用します。
this.setState({
date: new Date()
});
// setState は上記のオブジェクトを引数に指定するものと
// 下記の関数を引数に指定するものがあります。
this.setState((prevState, props) => {
return {
cnt: prevState.cnt + parseInt(props.incr)
};
});
// 以下のようにしても同じ結果です。
// this.setState(prevState => {
// return {
// cnt: prevState.cnt + 5
// };
// });
// 以下のようにしても同じ意味です。
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
// this.setState((prevState, props) => ({
// cnt: prevState.cnt + parseInt(props.incr)
// }));
// this.setState(function(prevState, props){
// return {
// cnt: prevState.cnt + parseInt(props.incr)
// };
// });
}
// React element を返します。
render() {
return (
<div>
<p>time: {this.state.date.toLocaleTimeString()}</p>
<p>cnt: {this.state.cnt}</p>
</div>
);
}
}
ReactDOM.render(
<Clock incr="5" />,
document.getElementById('root')
);
React におけるイベントハンドラは以下のように記述します。SyntheticEvent に記載のあるイベントをハンドリングできます。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// `handleClick` 内で `this` を利用するために忘れずに記述します。
this.handleClick = this.handleClick.bind(this);
}
// `this` を利用するイベントハンドラ
handleClick(e) {
e.preventDefault(); // <a> タグの画面遷移を無効化したい場合
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
// 引数のあるイベントハンドラ
handleClickWithArgs(myarg, e) {
console.log(myarg);
}
render() {
return (
<div>
<a href="#" onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</a>
<button onMouseOver={this.handleClickWithArgs.bind(this, 123)}>マウスオーバーでログ出力</button>
</div>
);
}
}
ReactDOM.render(
<MyComponent />,
document.getElementById('root')
);
if 文による分岐以外に、以下のような記法が利用できます。
ReactDOM.render(
<div>
<h1>hello</h1>
{true && <p>表示される</p>}
{false && <p>表示されない</p>}
{true ? <p>表示される</p> : <p>表示されない</p>}
</div>,
document.getElementById('root')
);
React component が React element として null
を返すと何も表示されません。
function App(props) {
if(!props.show) {
return null;
}
return <h1>hello</h1>;
}
ReactDOM.render(
<App show={false} />,
document.getElementById('root')
);
React component は複数の React elements を返すことができます。map を利用すると便利です。
function ListItem(props) {
// `map` 内ではないため `key` を指定する必要はありません。
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map(number =>
<ListItem
key={number.toString()} // `map` 内で生成する要素には何らかの `key` を指定します。
value={number} /> // `key` は props として渡されません。必要に応じて別途指定します。
);
const listItems2 = numbers.map((number, index) => // `index` も利用できます (非推奨)
<ListItem
key={index} // `key` は同じリスト `listItems2` 内でユニークであれば十分です。
value={number} />
);
return (
<div>
<ul>{listItems}</ul>
<ul>{listItems2}</ul>
</div>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
様々な component の共通部分を汎用的な component として切り出して、それを複数 component の内部で利用することでコード全体のメンテナンス性が上がります。このような階層化された component 群に関するいくつかの有益なサンプルを示します。
上位 component の state を下位 component に渡すことで、component 群が全体として持つ状態を統一できます。上位 component は下位 component にコールバック関数を合わせて渡しておき、下位 component 内で状態を更新する必要が発生した場合はそれを呼び出して上位 component の state を更新します。
class MyInnerComponent extends React.Component {
constructor(props) {
super(props);
// 前述のとおり `handleChange` 内で `this` を利用するために忘れずに記述します。
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onMyChange(e.target.value); // input の値を取得してコールバック関数を実行します。
}
render() {
return <input value={this.props.myval} onChange={this.handleChange} />;
}
}
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.handleChange1 = this.handleChange1.bind(this); // 前述のとおり `handleChange1/2` 内で
this.handleChange2 = this.handleChange2.bind(this); // `this` を利用するために忘れずに記述します。
this.state = {myval: 0.0};
}
handleChange1(v) {
const myval = parseFloat(v); // そのまま格納
this.setState({myval: Number.isNaN(myval) ? 0.0 : (myval / 1.0)});
}
handleChange2(v) {
const myval = parseFloat(v); // 半分にして格納
this.setState({myval: Number.isNaN(myval) ? 0.0 : (myval / 2.0)});
}
render() {
const myval = this.state.myval; // そのまま表示、二倍にして表示
return (
<div>
<MyInnerComponent myval={myval * 1.0} onMyChange={this.handleChange1} />
<MyInnerComponent myval={myval * 2.0} onMyChange={this.handleChange2} />
</div>
);
}
}
ReactDOM.render(
<MyComponent />,
document.getElementById('root')
);
props.children
を利用すると以下のような React element の渡し方ができます。
function MyInnerComponent(props) {
return (
<div>
{props.children}
</div>
);
}
function MyComponent() {
return (
<MyInnerComponent>
<h1>hello</h1>
</MyInnerComponent>
);
}
ReactDOM.render(
<MyComponent />,
document.getElementById('root')
);
複数の React element を渡す必要がある場合は、通常どおり属性値として渡します。
function MyInnerComponent(props) {
return (
<p>
{props.p1}, {props.p2}
</p>
);
}
function MyComponent() {
return (
<MyInnerComponent
p1={
<span>Hello</span>
}
p2={
<span>world!</span>
}
/>
);
}
ReactDOM.render(
<MyComponent />,
document.getElementById('root')
);