스타일링된 input 구현하기
input 컴포넌트의 입력 영역에 padding이 없으면 시각적으로 매력적이지 않습니다. padding을 너무 많이 주거나 적게 주는 것은 외부 컨테이너에 영향을 줄 수 있어 어려운 문제입니다. 해결책은 input 외부에 view를 추가하고, input 자체에는 padding을 설정하지 않는 것입니다. 대신 외부 view에 padding, 배경색, border와 같은 시각적 효과를 적용합니다.
외부 View input 컴포넌트 실제 효과
Page 내 data 설정하기
this.setData({
'속성명':값
})
this.setData({
name: '홍길동',
age: '25',
// 복잡한 문자열 조합 속성명의 경우, 가장 바깥쪽에 대괄호를 추가
['centerNameDataList[' + index + '].checked']: true
})
유동 레이아웃에서 scroll-view 공간 활용하기
scroll-view에 높이가 있어야 bindscrolltoupper, bindscrolltolower와 같은 이벤트가 정상적으로 작동합니다. 높이는 고정값, vh, 또는 백분율로 설정할 수 있습니다. 유동 레이아웃에서 컴포넌트가 특정 수치를 갖지 않을 수 있으므로, scroll-view 외부에 view를 추가하여 page 높이를 100%로 고정하고, view가 목표 공간을 채우도록 합니다. 그러면 scroll-view의 높이를 100%로 설정하여 부모 view를 완전히 채울 수 있습니다.
<view class="scroll-container">
<scroll-view></scroll-view>
</view>
page{ height: 100% }
.scroll-container{ flex:1; overflow: scroll }
scroll-view{ height: 100% }
고정 버튼과 스크롤 가능한 리스트를 가진 페이지 레이아웃
이 레이아웃은 일반적으로 상단 또는 하단에 고정된 버튼이나 다른 컴포넌트가 있고, 나머지 영역은 리스트 내용으로 구성됩니다. 요구사항은 리스트 영역의 내용은 스크롤이 가능해야 하고, 고정 영역의 컴포넌트는 움직이지 않아야 한다는 것입니다.
페이지의 기본 구조는 위에서 아래로 5개 부분으로 나뉩니다:
- 상단에 고정된 topView (여기서는 검색창의 가장 바깥쪽 컨테이너)
- 상단 고정 view와 같은 높이의 공백 자리 stake: 고정 레이아웃에서 list 영역의 top이 topView의 높이를 무시하고 페이지 시작 높이(0)로 직접 이동하게 되면 topView가 일부 리스트 영역을 가리게 됩니다. 따라서 list 영역의 top이 시각적으로 여전히 topView의 bottom에서 시작하도록 하기 위해 필요합니다.
- 리스트 영역 list: 높이가 동적으로 변하며 실제 데이터량에 따라 순서대로 배열됩니다.
- 하단 고정 view와 같은 높이의 공백 자리 stake: 상단 stake와 동일한 목적
- 하단에 고정된 bottomView (여기서는 하단 버튼)
구조가 올바르게 설정되면 구현은 매우 간단해집니다. 페이지에 고정된 부분은 다음과 같이 설정합니다:
.fixed-top{
position: fixed;
top: 0;
margin-top: 0;
}
.fixed-bottom{
position: fixed;
bottom: 0;
margin-bottom: 0;
}
stake의 높이를 설정해야 합니다. 고정된 부분이 고정된 높이를 가지는 것이 좋습니다. 그렇지 않으면 동적으로 계산해야 합니다.
.stake{
min-height: var(--element-height-56)
}
고정 부분이 설정되면 리스트 영역의 높이는 걱정할 필요가 없습니다. 동적으로 변하기 때문에 매우 편리합니다.
var 할당 시 주의사항
메서드 내에서 변수를 할당할 때, var a=b와 같이 직접 할당하면 b의 값이 변경될 수 있습니다.
const ApiConfig = {
endpoint:{
url: '/app/path?name=alice'
}
}
handleRequest: function(params){
var endpoint = ApiConfig.endpoint
if(params.age){
endpoint.url = `${endpoint.url}&age=${params.age}`
}
}
handleRequest를 처음 실행할 때 params.age = 10이라고 가정하면 endpoint의 최종 결과는 /app/path?name=alice&age=10이 됩니다. params.age의 값을 변경하여 다시 실행하면 ApiConfig.endpoint의 url이 이전 실행 결과를 저장하게 되어, 두 번째 실행 후 endpoint.url에는 age=xx가 두 번 포함됩니다. 이를 방지하려면 api를 새 객체 {}로 선언하고 할당해야 합니다. 새 객체의 속성(값)은 ApiConfig에서 가져올 수 있으며, api가 다른 객체와 직접 같지 않도록 해야 합니다.
var endpoint = {
url: ApiConfig.endpoint.url
}
디버깅 및 중단점 설정
미니프로그램 개발 도구를 열고 "디버거"를 열면 Source 페이지로 전환할 수 있습니다. Page에서 현재 디버깅할 JS를 찾아 해당 줄에 중단점을 설정하면 됩니다.
WeUI Upload 컴포넌트를 이용한 이미지 선택, 미리보기 및 업로드
먼저 page.json 파일에 참조를 추가합니다.
"usingComponents": {
"mp-uploader": "weui-miniprogram/uploader/uploader",
"mp-cells": "weui-miniprogram/cells/cells",
"mp-cell": "weui-miniprogram/cell/cell"
}
wxml에서는 다음과 같이 사용합니다.
<mp-uploader bindselect="onCertImageSelect" bindfail="uploadError" bindsuccess="uploadSuccess" select="{{selectFile}}"
upload="{{uploadFile}}" files="{{files}}" max-count="5" title="증빙 업로드" tips="" delete="true">
</mp-uploader>
이벤트 순서는 select→bindselect→upload→bindsuccess 또는 bindfail 입니다. select 필터 함수가 false를 반환하면 컴포넌트가 중단되며 후속 이벤트도 트리거되지 않습니다. 즉, 현재 동작이 종료됩니다. 실행 흐름을 이해하면 JS에서 각각의 이벤트 처리 로직을 구현할 수 있습니다. 실제 파일 업로드 로직은 upload 발생 시 이루어지며, 최종적으로 Promise 객체를 반환해야 하며, Promise의 resolve에는 url 배열 요소를 포함하는 object를 전달해야 합니다. 즉, resolve({ url:[] })
upload:function(files){
// files는 업로드할 파일로 구성된 객체입니다
// 파일 업로드 수행
return new Promise((resolve, reject)=>{
if( 모든 업로드 완료 ){
resolve({
urls: [ 성공 업로드 파일1, 성공 업로드 파일2]
})
}else{
reject('오류 발생')
}
})
}
성공적으로 업로드된 파일(즉, url 배열에 배치된 파일)은 컴포넌트가 자동으로 정상 방식으로 표시됩니다. 다른 성공하지 못한 파일은 Loading 상태로 계속 표시되며 멈추거나 error 상태로 변경되지 않습니다. reject가 호출되면 모두 error 상태가 됩니다. Uploader 컴포넌트의 장점은 선택한 파일을 페이지 수준 변수에 반드시 저장할 필요가 없다는 것입니다. upload 전 및 upload 시에는 매개변수를 통해 액세스할 수 있으며, 미리보기 시에도 수동 코드 작성이 필요하지 않습니다. 단점은 공식 문서가 정확하지 않으며 일부 속성의 용도를 알기 어렵다는 것입니다. 예를 들어 files를 잘못 작성하면 충돌이 발생할 수 있습니다.
npm 컴포넌트 가져오기
vant weapp 컴포넌트를 예로 들어보겠습니다. 먼저 nodejs를 설치합니다. nodejs에는 npm이 내장되어 있습니다. 기본 설치 후, 컨트롤에 npm -v를 입력하면 버전 번호가 나타납니다. 이는 설치 및 환경 변수 설정이 정상임을 의미합니다. npm을 별도로 업그레이드할 수도 있지만 필수는 아닙니다. 명령은 npm install npm@latest -g입니다.
다음으로, 컨트롤(또는 미니프로그램 개발 도구에 내장된 터미널, 미니프로그램 개발 도구가 npm 설치 후 열리거나 재시작해야 npm 전역 명령이 적용됨)에서 현재 작업 경로로 전환합니다(이것이 개발 환경에서 열리는 의미이며, 기본 경로는 프로젝트 아래에 있습니다). 컴포넌트 설치 명령 npm i @vant/weapp -S --production를 실행합니다.
다음으로, vant weapp 공식 빠른 시작 가이드에 따라 app.json, project.config.json, npm 빌드 세 가지 작업을 차례로 완료합니다. 이 세 가지 작업에서 발생할 수 있는 문제점은 다음과 같습니다:
- app.json에서 공식 문서에서는
"style":"v2"를 제거하라고 합니다. 이는 JS에 영향을 주지 않지만 기본 컴포넌트의 스타일에 영향을 줄 수 있습니다. vant weapp 스타일로 변경되므로 제거하지 않아도 사용 중인 컴포넌트가 정상적으로 작동하는지 테스트하여 확인할 수 있습니다. - project.config.json에서는 공식 문서의 안내와 개발 도구 버전의 영향에 주의해야 합니다. 현재는 공식 문서의 안내에 따라
"miniprogramNpmDistDir": "./"로 설정합니다. - 공식 문서에 따라 npm 빌드 명령을 실행하고 "npm 모듈 사용"을 선택합니다.
다음으로, page에서 필요에 따라 컴포넌트를 가져와 사용할 수 있습니다. 하지만 모두 UI 컴포넌트이므로 스타일 충돌이 발생할 수 있으므로 주의하고 확인해야 합니다.
vant weapp Uploader 사용 예시
먼저 npm 컴포넌트 가져오기 작업을 완료합니다. page.json에서 컴포넌트 참조를 선언합니다.
"usingComponents":{ "van-uploader":"@vant/weapp/uploader/index" }
wxml 파일에 컴포넌트를 추가합니다.
<van-uploader file-list="{{ certFileList }}" accept="image" max-size="5242880" max-count="9" upload-text="증빙 업로드" image-fit="aspectFill" bind:after-read="onAfterRead" bind:delete="onDelete" bind:oversize="onOverSize" />
문서에 따라 각 API를 구현합니다. 제3자 컴포넌트의 "완성도"는 원래 Uploader보다 좋은 것 같습니다. 예를 들어 van-uploader는 미리보기에 문제가 없으며 제어 항목도 명확합니다. 더 나은 점은 기본적으로 파일을 하나씩 선택한다는 것입니다. 이는 일괄 업로드의 어려움을 피하게 해줍니다.
테스트 버전 발행
기본적인 버전이 완성되면 특정 테스트 인원을 위해 테스트 버전 미니프로그램을 발행할 수 있습니다. 단계는 다음과 같습니다:
- 모든 캐시를 지움(필수는 아니지만 일부 이상한 문제를 줄여줍니다)
- 컴파일
- 업로드
- 미니프로그램 관리后台 버전 관리 기능에서 현재 버전을 테스트 버전으로 설정
다음 파일은 패키징 및 업로드되지 않음
- 프로그램이 요구하는 비프로젝트 파일, 예:
.ignore - 확장명을 변경한 파일, 예: 확장명이 없는 이미지는 업로드할 수 없습니다
실시간 로깅
미니프로그램은 로깅 기능인 RealtimeLogManager를 제공합니다. 사용 시에는 기존 메서드를 호출하기만 하면 로그가 자동으로 미니프로그램 관리后台에 업로드됩니다. 개발 관리-운영 중심-실시간 로그를 통해 볼 수 있습니다. 제한 사항이 많지만 일반 애플리케이션에는 충분합니다. 일반적인 요구사항을 직접 구현해도 구조는 미니프로그램과 유사하며 확실히 공식보다 좋지 않을 것입니다. 로그는 기본적으로 7일간 저장되며, 내보낼 수 없습니다. 모바일에서 실행될 때만 업로드할 수 있으며, 개발 도구에서 호출할 수 있지만 로그는 업로드되지 않습니다.
var log = wx.getRealtimeLogManager()
// 현재 기록할 내용에 필터 태그 추가
log.addFilterMsg()
log.info(...args)
log.warn(...args)
log.error(...args)