자바스크립트
고양이 사진첩 만들기 - (3) 캐싱, 이벤트 최적화, Breadcrumb 클릭
minkang
2021. 8. 26. 18:13
App.js
import Breadcrumb from './components/Breadcrumb.js';
import Nodes from './components/Nodes.js';
import ImageView from './components/ImageView.js';
import Loading from './components/Loading.js';
import { request } from './api/api.js';
const cache = {};
export default function App($app) {
this.state = {
isRoot: false,
nodes: [],
depth: [],
selectedFilePath: null,
isLoading: false,
};
const breadcrumb = new Breadcrumb({
$app,
initialState: [],
onClick: (index) => {
if (index === null) {
this.setState({
...this.state,
depth: [],
nodes: cache.rootNodes,
});
return;
}
if (index === this.state.depth.length - 1) {
return;
}
const nextState = { ...this.state };
const nextDepth = this.state.depth.slice(0, index + 1);
this.setState({
...nextState,
depth: nextDepth,
nodes: cache[nextDepth[nextDepth.length - 1].id],
});
},
});
const nodes = new Nodes({
$app,
initialState: {
isRoot: this.state.isRoot,
nodes: this.state.nodes,
},
onClick: async (node) => {
try {
if (node.type === 'DIRECTORY') {
if (cache[node.id]) {
this.setState({
...this.state,
isRoot: false,
depth: [...this.state.depth, node],
nodes: cache[node.id],
});
} else {
const nextNodes = await request(node.id);
this.setState({
...this.state,
isRoot: false,
depth: [...this.state.depth, node],
nodes: nextNodes,
});
cache[node.id] = nextNodes;
}
} else if (node.type === 'FILE') {
this.setState({
...this.state,
selectedFilePath: node.filePath,
});
}
} catch (e) {}
},
onBackClick: async () => {
try {
const nextState = { ...this.state };
nextState.depth.pop();
const prevNodeId =
nextState.depth.length === 0
? null
: nextState.depth[nextState.depth.length - 1].id;
if (prevNodeId == null) {
this.setState({
...nextState,
isRoot: true,
nodes: cache.rootNodes,
});
} else {
this.setState({
...nextNodes,
isRoot: false,
nodes: cache[prevNodes],
});
}
} catch (e) {}
},
});
const imageView = new ImageView({
$app,
initialState: this.state.selectedFilePath,
});
const loading = new Loading({ $app, initialState: this.state.isLoading });
this.setState = (nextState) => {
this.state = nextState;
breadcrumb.setState(this.state.depth);
nodes.setState({
isRoot: this.state.isRoot,
nodes: this.state.nodes,
});
imageView.setState(this.state.selectedFilePath);
loading.setState(this.state.isLoading);
};
const init = async () => {
this.setState({
...this.state,
isLoading: true,
});
try {
const rootNodes = await request();
this.setState({
...this.state,
isRoot: true,
nodes: rootNodes,
});
cache.rootNodes = rootNodes;
} catch (e) {
this.onError(e);
} finally {
this.setState({
...this.state,
isLoading: false,
});
}
};
init();
}
캐싱 구현하기
App 컴포넌트에서 중앙제어 하고 있기 때문에, App 컴포넌트에 캐시를 위한 object를 하나 만들어서 쓰는 것으로 간단하게 처리할 수 있습니다.
데이터를 불러올 때 마다 캐시에 저장하고 다시 요청할 때에는 캐시된 데이터를 불러오도록 수정하였습니다.
여기서 const 키워드로 선언하는 이유는 값이 재할당되는 것을 막기 위해서입니다.
자바스크립트에에서 객체는 const 키워드로 선언해도 식별자.프로퍼티명 형태로 직접 접근해서 값을 수정하는 것이 가능하지만
const cache = { isRoot : false };
const cache = "hi"; // 에러 발생
이런 상황을 방지하기 위해서 입니다.
Node.js
export default function Nodes({ $app, initialState, onClick, onBackClick }) {
this.state = initialState;
this.$target = document.createElement('ul');
$app.appendChild(this.$target);
this.setState = (nextState) => {
this.state = nextState;
this.render();
};
this.onClick = onClick;
this.onBackClick = onBackClick;
this.render = () => {
if (this.state.nodes) {
const nodesTemplate = this.state.nodes
.map((node) => {
const iconPath =
node.type === 'FILE'
? './assets/file.png'
: './assets/directory.png';
return `
<div class="Node" data-node-id="${node.id}">
<img src="${iconPath}" />
<div>${node.name}</div>
</div>
`;
})
.join('');
this.$target.innerHTML = !this.state.isRoot
? `<div class="Node"><img src="/assets/prev.png"></div>${nodesTemplate}`
: nodesTemplate;
}
this.$target.addEventListener('click', (e) => {
const $node = e.target.closest('.Node');
if ($node) {
const { nodeId } = $node.dataset;
if (!nodeId) {
this.onBackClick();
return;
}
const selectedNode = this.state.nodes.find(
(node) => node.id === nodeId
);
if (selectedNode) {
this.onClick(selectedNode);
}
}
});
};
this.render();
}
Breadcrumb.js
export default function Breadcrumb({ $app, initialState = [], onClick }) {
this.onClick = onClick;
this.state = initialState;
this.$target = document.createElement('nav');
this.$target.className = 'Breadcrumb';
$app.appendChild(this.$target);
this.setState = (nextState) => {
this.state = nextState;
this.render();
};
this.render = () => {
this.$target.innerHTML = `
<div class="nav-item">root</div>
${this.state
.map(
(node, index) => `
<div class="nav-item" data-index="${index}">${node.name}</div>
`
)
.join('')}
`;
};
this.$target.addEventListener('click', (e) => {
const $navItem = e.target.closest('.nav-item');
if ($navItem) {
const { index } = $navItem.dataset;
this.onClick(index ? parseInt(index, 10) : null);
}
});
this.render();
}
이벤트 최적화
이전에는 Nodes.js가 렌더링 될 때마다 모든 Node 요소에 click 이벤트를 다시 걸어주었었습니다.
이벤트 버블링을 이용해서 이벤트 위임을 하여, 하나의 노드에 이벤트를 걸어서 전부 처리할 수 있습니다.
출처