note
React初心者です。
環境
"dependencies": {
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "3.0.0"
},
概要
期待する動作
ボタンをクリックすると、時計が1つ増える。時計に表示する文字列は、各コンポーネントではなく、Appが管理する。時計は1秒ごとに更新される。
実装
下にあるコードを見たほうが早いと思いますが、簡単に説明。
Windowという名前のコンポーネントがある。props.text
を表示する以外のことは何もしない。
Appは、text
とwindows
というステートを持っている。
textには、時刻を文字列にしたものが格納されていて、setInterval
で1秒ごとに更新される。
windows
は、Windowコンポーネントが格納された配列である。
画面上のボタンをクリックすると、state.windows
に Windowコンポーネント を追加する。
Appのrender
の中にthis.state.windows
を表示する部分があるので、state.windows
に要素を追加した時点で画面が更新されるはず。
起きる問題
動的に追加されたWindowたちの文字列が変化しない。
Appのrender
に直接書いたWindowは更新されているのが分かる。
codesandbox
念のため、ブログにもコードを記載します。
import React from "react";
import ReactDOM from "react-dom";
class Window extends React.Component {
constructor(props) {
super(props);
}
render() {
return <pre>{this.props.text}</pre>;
}
}
class App extends React.Component {
constructor(prop) {
super(prop);
this.state = {
text: "",
windows: []
};
setInterval(() => {
this.updateText();
}, 1000);
}
updateText() {
this.setState({
text: new Date(Date.now()).toString()
});
}
addWindow() {
this.setState({
windows: this.state.windows.concat([<Window text={this.state.text} />])
//windows: this.state.windows.concat([() => <Window text={this.state.text} />])
});
}
render() {
return (
<div className="App">
<Window text={this.state.text} />
<hr />
<div>
{
this.state.windows
//this.state.windows.map(e => e())
}
</div>
<hr />
<div>
<button
onClick={() => {
this.addWindow();
}}
>
addWindow
</button>
</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
原因と解決策
上記のコードの、コメントアウト部分が、自分なりの解決策です。
下のように、domを直接配列に入れてしまっているのが間違い。おそらく、現時点でのthis.state.text
を含むレンダリング済みのデータが格納されてしまっている。
this.state.windows.concat([<Window text={this.state.text} />])
this.state.text
が変化したら再構築されて欲しいはずなので、配列にはdomを返す無名関数を格納するようにして、
this.state.windows.concat([() => {return (<Window text={this.state.text} />);}])
render()
側で無名関数を呼んで、domをレンダリングする。
<div>
{ this.state.windows.map(e => e()) }
</div>
これで期待通りの動作はするようになった。
原因と解決策(追記:2019/05/06)
state
にはコンポーネントを入れず、コンポーネントに渡す引数を保持させた方が良いとのコメントがありました。
この方法ならば、state
も最小限の大きさになり、可読性も上がりそうです。
配列にはキーワードや連想配列を格納するようにして、
this.state.windows.concat(["Window"])
render()
側でキーワードに応じてレンダリングするdomを分岐する。
<div>
{ this.state.windows.map(type =>
(type == "Window") ? (<Window text={this.state.text} />) :
(<p>{this.state.text}</p>))
}
</div>