React
https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/2880px-React-icon.svg.png
Motivation
シュッとした UI 作りたくない?ぼくは作りたいんだよな。仕事では特に必要ないが、趣味でフロントをやっていきたい気持ちがあるのでやっていきます。
所感
React の仮想 DOM は、書き換える必要がある箇所のみ書き換えるとのことだけど、この「書き換える必要がある」と React が認識する部分についてちゃんと把握しておかないと、パフォーマンスに影響が出てしまうのだろうな。逆にそこが React の仕組みをちゃんと知る必要がありそうで一番難しそうに思えている。
チュートリアル
一年前に購入していたオライリーの React 入門本を発掘したが、正直フロントエンド界隈は流れが早すぎるので公式チュートリアルなりをやるのが良いと感じた。
step by step で学ぶ場合。
実践で学ぶ場合。
React について
React における関心事の分離
🙅♂️ マークアップとロジックを別ファイルに分ける
🙆♂️ マークアップおよびロジックを含んだコンポーネント同士を疎結合させる
React において、上記の マークアップおよびロジックを含んだコンポーネント とは、React Component のことを指す。React Component は、ロジックを内包し、render() で UI コンポーネント (React Element) を返す。 React は、この React Component を基本単位として、これを組み合わせて UI を構築していくのが基本のようだ。 とりあえずはじめるには
react および react-dom パッケージを利用する。JSX をトランスパイルしたい場合は Babel を利用する。ちょっと試したいだけなら、Rreact CDN, Babel CDN を各々利用すれば動かせる。 code:html
<!DOCTYPE html>
<html>
<head>
<script type="text/babel">
// ...
</script>
</head>
<body>
<div id="root"></div>
</body>
</html>
React Component
概要
概念的には、JavaScript における関数に似ている
任意の引数 (props) を受け取り、React Element を返す
All React components must act like pure functions with respect to their props.
React Component をインスタンス化したものを React Element という
LifeCycle
mounting: React DOM が DOM に最初に描画されることをいう
unmounting: React DOM が DOM から削除されることをいう
構成要素
React Component に含まれるプロパティ/メソッドには、主に以下がある。
プロパティ
props Immutable な値
state mutable な値
メソッド
render() React Element を返す
props
React Component は、任意の引数をとって初期化できる。その時の任意の引数
class の場合はコンストラクタの引数として、関数の場合は単純に引数として渡す
props は read-only
一度設定したら直接書き換えてはいけない
書き換えようとするとエラーをはかれる
TypeError: Attempted to assign to readonly property.
全ての React Component は、props を引数にとる pure な関数 (=副作用のない関数) として動作すべき
pure な関数は、その入力を変更しないし、同じ引数に対しては必ず同じ結果を返す
state
React Component は pure な関数として振舞ってくれるのが理想
とはいえ、UI は状態を持つ
この状態を持つプロパティとして state がある
ユーザのアクションやネットワークレスポンス、その他何かしらによって、その出力を変更するのに利用する
State は直接書き換えてはいけない
直接書き換えてはいけない
setState を利用して書き換える
this.state には、constructor 内でのみアクセスする
this.state を直接書き換えても、コンポーネントは再描画されない
State は非同期に更新される可能性がある
setState は、React によってパフォーマンスのためにまとめて実行される可能性がある
従って、this.state, this.props は非同期に書き換わる可能性があるので、状態の計算時にそれらを参照しない
🙅♂️
code:javascript
this.setState({
counter: this.state.counter + this.props.increment
});
🙆♂️
code:javascript
this.setState((state, props) => {
coutner: state.counter + props.increment
});
簡単な例だと、以下のようなコードで確認できる
code:javascript
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
name: ""
};
this.handleFormChange = this.handleFormChange.bind(this);
}
handleFormChange(e) {
this.setState({
name: e.target.value
});
console.log(this.state.name);
}
render() {
return (
<div>
<input onChange={this.handleFormChange} />
</div>
);
}
}
ReactDOM.render(<Home />, document.getElementById("root"));
これを実行すると、以下のようになる。
https://gyazo.com/4cc7576adafe8707d9539916fb421b49
5 まで input には入力しているのに、ログには 4 までしか出力されていない。これは、setState が非同期関数になっているために、console.log が先に実行されてしまい、setState は次のイベントループで実行されているためである
これを避けて、setState 実行後に任意の処理を挟みたい場合は、callback を第二引数に指定すればそのように動く
code:javascript
handleFormChange(e) {
this.setState(
{
name: e.target.value
},
() => {
console.log(this.state.name);
}
);
}
State の更新はマージされる
setState の引数にしたオブジェクトと現在の state オブジェクトはマージされる
浅いコピーであるため、書き換えようとした対象のみが書き換わり、他に state にプロパティがあってもそれらはそのまま残る
いちいち全ての state のプロパティを毎回指定する必要はない
setState で状態をバラバラに更新しても、賢いマージは行われない
Tips: setState に渡す関数の汎用化
setState は (state, props) => {} のようなインタフェースの関数を引数にとる
この関数自体を返す関数 を用意しておくと抽象化ができる
code:javascript
// 抽象化なし
handleIsValid = () => {
this.setState(state => {
return {
isValid: !state.isValid,
}
});
}
code:javascript
// まだ抽象化できていない。専用のメソッド
const toggleIsValid = staet => {
return {
isValid: !state.isValid,
}
}
handleIsValid = () => {
this.setState(toggleIsValid);
}
code:javascript
// あるキーの値をトグルする、という関数に抽象化できている!
const toggleKey = key => state => {
return {
}
}
handleIsValid = () => {
this.setSate(toggleKey('isValid'));
}
render
React Element を返す
React Element は Immutable = 一度作成されたら、その子や属性を変更することはできない
映画のワンシーンのように、ある瞬間の UI を表現する
React Component の定義と描画
built-in
React.DOM は、既存の HTML DOM 要素を React Component 化したもので、React 自体に組み込まれている
custom
React.createClass を利用する (React 15 から非推奨)
React.Component を継承した class を作成する
関数を利用する
React Component の定義方法
code:javascript
// createClass を利用する **15から非推奨**
const HelloWorld = React.createClass({
render: function() {
return React.DOM.span(null, "Custom Component");
}
});
code:javascript
// 関数を利用する
function HelloWorld(props) {
return <h1>Hello, {props.name}</h1>;
}
code:javascript
// ES6 の class を利用する
class HelloWorld extends React.Component {
render() {
return <h1>Hello World</h1>
}
}
React Element の作成方法
code:javascript
// createElement を利用する
const element = React.createElement(
HelloWorld,
{ propField: propValue }
);
code:javascript
// Factory を利用する
const factory = React.createFactory(HelloWorld);
const element = factory({ propField: propValue });
code:javascript
// JSX を利用する
// JSX は Babel によって React.createElement() にコンパイルされる
const element = <HelloWorld propField={propValue} />
React Element の描画方法
code:javascript
ReactDOM.render(element, document.getElementById("root"))
React Component と Event Handling
Event Handling
よく、DOM 要素の機能自体 (a タグなら移動、など) が実行されるのを防ぐために、event handler 内で return false するらしい。が、preventDefault を明示的に呼び出すのが推奨されている
code:javascript
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
JSX のコールバックに渡す関数内での this の取り扱いには注意する。明示的に bind してやる必要がある。いちいち bind するのが面倒な場合は2つの方法がある
public class fields syntax
code:javascript
handler = () => {
console.log('this is:', this);
}
arrow function
これの問題点は、コンポーネントが描画されるたびにに異なるコールバックが作られること
このコールバックが prop として下位のコンポーネントに渡されていると、下位コンポーネント全てに再描画がかかり、パフォーマンスに影響する
code:javascript
<button onClick={(e) => this.handler(e)}>
Conditional Greeting
コンポーネント自体が、条件によって描画内容を変更する
子コンポーネントが、親コンポーネントの状態を変更するために、handler を子コンポーネントに渡すことができる
インタフェースを間に挟めば DIP になりそう
親コンポーネントが描画され続けているが子コンポーネントを描画したくない場合は、null を返す
React Component と State
コンポーネント単体の State
React.Component は、ES6 の class 定義か、function を利用した定義かのどちらかを選ぶことができる。state を利用したい場合は前者を選択するのが従来。後者で state を利用したい場合は、React hooks を利用する。 class 定義した場合、React.Component は state プロパティをもつ。これは、constructor で初期化し、setState で更新する。更新にはイベントハンドラーを利用し、そのイベントハンドラー自体は DOM 要素に紐づける。
code:javascript
class LifecycleClock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date() };
}
// DOM が描画されたら、タイマーをセット
componentDidMount() {
this.timerId = setInterval(() => this.tick(), 1000);
}
// DOM が消されたら、タイマーを消す
componentWillUnmount() {
clearInterval(this.timerId);
}
// イベントハンドラー。時間の状態を更新する
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello World</h1>
<h2>
<!-- 状態を参照する -->
ただいまの時刻は {this.state.date.toLocaleTimeString()} です
</h2>
</div>
);
}
}
Top-down (undirectional) Flow
コンポーネントは、関連する自身以外のコンポーネントが、ステートフルか?ステートレスか?function か?class か?といったことは意識すべきでない。したがって、コンポーネント内の state は其のコンポーネント自身しか参照できない。
ただ、親の state を子の prop として渡すことができる。これを、top-down、あるいは undirectional フローとよぶ。状態は特定のコンポーネントのみがもち、其の状態によって表現されるあらゆるデータやUIは、ツリーにおけるその 下 のコンポーネントにのみ影響を与える。
ReactDOM
何か変更があった場合、前回の ReactDOM と比較し、変更があった箇所のみ 更新する
例えば、テキストを含んだ DOM ツリーの Element を更新する場合でも、テキストの変更があった箇所のみが更新される
ある瞬間に UI がどのように見えるべきか?を考える方が、それをどのように変更するか?を考えるよりもバグを減らせる
以下は自分だけがわかればよい図。
https://gyazo.com/6d27b71b7380005a179652622c99de27
Controlled Components
HTML の DOM 要素には、それ自体が状態を持つものが存在する。その状態を React 側で管理するようにしたのが Controlled Components となる。状態をもつ DOM 要素は、例えば input, textarea, select 等。
状態を React Component 側に持たせること と、状態を適切に更新するハンドラを用意すること の2つが必要になる。
以下は自分だけがわかればよい図。
https://gyazo.com/035f4894a1a11ffaa183e9c5280e459b
Lifting State up
親コンポーネントと複数の子コンポーネントがあって、子コンポーネント同士で状態の同期をとりたい、という場合
子コンポーネントに状態をもたせると、子コンポーネントは各々独立した状態を持ってしまい、同期をとれない
子コンポーネント同士に状態を持たせたまま同期するのではなく、source of truth としての状態をおき、それを各子コンポーネントが参照する形が良い
直近の親コンポーネントに状態を持たせ、子コンポーネントを Controlled Components とする
親コンポーネントの状態を子コンポーネントに反映させる
以下は自分だけがわかればよい図。
https://gyazo.com/efada8370ea6b2c038f0fb996e1de46f
Rreact における element を生み出す
React にとって JSX は必須ではないが、JS コード上で UI を扱う上で見やすい。また、React に便利なエラーやワーニングメッセージを出力させることもできる
---
マップとキー
リストを描画したい場合は、JSX 要素の配列を利用できる
キーを指定しないとワーニングが出る
キーは React が item を一意に識別するのに利用する
item の順序が変わり得る場合、index をキーにするのはやめた方が良い
パフォーマンスの問題
コンポーネントの状態の問題
Keys only make sense in the context of the surrounding array.
つまり、配列で囲まれた要素に対してのみ、キーは追加する必要がある
🙅♂️
code:javascript
// ListItem が配列になっているのであって、その中の li にキーを設定する必要はない
function ListItem(props) {
...
<li key={value.toString()}>
{value}
</li>
...
}
function NumberList(props) {
...
<ListItem value={number} />
...
}
🙆♂️
code:javascript
function ListItem(props) {
...
<li>{props.value}</li>
...
}
function NumberList(props) {
...
<ListItem key={number.toString()} value={number} />
...
}
キーは、1つの配列内でのみ一意であればよく、グローバルで一意である必要はないa[]
キーは、React には渡されるがコンポーネントには渡されない
コンポーネント内でキーを参照したい場合は、それを prop として key とは別に渡す必要がある
リンク
https://youtu.be/dpw9EHDh2bM?t=695