[๋ฆฌ์กํธ๋ฅผ ๋ค๋ฃจ๋ ๊ธฐ์ ] 5์ฅ - ref: DOM์ ์ด๋ฆ ๋ฌ๊ธฐ
์ด ๊ธ์ ๊น๋ฏผ์ค(velopert)๋์ ๋ฆฌ์กํธ๋ฅผ ๋ค๋ฃจ๋ ๊ธฐ์ ์์ฐธ์กฐํ์ฌ ์์ฑํ ๊ธ์ ๋๋ค.
์ผ๋ฐ HTML์์ DOM ์์์ ์ด๋ฆ์ ๋ฌ ๋ id๋ฅผ ์ฌ์ฉํ๋ค.
<div id="my-element"></div>
์ด๋ ๊ฒ ์์์ id๋ฅผ ๋ฌ๋ฉด CSS์์ ํน์ id์ ํน์ ์คํ์ผ์ ์ ์ฉํ๊ฑฐ๋ ์๋ฐ์คํฌ๋ฆฝํธ์์ ํด๋น id๋ฅผ ๊ฐ์ง ์์๋ฅผ ์ฐพ์ ์์ ํ ์ ์๋ค.
HTML์์ id๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ฒ๋ผ, ๋ฆฌ์กํธ ๋ด๋ถ์์ DOM์ ์ด๋ฆ์ ๋ค๋ ๋ฐฉ๋ฒ์ด ์๋๋ฐ๊ทธ๊ฒ์ด ref(reference)์ ๊ฐ๋ ์ด๋ค.
๋ฆฌ์กํธ ์ปดํฌ๋ํธ ์์์๋ id๋ฅผ ์ฌ์ฉํ ์ ์์ง๋ง, HTML์์ DOM์ id๋ ์ ์ผํด์ผํ๋๋ฐ, ์ปดํฌ๋ํธ๊ฐ ์ฌ๋ฌ ๋ฒ ์ฌ์ฉ๋๋ฉด ์ค๋ณต id๊ฐ ์๊ธฐ๋ ์๋ชป๋ ์ฌ์ฉ์ด๋ค. ref๋ ์ ์ญ์ ์ผ๋ก ์๋ํ์ง ์๊ณ , ์ปดํฌ๋ํธ ๋ด๋ถ์์๋ง ์๋ํ๊ธฐ ๋๋ฌธ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๋๋ค.
#
ref๋ ์ด๋ค ์ํฉ์์ ์ฌ์ฉํด์ผ ํ ๊น?ref๋ "ํน์ DOM์ ๊ผญ ์ง์ ์ ์ผ๋ก ๊ฑด๋๋ ค์ผ ํ ๋" ์ฌ์ฉํ๋ค. ์์ ์๋ฐ์คํฌ๋ฆฝํธํน์ jQuery๋ก ๋ง๋ ์น์ฌ์ดํธ์์ input ์ ๊ฒ์ฆํ ๋๋ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ๋ค.
<!DOCTYPE html><html><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>Example</title> <style> .success { background-color: lightgreen; } .failure { background-color: lightcoral; } </style> <script> function validate() { var input = document.getElementById('password'); input.className=''; if(input.value==='0000') { input.className='success'; } else { input.className='failure'; } } </script></head><body> <input type="password" id="password"></input> <button onclick="validate()">Validate</button></body></html>
๋ฆฌ์กํธ์์๋ ์ด๋ฐ ์์
์ด ๊ตณ์ด DOM์ ์ ๊ทผํ์ง ์์๋ state
๋ก ๊ตฌํํ ์ ์๋ค. src ๋๋ ํ ๋ฆฌ์ ValidationSample.css
์ ValidationSample.js
๋ฅผ ๋ง๋ค์ด๋ณด์.
ValidationSample.css
.success { background-color: lightgreen;}
.failure { background-color: lightcoral;}
ValidationSample.js
import React, {Component} from 'react';import './ValidationSample.css';
class ValidationSample extends Component { state = { password: '', clicked: false, validated: false, };
handleChange = (e) => { this.setState({ password: e.target.value, }); };
handleButtonClick = () => { this.setState({ clicked: true, validated: this.state.password === '0000', }); };
render() { return ( <div> <input type="password" value={this.state.password} onChange={this.handleChange} className={ this.state.clicked ? this.state.validated ? 'success' : 'failure' : '' } /> <button onClick={this.handleButtonClick}>Validate</button> </div> ); }}
export default ValidationSample;
ValidationSampe.js
๋ฅผ ๋ณด๋ฉด, input์ onChange
์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ ๋, handleChange
๋ฅผ ํธ์ถํ์ฌ password ๊ฐ์ ์
๋ฐ์ดํธํ๊ณ , button ์์ onClick
์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ ๋ handleButtonClick
์ ํธ์ถํ์ฌ clicked
๊ฐ์ ์ฐธ์ผ๋ก ์ค์ ํ๊ณ , validated
๊ฐ์ ๊ฒ์ฆ ๊ฒฐ๊ณผ๋ก ์ค์ ํ๋ค.
input ์ className
์ ๋ฒํผ์ ๋๋ฅด๊ธฐ ์ ์๋ ๋น ๋ฌธ์์ด, ๋ฒํผ์ ๋๋ฅธ ํ์ ๊ฒ์ฆ์ด์ฑ๊ณตํ๋ฉด success
, ์คํจํ๋ฉด failure
๋ก ์ค์ ํ๋ค. ์ด์ ๋ฐ๋ผ input ์ ์์์ด์ด๋ก์ ๋๋ ๋นจ๊ฐ์์ผ๋ก ๋ํ๋๋ค.
App ์ปดํฌ๋ํธ์์ ValidationSample
์ปดํฌ๋ํธ๋ฅผ ๋ถ๋ฌ์ ๋ ๋๋งํด๋ณธ๋ค.
App.js
import React, {Component} from 'react';import ValidationSample from './ValidationSample';
class App extends Component { render() { return <ValidationSample />; }}
export default App;
์ํ๋ ๊ฒฐ๊ณผ๊ฐ ์ ๋ํ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค. ์ด๋ ๊ฒ ์๋ฐ์คํฌ๋ฆฝํธ์์๋ DOM์์ ๊ทผํด์ผ ํ๋ ๊ธฐ๋ฅ์ ๋ฆฌ์กํธ์์๋ state๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํํ ์ ์๋ค. ํ์ง๋ง state ๋ง์ผ๋ก ํด๊ฒฐํ ์ ์๋ ๊ธฐ๋ฅ๋ค์ด ์๋ค.
(์์)
ํน์ input์ ํฌ์ปค์ค ์ฃผ๊ธฐ
์คํฌ๋กค ๋ฐ์ค ์กฐ์ํ๊ธฐ
Canvas ์์์ ๊ทธ๋ฆผ ๊ทธ๋ฆฌ๊ธฐ
์ด๋ ๊ฒ DOM์ ์ง์ ์ ๊ทผํด์ผ ํ๋ ์ํฉ์ ์ํด ref๋ฅผ ์ฌ์ฉํ๋ค.
#
ref ์ฌ์ฉref๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ ๊ฐ์ง์ด๋ค.
#
์ฝ๋ฐฑ ํจ์๋ฅผ ํตํ ref ์ค์ ref๋ฅผ ๋ง๋๋ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ๋ฐฉ๋ฒ์ ์ฝ๋ฐฑ ํจ์๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค. ref๋ฅผ ๋ฌ๊ณ ์ ํ๋์์์ ์ฝ๋ฐฑ ํจ์ ref๋ฅผ props๋ก ์ ๋ฌํด์ฃผ๋ฉด ๋๋ค. ์ด ์ฝ๋ฐฑ ํจ์๋ ref ๊ฐ์ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ๊ณ , ref๋ฅผ ์ปดํฌ๋ํธ์ ๋ฉค๋ฒ ๋ณ์๋ก ์ค์ ํด์ค๋ค.
<input ref={(ref) => { this.input = ref; }}/>
์ด๋ ๊ฒ ํ๋ฉด this.input
์ด input ์์์ DOM์ ๊ฐ๋ฆฌํค๊ฒ ๋๋ค. ์ด ๋, ์ด๋ฆ์ this.input
๋ฟ๋ง ์๋๋ผ ์ํ๋ ๊ฒ์ผ๋ก ์์ ๋กญ๊ฒ ์ค์ ํ ์ ์๋ค.
#
createRef๋ฅผ ํตํ ref ์ค์ ๋ฆฌ์กํธ์ ๋ด์ฅ๋์ด ์๋ createRef ๋ผ๋ ํจ์๋ฅผ ์ฌ์ฉํด ref๋ฅผ ๋ง๋ค ์๋ ์๋ค. ์ด ๊ธฐ๋ฅ์ ๋ฆฌ์กํธ v16.3๋ถํฐ ๋์ ๋์๋ค.
import React, {Component} from 'react';
class RefSample extends Component { input = React.createRef();
handleFocus = () => { this.input.current.focus(); };
render() { return ( <div> <input ref={this.input} /> </div> ); }}
export default RefSample;
createRef๋ฅผ ์ฌ์ฉํ์ฌ ref๋ฅผ ๋ง๋ค๋ ค๋ฉด ๋จผ์ ์ปดํฌ๋ํธ ๋ด๋ถ์์ ๋ฉค๋ฒ ๋ณ์๋ก React.createRef()
๋ฅผ ๋ด์์ค๋ค. ๊ทธ๋ฆฌ๊ณ ํด๋น ๋ฉค๋ฒ ๋ณ์๋ฅผ ref๋ฅผ ๋ฌ๊ณ ์ ํ๋ ์์์ ref props๋ก ๋ฃ์ด์ฃผ๋ฉด ๋๋ค.
์ด๋ ๊ฒ ์ค์ ํ๊ณ ๋์ค์ DOM์ ์ ๊ทผํ๋ ค๋ฉด this.input.current
์ฒ๋ผ ๋ค์ .current
๋ฅผ ๋ฃ์ด์ฃผ์ด์ผ ํ๋ค.
#
ref๋ก ๋ฒํผ ํด๋ฆญ ์ input์ผ๋ก ํฌ์ปค์ค ์ด๋ ๊ตฌํ์์์ ๋ง๋ค์๋ ValidationSample
์์ Validate ๋ฒํผ ํด๋ฆญ ์ input์ผ๋ก ํฌ์ปค์ค๊ฐ๋์ด๊ฐ๋๋ก ์ฝ๋๋ฅผ ์์ ํด๋ณธ๋ค.
#
input์ ref ๋ฌ๊ธฐ๋ฐฐ์ด ๋๋ก input ์์์ ref๋ฅผ ์ ์ฉํ๋ค.
ValidationSample.js
(...) <input> ref={(ref) => this.input=ref} (...) />
onClick
์ด๋ฒคํธ ์์ #
๋ฒํผ ๋ฒํผ์์ onClick
์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋ input์ ํฌ์ปค์ค๋ฅผ ์ฃผ๋๋ก ์ฝ๋๋ฅผ ์์ ํ๋ค. ref ๋ฅผ ์ ์ฉํ๊ธฐ ๋๋ฌธ์ ์ด์ this.input
์ผ๋ก DOM์ ์ ๊ทผํ ์ ์์ผ๋ฏ๋ก, ์ผ๋ฐ DOM ์ ํจ์๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
ValidationSample.js
handleButtonClick = () => { this.setState({ clicked: true, validated: this.state.password === '0000', }); this.input.focus();};
์ด์ ์ฝ๋๋ฅผ ์คํํด๋ณด๋ฉด ๋ฒํผ ํด๋ฆญ์ ํฌ์ปค์ค๊ฐ input ์์๋ก ๋์ด๊ฐ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
#
์ปดํฌ๋ํธ์ ref ๋ฌ๊ธฐ๋ฆฌ์กํธ์์๋ ์ปดํฌ๋ํธ์๋ ref๋ฅผ ๋ฌ ์ ์๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ปดํฌ๋ํธ ๋ด๋ถ์ ์๋ DOM ์ ์ปดํฌ๋ํธ ์ธ๋ถ์์ ์ฌ์ฉํ ์ ์๋ค.
#
์ฌ์ฉ๋ฒ<MyComponent ref={(ref) => { this.myComponent = ref; }}/>
์ด๋ ๊ฒ ํ ์ดํ์, myComponent.handleClick
, myComponent.input
๋ฑ์ผ๋ก ์ปดํฌ๋ํธ ๋ด๋ถ ref(DOM ์์)์ ์ ๊ทผํ ์ ์๊ฒ ๋๋ค.
#
์คํฌ๋กค ๋ฐ์ค ์์ ์ด์ ์คํฌ๋กค ๋ฐ์ค ์์ ๋ฅผ ๋ง๋ค์ด ๋ณธ๋ค.
ScrollBox.js
import React, {Component} from 'react';
class ScrollBox extends Component { render() { const style = { border: '1px solid black', height: '300px', width: '300px', overflow: 'auto', position: 'relative', };
const innerStyle = { width: '100%', height: '650px', background: 'linear-gradient(white, black)', };
return ( <div style={style} ref={(ref) => { this.box = ref; }} > <div style={innerStyle} /> </div> ); }}
export default ScrollBox;
App ์ปดํฌ๋ํธ์์๋ ๊ธฐ์กด ValidationSample
์ ์ง์ฐ๊ณ ScrollBox
์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๋ค.
App.js
import React, {Component} from 'react';import ScrollBox from './ScrollBox';
class App extends Component { render() { return ( <div> <ScrollBox /> </div> ); }}
export default App;
์ฝ๋๋ฅผ ์ ์ฅํ๋ฉด, ์น ๋ธ๋ผ์ฐ์ ์ ์คํฌ๋กค ๋ฐ์ค๊ฐ ์ ๋ ๋๋ง๋๋ค.
์ด์ ๋ฒํผ์ ์ถ๊ฐํ๊ณ ๋ฒํผ์ ํด๋ฆญํ์ ๋, ์คํฌ๋กค ๋ฐ์ค์ ์คํฌ๋กค๋ฐ๋ฅผ ๋งจ ์๋์ชฝ์ผ๋ก๋ด๋ฆฌ๋ ๋ฉ์๋๋ฅผ ๋ง๋ค์ด๋ณธ๋ค. ์ด ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด DOM ๋ ธ๋๊ฐ ๊ฐ์ง ๊ฐ์ ์ฌ์ฉํ๋ค.
scrollTop
: ์ธ๋ก ์คํฌ๋กค๋ฐ ์์นscrollHeight
: ์คํฌ๋กค์ด ์๋ ๋ฐ์ค ์์div
๋์ดclientHeight
: ์คํฌ๋กค์ด ์๋ ๋ฐ์ค์ ๋์ด
์คํฌ๋กค์ ๋ด๋ ค์ผ ๋ณผ ์ ์๋ ๋ฐ์ค ๋ด๋ถ์ ๊ธด ์ฝํ ์ธ โ
scrollHeight
๊ธด ์ฝํ ์ธ ๋ฅผ์คํฌ๋กค๋ฐ๋ก ๊ฐ์ธ๋ ๋ฐ์ค โclientHeight
์คํฌ๋กค๋ฐ์ ์์น scrollTop
์ 0๋ถํฐ (๋ฐ์ค ๋ด๋ถ์ ๊ธด ์ฝํ
์ธ - ์คํฌ๋กค๋ฐ๋ก ๊ฐ์ธ๊ณ ์๋ ๋ฐ๊นฅ์ ๋ฐ์ค)์ ํฌ๊ธฐ ๊ฐ์ ๊ฐ์ง๋ค. (๋ฐ๊นฅ์ ๋ฐ์ค๊ฐ 300์ด๊ณ ๋ด๋ถ์ ์ฝํ
์ธ ๊ฐ 650 ์ด๋ผ๋ฉด ์คํฌ๋กค๋ฐ์ ์์น๊ฐ 0~350)
๋ฐ๋ผ์ ์คํฌ๋กค๋ฐ๋ฅผ ๋งจ ์๋์ชฝ์ผ๋ก ๋ด๋ฆฌ๋ ค๋ฉด scrollHeight
์์ clientHeight
๋ฅผ๋นผ๋ฉด ๋๋ค.
ScrollBox.js
import React, {Component} from 'react';
class ScrollBox extends Component { scrollToBottom = () => { const {scrollHeight, clientHeight} = this.box; this.box.scrollTop = scrollHeight - clientHeight; }; render() { const style = { border: '1px solid black', height: '300px', width: '300px', overflow: 'auto', position: 'relative', };
const innerStyle = { width: '100%', height: '650px', background: 'linear-gradient(white, black)', };
return ( <div style={style} ref={(ref) => { this.box = ref; }} > <div style={innerStyle} /> </div> ); }}
export default ScrollBox;
ScrollBox
์ปดํฌ๋ํธ์์, this.box
๋ก ์คํฌ๋กค๋ฐ๊ฐ ์๋ ๋ฐ์ค DOM์ ์ ๊ทผํ ์ ์๊ณ , scrollToBottom
์ด๋ผ๋ ๋ฉ์๋๊ฐ ์ด DOM์ ์ ๊ทผํ์ฌ ์คํฌ๋กค๋ฐ์ ์์น scrollTop
๋ฅผ ๋งจ ์๋๋ก ๋ณ๊ฒฝํ๋ค.
์ด๋ ๊ฒ ๋ง๋ค๊ณ ๋์ ๋ถ๋ชจ ์ปดํฌ๋ํธ์ธ App ์ปดํฌ๋ํธ์์ ScrollBox
์ปดํฌ๋ํธ์ ref๋ฅผ๋ฌ๊ณ , ๋ฒํผ์ ๋ง๋ ๋ค์, onClick
์ด๋ฒคํธ์ ScrollBox
์ปดํฌ๋ํธ์ ๋ฉ์๋์ธ scrollToBottom
๋ฅผ ์คํํ๋๋ก ํ๋ค.
App.js
import React, {Component} from 'react';import ScrollBox from './ScrollBox';
class App extends Component { render() { return ( <div> <ScrollBox ref={(ref) => (this.ScrollBox = ref)} /> <button onClick={() => this.ScrollBox.scrollToBottom()}> ๋งจ ๋ฐ์ผ๋ก </button> </div> ); }}
export default App;
๋งจ ๋ฐ์ผ๋ก ๋ฒํผ์ ๋๋ ์ ๋ ์คํฌ๋กค๋ฐ๊ฐ ๋งจ ๋ฐ์ผ๋ก ์ด๋ํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
๊ทธ๋ฐ๋ฐ ์ฃผ์ํ ์ ์ด ์๋๋ฐ, button ์์์ onClick
์ด๋ฒคํธ๋ฅผ ๋ฑ๋กํ ๋, onClick = {this.scrollBox.scrollToBottom}
์ผ๋ก ์์ฑํ๋ ๊ฒ์ด ๋ฌธ๋ฒ์์ผ๋ก ํ๋ฆฐ๊ฒ์ ์๋๋ค. ํ์ง๋ง ์ปดํฌ๋ํธ๊ฐ ์ฒ์ ๋ ๋๋ง ๋ ๋๋ this.scrollBox
๊ฐ์ด undefined ์ด๋ค. (ref ๋ฑ๋ก์ด ์ฝ๋ฐฑํจ์ ์ด๋ฏ๋ก)
๋ฐ๋ผ์ ํ์ดํ ํจ์ ๋ฌธ๋ฒ์ผ๋ก ์๋ก์ด ํจ์๋ฅผ ๋ง๋ค๊ณ , ๊ทธ ๋ด๋ถ์์ this.scrollBox.scrollToBottom
๋ฉ์๋๋ฅผ ์คํํ๋ฉด ๋ฒํผ์ ๋๋ฅผ ๋, (์ด๋ฏธ ํ๋ฒ ๋ ๋๋ง์ ๊ฑฐ์ณ this.scrollBox
๋ฅผ ์ค์ ํ ์์ ) this.scrollBox.scrollTobottom
๊ฐ์ ์ฝ์ด ์์ ์คํํ๋ฏ๋ก ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์๋๋ค.
#
์ ๋ฆฌ์ปดํฌ๋ํธ ๋ด๋ถ์์ DOM์ ์ง์ ์ ๊ทผํด์ผ ํ ๋๋ ref๋ฅผ ์ฌ์ฉํ๋ค. ํ์ง๋ง ref๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ์ํ๋ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ ์๋์ง๋ฅผ ๋ฐ๋์ ๊ณ ๋ คํด์ผ ํ๋ค.
์๋ก ๋ค๋ฅธ ์ปดํฌ๋ํธ๋ผ๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ๊ต๋ฅํ ๋ ref๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด๋ผ๊ณ ์คํดํ ์ ์๋๋ฐ, ๊ทธ๋ ๊ฒ ํ๋ ๊ฒ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์กฐ๋ฅผ ๊ผฌ์ด๊ฒ ๋ง๋ค ์ ์๋ ์ํํ ๋ฐฉ๋ฒ์ด๋ค. ๋ฐ๋ผ์ ๋ฐ์ดํฐ๋ฅผ ๊ต๋ฅํ ๋๋ ์ธ์ ๋ ๋ถ๋ชจ - ์์ ํ๋ฆ์ผ๋ก ๊ต๋ฅํ๋ ๊ฒ์ด ์ข๋ค.