import * as React from 'react'
type ContainerProps = {
children: typeof Body
}
type BodyProps = {
id: string
}
function Container({ children }: ContainerProps) {
const id = '1234'
const Child = children
return <Child id={id} />
}
export function Body({ id }: BodyProps) {
return <>{id}</>
}
export function Page() {
return <Container>{Body}</Container>
}
Body 는 컴포넌트라 children({ id }) 로 부르지 않고 JSX 로 렌더한다.1
컨테이너/프레젠테이션 패턴을 활용한 React 컴포넌트 구조.
- 컨테이너 컴포넌트(
Container)는 상태를 관리하고 데이터를 하위 컴포넌트에 전달함. - 프레젠테이션 컴포넌트(
Body)는 데이터를 받아서 UI를 렌더링함. - 이 구조는 테스트를 쉽게 하고, 컴포넌트의 역할을 명확하게 분리함.
Footnotes
-
Components and Hooks must be pure – React: “Components should only be used in JSX. Don’t call them as regular functions.” ↩
/**
* - 인자 수 확인: `curry` 함수는 전달된 함수 `fn`의 인자 수를 확인
* - 인자 수가 충분한 경우: 만약 `args`의 길이가 `fn`의 인자 수 이상이면, `fn`을 호출하고 결과를 반환
* - 인자 수가 부족한 경우: 그렇지 않으면 추가 인자(`moreArgs`)를 받을 수 있는 새 함수를 반환. 이 함수는 기존 인자(`args`)와 새로운 인자(`moreArgs`)를 합쳐 다시 `curry(fn)`을 호출하여 최종적으로 인자가 충분할 때까지 이 과정을 반복
*/
const curry =
(fn: Function) =>
(...args: any[]) =>
args.length >= fn.length
? fn(...args)
: (...moreArgs: any[]) => curry(fn)(...args, ...moreArgs)
/**
* - `...fns: Function[]` 여러 개의 함수를 인자로 받는다
* - 내부에서 `reduce` 메서드를 사용하여 함수 배열을 순회 초기값으로 `x`를 사용하며, 각 함수 `fn`을 차례로 호출하여 결과를 다음 함수로 전달
*/
const pipe =
(...fns: Function[]) =>
(x: any) =>
fns.reduce((acc, fn) => fn(acc), x)
/**
* - `...fns`: 여러 개의 함수를 매개변수로 받음
* - `(x)`: 초기값 `x`를 인자로 받아 결과를 리턴
* - `fns.reduceRight(...)`: 배열의 오른쪽부터 왼쪽으로 각 함수를 적용합니다. `y`는 이전 함수의 결과이며, `f`는 현재 함수
* - `f(y)`: 현재 함수 `f`를 이전 함수의 결과 `y`에 적용
*/
const compose =
(...fns) =>
(x) =>
fns.reduceRight((y, f) => f(y), x)
- 함수 조합(Composition): 작은 함수들을 조합하여 새로운 함수를 만들며, 예를 들어
compose(f, g, h)는f(g(h(x)))로 실행 compose: 오른쪽에서 왼쪽으로 함수를 실행하며,compose(square, double)(3)은 36을 반환pipe: 왼쪽에서 오른쪽으로 함수를 실행하여,pipe(double, square)(3)의 결과는 36- 커링(Currying):
f(a, b, c)를f(a)(b)(c)형태로 변환하고, 예를 들어add(1)(2)(3)의 결과는 61 - 부분 적용(Partial Application): 일부 인자만 미리 적용하여 새로운 함수를 만들 수 있으며,
const double = multiply(2, _)로 정의 - 포인트-프리 스타일: 변수를 사용하지 않고 함수를 조합하여 작성하며, 예를 들면
compose(square, double)와 같은 형태 - 데이터 마지막 원칙(Data Last): 데이터를 마지막 인자로 배치하여 조합성을 높이는데, 예를 들어
const halve = divideDataLast(2)와 같이 사용
- Higher-Order Functions
- How to compose JavaScript functions that take multiple parameters (the epic guide)
- JavaScript function composition: What’s the big deal?
Footnotes
-
Function: length - MDN: “The
lengthdata property of aFunctioninstance indicates the number of parameters expected by the function.” ↩
JSX 문자열
- https://github.com/TroyAlford/react-jsx-parser
- https://github.com/FormidableLabs/react-live
- https://github.com/seek-oss/playroom
프로젝트 파일 — iframe 안에서 컴파일·렌더, 의존성은 esm.sh
번들 URL
- 프런트엔드(클라이언트)에서 MP4 파일의 오디오 존재 여부를 확인하기
- 브라우저 API 사용 시 호환성 문제 발생 (Chrome, Safari, Firefox 각각 다른 API 사용)
- FileReader API를 사용하여 서버에서 응답받은 바이너리 데이터를 읽고, 이를 분석해 오디오 여부 확인.
- 오디오 정보가 발견되지 않으면 추가 데이터 요청하여 확인 범위를 증가시켜 반복.
await ffmpeg.writeFile('input.mp4', await fetchFile(file))
await ffmpeg.ffprobe(['-i', 'input.mp4', '-show_streams', '-o', 'output.txt'])
const data = await ffmpeg.readFile('output.txt')
…하지만 ffmpeg의 중요성을 깨달았다.
Figma의 권한 관리 DSL - JSON 직렬화 가능한 DSL로 정책 표현, TypeScript 기반 평가 엔진 구현.
기존 문제: 불필요한 복잡성, 계층적 권한 비효율, DB 부하, 여러 진실 소스
type ExpressionDef = BinaryExpressionDef | OrExpressionDef | AndExpressionDef
// 바이너리 표현식: [필드, 연산자, 값]
const binaryExpression = ['file.id', '<>', null] satisfies ExpressionDef
// AND/OR 조합
const andExpression = {
and: [
['file.id', '<>', null],
['team.permission', '=', 'open'],
],
} satisfies ExpressionDefstatic create 패턴 - 생성자 대신 정적 메서드로 객체 생성
언제 사용?
- 생성 로직이 복잡하거나 유효성 검사 필요
- 생성 실패 시 null/Result 반환 (생성자는 항상 인스턴스 반환)
- 팩토리 패턴, 싱글톤
class User {
private constructor(private readonly name: string) {}
// 유효성 검사 + 실패 시 null 반환
static create(name: string): User | null {
if (!name || name.length < 3) return null
return new User(name)
}
}
// 팩토리 패턴
class Shape {
static create(type: 'circle' | 'rect', size: number): Shape {
return type === 'circle' ? new Circle(size) : new Rectangle(size)
}
}
// 싱글톤
class Config {
private static instance: Config
private constructor() {}
static create() {
return Config.instance ??= new Config()
}
}
단순 초기화만 필요하면 일반 생성자가 더 직관적. 복잡한 생성 로직에만 사용.
- https://stackoverflow.com/questions/58136102/deploy-individual-services-from-a-monorepo-using-github-actions
- https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#example-including-paths
최소한 하나의 경로가 paths 필터의 패턴과 일치하면 워크플로가 실행
on:
push:
paths:
- '**.js'
type JsonPrimitive = string | number | boolean | null
type JsonObject = { [Key in string]: JsonValue } & {
[Key in string]?: JsonValue | undefined
}
type JsonArray = JsonValue[] | readonly JsonValue[]
type JsonValue = JsonPrimitive | JsonObject | JsonArray
예전에 JSON 타입 정의가 필요해서 찾아봤던 내용
JsonObject: 문자열 키와 JsonValue 타입의 값을 가진 JSON 객체를 정의JsonArray: JsonValue 타입의 요소를 포함하는 JSON 배열을 정의JsonPrimitive: 문자열, 숫자, 불린, 또는 null과 같은 유효한 JSON 기본 값을 정의JsonValue: 유효한 JSON 값을 나타내며, JsonPrimitive, JsonObject, 또는 JsonArray로 구성
document.activeElement — 현재 포커스된 요소
const el = document.activeElement
if (el.tagName === 'INPUT') console.log(el.value)
// 모달 열릴 때 닫기 버튼으로 포커스 이동
document.querySelector('.modal .close-button').focus()
https://www.emgoto.com/jest-partial-match/
objectContaining·arrayContaining 은 toEqual·toHaveBeenCalledWith 안에 중첩해서 쓴다.1
test('호출 인자 부분 매칭', () => {
const mockFunction = jest.fn()
mockFunction({ id: 1, name: 'Alice', tags: ['developer', 'designer'] })
expect(mockFunction).toHaveBeenCalledWith(
expect.objectContaining({
name: 'Alice',
tags: expect.arrayContaining(['designer']),
})
)
})
test('배열 안 객체 부분 매칭', () => {
const receivedArray = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
]
expect(receivedArray).toEqual(
expect.arrayContaining([
expect.objectContaining({ name: 'Alice' }),
expect.objectContaining({ id: 2 }),
])
)
})
Footnotes
-
Expect - Jest: “You can use it instead of a literal value:” … “in
toEqualortoHaveBeenCalledWith” / “to match a property inobjectContainingortoMatchObject” ↩