Reactを学ぶ9

作業時間:2.5時間 経過時間:31時間
state(リフトアップ)
子コンポーネント同士でstateのデータを共有するには?
React での state の共有は、state を、それを必要とするコンポーネントすべての直近の共通祖先コンポーネントに移動することによって実現します。これを “state のリフトアップ (lifting state up)” と呼びます。
親コンポーネントのstateにデータをおいておく。 双方向バインディングではない。あくまで、トップダウンの流れ。
props もしくは state から作りだす事のできるデータについては、おそらく state に保持すべきではないでしょう。
stateとして扱うか、propsとして扱えるようにしておくか、まだ分かりきっていない。
const BoilingVerdict = props =>{
if(props.celsius >= 100){
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
const toCelsius = fahrenheit => {
return (fahrenheit - 32) * 5 / 9;
}
const toFahrenheit = celsius =>{
return (celsius * 9 / 5) + 32;
}
const 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();
}
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
}
class TemperatureInput extends React.Component {
constructor(props){
super(props);
this.handleChange = this.handleChange.bind(this);
//this.state = {temperature: ''}
}
handleChange(e){
//this.setState({temperature: e.target.value})
this.props.onTemperatureChange(e.target.value);
}
render(){
//const temperature = this.state.temperature;
const temperature = this.props.temperature;
const scale = this.props.scale;
return(
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</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')
)
コンポジションvs継承
基本的に継承は使わずにコンポジションを推奨している。 コンポーネントを知らなくても、props.childrenで受け取り適用できる。 親コンポーネントのJSXの外側で子コンポーネントを呼び出すのが肝かな。
const FancyBorder = props =>{
return (
<div className={'FancyBorder FancyBorder-'+props.color}>
{props.children}
</div>
)
}
const WelcomeDialog = () =>{
return (
<FancyBorder>
<h1 className="Dialog-title">hoge</h1>
<p>thank you</p>
</FancyBorder>
)
}
ReactDOM.render(
<WelcomeDialog />,
document.getElementById('root')
)
/* レンダリング結果
<div class="FancyBorder FancyBorder-undefined">
<h1 class="Dialog-title">hoge</h1>
<p>thank you</p>
</div>
*/
複数箇所に子要素を追加したい場合
const Contacts = () => {
return (
<div>contacts</div>
)
}
const Chat = () => {
return (
<div>chat</div>
)
}
const SplitPane = props => {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
)
}
const App = () => {
return (
<SplitPane left={<Contacts />} right={<Chat />} />
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
propsを経由して、コンポーネントをやりとりしている、ということだ。 要素をそのまま利用しているわけではない。
const FancyBorder = props => {
return (
<div className="hoge">
{props.children}
</div>
)
}
const Dialog = props => {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
</FancyBorder>
)
}
const WelcomeDialog = () => {
return (
<Dialog title="Welcom" message="Thank you" />
)
}
ReactDOM.render(
<WelcomeDialog />,
document.getElementById('root')
)
/*
<div class="hoge">
<h1 class="Dialog-title">Welcom</h1>
<p class="Dialog-message">Thank you</p>
</div>
*/
classとの併用も可能。
const FancyBorder = props => {
return (
<div className="hoge">
{props.children}
</div>
)
}
const Dialog = props => {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
</FancyBorder>
)
}
class SignUpDialog extends React.Component {
constructor(props){
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
this.state = {login: ''};
}
render() {
return (
<Dialog title="Mars Exploration Program" message="How should we refer to you?">
<input value={this.state.login} onChange={this.handleSignUp} />
<button onClick={this.handleSignUp}>Sign Me Up</button>
</Dialog>
)
}
handleChange(e) {
this.setState({login:e.target.value})
}
handleSignUp() {
alert(`Welcome aboard, ${this.state.login}!`);
}
}
const WelcomeDialog = () => {
return (
<Dialog title="Welcom" message="Thank you" />
)
}
ReactDOM.render(
<SignUpDialog />,
document.getElementById('root')
)
/*
<div class="hoge">
<h1 class="Dialog-title">Mars Exploration Program</h1>
<p class="Dialog-message">How should we refer to you?</p>
</div>
*/