LoginSignup
0
4

More than 1 year has passed since last update.

【React】子コンポーネントから親コンポーネントへの値受け渡し…まとめ(クラスコンポーネントの場合)

Last updated at Posted at 2021-04-23

Reactで子コンポーネントに関数を受け渡す方法は以下のようにpropsで橋渡しをすることがわかりました。

ですが、今回のように子コンポーネントの中に引数が存在して、それを処理したい場合どうするかというのが課題で、調べてみた結果、親コンポーネントから子コンポーネントへ値を受け渡すのは可能なのに対し、ReactにはVueやAngularのように子コンポーネントから親コンポーネントへ値を受け渡すemitメソッドのようなものは存在しないようです。

ところが、今回実装したいのはイベントが子コンポーネントのJSXにあり、しかも親コンポーネントから引っ張ってきた値を子コンポーネントで処理して、親コンポーネントにある結果欄に値を出力する操作です。…その方法を探ってみた結果、Reactでの解釈は

  • 子コンポーネントで行われるべきイベント処理を受け渡し、親コンポーネントで同期する

これで、次のようなデータ処理(サンプルとして電卓機能を作成)が可能になります。

※次章では、子コンポーネントしか持っていない値を親コンポーネントに受け渡す方法も載せています。

1:イベントのみが子コンポーネントに存在する場合

値を表示させる

元になったサイトは以下の通りで、コンポーネントを使って簡易な電卓ツールを作成していきます。

これを応用して電卓のキーを作成します。

では、このキーを打鍵すると文字が表示されるようにして、以下のように振り分けます。

  • 文字が数字の場合は、数値に変換する。引き続き数字の場合は桁をずらし、計算記号が打たれた場合は被序数を決定。
  • 文字が計算記号の場合は、被序数と序数を確定し、計算を行う
  • 文字が=の場合は合計を計算する。
  • 文字がCの場合は全てリセットする。

このようにして作ったサンプルが以下のプログラムとなります。

Calc.js
import React from "react";
import Setkey from "./Setkey"


export default class Calc extends React.Component {
	constructor(props){
		super(props)
		this.state = {
			lnum: null, //今までの値
			cnum: 0, //追加の値
			sum: 0, //合計値
			str: '', //打ち込んだ文字
			sign: '', //計算記号
      vals:[
        [['7','7'],['8','8'],['9','9'],['div','÷']],
        [['4','4'],['5','5'],['6','6'],['mul','×']],
        [['1','1'],['2','2'],['3','3'],['sub','-']],
        [['0','0'],['eq','='],['c','C'],['add','+']],
			],
			
		}
		this.getCharstate = this.getChar.bind(this) //メソッドのバインド
	}
	
	getChar(chr,str){
		let lnum = this.state.lnum //被序数
		let cnum = this.state.cnum //序数
		let sum = this.state.sum //合計
		let sign = this.state.sign
		str = this.state.str + str
		if(chr.match(/[0-9]/g)!== null){
			let num = parseInt(chr)
			cnum = cnum * 10 + num //数値が打ち込まれるごとに桁をずらしていく
		}else if(chr.match(/(c|eq)/g) == null){
			if(lnum != null){
				lnum = this.calc(sign,lnum,cnum)
			}else{
				if(chr == "sub"){
					lnum = 0
				}
				lnum = cnum
			}
			sign = chr
			cnum = 0
		}else if( chr == "eq"){
			lnum = this.calc(sign,lnum,cnum)
			sum = lnum
		}else{
			lnum = null
			cnum = 0
			sum = 0
			str = ''
		}
		this.setState({lnum: lnum})
		this.setState({cnum: cnum})
		this.setState({sum: sum})
		this.setState({str: str})
		this.setState({sign: sign})
	}
	
	//計算処理
	calc(mode,lnum,cnum){
			switch(mode){
				case "add": lnum = cnum + lnum
				break;
				case "sub": lnum = lnum - cnum
				break;
				case "mul": lnum = lnum * cnum
				break;
				case "div": lnum = lnum / cnum
				break;
			}
			return lnum
	}

	render(){
		return (
			<>
			{
				this.state.vals.map((val,key)=>{
					return (
						<div key={key}>
						{
							val.map((v,i)=>{
								return(<Setkey key={i} value={v[1]} getChar={()=>{this.getChar(v[0],v[1])} }/>)
							})
						}
						</div>
					)
				})
			}
			<div>
				<p>打ち込んだ文字:{this.state.str}</p>
				<p>合計:{this.state.sum}</p>
			</div>
			</>
		)
  }
}
Setkey.js
import React from "react";

export default class Setkey extends React.Component {
  render() {
    return (
      <button className="square" onClick={()=>{this.props.getChar()}}>
        {this.props.value}
      </button>
    );
  }
}

これで最低限の電卓として機能していますが、ここからが本題です。クリックイベントとそれによるgetCharメソッドは子コンポーネントで生成されたJSX内に存在しているのですが、だからといって前述したように子コンポーネントに処理用メソッドを作って計算したとしても、親コンポーネントに値を受け渡せません。そこで、親コンポーネントに子コンポーネントと同じ関数を橋渡ししておき、子コンポーネントからイベントが実行されても、親コンポーネントがそのイベントを受けたように見せかけ、それを同期させることで実現できます。

なので、まずはテンプレートの生成部分をこのように記述する必要があります。

    <Setkey key={i} value={v[1]} getChar={()=>{this.getChar(v[0],v[1])} }/>

ここで注意しなければいけないのは、厳密には式左側のgetCharとメソッドのthis.getCharは別物で、あくまで式の左側は、子コンポーネントに橋渡ししている、同期用のメソッド(コールバック関数)になります。

なので、書き分けてみるとこのようになり、海外サイトではhogeStateをコールバック関数に、hogeをメソッドとしていることが多く見られます。

Calc.js
return(<Setkey key={i} value={v[1]} getCharstate={()=>{this.getChar(v[0],v[1])} }/>)
Setkey.js
export default class Setkey extends React.Component {
  render() {
    return (
      <button className="square" onClick={()=>{this.props.getCharState()}}>
        {this.props.value}
      </button>
    );
  }
}

2:子コンポーネントからイベントと値を受け渡す

子コンポーネントからイベントと値を受け渡す場合は、先程使用しなかった子コンポーネント内のコールバック関数に引数を代入し、それを親コンポーネント内、無名関数の引数に設定、その値をそのままメソッドの引数に代入することで実現できます。

先程の電卓のキー配置部分も子コンポーネントに移動し、キーの値も子コンポーネントから受け渡すように改造してみました。

Calc2.js
import React from "react";
import Setkey from "./Setkey2"

export default class Calc extends React.Component {
	constructor(){
		super()
		this.state = {
			lnum: null, //今までの値
			cnum: 0, //追加の値
			sum: 0, //合計値
			str: '', //打ち込んだ文字
			sign: '', //計算記号
		}
		this.getCharstate = this.getChar.bind(this) //メソッドのバインド
	}
	
	getChar(chr,str){
		let lnum = this.state.lnum //被序数
		let cnum = this.state.cnum //序数
		let sum = this.state.sum //合計
		let sign = this.state.sign
		str = this.state.str + str
		if(chr.match(/[0-9]/g)!== null){
			let num = parseInt(chr)
			cnum = cnum * 10 + num //数値が打ち込まれるごとに桁をずらしていく
		}else if(chr.match(/(c|eq)/g) == null){
			if(lnum != null){
				lnum = this.calc(sign,lnum,cnum)
			}else{
				if(chr == "sub"){
					lnum = 0
				}
				lnum = cnum
			}
			sign = chr
			cnum = 0
		}else if( chr == "eq"){
			lnum = this.calc(sign,lnum,cnum)
			sum = lnum
		}else{
			lnum = null
			cnum = 0
			sum = 0
			str = ''
		}
		this.setState({lnum: lnum})
		this.setState({cnum: cnum})
		this.setState({sum: sum})
		this.setState({str: str})
		this.setState({sign: sign})
	}
	
	//計算処理
	calc(mode,lnum,cnum){
			switch(mode){
				case "add": lnum = cnum + lnum
				break;
				case "sub": lnum = lnum - cnum
				break;
				case "mul": lnum = lnum * cnum
				break;
				case "div": lnum = lnum / cnum
				break;
			}
			return lnum
	}

	render(){
		return (
			<>
			{
				<Setkey
					getCharstate = { (c,s)=>this.getChar(c,s)}
				/>
			}
			<div>
				<p>打ち込んだ文字:{this.state.str}</p>
				<p>合計:{this.state.sum}</p>
					これまでの値{this.state.lnum}<br/>
					追加の値{this.state.cnum}<br/>
					打ち込んだ文字{this.state.str}<br/>
			</div>
			</>
		)
  }
}
Setkey2.js
import React from "react";

export default class Setkey extends React.Component {
	constructor(props){
		super(props)
		this.state = {
      vals:[
        [['7','7'],['8','8'],['9','9'],['div','÷']],
        [['4','4'],['5','5'],['6','6'],['mul','×']],
        [['1','1'],['2','2'],['3','3'],['sub','-']],
        [['0','0'],['eq','='],['c','C'],['add','+']],
			]
		};
	}
	
  render() {
    return (
			<>
			{this.state.vals.map((val,key)=>{
					return (
						<div key={key}>
						{
							val.map((v,i)=>{
								return(
								<React.Fragment key={i}>
								<button className="square" onClick={()=>{this.props.getCharstate(v[0],v[1])}}>
									{v[1]}
								</button>
								</React.Fragment>
								)
							})
						}
						</div>
					)
				})
			}
			</>
    )
  }
}

ポイントは以下の部分です。この引数cとsは子コンポーネントのイベントの引数に入っていたもので、それを直接プッシュキーを操作するgetCharメソッドの引数に渡しています。

Calc2.js
    <Setkey
        getCharstate = { (c,s)=>this.getChar(c,s)}
    />

3:子コンポーネントから値のみを受け渡す

子コンポーネントからロード時に値を受け渡すにはライフサイクルメソッドの一つ、componentDidMountメソッドを利用します。先程のシステムからタイトルを子コンポーネントから取得するように改造してみました。

注意点は親コンポーネントにも受け皿となる変数を定義しておく必要があります。また、何も処理を施さず親コンポーネントで値を表示させるだけなら、コールバック関数をsetStateメソッドで受け取ることもできます。

Calc3.js
import React from "react";
import Setkey from "./Setkey3"

export default class Calc extends React.Component {
	constructor(){
		super()
		this.state = {
			lnum: null, //今までの値
			cnum: 0, //追加の値
			sum: 0, //合計値
			str: '', //打ち込んだ文字
			sign: '', //計算記号
			title: '', //タイトル格納用
		}
		this.getCharstate = this.getChar.bind(this) //メソッドのバインド
	}
	
  /*中略*/


	render(){
		return (
			<>	
			<h4>{this.state.title}</h4>
			{
				<Setkey
					setTitle = {(t)=>this.setState({title:t})}
					getCharstate = { (c,s)=>this.getChar(c,s)}
				/>
			}
			<div>
				<p>打ち込んだ文字:{this.state.str}</p>
				<p>合計:{this.state.sum}</p>
			</div>
			</>
		)
  }
}
Setkey3.js
import React from "react";

export default class Setkey extends React.Component {
	constructor(props){
		super(props)
		this.state = {
      vals:[
        [['7','7'],['8','8'],['9','9'],['div','÷']],
        [['4','4'],['5','5'],['6','6'],['mul','×']],
        [['1','1'],['2','2'],['3','3'],['sub','-']],
        [['0','0'],['eq','='],['c','C'],['add','+']],
			]
		};
	}
    //このメソッドがライフサイクルメソッドで、ロード時に実行される
	componentDidMount(){
		const title = "クラスコンポーネントで電卓演習"
		this.props.setTitleState(title) //同期用メソッド
	}
	
  render() {
    return (
			<>
			{this.state.vals.map((val,key)=>{
					return (
						<div key={key}>
						{
							val.map((v,i)=>{
								return(
								<React.Fragment key={i}>
								<button className="square" onClick={()=>{this.props.getCharstate(v[0],v[1])}}>
									{v[1]}
								</button>
								</React.Fragment>
								)
							})
						}
						</div>
					)
				})
			}
			</>
    )
  }
}

関数コンポーネントの場合

関数コンポーネントの場合は、新しい記事で独立させました。

0
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
4