はじめに
ES2019で正式採用になるであろうClassFieldsの挙動について、よく考えず使っていたので、今更ではあるが、どのような動作をするかチェックした。
なお、現時点では stage3。tc39/proposal-class-fields
tl;dl
- class fieldsはめっちゃ便利なのでどんどん利用していこう
- 自分で手を動かすと理解が深まるよね
それどういうやつ
class Sample {
x = 0; // public field
#x = 0; // private field
handleOnClick = (event) => {
// ...
this.x++;
} // public field
}
以上のように、簡単にプロパティを定義できる。
チェックのための環境
最低限の動作をするプロジェクトを作成してテストする。
コードはここに公開済み。
https://github.com/tomoyamachi/minimum-react-dom-check
各種ライブラリ
基本ツールのバージョン情報は以下の通り。
"react": "16.6.3",
"react-dom": "16.6.3"
"babel-core": "^6.26.3",
"webpack": "^4.28.2",
"webpack-dev-server": "^3.1.14",
"why-did-you-update": "^1.0.6"
これにビルド時に必要なbabelのpreset/pluginを追加する。
今回は、class-properties用のpluginを入れる。
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
利用方法
$ git clone https://github.com/tomoyamachi/minimum-react-dom-check
$ yarn
$ yarn start
http://localhost:8080 を開けばOK.
不要な再レンダリングが発生していると、 why-did-you-updateが教えてくれる。
検証内容
今回は、Publicなclass fieldsの動作確認とともに、バッドプラクティスと名高い、propsにbindや無名関数を直接いれちゃだめなやつも一緒にチェックした。
なお挙動確認のため、無理矢理stateを利用するなど、冗長になっている。
constructorでbindして渡す
class ButtonWithBind extends React.Component {
constructor(props) {
super(props);
this.state = {type: "WithBind"}
this.handleClick = this.handleClick.bind(this);
}
handleClick(message) {
console.log(message, this.props.count)
this.props.countUp();
}
render() {
const {type} = this.state;
return <Button type={type} key={type} handleClick={this.handleClick}/>;
}
}
propsで直接bindして渡す
class ButtonWithoutBind extends React.Component {
constructor(props) {
super(props);
this.state = {type: "WithoutBind"}
}
handleClick(message) {
console.log(message, this.props.count)
this.props.countUp();
}
render() {
const {type} = this.state;
return <Button type={type} key={type} handleClick={this.handleClick.bind(this)}/>;
}
}
propsで即時関数として渡す
class ButtonDirectCall extends React.Component {
constructor(props) {
super(props);
this.state = {type: "DirectCall"}
}
handleClick(message) {
console.log(message, this.props.count)
this.props.countUp();
}
render() {
const {type} = this.state;
return <Button type={type} key={type} handleClick={(message) => this.handleClick(message)}/>;
}
}
propsで条件式を利用して渡す
class ButtonCondition extends React.Component {
constructor(props) {
super(props);
this.state = {type: "Condition"}
this.handleClick = this.handleClick.bind(this)
}
handleClick(message) {
console.log(message, this.props.count)
this.props.countUp();
}
render() {
const {type} = this.state;
return <Button type={type} key={type} handleClick={true && this.handleClick}/>;
}
}
Class Fieldsを利用
class ButtonClassState extends React.Component {
state = {type: "ClassState"}
handleClick = (message) => {
console.log(message, this.props.count)
this.props.countUp();
}
render() {
const {type} = this.state;
return <Button type={type} key={type} handleClick={this.handleClick}/>;
}
}
Buttonコンポーネントの作成
PureComponentにしないと、全パターンでre-renderされるので注意。
class Button extends React.PureComponent {
state = {type: this.props.type}
render() {
const {type, handleClick} = this.props;
return (
<button onClick={() => handleClick(type)}>
{type}
</button>)
}
};
そしてボタンを押してコンソールを確認する。
propsで直接bindして渡す
と propsで即時関数として渡す
が意味もなく再レンダリングされているのがわかる。
知識通りの挙動ではあるが、やはり手を動かして作ると実感が湧くのでいい。
結果 : ReactでClassFieldsを利用すると...
constructorがなくなり、コードがすっきりする
一番わかり易いメリットはこれ。
特に、関数に対してbindしてプロパティに渡すをしなくてよくなるのは助かる。
stateなどがプロパティに過ぎないことが理解しやすい
本来プロパティはクラスの属性なので、constructorの中で定義すると違和感があった。それもなくなる。
たとえば、 this.func = this.func.bind(this)
はプロパティに関数を代入する行為に過ぎないはずだが、それが分かりづらい。黒魔術感が強い。
JS初心者の頃、この記述の必要性がわからず、もやもやした。
まとめ
Private Class Fieldsの挙動もチェックしたけど、後日記事にしていくつもり。
Static Class Fieldsをガンガン利用していこう。