개발자도구에 찍힌
console.log(title, content)
값은 총체적 난국이었다. title
엔 객체가 들어오고, content
는 undefined값이 찍힌다.
문제가 발생한 코드는 아래와 같다.
(뎁스구조를 명시하고, import, export는 생략되었다)
// 자식컴포넌트 ToDoForm
function ToDoForm({
title, content,
onClickAddTask, onChangeContents,
}: ToDoFormProps) {
return (
<form className='to-do-form'>
<div>
<label htmlFor="title">제목</label>
<input
type="text"
id="title"
onChange={onChangeContents}
value={title}
name="title"
/>
<label htmlFor="content">내용</label>
<input
type="text"
id="content"
onChange={onChangeContents}
value={content}
name="content"
/>
</div>
<div>
<button
type="button"
onClick={() => onClickAddTask()}
>
추가하기
</button>
</div>
</form >
);
}
// 부모컴포넌트 App
function App() {
const [contents, setContents] = useState<Contents>({
tasks: [{
id: 1,
title: '',
content: '',
isDone: false,
}],
});
const {
tasks, tasks: [title, content]
} = contents;
const newTask = tasks[tasks.length - 1];
const handleChangeContents = (event: React.ChangeEvent<HTMLInputElement>) => {
const { target: { name, value } } = event;
setContents({
...contents,
tasks: [{
...newTask,
[name]: value,
}],
})
}
function handleClickAddTask() {
const newId = newTask.id + 1;
setContents({
...contents,
tasks: [...tasks,
{
id: newId,
title: '',
content: '',
isDone: false,
}
],
})
}
return (
<>
<TopNavBar />
<ToDoForm
title={title}
content={content}
onClickAddTask={handleClickAddTask}
onChangeContents={handleChangeContents}
/>
</>
);
}
원인을 알기 위해 일단 '[object Object]'
가 무엇인지 알아야할 것 같았다. 찾다보니 이번주에 공부하고 있는 주제인 프로토타입과 관련이 있었다.
Object.prototype.toString
은 인자로 넘겨받은 object의 클래스 타입을 반환하는 메소드다.
자바스크립트는 객체지향 프로그래밍이다. 즉, 원시타입을 제외한 나머지 값들은 모두 객체다. 그리고 js의 타입들 중 null과 undefined를 제외한 모든 타입들은 Object의 인스턴스들이다.
즉, [[prototype]]
프로퍼티를 가지고 있어 프로토타입 체이닝을 통해 Object.prototype
까지 도달할 수 있다는 뜻이다. 실제로 Number instanceof Object
를 콘솔창에 찍어보면 true를 반환하는 것을 확인할 수 있다.
function A(a) {
this.a = a;
this.toString = function() {
return this.a;
}
}
const NewA = new A('sunyoung');
NewA.toString(); // NewA인스턴스는 A생성자함수의 메서드 toString을 사용할 수 있다.
// 'sunyoung'
js의 모든 객체는 자신만의 메소드를 가질 수 있다. 위 예제의 프로세스는 아래와 같다. 아래의 반복작업은 toString
을 찾을때 까지 혹은 [[prototype]]
프로퍼티에 연결된 객체가 null일 때 까지 반복한다.
1/ toString
을 호출했을 때 자바스크립트 엔진은 NewA
가 자신의 스코프에서 toString
을 갖고있는지 확인한다.
2-1/ 만약 갖고 있으면, 해당 스코프의 toString
이 호출된다.
2-2/ 만약 갖고 있지 않다면, NewA
의 [[prototype]]
프로퍼티(즉, __proto__
접근자 프로퍼티)에 연결된 상위객체로 들어가 그 스코프에 toString이 있는지 확인한다.
3-1/ 만약 갖고 있으면 해당 스코프 내의 toString
이 실행된다.
3-2/ 갖고 있지 않다면 같은 작업을 반복한다.
다시 본론으로 돌아와, Object.prototype.toString
은 인자로 넘겨받은 object의 클래스 타입을 반환하는 메소드다. Object.prototype.toString.apply({})
을 콘솔창에 출력하면 '[object Object]'
이 출력된다.
여기서 call, apply는 아래에서 볼 수 있듯이, [1,2,3]
인자를 전달했을 때 this
가 그 배열을 참조할 수 있게 해준다. 아무 인자도 전달하지 않으면 this
가 Window
전역객체를 참조한다.
function print() {
console.log(this);
}
print(); // Window
print.call([1, 2, 3]); // [1, 2, 3]
print.apply([1, 2, 3]); // [1, 2, 3]
toString의 동작방식은 ECMAScript 명세서에 정의되어 있다. 아래와 같이 해당 명세서에 따르면 Object.prototype.toSting
은 인자로 객체를 전달 받았기 때문에, '[object Object]'
을 전달받은게 된다.
다시 처음으로 돌아가보자. 내가 처음에 한 말을 인용하자면,
console.log(title, content)`값은 총체적 난국이었다.
title엔 객체가 들어오고, content는 undefined값이 찍힌다.
typeof title
은 object로 콘솔창에 출력된다. 결국 title
에 객체값이 들어오기 때문에 인풋창에 '[object Object]'
가 찍히는거였다.
아래 코드에서 내가 구조분해할당을 잘못하고 있어서 title
에 객체가 들어오는거였다.
const [contents, setContents] = useState<Contents>({
tasks: [{
id: 1,
title: '',
content: '',
isDone: false,
}],
});
const {
tasks, tasks: [title, content]
} = contents;
그래서 코드를 아래와 같이 고쳐줘서 해결할 수 있었다.
const [contents, setContents] = useState<Contents>({
tasks: [{
id: 1,
title: '',
content: '',
isDone: false,
}],
});
const {
tasks, tasks: [title, content]
} = contents;
문제는 해결했지만 왜 input태그의 value값에 객체가 들어오니까 '[object Object]'
로 반환되는걸까? How to Display JavaScript Objects? 페이지를 보면, 아래와 같이 doc에서 id값이 'demo'인 요소를 찾아서 innerHTML값을 person(객체)로 할당하여 DOM을 조작하려한다. 그러면 내가 의문을 갖는 것과 동일하게 '[object Object]'
가 p태그안에 들어가는 것을 볼 수 있다. 그러나 객체를 배열로 변환하거나 프로퍼티값을 직접 넣어주면 p태그안에 값들이 잘 출력된다. 왜 그러는걸까? 이는 innerHTML이 동작하는 방식과 관련이 있다.
<!DOCTYPE html>
<html>
<body>
<h2>JavaScript Objects</h2>
<p>Displaying a JavaScript object will output [object Object]:</p>
<p id="demo"></p>
<script>
const person = {
name: "John",
age: 30,
city: "New York"
};
document.getElementById("demo").innerHTML = person;
</script>
</body>
</html>
Element.innerHTML 문서를 보면, 아래와 같이 나와있다. 특히, 문자열 htmlString innerHTML을 파싱(구문 분석)하여 노드로 대체한다고 나와있는 부분을 보자.
즉, 아래와 같은 형태가 된다. 결국 innerHTML
은 주어진 값을 문자열로 toString
으로 변환하는 것이고, Object가 기본으로 toString
이 [object Object]
이기 때문에 그렇게 나온 것을 확인할 수 있었다.
['<div>Hello</div>'].toString() // '<div>Hello</div>'
({value: '<div>Hello</div>'}).toString() // // '[object Object]'