Ref와 DOM
1. Ref를 사용해야 할 때
- 포커스, 텍스트 선택영역, 혹은 미디어의 재생을 관리할 때.
- 애니메이션을 직접적으로 실행시킬 때.
- 서드 파티 DOM 라이브러리를 React와 같이 사용할 때.
선언적으로 해결될 수 있는 문제에서는 ref 사용을 지양하세요.
2. Ref 생성하기
Ref는 React.createRef()
를 통해 생성되고 ref
어트리뷰트를 통해 React 엘리먼트에 부착됩니다. 보통, 컴포넌트의 인스턴스가 생성될 때 Ref를 프로퍼티로서 추가하고, 그럼으로서 컴포넌트의 인스턴스의 어느 곳에서도 Ref에 접근할 수 있게 합니다.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
3. Ref에 접근하기
render
메서드 안에서 ref가 엘리먼트에게 전달되었을 때, 그 노드를 향한 참조는 ref의 current
어트리뷰트에 담기게 됩니다.
const node = this.myRef.current;
ref의 값은 노드의 유형에 따라 다릅니다.
ref
어트리뷰트가 HTML 엘리먼트에 쓰였다면, 생성자에서React.createRef()
로 생성된ref
는 자신을 전달받은 DOM 엘리먼트를current
프로퍼티의 값으로서 받습니다.ref
어트리뷰트가 커스텀 클래스 컴포넌트에 쓰였다면,ref
객체는 마운트된 컴포넌트의 인스턴스를current
프로퍼티의 값으로서 받습니다.- 함수 컴포넌트는 인스턴스가 없기 때문에 함수 컴포넌트에 ref 어트리뷰트를 사용할 수 없습니다.
React-Ref 연습하면서 만들어 본 코드펜
Todo list
React 안티 패턴 => Ref로 수정
마우스 클릭 제어 시 satate 관리가 까다로워서 DOM에 직접 접근하는 방식을 사용하고 있었다. 왠지 찝찝해서 찾아보니 안티패턴이었다니..
사용하던 안티패턴
- 컴포넌트에 id속성 사용
- 컴포넌트는 (추후에)여러개가 생성될 수 있기 때문에 id를 사용하면 안된다!
- getElement나 querySelector 등 id나 class명으로 DOM에 직접적으로 접근
- 컴포넌트의 캡슐화를 위해 위와 같이 DOM접근을 하면 안된다.
이걸 어떻게 처리해야 하나 고민하던 차 구글링을 통해(갓구글!) Ref를 사용하면 된다는 것을 알게되었다.
state가 자꾸 늘어나서 코드는 지저분해지지만.. 그건 나중에 리팩토링하면 된다!
constructor의 state에 ref를 저장할 status 추가
this.state = {
titles: props.titles,
todos: props.todos,
indexOfTodos: props.indexOfTodos,
nowTitle: null,
searchState: {
isSearching: false,
text: null
},
displayState: "All",
isAddingTitle: false,
isAddingTodo: false,
titleInput: null, //* title Input Ref 저장
todoInput: null //* todo Input Ref 저장
};
ref를 관리할 함수
handleRef(type, ref) {
this.setState({ [type]: ref });
}
App의 onClick이벤트의 함수 부분을 바꿨다.
watchAppClick(e) {
const { isAddingTitle, titleInput, isAddingTodo, todoInput } = this.state;
if (isAddingTitle) { //title 추가 중
if (e.target !== titleInput) {
//클릭 이벤트 발생 시 타겟이 titleInput이 아니다!
this.handleIsAddingTitle(false); //title 추가 중 flag를 false로
if (titleInput) { //titleInput이 비어있지 않음
this.handleAddTitle(titleInput.value); //titleInput의 value를 새 title로 추가
this.handleRef("titleInput", null);
}
}
}
if (isAddingTodo) { //todo를 추가 중
console.log(isAddingTodo, todoInput);
if (e.target !== todoInput) {
//클릭 이벤트 타겟이 todoInput이 아님
this.handleIsAddingTodo(false); //todo 추가 중 flag를 false로
if (todoInput) {
this.handleAddTodo(this.state.nowTitle, todoInput.value); //nowTitle을 title로, todoInput의 value를 value로 새로운 todo 생성
this.handleRef("todoInput", null); //todoInput 초기화
}
}
}
}
Add title component
export default class AddTitle extends Component {
constructor(props) {
super(props);
this.titleInput = React.createRef();
this.handleOnClick = this.handleOnClick.bind(this);
this.handleOnChange = this.handleOnChange.bind(this);
this.handleKeyClick = this.handleKeyClick.bind(this);
}
handleOnChange(e) {
//input text에 변화가 있을 때 마다
// 1. now title 변경
// 2. titleInput 변경
this.props.handleNowTitle(e.target.value);
this.props.handleRef("titleInput", this.titleInput.current);
}
handleKeyClick(e) {
if (e.keyCode === 13) {
this.props.handleIsAddingTitle(false);
this.props.handleAddTitle(e.target.value);
}
}
handleOnClick() {
this.props.handleIsAddingTitle(true);
}
render() {
const { isAddingTitle } = this.props;
return (
<div>
{isAddingTitle ? <input type="text" ref={this.titleInput} onChange={this.handleOnChange} onKeyDown={this.handleKeyClick} placeholder="입력" autoFocus></input> : null}
<button onClick={this.handleOnClick}>+ 목록 추가</button>
</div>
);
}
}
Ant design사용
Ant design을 사용하려면 일단 npm install이나 yarn add를 해줘야 한다.
$ npm install --save antd
설치가 끝나면 아래의 이미지처럼 package.json파일의 dependencies에 antd가 생긴다.
그 후 antd에서 필요한 component와 스타일을 import해서 사용하면 된다.
import React from "react";
import { Checkbox } from "antd"; //antd의 Checkbox사용
import "antd/dist/antd.css"; //antd의 스타일 적용
import "./Todo.css";
export default function Todo({ todo, handleTodo, isSearching }) {
const parseTodo = JSON.parse(todo);
function handleCheckbox(e) {
handleTodo(parseTodo.index, "completed", !parseTodo.completed);
}
const spletedByEnter = parseTodo.text.split("\n");
return (
<div className="Todo">
{isSearching ? `[${parseTodo.title}] ` : null}
<Checkbox onClick={handleCheckbox} checked={parseTodo.completed} /> {/*antd 의 Checkbox 사용: Component처럼 사용한다*/}
<span>
{spletedByEnter.map((text, key) => (
<p key={key}>{text}</p>
))}
</span>
</div>
);
}
아래는 적용 후 화면
'TIL' 카테고리의 다른 글
191227(금) TIL. DB SQL-1 (0) | 2019.12.27 |
---|---|
191223(월) TIL. Promise (0) | 2019.12.24 |
191220(금) TIL React Todo list 4 (0) | 2019.12.20 |
191219(목) TIL React Todo list 3 (0) | 2019.12.19 |
191218(수) TIL React Todo list 2 (0) | 2019.12.18 |