프론트엔드 개발 문제 해결
GitLab v19.4요약
문제가 생겼다면 이 가이드가 도움이 될 수 있습니다 ¯\_(ツ)_/¯. 이 가이드에 없는 프론트엔드 개발 문제를 겪었다면, 해당 문제와 가능한 해결책을 이 가이드에 추가하는 것을 고려합니다. 이 문제는 Vue 컴포넌트 테스트에서 기대값이 실패했을 때, Jest가 콘솔에서 diff를 pretty print 하려다 오류를 던지면서 발생할 수 있습니다.
문제가 생겼다면 이 가이드가 도움이 될 수 있습니다 ¯\_(ツ)_/¯.
문제 해결#
이 가이드에 해당 문제가 없는 경우#
이 가이드에 없는 프론트엔드 개발 문제를 겪었다면, 해당 문제와 가능한 해결책을 이 가이드에 추가하는 것을 고려합니다. 이렇게 하면 이후의 개발자들이 여러분의 경험과 지식을 바탕으로 같은 난관을 더 잘 넘어설 수 있습니다.
테스트 문제#
Property or method `nodeType` is not defined 이 나오지만 어디에서도 nodeType을 사용하지 않는 경우#
이 문제는 Vue 컴포넌트 테스트에서 기대값이 실패했을 때, Jest가 콘솔에서 diff를 pretty print 하려다 오류를
던지면서 발생할 수 있습니다. 배열을 속성으로 하는 toEqual 사용도 원인 중 하나로
지목되었습니다.
자세한 개요와 조사 내용은 이 영상을 참고합니다.
해결 방법 - Vue 워처를 가진 객체를 복제해 봅니다
- expect(wrapper.findComponent(ChildComponent).props()).toEqual(...);
+ expect(cloneDeep(wrapper.findComponent(ChildComponent).props())).toEqual(...)
해결 방법 - toEqual 대신 toMatchObject를 사용해 봅니다
- expect(wrapper.findComponent(ChildComponent).props()).toEqual(...);
+ expect(wrapper.findComponent(ChildComponent).props()).toMatchObject(...);
toMatchObject는 단언의 성격 자체를 바꾸므로, 기대값에서 일부 항목이 빠져 있어도 실패하지 않습니다.
스크립트 문제#
GitLab 리포지터리 안에서 스크립트를 실행할 때 발생하는 core-js 오류#
다음 명령은 GitLab 리포지터리를 ~/workspace/gdk 디렉터리에 설정했다고
가정합니다. 코드 변환처럼 GitLab 리포지터리 안에서 스크립트를 실행할 때
다음과 같은 core-js 관련 문제가 발생할 수 있습니다.
~/workspace/gdk/gitlab/node_modules/core-js/modules/es.global-this.js:7
$({
^
TypeError: $ is not a function
at Object.<anonymous> (~/workspace/gdk/gitlab/node_modules/core-js/modules/es.global-this.js:6:1)
at Module._compile (internal/modules/cjs/loader.js:1063:30)
at Module._compile (~/workspace/gdk/gitlab/node_modules/pirates/lib/index.js:99:24)
at Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
at Object.newLoader [as .js] (~/workspace/gdk/gitlab/node_modules/pirates/lib/index.js:104:7)
at Module.load (internal/modules/cjs/loader.js:928:32)
at Function.Module._load (internal/modules/cjs/loader.js:769:14)
at Module.require (internal/modules/cjs/loader.js:952:19)
at require (internal/modules/cjs/helpers.js:88:18)
at Object.<anonymous> (~/workspace/gdk/gitlab/node_modules/core-js/modules/esnext.global-this.js:2:1)
해결 방법 - 스크립트를 별도 리포지터리로 옮기고 GitLab 리포지터리의 파일을 가리키게 합니다
Vue 컴포넌트 사용 문제#
GlFilteredSearch를 사용하는 컴포넌트를 렌더링하고 그 컴포넌트나 부모가 Vue Apollo를 사용하는 경우#
GlFilteredSearch 컴포넌트를 렌더링하려고 할 때 컴포넌트의 provide 함수에서 다음 오류가 발생할 수 있습니다.
cannot read suggestionsListClass of undefined
현재 vue-apollo는 컴포넌트 라이프사이클의 beforeCreate 단계에서 컴포넌트의 provide()를 수동으로 호출하려고 시도합니다. 따라서 provide()가 created 이후에야 설정되는 props를 참조하면 오류가 발생합니다.
더 자세한 배경은 이 닫힌 MR을 참고합니다.
해결 방법 - 최상위 Vue 인스턴스 옵션에 apolloProvider를 제공해 봅니다
VueApollo는 $options에 apolloProvider가 제공된 것을 확인하면 provide() 수동 실행을 건너뜁니다.
new Vue(
el,
+ apolloProvider: {},
render(h) {
return h(App);
},
);
Apollo Client 문제 해결#
캐시에 쓸 때 발생하는 콘솔 오류#
Missing field 'descriptionHtml' while writing result 같은 오류가 보인다면, Apollo Client 캐시에 쓸 때 GraphQL 응답 구조를 지키지 않고 있다는 뜻입니다. 웹 애플리케이션에서 GraphQL 오류("Missing field 'description'")가 발생한 것으로 보이며, Apollo Client의 캐시와 데이터 업데이트를 다루는 방식과 관련이 있을 가능성이 높습니다. 오류 스택 트레이스는 문제가 발생한 Apollo Client 코드의 구체적인 위치를 알려 줍니다.
핵심 문제:
"Missing field 'description'" 오류는 GraphQL 쿼리가 응답에 "description" 필드를 기대하지만, 백엔드에서 받은 데이터(또는 Apollo Client가 처리한 결과)에 그 필드가 없다는 뜻입니다. 이 때문에 Apollo Client 캐시가 불완전한 데이터로 스토어를 업데이트하려다 실패합니다.
이를 디버깅하려면 아래 단계를 따릅니다.
- 오류 스택 개발자 콘솔을 엽니다
Missing field 'description' while writing result {
"type": "DESCRIPTION",
"lastEditedAt": null,
"lastEditedBy": null,
"taskCompletionStatus": null,
"__typename": "WorkItemWidgetDescription"
}
- GraphQL 쿼리가 "description" 필드를 요청하고 있는지 다시 확인합니다. 포함되어 있지 않으면 Apollo Client는 응답에서 그 필드를 찾을 수 없습니다.
- 백엔드가 "WorkItemWidgetDescription" 유형의 응답에서 "description" 필드를 반환하지 않고 있을 수 있습니다. 백엔드 API가 기대한 대로 데이터를 보내고 있는지 확인합니다.
cache.readQuery메서드로 Apollo Client 캐시의 내용을 확인합니다. 해당 쿼리의 캐시 데이터에 "description" 필드가 있는지 확인합니다- 오류 스택 트레이스를 열어 보면 Apollo Client가 캐시에 데이터를 쓰는 방식과 관련된 문제일 수 있습니다. 캐시가 올바르게 업데이트되지 않아 필드가 누락되었을 수 있습니다
- Apollo Client 코드에 콘솔 로그를 추가해(예: 캐시에 쓰기 전과 후) 처리되는 데이터를 추적하고 "description" 필드가 어디에서 누락되는지 확인합니다.
해결책
Apollo Client 코드에서 올바른 writeQuery 또는 writeFragment 메서드를 사용해 "description" 필드를 포함한 완전한 데이터로 캐시를 업데이트해야 합니다
스택 트레이스에서 이 문제가 시작된 메서드를 확인할 수 있습니다. 캐시에 쓸 때 "description" 필드를 반드시 추가합니다
같은 변수인데도 쿼리가 캐시되지 않는 경우#
Apollo GraphQL 쿼리는 다음과 같은 여러 상황에서 캐시되지 않을 수 있습니다.
- 캐시 미스 또는 부분 캐시, 쿼리 무효화 또는 변경: 쿼리가 부분 데이터만 반환하거나 캐시 미스가 발생하면(요청한 데이터의 일부가 캐시에 없는 경우) Apollo가 결과를 효과적으로 캐시하지 못할 수 있습니다.
쿼리와 관련된 데이터가 무효화되거나 업데이트되었다면 캐시에 유효한 정보가 없을 수 있습니다. 예를 들면 다음과 같습니다.
뮤테이션을 사용할 때는 refetchQueries를 구성하거나 뮤테이션 이후 캐시를 수동으로 업데이트하지 않으면 캐시가 자동으로 갱신되지 않을 수 있습니다.
예를 들어 첫 번째 쿼리에는 이후 쿼리에서 요청하지 않은 필드가 몇 개 있습니다.
query workItemTreeQuery($id: WorkItemID!, $pageSize: Int = 100, $endCursor: String) {
workItem(id: $id) {
namespace {
id
}
userPermissions {
deleteWorkItem
updateWorkItem
}
}
}
query workItemTreeQuery($id: WorkItemID!, $pageSize: Int = 100, $endCursor: String) {
workItem(id: $id) {
namespace {
id
+ fullPath
}
userPermissions {
deleteWorkItem
updateWorkItem
+ adminParentLink
+ setWorkItemMetadata
+ createNote
+ adminWorkItemLink
}
}
}
fetchPolicy설정: Apollo Client는 fetchPolicy로 쿼리가 캐시와 어떻게 상호작용할지 제어합니다. 정책에 따라 fetchPolicy가no-cache이면 쿼리가 캐시를 완전히 우회합니다. 이 정책에서는 쿼리의 어떤 부분도 캐시에 기록되지 않습니다. 각 쿼리가 서버에서 직접 데이터를 가져오고 결과를 캐시에 저장하지 않으므로 여러 쿼리가 반복해서 실행됩니다- 서로 다른 Apollo Client 인스턴스에서 같은 쿼리가 실행되는 경우입니다. 두 쿼리를 실행하는 클라이언트가 서로 다른 클라이언트일 수 있습니다.
id또는__typename누락: Apollo Client는id와__typename으로 엔티티를 고유하게 식별하고 캐시합니다. 쿼리 응답에 이 필드들이 없으면 Apollo가 결과를 제대로 캐시하지 못할 수 있습니다.- 복잡하거나 중첩된 쿼리: 일부 쿼리는 지나치게 복잡하거나 중첩되어 있어 Apollo Client가 올바르게 캐시하기 어려울 수 있습니다. 반환된 데이터 구조가 캐시 스키마에 깔끔하게 매핑되지 않을 때 이런 일이 생기며, 수동 캐시 관리가 필요합니다.
- 페이지네이션 쿼리: fetchMore를 사용하는 쿼리처럼 페이지네이션이 포함된 경우, 캐시를 명시적으로 업데이트하지 않으면 Apollo가 결과를 제대로 캐시하지 못할 수 있습니다.
이 모든 경우에 쿼리 캐싱을 효과적으로 처리하려면 Apollo의 캐시 정책을 구성하거나 캐시를 수동으로 업데이트해야 할 수 있습니다.