ν΄λΉ ν¬μ€νΈλ
Next.js
μPrisma
κΈ°λ°μΌλ‘ μμ±νμ΅λλ€.
κΈ°λ³Έμ μΌλ‘λ κ²μκΈκ³Ό ν€μλλ κ°κ°μ΄ μ¬λ¬ κ°λ₯Ό μμ ν μ μμ΄μ N:M
κ΄κ³μ
λλ€.
μ²μμλ N:M
μ μκ°νκ³ κ΅¬ννκΈ°κ° μ΄λ €μμ κ° κ²μκΈμ ν€μλλ€μ 곡백 κΈ°μ€μΌλ‘ λΆλ¦¬ν λ¬Έμμ΄λ‘ λ£μ΄μ ꡬννμ΅λλ€.
μ μμ μΌλ‘ λμμ νμ§λ§ μνμ΄ λμ΄λ μλ‘ DBμ λΆλ΄μ΄ μκΈ°κ² λ κ±°λΌκ³ μκ°ν΄μ μ΄νμ N:M
κ΄κ³λ‘ μμ νμ΅λλ€.
N:M
κ΄κ³μ΄κΈ° λλ¬Έμ νμ°μ μΌλ‘ μ€κ° ν
μ΄λΈμ΄ μκΈ°κ² λ©λλ€.
μ€κ° ν
μ΄λΈμ λͺ
μμ μΌλ‘ μ΄λ¦μ μ§μ νκ³ μ»¬λΌμ μΆκ°ν΄ μ€ μ μμ§λ§ κ΅³μ΄ νμνμ§μκΈ° λλ¬Έμ μμμ μΌλ‘ μμ±νλλ‘ λλμ΅λλ€.
μλ§λ productId
, keywordId
λ κ°μ 컬λΌμ κ°κ³ μλ ν
μ΄λΈμ΄ μκΈΈκ²λλ€.
model Product {
id Int @id @default(autoincrement())
name String @db.VarChar(30)
price Int
description String @db.MediumText
image String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
keywords Keyword[]
# ... μλ΅
}
model Keyword {
id Int @id @default(autoincrement())
keyword String @unique @db.VarChar(20)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
products Product[]
}
ν€μλλ€μ 곡백μ κΈ°μ€μΌλ‘ λΆλ¦¬ν΄μ μ λ ₯λ°μ΅λλ€.
// async ν¨μ λ΄λΆμ μλ€κ³ κ°μ
// ν€μλ μμ± or μ°Ύκ³ μνκ³Ό μ°κ²°
const keywordsPromise = (keywords as string).split(" ").map((keyword) =>
prisma.keyword.upsert({
create: {
keyword,
products: {
connect: {
id: createdProduct.id,
},
},
},
update: {
products: {
connect: {
id: createdProduct.id,
},
},
},
where: {
keyword,
},
})
);
await Promise.allSettled(keywordsPromise);
// async ν¨μ λ΄λΆμ μλ€κ³ κ°μ
// "records"λ μνμ μν κ΄λ ¨ν κ²μ΄λΌ 무μν΄λ λ¨
const keyword = req.query.keyword + "";
const findKeywordProducts = await prisma.product.findMany({
take: offset,
skip: page * offset,
where: {
keywords: {
some: {
keyword,
},
},
records: {
some: {
OR: condition,
},
},
},
include: {
_count: {
select: {
answers: true,
records: true,
},
},
records: {
select: {
kinds: true,
},
},
},
orderBy: [
{
createdAt: "desc",
},
],
});
νλ μ΄μμ κΈμλ₯Ό ν¬ν¨νλ ν€μλκ° μλ κ²½μ°μ λͺ¨λ κ°μ Έμ€λ λ‘μ§μ λλ€.
// async ν¨μ λ΄λΆμ μλ€κ³ κ°μ
const keywords = await prisma.keyword.findMany({
where: {
keyword: {
contains: keyword,
},
},
select: {
keyword: true,
},
});
λλ°μ΄μ€λ μ°μν΄μ κ°μ μμ²μ΄ λ€μ΄μ¬ κ²½μ° μ μΌ λ§μ§λ§ μμ²λ§ μ ν¨νκ² μ²λ¦¬νλ λ°©λ²
μ¬μ©μκ° μ°μμ μΌλ‘ ν€μλλ₯Ό μ λ ₯ν λλ μμ²μ 보λ΄μ§ μκ³ 0.3μ΄ μ λ μ λ ₯μ΄ λ©μΆλ©΄ νμ¬κΉμ§ μ λ ₯ν λ¬Έμμ΄μ λ°±μλλ‘ μ μ‘ν΄μ μ λ ₯ν λ¬Έμμ΄μ ν¬ν¨νλ λͺ¨λ ν€μλλ€μ λ°νν΄μ€λλ€.
// debounceκ° trueμΌ κ²½μ°μ ν€μλ κ²μ μμ²μ 보λ΄λλ‘ μ²λ¦¬ν¨
// 2022/04/16 - ν€μλ κ²μ μ λλ°μ΄μ€ μ μ©ν λ μ¬μ©νλ λ³μ - by 1-blue
const [debounce, setDebounce] = useState(false);
// 2022/04/16 - ν€μλ κ²μ μ λλ°μ΄μ€ μ μ©ν λ μ¬μ©νλ ν¨μ - by 1-blue
const debounceKeyword = useCallback(() => setDebounce(true), [setDebounce]);
// 2022/04/16 - ν€μλ κ²μ μ λλ°μ΄μ€ μ μ© - by 1-blue
useEffect(() => {
const timerId = setTimeout(debounceKeyword, 300);
return () => {
clearTimeout(timerId);
setDebounce(false);
};
}, [debounceKeyword, keyword, setDebounce]);
<input />
μ ν¬μ»€μ€λ₯Ό μ£Όλ©΄ μΆμ² κ²μμ΄κ° λλλ§λκ³ , ν¬μ»€μ€λ₯Ό λ λλ©΄ onFocus
μ onBlur
λ₯Ό μ΄μ©ν΄μ μΆμ² κ²μμ΄λ€μ΄ μ¬λΌμ§λλ‘ λ§λ€μμ΅λλ€.
νμ§λ§ μΆμ² κ²μμ΄λ₯Ό ν΄λ¦νλ μκ°μ μΆμ² κ²μμ΄μ <input />
μμ ν¬μ»€μ€κ° μμ΄μ Έμ μΆμ² κ²μμ΄κ° μ¬λΌμ Έμ κ²μμ΄ μλλ λ¬Έμ κ° λ°μνμ΅λλ€.
λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄μ μ΄μ μ μ¬μ©ν΄λ΄€λ λͺ¨λ¬ μμμΈ ν΄λ¦ μ λ«λ κΈ°λ₯μ κ°μ Έμμ μ μ©νμ΅λλ€.
<input />
κ³Ό μΆμ² ν€μλλ₯Ό κ°μΈλ wrapper
μ λ£μ΄λκ³ wrapper
λ₯Ό μ μΈν μμμ ν΄λ¦νλ©΄ μΆμ² κ²μμ΄κ° μ¬λΌμ§λλ‘ κ΅¬νν΄μ λ¬Έμ λ₯Ό ν΄κ²°νμ΅λλ€.
// 2022/04/16 - ν€μλ κ° - by 1-blue ( react-hook-formμ useForm() )
const keyword = watch("keyword");
// 2022/04/16 - ν€μλ ν¬μ»€μ€ μ¬λΆ λ° κ΄λ ¨ ν€μλ 보μ¬μ€μ§ κ²°μ ν λ³μ - by 1-blue
const [isFocus, setIsFocus] = useState(false);
// κ²μμ°½κ³Ό μΆμ² κ²μμ΄λ₯Ό μμμΌλ‘ κ°μ§λ element
const wrapperRef = useRef<HTMLDivElement>(null);
// 2022/04/16 - μμμΈ ν΄λ¦ μ μΆμ² ν€μλ μ°½ λ«κΈ° - by 1-blue
const handleCloseModal = useCallback(
(e: any) => {
if (
isFocus &&
(!wrapperRef.current || !wrapperRef.current.contains(e.target))
)
setIsFocus(false);
},
[isFocus, setIsFocus, wrapperRef]
);
// 2022/04/16 - μΆμ² ν€μλ μ°½ λ«κΈ° μ΄λ²€νΈ λ±λ‘ - by 1-blue
useEffect(() => {
setTimeout(() => window.addEventListener("click", handleCloseModal), 0);
return () => window.removeEventListener("click", handleCloseModal);
}, [handleCloseModal]);
// ... λλ¨Έμ§ μλ΅