The source code for this blog is available on GitHub.

Blog.

Reactを学ぶ9

Cover Image for Reactを学ぶ9

React (state のリフトアップ)

作業時間: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>
*/

More Stories