
웹스퀘어(WebSquare) 개발을 하다 보면 그리드 데이터를 표시할 때 customFormatter를 자주 사용하게 됩니다. 하지만 잘못 사용하면 심각한 성능 문제를 일으킬 수 있습니다. 이 글에서는 customFormatter를 언제 사용하고 언제 지양해야 하는지, 그리고 더 나은 대안들을 소개하겠습니다.
customFormatter는 다음 상황에서 모든 셀마다 호출됩니다:
dataList.setJSON())gridView.redraw())// ❌ 성능에 악영향을 주는 예시
scwin.badFormatter = function(data, formattedData, rowIndex, colIndex) {
// 1. 복잡한 계산
var result = heavyCalculation(data);
// 2. DOM 조작
$("#someElement").addClass("active");
// 3. 외부 API 호출 (최악!)
ajaxLib.ajax("someApi", { ... });
return result;
};
1000개 행 × 5개 컬럼 = 5000번 함수 호출!
데이터가 많아질수록 exponential하게 성능이 저하됩니다.
// ❌ 불필요한 customFormatter 사용
scwin.simpleFormatter = function(data) {
return data + "원";
}
// ✅ 데이터 전처리 방식
result.forEach(function(item) {
item.priceDisplay = item.price + "원";
});
// ❌ 매번 동일한 HTML 생성
scwin.linkFormatter = function(data) {
return '<a href="/detail">' + data + '</a>';
}
// ✅ CSS + 이벤트 핸들러 방식
// ✅ 적절한 사용 예시
scwin.statusFormatter = function(data, formattedData, rowIndex) {
var rowData = dcDataList.getRowJSON(rowIndex);
if (rowData.status === 'ACTIVE') {
return '<span style="color: green;">●</span> ' + data;
} else if (rowData.status === 'INACTIVE') {
return '<span style="color: red;">●</span> ' + data;
}
return data;
};
// ✅ 동적 링크 생성
scwin.dynamicLinkFormatter = function(data, formattedData, rowIndex) {
var rowData = dcDataList.getRowJSON(rowIndex);
var docId = rowData.docId;
var seq = rowData.gudlSeq;
return '<a href="javascript:scwin.openDocument(\'' + docId + '\', ' + seq + ')">' + data + '</a>';
};
// ✅ 조건부 숫자 포맷팅
scwin.numberFormatter = function(data) {
if (!data) return "-";
var num = parseInt(data);
if (num >= 1000000) {
return (num / 1000000).toFixed(1) + "M";
} else if (num >= 1000) {
return (num / 1000).toFixed(1) + "K";
}
return num.toLocaleString();
};
백엔드에서 표시용 데이터를 미리 가공
// Java 백엔드 예시
@Service
public class GudlService {
public List<GudlDto> getGudlList() {
List<GudlDto> list = gudlRepository.findAll();
return list.stream()
.map(this::addDisplayFields)
.collect(Collectors.toList());
}
private GudlDto addDisplayFields(GudlDto gudl) {
// 표시용 필드 추가
gudl.setDisplayName(gudl.getGudlNm() + " (코드: " + gudl.getCmpnCd() + ")");
gudl.setLinkHtml("<a href='/detail/" + gudl.getDocId() + "'>" + gudl.getGudlNm() + "</a>");
gudl.setStatusIcon(getStatusIcon(gudl.getStatus()));
return gudl;
}
}
API 응답 후 즉시 데이터 가공
scwin.searchGudlNm = function(docId) {
ajaxLib.ajax("API주소", {
method: "POST",
requestData: {
cmpnCd: scwin.cmpnCd,
docId: docId
},
callback: function(result, e) {
// ✅ 데이터 전처리
var processedData = result.map(function(item, index) {
return {
...item,
// 표시용 필드 추가
displayName: item.gudlNm + " (" + item.cmpnCd + ")",
linkHtml: '<a href="javascript:scwin.openDocument(\'' +
item.docId + '\', ' + item.gudlSeq + ')">' +
item.gudlNm + '</a>',
statusIcon: scwin.getStatusIcon(item.status),
rowNumber: index + 1
};
});
dcGudlNmList1.setJSON(processedData);
}
});
};
스타일링과 이벤트 처리 분리
// GridView 설정
<w2:column width="300" inputType="output" id="gudlNm"
blockSelect="false" displayMode="label"
textAlign="left" class="link-style">
</w2:column>
// CSS 스타일링
.link-style {
color: #0066cc;
text-decoration: underline;
cursor: pointer;
}
// 이벤트 핸들러
scwin.grvGudlNm_oncellclick = function(row, col, dataList) {
var rowData = dataList.getRowJSON(row);
scwin.openDocument(rowData.docId, rowData.gudlSeq);
};
복잡한 HTML이 필요한 경우
// 템플릿 정의
scwin.templates = {
gudlLink: '<div class="gudl-item">' +
'<a href="javascript:scwin.openDocument(\'{docId}\', {gudlSeq})">' +
'<strong>{gudlNm}</strong>' +
'<span class="code">({cmpnCd})</span>' +
'</a></div>',
statusBadge: '<span class="badge badge-{status}">{statusText}</span>'
};
// 데이터 전처리에서 템플릿 적용
function applyTemplate(template, data) {
return template.replace(/\{(\w+)\}/g, function(match, key) {
return data[key] || '';
});
}
// 사용
processedData = result.map(function(item) {
return {
...item,
displayHtml: applyTemplate(scwin.templates.gudlLink, item)
};
});
// 1000개 행 기준 로딩 시간: ~2.5초
// 메모리 사용량: 높음
// 스크롤 성능: 끊김 현상
// 1000개 행 기준 로딩 시간: ~0.3초
// 메모리 사용량: 낮음
// 스크롤 성능: 부드러움
하나라도 해당되면 대안 방식 고려
// 단계별 개선 방법
// 1단계: 간단한 케이스부터 전처리로 변경
// 2단계: 복잡한 customFormatter 로직 분석
// 3단계: 서버사이드 전처리 도입 검토
// 4단계: 성능 측정 및 최적화
// 성능 측정 코드
scwin.measurePerformance = function(functionName, fn) {
var start = performance.now();
var result = fn();
var end = performance.now();
console.log(functionName + ' 실행 시간: ' + (end - start) + 'ms');
return result;
};
// 사용 예시
scwin.measurePerformance('데이터 로딩', function() {
dcGudlNmList1.setJSON(processedData);
});
customFormatter는 강력한 기능이지만 성능에 큰 영향을 미칠 수 있습니다. 단순한 표시 작업은 데이터 전처리로, 복잡한 동적 처리만 customFormatter로 처리하는 것이 최적의 접근법입니다.
특히 대용량 데이터를 다루는 엔터프라이즈 환경에서는 이런 최적화가 사용자 경험에 직접적인 영향을 미치므로, 개발 초기부터 성능을 고려한 설계가 중요합니다.
핵심 원칙:
1. 가능하면 서버에서 전처리
2. 클라이언트 전처리가 차선책
3. customFormatter는 정말 필요할 때만
4. 성능 측정을 통한 지속적 개선
이러한 원칙을 따르면 빠르고 안정적인 WebSquare 애플리케이션을 구축할 수 있습니다.