이 예제는 모던 자바스크립트 튜토리얼 과제입니다.
중첩된 객체의 데이터로 ul·li 리스트를 생성하는 createTree 함수를 만들어 보세요.
const data = {
"Fish": {
"trout": {},
"salmon": {}
},
"Tree": {
"Huge": {
"sequoia": {},
"oak": {}
},
"Flowering": {
"apple tree": {},
"magnolia": {}
}
}
};
코드 형식:
let container = document.getElementById('container');
createTree(container, data); // container 요소 내에 트리를 생성합니다.
내용이 없는 <ul></ul>
처럼 ‘불필요한’ 요소가 트리에 존재해서는 안된다는 점을 참고하세요.
코드 요구사항에 자식 노드가 없는 요소는 트리에 존재하면 안된다고 되어 있으므로, 전달받는 데이터가 빈 객체가 아닐 때만 리스트를 생성할 수 있습니다.
따라서, 전달받는 객체가 빈 객체인지 확인하는 작업이 필요합니다.
Object.keys(obj)
는 객체에 정의된 프로퍼티 키로 구성된 새로운 배열을 만들어주기 때문에, 이 값의 길이가 0
보다 크다면 해당 객체는 프로퍼티가 존재함을 알 수 있습니다.
const objKeys = Object.keys(data);
if (objKeys.length > 0) {
// 코드 작성....
}
이제 빈 객체는 모두 걸러냈으니, 해당 배열은 프로퍼티가 무조건 존재하겠죠?
배열에 프로퍼티가 할당되어 있기 때문에, 이를 반복하여 <li></li>
요소의 자식 노드로 할당해주고 이를 <ul>
요소에 추가해주면 하나의 리스트를 만들 수 있습니다.
const objKeys = Object.keys(data);
if (objKeys.length > 0) {
const ul = document.createElement('ul');
objKeys.forEach(key => {
const li = document.createElement('li');
li.textContent = key;
ul.append(li);
});
return ul; // 리스트를 감싸는 ul 요소 리턴.
}
하지만, 아직 중첩된 객체에까지는 접근하지 못했습니다.
중첩된 객체도 마찬가지로 위와 같은 작업이 필요하기 때문에, 코드를 반복하는 작업이 필요할 것 같습니다. 위의 코드를 재사용하기 위해, 함수로 일단 변경해보겠습니다.
function createListTree (data: any) {
const objKeys = Object.keys(data);
if (objKeys.length > 0) {
const ul = document.createElement('ul');
objKeys.forEach(key => {
const li = document.createElement('li');
li.textContent = key;
ul.append(li);
});
return ul;
}
};
이제 함수 안에서 같은 함수를 반복하면 중첩된 객체에도 동일한 작업을 실행할 수 있을 겁니다.
그런데 함수를 어디에 놓아야 할까요?
중첩된 객체는 이미 생성된 <li></li>
의 자식 요소인 <ul></ul>
리스트로 생성될 것입니다.
그렇다면, 상위에 생성된 <li></li>
가 그의 부모 요소인 ul
로 합쳐지기 전에 실행한다면, 중첩된 객체의 <ul></ul>
리스트가 부모 요소 li
의 자식 노드로 추가될 것입니다.
(사실 순서를 바꿔서 부모 요소에 먼저 추가하고, 중첩된 객체에서 가져온 ul
을 추가해도 상관없지만, 완성된 <li>
요소로 추가하는 것이 의미적으로 더 낫다고 판단했습니다.)
말로 설명하니 복잡한 것 같아, 일단 먼저 코드로 구현해보도록 하겠습니다.
function createListTree (data: any) {
const objKeys = Object.keys(data);
if (objKeys.length > 0) {
const ul = document.createElement('ul');
objKeys.forEach(key => {
const li = document.createElement('li');
li.textContent = key;
// createListTree 함수를 실행해야 하는 위치
const childrenUl = createListTree(date[key]);
if (childrenUl) {
li.append(childrenUl);
}
ul.append(li);
});
return ul;
}
};
중첩된 객체의 경우, 프로퍼티마다 접근하는 것이 필요하기 때문에 createListTree
함수의 매개변수에 대괄호 표기법을 이용해 프로퍼티의 값을 전달했습니다.
중첩된 객체가 빈 객체가 아니라면, 리스트를 가진 ul
요소를 리턴하기 때문에 이를 부모 요소인 li
의 자식 노드로 추가할 수 있습니다.
이제 몇 단계의 중첩 단계를 가진 객체이던, 객체를 순회하여 리스트 요소를 만들 수 있습니다.
이제 마지막으로 완성된 하나의 ul
을 렌더링하는 작업이 필요합니다.
코드 요구사항에 보면, container
라는 요소를 전달하여 이 요소에 리스트를 렌더링하라고 되어 있으니, 요소를 렌더링할 함수를 추가하겠습니다.
이 함수는 요소가 렌더링될 부모 요소와, 렌더링할 요소를 매개변수로 갖습니다.
const data = {
"Fish": {
"trout": {},
"salmon": {}
},
"Tree": {
"Huge": {
"sequoia": {},
"oak": {}
},
"Flowering": {
"apple tree": {},
"magnolia": {}
}
}
};
function createListTree (data: any) {
// 코드 생략
}
function renderListTree (container: Element, element: Element) {
container.append(element);
};
const root = document.querySelector('#root'); // 다음 요소가 있다고 가정함.
const list = createListTree(data);
if (root) {
if (list) {
renderListTree(root, list);
}
}
이 예제는 모던 자바스크립트 튜토리얼 과제입니다.
중첩된 ul·li 로 이루어진 트리가 있습니다.
각 <li>
가 가진 자손 요소들의 수를 표시하는 코드를 작성해 보세요. (자식이 없는 노드는 생략하세요.)
위의 상황에, 이 문제를 추가적으로 덧붙여보겠습니다.
각 <li>
요소가 가진 모든 <li>
요소를 파악하기 위해, 먼저 해당 요소를 모두 검색합니다.
검색이 끝나면, 각각의 <li>
요소 안에 <li>
요소가 몇개 있는지 파악하기 위해 또 검색해야 합니다.
특정 요소에 대한 검색을 2번해야 합니다. 한번은 전체 위치에서, 한번은 전체 위치에서 찾은 요소에서.
// 코드 생략...
if (root) {
if (list) {
const allLiElement = list.querySelectorAll('li');
Array.from(allLiElement).forEach(li => {
const childrenLiLength = li.querySelectorAll('li').length;
});
}
}
querySelectorAll
로 가져온 요소 리스트는 실제 배열이 아니기 때문에, 순회하려면 for of
문을 사용해야 합니다.
저는 편리하게 배열 메서드를 사용하고자, Array.from
을 이용해 해당 리스트를 배열로 변경했습니다.
이렇게 찾은 각 <li>
요소에는 자식 <li>
요소가 있을 수도 있고, 없을 수도 있습니다.
코드의 요구사항에 자식 노드가 없는 경우에는, 개수 표기를 생략하라고 되어 있으니 이를 표기하지 않도록 조건을 추가합니다.
// 코드 생략...
if (root) {
if (list) {
const allLiElement = list.querySelectorAll('li');
Array.from(allLiElement).forEach(li => {
const childrenLiLength = li.querySelectorAll('li').length;
if (childrenLiLength && li.firstChild) {
li.firstChild.textContent += ` [${childrenLiLength}]`;
}
});
renderListTree(container, list);
}
}
이제 요구사항대로, 갖고 있는 자식 <li>
요소의 개수를 표시할 수 있습니다.
(타입스크립트 사용으로 인해, null
로 타입이 체크되는 것이 많아 if
문을 많이 사용하다보니, 코드가 조금 길어졌네요.)
1번 문제를 해결하는데 많은 시간이 소요되긴 했는데, 해답을 보지 않고 최대한 제 생각을 담아 코드를 구현하려고 노력했습니다. 문제에서는 2개의 과정을 모두 해보도록 권장했지만, 일단은 1가지 밖에 성공하지 못했습니다.
코드를 모두 구현하고 해답을 확인한 뒤에는, 해답의 좋은 부분을 제 코드로 가져와 추가하였습니다.
가져온 부분은 트리를 생성하는 코드와, 렌더하는 코드를 각각의 함수로 분리하는 과정입니다.
또한, 저는 유사배열을 반복할 때 무조건 배열로 변경 후 처리하려는 습관이 있는데, 해답에서는 굳이 배열로 변경하지 않고, for of
문을 사용하여 순회합니다. 이러한 점은 참고하여 나중에 어떤 방법이 효율적일지 한번 고려해봐야 할 것 같습니다.
그 외에도 보통 반복문에서 조건을 추가할 때, 저의 경우에는 보통 조건에 해당할 때 어떤 코드를 실행하라는 식으로 많이 작성했는데, 해답지에는 continue
문을 이용해 조건에 해당하지 않는 경우에는 코드 실행을 생략하고 다음 반복으로 넘어가도록 하는 코드를 많이 사용한 것을 확인할 수 있었는데요.
개인적으로 조건문과 return
문의 조합을 무지성으로 사용하는 경향이 있었는데, continue
문을 사용하니 if
문 안에서 코드를 작성할 필요가 없어지고, 중괄호 표기 같은 것들을 줄여주기 때문에 제가 작성한 코드보다 훨씬 간결한 코드를 작성할 수 있었습니다.