import * as React from 'react'
import { IsEqual } from 'type-plus'
type ModalProps =
| {
variant: 'no-title'
}
| {
variant: 'title'
title: string
}
export const Modal = (props: ModalProps) => {
if (props.variant === 'no-title') {
return <div>No title</div>
} else {
type Test = IsEqual<
typeof props,
{
variant: 'title'
title: string
}
>
return <div>Title: {props.title}</div>
}
}
export const Test = () => {
return (
<div>
<Modal variant="title" title="Hello" />
<Modal variant="no-title" />
{/* @ts-expect-error */}
<Modal />
<Modal
variant="no-title"
// @ts-expect-error
title="Oh dear"
/>
</div>
)
}
import * as React from 'react'
type Prettify<T> = {
[K in keyof T]: T[K]
} & {}
type T = React.ComponentProps<'div'>
type P = Prettify<T>
React 제네릭 컴포넌트 패턴 - <T extends Record<string, any>>로 row/item 타입 추론
import * as React from 'react'
type Props<TRow> = {
rows: TRow[]
renderRow: (row: TRow, index: number) => React.ReactNode
}
const Table = <TRow extends Record<string, any>>({
rows,
renderRow,
}: Props<TRow>) => {
return (
<table>
<tbody>{rows.map((row, index) => renderRow(row, index))}</tbody>
</table>
)
}
function App() {
return (
<Table
rows={[{ name: 'lee' }]}
renderRow={(row, index) => (
<tr key={index}>
<td>{row.name}</td>
</tr>
)}
/>
)
}
import * as React from 'react'
import { UseComboboxProps, useCombobox } from 'downshift'
function Combobox<T extends Record<string, any>>(
props: UseComboboxProps<T> & {
renderItem: (item: T) => React.ReactNode
}
) {
const combobox = useCombobox(props)
return (
<div>
<input
placeholder="구성원을 검색해주세요."
{...combobox.getInputProps()}
/>
<div {...combobox.getMenuProps()}>
{combobox.isOpen &&
props.items.map((item, index) => (
<div key={item.id} {...combobox.getItemProps({ item, index })}>
{props.renderItem(item)}
</div>
))}
</div>
</div>
)
}
function App() {
return (
<Combobox
items={[{ id: 1, name: 'eunsoo' }]}
itemToString={(item) => `${item?.id}`}
renderItem={(item) => {
return <div>{item.name}</div>
}}
onInputValueChange={({ inputValue }) => {
console.log(inputValue)
}}
/>
)
}
export function parseNativeEmoji(unified: string): string {
return unified
.split('-')
.map((hex) => String.fromCodePoint(parseInt(hex, 16)))
.join('')
}
unified문자열을-로 분리하여 개별 유니코드 코드 포인트를 얻는다.- 각 16진수 코드 포인트를 정수로 변환하고, 그에 해당하는 캐릭터를 반환한다.
- 최종적으로 변환된 캐릭터들을 연결하여 하나의 문자열로 생성한다.
import type { A } from 'a'
const emptyCsvFile = new File([''], 'default.csv', { type: 'text/csv' })
const emptyPngFile = new File([''], 'default.png', { type: 'image/png' })
formData.append('product_coupons', emptyCsvFile)
formData.append('product_imgs', emptyPngFile)
console.assert(emptyCsvFile.size === 0)
빈 파일을 FormData에 첨부하여 전송하는 방법
File객체를 사용하여 빈 파일을 생성할 수 있다.- 파일의
MIME타입을 지정하여 타입에 맞는 빈 파일을 만들 수 있다. - 생성된 빈 파일을
FormData객체에 추가하여 전송할 수 있다.
이는 서버에 빈 형태의 파일을 전송할 때 유용하며, API 테스트 및 디폴트 값 설정에 활용될 수 있다.
브라우저에서 PDF 및 이미지 파일에 대한 OCR 실행
- PDF.js를 사용하여 PDF에서 이미지를 추출
- Tesseract OCR로 추출된 이미지에서 텍스트를 인식
- 직접 브라우저 상에서 OCR 작업을 실행
getServerSideProps
|> getServerSideSitemapLegacy
|> withXMLResponseLegacy
|>
res.setHeader('Content-Type', 'text/xml')
res.write(content)
res.end()
SSR에서 XML 형식의 사이트맵을 생성하기.
import * as React from 'react'
type ContainerProps = {
children: typeof Body
}
type BodyProps = {
id: string
}
function Container({ children }: ContainerProps) {
const id = '1234'
return <>{children({ id })}</>
}
export function Body({ id }: BodyProps) {
return <>{id}</>
}
export function Page() {
return <Container>{Body}</Container>
}
컨테이너/프레젠테이션 패턴을 활용한 React 컴포넌트 구조.
- 컨테이너 컴포넌트(
Container)는 상태를 관리하고 데이터를 하위 컴포넌트에 전달함. - 프레젠테이션 컴포넌트(
Body)는 데이터를 받아서 UI를 렌더링함. - 이 구조는 테스트를 쉽게 하고, 컴포넌트의 역할을 명확하게 분리함.
/**
* - 인자 수 확인: `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)의 결과는 6 - 부분 적용(Partial Application): 일부 인자만 미리 적용하여 새로운 함수를 만들 수 있으며,
const double = multiply(2, _)로 정의 - 포인트-프리 스타일: 변수를 사용하지 않고 함수를 조합하여 작성하며, 예를 들면
compose(square, double)와 같은 형태 - 데이터 마지막 원칙(Data Last): 데이터를 마지막 인자로 배치하여 조합성을 높이는데, 예를 들어
const halve = divideDataLast(2)와 같이 사용