템플릿 리터럴 타입을 활용하여 타입 정의하기
import * as React from 'react'
type RenderPropNames = 'Title' | 'Content' | 'Actions'
type RenderProps = {
[K in RenderPropNames as `render${K}`]: () => React.ReactNode
}
type Props = RenderProps
/**
* @example
*
* ```tsx
* <DialogComponent
* renderTitle={() => <h2>Title</h2>}
* renderContent={() => <p>Content</p>}
* renderActions={() => (
* <div>
* <button onClick={handleClose}>Close</button>
* <button onClick={handleSubmit}>Submit</button>
* </div>
* )}
* />
* ```
*/
function Dialog({
renderTitle,
renderContent,
renderActions,
}: Props) => {
return (
<div data-scope="root">
<div data-part="content">
{renderTitle()}
{renderContent()}
</div>
<div data-part="actions">{renderActions()}</div>
</div>
)
}
대화 상자(Dialog) 유형
Modal - 상호작용 차단
- System Modal: 닫기 전까지 다른 작업 불가
- Application Modal: 프로그램 일시 중단
- Document Modal: 부모 창만 차단 (macOS 시트)
Modeless - 상호작용 허용 (예: 툴바)
모달은 워크플로우 방해 → 비필수 작업에는 모델리스 선호
313.md 참고
Vitest/Jest Fake Timers로 시간 제어
describe('time-dependent tests', () => {
beforeEach(() => {
vi.useFakeTimers() // jest.useFakeTimers('modern')
})
afterEach(() => {
vi.useRealTimers() // jest.useRealTimers()
})
it('특정 시간에 동작 확인', () => {
vi.setSystemTime(new Date(2000, 1, 1, 13)) // 13시 설정
expect(purchase()).toEqual({ message: 'Success' })
})
it('영업시간 외 동작', () => {
vi.setSystemTime(new Date(2000, 1, 1, 19)) // 19시 설정
expect(purchase()).toEqual({ message: 'Error' })
})
})
@sinonjs/fake-timers 기반. Date, setTimeout 등 모킹.
두 개의 오버로딩된 메서드에서 각각의 반환 타입을 명시적으로 추출하기
type Year = {
year(): number
year(u: string): string
}
type ReturnTo<T, R> = T extends R ? R : never
type GetYear = ReturnTo<Year['year'], () => number>
type SetYear = ReturnTo<Year['year'], (u: string) => string>
DFS (깊이 우선 탐색)
// 재귀 방식
function dfs(graph, node, visited = new Set()) {
visited.add(node)
console.log(node)
graph[node].forEach((neighbor) => {
if (!visited.has(neighbor)) dfs(graph, neighbor, visited)
})
}
// 스택 방식
function dfsStack(graph, startNode) {
const stack = [startNode]
const visited = new Set()
while (stack.length > 0) {
const node = stack.pop()
if (!visited.has(node)) {
console.log(node)
visited.add(node)
graph[node]
.slice()
.reverse()
.forEach((neighbor) => {
if (!visited.has(neighbor)) stack.push(neighbor)
})
}
}
}
const graph = {
0: [1, 2],
1: [0, 3, 4],
2: [0, 5],
3: [1],
4: [1],
5: [2],
}
dfs(graph, 0) // 0, 1, 3, 4, 2, 5
JavaScript delete 연산자
const obj = { name: 'Alice', age: 25 }
delete obj.age // true
// 존재하지 않는 속성 삭제도 true
delete obj.city // true
// configurable: false는 삭제 불가
const locked = Object.defineProperty({}, 'readOnly', {
value: 'I cannot be deleted',
configurable: false,
})
delete locked.readOnly // false
// 전역 변수 삭제 불가
let globalVar = 'exists'
delete globalVar // false
// 배열 요소 삭제 (hole 생성)
let arr = [1, 2, 3]
delete arr[1] // [1, <empty>, 3]
- 설정 가능한(configurable) 속성에만 사용
- 배열은
splice권장
- 문제: 스프레드시트 데이터가 커서 내용 확인이 어려움.
- 해결: 구글 드라이브에 스프레드시트 데이터를 CSV 파일로 업로드하여 확인.
/**
* 현재 활성 스프레드시트의 첫 번째 시트 데이터를 CSV 파일로 저장하고,
* 구글 드라이브에 업로드합니다.
* 생성된 파일의 URL은 콘솔에 로그로 출력됩니다.
*/
function saveSpreadsheetDataAsCSV() {
/** 현재 활성 스프레드시트 */
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0]
/** 시트의 모든 데이터의 2차원 배열 */
const data = sheet.getDataRange().getValues()
/** CSV 데이터 */
const csv = data.map((row) => row.join(',')).join('\n')
/** 파일명 */
const fileName = 'spreadsheet_data.csv'
/** 구글 드라이브에 업로드 완료된 CSV 파일 */
const file = DriveApp.createFile(fileName, csv, MimeType.CSV)
console.log('파일이 생성되었습니다: ' + file.getUrl())
}
class ColorManipulator {
private baseColor: string
private targetColor: string
constructor(baseColor: string, targetColor: string) {
this.baseColor = baseColor
this.targetColor = targetColor
}
private hexToRgb(hex: string) {
const bigint = parseInt(hex.slice(1), 16)
return {
r: (bigint >> 16) & 255,
g: (bigint >> 8) & 255,
b: bigint & 255,
}
}
public calculateOpacity() {
const target = this.hexToRgb(this.targetColor)
const baseRG = this.hexToRgb(this.baseColor)
const opacities = [
(target.r - baseRG.r) / (255 - baseRG.r),
(target.g - baseRG.g) / (255 - baseRG.g),
(target.b - baseRG.b) / (255 - baseRG.b),
]
const averageOpacity =
opacities.reduce((sum, value) => sum + value, 0) / opacities.length
return averageOpacity
}
public getCssRGBA() {
const opacity = this.calculateOpacity()
return `rgba(0, 0, 0, ${opacity.toFixed(2)})`
}
}
const manipulator = new ColorManipulator('#000000', '#D1D7DE')
const opacity = manipulator.calculateOpacity()
const cssRGBA = manipulator.getCssRGBA()
구글 스프레드시트에서 중복 값에 스타일 주기 — 서식 > 조건부 서식 > 맞춤 수식에 넣는다.
=COUNTIF(A:A, A:A) > 1
출처
Google Spreadsheet 팝업
function showAlert() {
const ui = SpreadsheetApp.getUi()
ui.alert('팝업 창', '표시할 텍스트', ui.ButtonSet.OK)
}
// 메뉴에 추가
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('팝업 메뉴')
.addItem('팝업 띄우기', 'showAlert')
.addToUi()
}