URL.parse() 메서드를 활용하여 URL 객체를 생성하고 처리하는 방법
URL.parse(url)메서드는 주어진 URL에 따라 새 URL 객체를 생성- 유효하지 않은 URL 값이 주어질 경우
null을 반환 - 두 번째 파라미터 base는 상대 URL을 해석하기 위한 기준 URL로 사용되며, 이를 통해 URL의 경로가 올바르게 조정됨
- URL 객체나 다른 문자열을 파라미터로 사용할 수 있으며, 내부에서 문자열로 변환됨
describe('null 입력에 대한 URL API 동작 테스트', () => {
test('new URL(null)은 TypeError를 발생시킨다', () => {
expect(() => new URL(null)).toThrow(TypeError)
})
test('URL.canParse(null)은 false를 반환한다', () => {
expect(URL.canParse(null)).toBe(false)
})
test('URL.parse(null)은 null을 반환한다', () => {
expect(() => URL.parse(null)).toBe(null)
})
}) - 검색 매개변수별로 데이터 유효성 검증
useSearchParams와Zod를 활용해 모든 검색 매개변수를 병합하고 유효성 검증
import { useMemo } from 'react'
import { useSearchParams } from 'react-router-dom'
import { z } from 'zod'
const CombinedSchema = PaginationSchema.merge(FilterSchema)
.merge(SortSchema)
.merge(SearchSchema)
.partial()
export function useSearchParamsWithSchema() {
const [searchParams, setSearchParams] = useSearchParams()
const parsedParams = useMemo(() => {
// searchParams를 객체로 변환
const paramsObject = Object.fromEntries(searchParams.entries())
// 결합된 스키마로 유효성 검사 및 파싱
const result = CombinedSchema.safeParse(paramsObject)
if (result.success) {
return result.data
}
console.error(result.error)
return {}
}, [searchParams])
/**
* 새로운 검색 매개변수로 업데이트하는 함수입니다.
*
* 1. 현재 검색 매개변수의 복사본을 생성합니다.
* 2. 새로운 매개변수를 순회하며 값을 설정하거나 삭제합니다.
* 3. 최종적으로 업데이트된 매개변수를 state에 설정합니다.
*/
const updateSearchParams = (newParams: Record<string, string>) => {
const updatedParams = new URLSearchParams(searchParams)
Object.entries(newParams).forEach(([key, value]) => {
if (value) {
// 업데이트된 매개변수로 상태 갱신
updatedParams.set(key, value)
} else {
// 값이 없으면 삭제
updatedParams.delete(key)
}
})
// 업데이트된 매개변수로 상태 갱신
setSearchParams(updatedParams)
}
return {
parsedParams,
updateSearchParams,
}
} 취소 가능한 fetch 요청을 생성하는 createCancelableFetch 함수 구현
/**
* 취소 가능한 fetch 요청
*
* @param url - 요청 URL
* @param options - fetch 옵션
* @returns {run, cancel} - run: 요청 실행 함수, cancel: 요청 취소 함수
*
* @example
*
* ```ts
* const { run, cancel } = createCancelableFetch('/api/data')
*
* run()
* .then((data) => console.log('Fetched data:', data))
* .catch((err) => console.error('Error or canceled:', err))
*
* cancel()
* ```
*/
function createCancelableFetch(url: string, options: RequestInit = {}) {
const abortController = new AbortController()
const run = async () => {
const response = await fetch(url, {
...options,
signal: abortController.signal,
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return response.json()
}
const cancel = () => abortController.abort()
return {
run,
cancel,
}
}
AbortController를 사용하여 요청을 중단할 수 있는 기능 제공run함수로fetch요청을 실행하고,cancel함수로 요청을 취소할 수 있음- 요청 실패 시 오류를 처리하고 예외를 발생시킴
field-sizing를 사용하면 콘텐츠를 기반으로 크기 조절을 사용 설정하는 데 CSS 한 줄이 필요합니다. 이 콘텐츠 기반 크기 조절 스타일은textarea외에도 다른 요소에도 적용됩니다.
- https://github.com/arthurfiorette/proposal-safe-assignment-operator
- https://github.com/arthurfiorette/tuple-it
비동기 호출의 결과와 오류를 튜플 형식으로 반환
const [error, data] = await tuple(someAsyncFunction())
// 성공: [null, 결과값]
// 실패: [error] 또는 [new TupleItError(error)]
오류 처리 간소화 - 단일 체크로 오류 관리 가능
💡
useStateObject는 React의useState를 확장한 가벼운 래퍼로, 객체 상태 관리를 간편하게 할 수 있도록 설계되었습니다.
export type StateObject<T extends object> = T & {
set: React.Dispatch<React.SetStateAction<T>>
setItem: <K extends keyof T>(key: K, value: T[K]) => void
merge: (newState: Partial<T>) => void
reset: () => void
}
그렇다면 Map과 Set도 시도해보기
function useStateMap<K, V>(init: Iterable<[K, V]> = []) {
const [map, setMap] = useState(new Map<K, V>(init))
const update = useCallback(
(updater: (currentMap: Map<K, V>) => void) => {
setMap((prev) => {
const newMap = new Map(prev)
updater(newMap)
return newMap
})
},
[setMap]
)
return {
map,
set: (key: K, value: V) => update((m) => m.set(key, value)),
delete: (key: K) => update((m) => m.delete(key)),
clear: () => setMap(new Map()),
has: (key: K) => map.has(key),
get: (key: K) => map.get(key),
entries: () => Array.from(map.entries()),
size: map.size,
}
}
function useStateSet<T>(init: Iterable<T> = []) {
const [set, setSet] = useState(new Set<T>(init))
const update = useCallback(
(updater: (currentSet: Set<T>) => void) => {
setSet((prev) => {
const newSet = new Set(prev)
updater(newSet)
return newSet
})
},
[setSet]
)
return {
set,
add: (value: T) => update((s) => s.add(value)),
delete: (value: T) => update((s) => s.delete(value)),
has: (value: T) => set.has(value),
clear: () => setSet(new Set()),
entries: () => Array.from(set),
size: set.size,
}
}
thisisunsafe
-> 로컬 환경 또는 테스트 서버에서 강제 접근
- 웹사이트의 SSL/TLS 인증서를 신뢰할 수 없을 때, 이 사이트에 연결할 수 없음 또는 이 연결은 비공개로 설정되지 않았습니다
NET::ERR_CERT_AUTHORITY_INVALID,NET::ERR_CERT_COMMON_NAME_INVALID같은 오류 코드가 나타남
useSyncExternalStore 활용 가능한 부분들
템플릿 리터럴 타입을 활용하여 타입 정의하기
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>
)
}