#5 useEffect,ライフサイクル/じゃんけんゲーム


インフラのZero Choさんの講義を見て整理した内容です
https://www.inflearn.com/course/web-game-react

コード#コード#


クラスバージョン


ResponseCheck.jsx

import React, {Component} from 'react';

const rspCoord = {
	바위 : "0",
	가위 : "-142px",: '-284px',
}

const scores = {
	가위 : 1,
	바위 : 0,: -1,
}

const computerChoice = (imgCoord) => {
	
	return Object.entries(rspCoord).find((v) => {
		return v[1] === imgCoord;
	})[0];
}

class RSP extends Component {

	state = {
		result : "1",
		imgCoord : "0",
		score : 0,
	}
	interval;
	
	componentDidMount()
	{
		this.interval = setInterval(this.changeHand, 100);

	}
	componentDidUpdate()
	{
		console.log("componentDidUpdate");
	} 
	componentWillUnmount()
	{
		console.log("componentWillUnmount");
		clearInterval(this.interval);

	}
	changeHand = () => {
		{
			const {imgCoord} = this.state;	
			if (imgCoord === rspCoord.바위) {
				this.setState({
					imgCoord : rspCoord.가위
				});
			} else if (imgCoord === rspCoord.가위) {
				this.setState({
					imgCoord : rspCoord.});
			} else if (imgCoord === rspCoord.) {
					this.setState({
						imgCoord : rspCoord.바위
					});
			}
		}
	}
	onClickBtn = (choice) => (e) => {
		console.log(e);
		clearInterval(this.interval);
		const myScore = scores[choice];
		const cquScore = scores[computerChoice(this.state.imgCoord)];
		const diff = myScore - cquScore;
		if (diff === 0) {
			this.setState({
				result : "비겼습니다."
			})
		} else if ([-1, 2].includes(diff)) {
			this.setState((prevState) => {
				return {
					result : "이겼습니다!",
					score : prevState.score + 1,
				}
			})
		} else {
			this.setState((prevState) => {
				return {
					result : "졌습니다...",
					score : prevState.score - 1,
				}
			})
		}
		setTimeout(() => {
			this.interval = setInterval(this.changeHand, 100);
		}, 2000);
	}
	render() {
		const {result, score, imgCoord } = this.state;
		console.log("rendering");
		return (
			<>
			<div id="computer" style={{ background: `url(https://en.pimg.jp/023/182/267/1/23182267.jpg) ${imgCoord} 0` }} />
			<div>
			  <button id="rock" className="btn" onClick={this.onClickBtn('바위')}>바위</button>
			  <button id="scissor" className="btn" onClick={this.onClickBtn('가위')}>가위</button>
			  <button id="paper" className="btn" onClick={this.onClickBtn('보')}></button>
			</div>
			<div>{result}</div>
			<div>현재 {score}</div>
		  </>
		)
	}
}

export default RSP;

関数のバージョン


ResponseCheck.jsx

import React, {useRef, useState, useEffect, memo} from 'react';

const rspCoord = {
	바위 : "0",
	가위 : "-142px",: '-284px',
}

const scores = {
	가위 : 1,
	바위 : 0,: -1,
}

const computerChoice = (imgCoord) => {
	
	return Object.entries(rspCoord).find((v) => {
		return v[1] === imgCoord;
	})[0];
}

const RSP = () => {
	const [result, setResult] = useState("");
	const [imgCoord, setImgCoord] = useState("0");
	const [score, setScore] = useState(0);
	const interval = useRef(null);
	
	useEffect(() => { //componentDidMount, componentDidUpdate 역할
		interval.current = setInterval(changeHand, 300);
		return () => { // componentWillUnmount 역할
			clearInterval(interval.current);
		}		
	}, [imgCoord]);

	const changeHand = () => {
			if (imgCoord === rspCoord.바위) {
				setImgCoord(rspCoord.가위);
			} else if (imgCoord === rspCoord.가위) {
				setImgCoord(rspCoord.);
			} else if (imgCoord === rspCoord.) {
				setImgCoord(rspCoord.바위);
			}
	}
	const onClickBtn = (choice) => (e) => {
		console.log(e);
		clearInterval(interval.current);
		const myScore = scores[choice];
		const cquScore = scores[computerChoice(imgCoord)];
		const diff = myScore - cquScore;
		if (diff === 0) {
			setResult("비겼습니다.");
		} else if ([-1, 2].includes(diff)) {
			setResult("이겼습니다!");
			setScore((prevScore) => {
				return (prevScore + 1);
			})
		} else {
			setResult("졌습니다...");
			setScore((prevScore) => {
				return (prevScore - 1);
			})
		}
		setTimeout(() => {
			interval.current = setInterval(changeHand, 100);
		}, 2000);
	}
		return (
			<>
			<div id="computer" style={{ background: `url(https://en.pimg.jp/023/182/267/1/23182267.jpg) ${imgCoord} 0` }} />
			<div>
			  <button id="rock" className="btn" onClick={onClickBtn('바위')}>바위</button>
			  <button id="scissor" className="btn" onClick={onClickBtn('가위')}>가위</button>
			  <button id="paper" className="btn" onClick={onClickBtn('보')}></button>
			</div>
			<div>{result}</div>
			<div>현재 {score}</div>
		  </>
		)
}

export default RSP;

#5-1ライフサイクル


まず、コンポーネントを作成した後、パフォーマンスに問題が発生した場合は、PureComponentを考慮したほうがいいです.
import React, {Component, PureComponent, useRef, useState, memo, createRef} from 'react';

class RSP extends Component {

	state = {
		result : "",
		imgCoord : 0,
		score : 0,
	}
	render() {
		const {result, score, imgCoord } = this.state;
		return (

			<>
			<div id="computer" style={{ background: `url(https://en.pimg.jp/023/182/267/1/23182267.jpg) ${imgCoord} 0` }} />
			<div>
			  <button id="rock" className="btn" onClick={this.onClickBtn('바위')}>바위</button>
			  <button id="scissor" className="btn" onClick={this.onClickBtn('가위')}>가위</button>
			  <button id="paper" className="btn" onClick={this.onClickBtn('보')}></button>
			</div>
			<div>{result}</div>
			<div>현재 {score}</div>
		  </>
		)
	}
}

export default RSP;
基本コード構造.
パラメータとしてのimgcoordは、画像座標を指定する役割を果たします.

ライフサイクル


extended素子はclientです.jsxファイルでDOMをレンダリングしてアタッチした瞬間、特定の操作を実行できます.
構成部品の生成と消滅の瞬間に構成部品を管理できます.
shouldComponentUpdateもライフサイクルの1つです.

componentDidMount()


render()が初めて実行され、正常に実行された場合は、それを実行します.
レンダリング発生時に実行しない
非同期リクエストが頻繁に発生します.

shouldComponentUpdate(nextProps, nextState, nextContext)


値が変わったらtrueを返します.
変更しない場合はfalseに戻ります

componentDidUpdate()


を選択して設定できます.

componentWillUnmount()


親コンポーネントが除去される前に実行します.
ComponentDidMountで実行される非同期リクエストのクリアは比較的多くなります.

ライフサイクルの流れ


クラスの場合


作成(レンダリング):constructor → render → ref → componentDidMount変更:setState/props 변경 → shouldComponentUpdate(true) → render -> componentDidUpdate削除さくじょ:componentWillUnmount → 소멸

#5-2 setIntervalをライフサイクルに関連付ける

import React, {Component, PureComponent, useRef, useState, memo, createRef} from 'react';

class RSP extends Component {

	state = {
		result : "1",
		imgCoord : 0,
		score : 0,
	}
	interval;
	componentDidMount()
	{
		console.log("componentDidMount");
		this.interval = setInterval(() => {
			console.log("roop");
		}, 1000);

	}
	componentDidUpdate()
	{
		console.log("componentDidUpdate");
	} 
	componentWillUnmount()
	{
		console.log("componentWillUnmount");
		clearInterval(this.interval);

	}
	onClickBtn = () => {
	}
	render() {
		const {result, score, imgCoord } = this.state;
		console.log("rendering");
		return (

			<>
			<div id="computer" style={{ background: `url(https://en.pimg.jp/023/182/267/1/23182267.jpg) ${imgCoord} 0` }} />
			<div>
			  <button id="rock" className="btn" onClick={this.onClickBtn('바위')}>바위</button>
			  <button id="scissor" className="btn" onClick={this.onClickBtn('가위')}>가위</button>
			  <button id="paper" className="btn" onClick={this.onClickBtn('보')}></button>
			</div>
			<div>{result}</div>
			<div>현재 {score}</div>
		  </>
		)
	}
}

export default RSP;
componentDidMount()にsetIntervalが設定されています.1000ミリ秒周期の無限反復関数の内容.
単独で処理しないと、プログラムが閉じてもウィンドウがリフレッシュされてもsetIntervalは戻り続けます.つまり、余分なメモリが漏れてしまいます.
clearIntervalを使用して運転を停止できます.setIntervalをclassオブジェクトに割り当て、ComponentWillUnmountで重複を停止します.
import React, {Component, PureComponent, useRef, useState, memo, createRef} from 'react';

const rspCoord = {
	가위 : "0",
	바위 : "-142px",: '-284px',
}

const score = {
	가위 : "1",: "0",: "-1",
}

class RSP extends Component {

	state = {
		result : "1",
		imgCoord : "0",
		score : 0,
	}
	interval;
	componentDidMount()
	{
		console.log("check");

		this.interval = setInterval(() => {
			const {imgCoord} = this.state;
			console.log(imgCoord);
	
			if (imgCoord === rspCoord.바위) {
				this.setState({
					imgCoord : rspCoord.가위
				});
			} else if (imgCoord === rspCoord.가위) {
				this.setState({
					imgCoord : rspCoord.});
			} else if (imgCoord === rspCoord.) {
					this.setState({
						imgCoord : rspCoord.바위
					});
			}
		}, 2000);

	}
	componentDidUpdate()
	{
		console.log("componentDidUpdate");
	} 
	componentWillUnmount()
	{
		console.log("componentWillUnmount");
		clearInterval(this.interval);

	}
	onClickBtn = () => {

	}
	render() {
		const {result, score, imgCoord } = this.state;
		console.log("rendering");
		return (

			<>
			<div id="computer" style={{ background: `url(https://en.pimg.jp/023/182/267/1/23182267.jpg) ${imgCoord} 0` }} />
			<div>
			  <button id="rock" className="btn" onClick={this.onClickBtn('바위')}>바위</button>
			  <button id="scissor" className="btn" onClick={this.onClickBtn('가위')}>가위</button>
			  <button id="paper" className="btn" onClick={this.onClickBtn('보')}></button>
			</div>
			<div>{result}</div>
			<div>현재 {score}</div>
		  </>
		)
	}
}

export default RSP;
setIntervalで非同期反復ハサミ石布の関数を実行します.
非同期では、関数の外部の変数を参照すると、エンクロージャの問題が発生します.

#5-3じゃんけんゲームを作る

import React, {Component, PureComponent, useRef, useState, memo, createRef} from 'react';

const rspCoord = {
	바위 : "0",
	가위 : "-142px",: '-284px',
}

const scores = {
	가위 : 1,
	바위 : 0,: -1,
}

const computerChoice = (imgCoord) => {
	
	return Object.entries(rspCoord).find((v) => {
		return v[1] === imgCoord;
	})[0];
}

class RSP extends Component {

	state = {
		result : "1",
		imgCoord : "0",
		score : 0,
	}
	interval;
	
	componentDidMount()
	{
		this.interval = setInterval(() => this.changeHand(this.state.imgCoord), 100);

	}
	componentDidUpdate()
	{
		console.log("componentDidUpdate");
	} 
	componentWillUnmount()
	{
		console.log("componentWillUnmount");
		clearInterval(this.interval);

	}
	changeHand = (imgCoord) => {
		{
			console.log(imgCoord);
	
			if (imgCoord === rspCoord.바위) {
				this.setState({
					imgCoord : rspCoord.가위
				});
			} else if (imgCoord === rspCoord.가위) {
				this.setState({
					imgCoord : rspCoord.});
			} else if (imgCoord === rspCoord.) {
					this.setState({
						imgCoord : rspCoord.바위
					});
			}
		}
	}
	onClickBtn = (choice) => {
		clearInterval(this.interval);
		const myScore = scores[choice];
		const cquScore = scores[computerChoice(this.state.imgCoord)];
		const diff = myScore - cquScore;
		if (diff === 0) {
			this.setState({
				result : "비겼습니다."
			})
		} else if ([-1, 2].includes(diff)) {
			this.setState((prevState) => {
				return {
					result : "이겼습니다!",
					score : prevState.score + 1,
				}
			})
		} else {
			this.setState((prevState) => {
				return {
					result : "졌습니다...",
					score : prevState.score - 1,
				}
			})
		}
		setTimeout(() => {
			this.interval = setInterval(() => this.changeHand(this.state.imgCoord), 100);
		}, 2000);
	}
	render() {
		const {result, score, imgCoord } = this.state;
		console.log("rendering");
		return (
			<>
			<div id="computer" style={{ background: `url(https://en.pimg.jp/023/182/267/1/23182267.jpg) ${imgCoord} 0` }} />
			<div>
			  <button id="rock" className="btn" onClick={() => this.onClickBtn('바위')}>바위</button>
			  <button id="scissor" className="btn" onClick={() => this.onClickBtn('가위')}>가위</button>
			  <button id="paper" className="btn" onClick={() => this.onClickBtn('보')}></button>
			</div>
			<div>{result}</div>
			<div>현재 {score}</div>
		  </>
		)
	}
}

export default RSP;
ユーザがボタンを押す→Intervalを止める→スコア計算、計算結果計算→再Interval
settimeoutを2秒後にsetIntervalを実行させます.this.interval = setInterval(() => this.changeHand(this.state.imgCoord), 100); imgCoordをパラメータに変換してみます.正常に動作.

#5-4. 高次関数とQ&A


ユーティリティーモード(高次関数)

onClickBtn = (choice) => (e) => {
		console.log(e);
		clearInterval(this.interval);
		const myScore = scores[choice];
		const cquScore = scores[computerChoice(this.state.imgCoord)];
		const diff = myScore - cquScore;
		if (diff === 0) {
			this.setState({
				result : "비겼습니다."
			})
		} else if ([-1, 2].includes(diff)) {
			this.setState((prevState) => {
				return {
					result : "이겼습니다!",
					score : prevState.score + 1,
				}
			})
		} else {
			this.setState((prevState) => {
				return {
					result : "졌습니다...",
					score : prevState.score - 1,
				}
			})
		}
		setTimeout(() => {
			this.interval = setInterval(this.changeHand, 100);
		}, 2000);
	}
	render() {
		const {result, score, imgCoord } = this.state;
		console.log("rendering");
		return (
			<>
			<div id="computer" style={{ background: `url(https://en.pimg.jp/023/182/267/1/23182267.jpg) ${imgCoord} 0` }} />
			<div>
			  <button id="rock" className="btn" onClick={this.onClickBtn('바위')}>바위</button>
			  <button id="scissor" className="btn" onClick={this.onClickBtn('가위')}>가위</button>
			  <button id="paper" className="btn" onClick={this.onClickBtn('보')}></button>
			</div>
			<div>{result}</div>
			<div>현재 {score}</div>
		  </>
		)
	}
メソッドをrenderで呼び出すときに使用する矢印関数を、呼び出す関数の宣言部分に入れることができます.(順番が大事!)
上のコードでは、eにbuttonラベルに関する情報があります.

hooksのライフサイクル


hooksにはこのようなライフサイクルは存在しません.

setStateを書き続けたとき?


連続したsetStateがあれば、反応器は自分で集めて一度に処理します.

#5-5. HooksとUseEffect

import React, {useRef, useState, useEffect, memo} from 'react';

const rspCoord = {
	바위 : "0",
	가위 : "-142px",: '-284px',
}

const scores = {
	가위 : 1,
	바위 : 0,: -1,
}

const computerChoice = (imgCoord) => {
	
	return Object.entries(rspCoord).find((v) => {
		return v[1] === imgCoord;
	})[0];
}

const RSP = () => {
	const [result, setResult] = useState("");
	const [imgCoord, setImgCoord] = useState("0");
	const [score, setScore] = useState(0);
	const interval = useRef(null);
	
	useEffect(() => { //componentDidMount, componentDidUpdate 역할
		interval.current = setInterval(changeHand, 300);
		return () => { // componentWillUnmount 역할
			clearInterval(interval.current);
		}		
	}, [imgCoord]);

	const changeHand = () => {
			if (imgCoord === rspCoord.바위) {
				setImgCoord(rspCoord.가위);
			} else if (imgCoord === rspCoord.가위) {
				setImgCoord(rspCoord.);
			} else if (imgCoord === rspCoord.) {
				setImgCoord(rspCoord.바위);
			}
	}
	const onClickBtn = (choice) => (e) => {
		console.log(e);
		clearInterval(interval.current);
		const myScore = scores[choice];
		const cquScore = scores[computerChoice(imgCoord)];
		const diff = myScore - cquScore;
		if (diff === 0) {
			setResult("비겼습니다.");
		} else if ([-1, 2].includes(diff)) {
			setResult("이겼습니다!");
			setScore((prevScore) => {
				return (prevScore + 1);
			})
		} else {
			setResult("졌습니다...");
			setScore((prevScore) => {
				return (prevScore - 1);
			})
		}
		setTimeout(() => {
			interval.current = setInterval(changeHand, 100);
		}, 2000);
	}
		return (
			<>
			<div id="computer" style={{ background: `url(https://en.pimg.jp/023/182/267/1/23182267.jpg) ${imgCoord} 0` }} />
			<div>
			  <button id="rock" className="btn" onClick={onClickBtn('바위')}>바위</button>
			  <button id="scissor" className="btn" onClick={onClickBtn('가위')}>가위</button>
			  <button id="paper" className="btn" onClick={onClickBtn('보')}></button>
			</div>
			<div>{result}</div>
			<div>현재 {score}</div>
		  </>
		)
}

export default RSP;
hooksにはライフサイクルは存在しませんが、userEffectを使用して模倣することができます.

useEffect()


関数要素にはuserefやuserStateのように書くべきです.
componentDidMount、componentDidUpdate、componentWillUnmountとは一対一ではありませんが、3つを組み合わせた場合と同じ役割を果たします.
useEffect(() => { //componentDidMount, componentDidUpdate 역할
		console.log("다시 실행");
		interval.current = setInterval(changeHand, 300);
		return () => { // componentWillUnmount 역할
			console.log("종료");
			clearInterval(interval.current);
		}		
	}, [imgCoord]);
最初のパラメータは実行する関数で、2番目のパラメータ(配列)は変更する状態を配列に入れます.
コードを実行すると、setIntervalにもかかわらずuserEffect関数が繰り返し実行され、返されることがわかります.
2番目のパラメータに入る状態が変化するたびにuserEffectが実行されます.
2番目のファクタ
  • が空の場合、何を変更しても1回のみ実行されます.
  • setIntervalが設定されていてもすぐにclearIntervalが行われるので、事実上、setTimeoutが無限に繰り返されていると考えられます.
    これはhookの特徴に関連していますか.つまり、
  • の状態が変更されるたびに、関数全体が実行され、表示されますか.これはuseEffectだけでなく、関数全体が再実行されるためですか?
  • #5-6クラスとHooksライフサイクルの比較


    カテゴリ

    componentDidMount() {
    	this.setState({
    		imgCoord : 1,
    		score : 2,
    		result : 3,
    })
    }
    componentDidMount()、componentDidUpdate()、componentWillUnmount()など、指定した方法でのみ処理できます.
    ライフサイクルごとにすべてのステータスを担当します.
    ライフサイクルごとのすべてのステータス

    Hooks(関数)

    useEffect(() => {
    	setImgCoord();
    	setScore();
    	}		
    }, [imgCoord, score]);
    useEffect(() => {
    	setImgCoord();
    	setResult();
    	}		
    }, [imgCoord, result]);
    UserEffectは複数回使用できます.つまり、州ごとに異なる処理を行うことができます.
  • セグメントは、2番目のパラメータである配列の内部において、userEffectが実行されるたびに値が変化する状態を加えなければならない.
  • 各ステータスのすべてのライフサイクルを設定できます.
    各州または複数の州のすべてのライフサイクル

    UseLayoutEffect


    画面が完全に変更された後(再調整後)に実行されるuserEffectとは異なり、ユーザは、画面の拡大や縮小などのレイアウトの変化を検出することができる.