- μ¬μ©μ±κ³Ό ν¨μ¨μ μ€μ¬μΌλ‘ λμμΈκ³Ό λ°μ΄ν° μμ μ± κ°ν
- μΉκ³Ό μ± λͺ¨λ μ΅μ νλ λμμΈμ μ 곡
- μ¬μ©μ μΉνμ μΈ κ²½νκ³Ό μ§κ΄μ μΈν°νμ΄μ€λ₯Ό ν΅ν λΉ λ₯Έ μ μ λͺ©ν
- κΈ°μ‘΄ 곡μ νμ΄μ§ κΈ°λ₯μμ μ‘°κΈλ μ λ°μ΄νΈλ 곡μ νμ΄μ§ κΈ°λ₯ ꡬν
μ΄μ λ μλ²μμ λ νΈλ¦¬ν μλΉμ€λ₯Ό λ§λ보μΈμ!!π€
κ°λ° κΈ°κ° : 2023λ 09μ 14 ~ 2023.10.05
Front-end κΈ°μ μ€ν | |
Front-end λ°°ν¬ | |
λ°°ν¬ | π JavaJober[μλ°μλ²] |
λ Έμ | π λ Έμ λ°λ‘κ°κΈ° |
-> ν
νλ¦Ώ μμ± ν΄λ¦μ μΆμ² ν
νλ¦ΏβΆ inputμ°½μ focusμ μΉ΄ν
κ³ λ¦¬ ν
νλ¦Ώ βΆ inputμ°½μ κ²μμ΄ μ
λ ₯μ κ²μ ν
νλ¦Ώ μ΄ 3λ²μ νμ΄μ§ μν λ³νκ° μκ² λ©λλ€.
μ²μ μ½λ μμ±μ κ³΅ν΅ λͺ¨λ¬ λ μ΄μμ μμ λͺ¨λ νμ΄μ§λ₯Ό κ΄λ¦¬ν κ°κ°μ state κ°μ μμ±νκ³ true, false λ‘ λͺ¨λ¬μμ 컨ν
μΈ μνκ°μ λ³κ²½νκ² νμμΌλ©°, 2λ²μ§Έ νμ΄μ§κ° 보μΌμ 1λ²μ§Έ νμ΄μ§κ° 보μ΄μ§ μλλ‘ νκΈ° μν΄ false κ°μ μ£Όμμ΅λλ€.
μ΄λ κ² νλμ μ»΄ν¬λνΈκ° λ³κ²½λ λλ§λ€ μ΄μ μ»΄ν¬λνΈκ° 보μ΄μ§ μκ² νκΈ° μν΄ true, false(boolean νμ
)μΌλ‘ μ»΄ν¬λνΈ κ΄λ¦¬λ₯Ό νλ€λ³΄λ , κ³΅ν΅ λͺ¨λ¬ μμ λ λ§μ μ»΄ν¬λνΈκ° λ³κ²½λ μ κ΄λ¦¬ νκΈ° μ΄λ ΅κ³ μ½λκ° λ³΅μ‘ ν΄μ§λ λ¬Έμ μ μ΄ μκ²Όμ΅λλ€.
export const ModalOpen = () => {
const { Search } = Input;
// λͺ¨λ¬ μ€νμ κ΄λ¦¬νκΈ° μν μνκ΄λ¦¬
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
// μ²μ μΆμ² ν
νλ¦Ώμ 보μ¬μ£ΌκΈ° μν μνκ΄λ¦¬
const [showBestTemplate, setShowBestTemplate] = useState<boolean>(true);
// μΈνμ°½μ ν¬μ»€μ€μ 보μ¬μ£ΌκΈ° μν μνκ΄λ¦¬
const [categoryTemplate, setCategoryTemplate] = useState<boolean>(false);
// μΈνμ°½μ μ
λ ₯μ λ³κ²½λλ μνκ΄λ¦¬
const [inputText, setInputText] = useState('');
// κ²μ λ²νΌ ν΄λ¦μ μ€νλλ ν¨μ
const onSearch = (value: string) => {
console.log(value);
alert('');
};
// λͺ¨λ¬μ°½μ 보μ¬μ£Όλ ν¨μ
const showModal = () => {
setIsModalOpen(true);
setShowBestTemplate(true);
setCategoryTemplate(false);
};
const handleSearchFocus = () => {
setShowBestTemplate(false); // Search μ
λ ₯μ ν¬μ»€μ€κ° ν΄λ¦λλ©΄ BestTemplate μ¨κΉ
setCategoryTemplate(true);
if (inputText.length > 0) {
setCategoryTemplate(false);
} else {
return;
}
};
const handleOk = () => {
setIsModalOpen(false);
setShowBestTemplate(false);
//setInputText('');
};
const handleCancel = () => {
alert('μ·¨μ');
setIsModalOpen(false);
setShowBestTemplate(true);
setCategoryTemplate(false);
//setInputText('');
};
const handleChangeText = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputText(e.target.value);
if (e.target.value.length > 0) {
setCategoryTemplate(false);
setShowBestTemplate(false);
console.log(e.target.value);
} else {
setCategoryTemplate(true);
}
};
return (
<>
<Button className="buttonOpen" type="primary" onClick={showModal}>
ν
νλ¦Ώ μμ±
</Button>
<Modals
title="Basic Modal"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
maskClosable={false}
>
<ModalHeader>
<p>ν
νλ¦Ώ μ ννκΈ°</p>
</ModalHeader>
<SettingTemplet>
<p className="settingtText">ν
νλ¦Ώ μ€μ νκΈ°</p>
<SelectBox>
<InputBox>
<Select
className="selectbox"
defaultValue="λ¬Έμμ λͺ©"
allowClear
options={[{ value: 'λ¬Έμ', label: 'λ¬Έμμ λͺ©' }]}
/>
<Search
className="searchBox"
type="text"
placeholder="input search text"
onSearch={onSearch}
onFocus={handleSearchFocus}
value={inputText}
onChange={handleChangeText}
/>
</InputBox>
// λ³κ²½μ
{showBestTemplate && <BestTemplate />}
{categoryTemplate && <CategoryTemplet />}
{inputText && <SelecteSearchTemplate inputText={inputText} />}
</SelectBox>
</SettingTemplet>
</Modals>
</>
);
};
-> ν€κ°μ λ§λ μ»΄ν¬λνΈ κ°μ²΄λ₯Ό μμ±νμ¬ ν΄λΉ κ°μ²΄λ₯Ό μνκ΄λ¦¬ νλλ‘ κ΅¬ννμμ΅λλ€. νλμ setState λ₯Ό ν΅νμ¬ κ°κ°μ μ»΄ν¬λνΈλ₯Ό λ³κ²½μμΌ μ£Όλλ‘ νμμ΅λλ€.
export const ModalOpen = () => {
const { Search } = Input;
// modal contents λ₯Ό κ΄λ¦¬νλ state, type μμ±
const [procedure, setProcedure] = useState<'recommand' | 'category' | 'search'>('recommand');
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const [inputText, setInputText] = useState('');
// ν€κ°μ λ§λ μ»΄ν¬λνΈ κ°μ²΄ μμ±
const PROCEDURE_MAPPER = {
recommand: <BestTemplate />,
category: <CategoryTemplate />,
search: <SelecteSearchTemplate inputText={inputText} />,
};
// κ²μ λ²νΌ ν΄λ¦μ μ€νλλ ν¨μ
const onSearch = (value: string) => {
console.log(value);
alert('');
};
// λͺ¨λ¬μ°½μ 보μ¬μ£Όλ ν¨μ
const showModal = () => {
setIsModalOpen(true);
};
const handleSearchFocus = () => {
setProcedure('category');
};
const handleOk = () => {
setIsModalOpen(false);
setInputText('');
setProcedure('recommand');
};
const handleCancel = () => {
setIsModalOpen(false);
setInputText('');
setProcedure('recommand');
};
const handleChangeText = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputText(e.target.value);
if (e.target.value.length > 0) {
setProcedure('search');
} else {
setProcedure('category');
}
};
return (
<>
<Button className="buttonOpen" type="primary" onClick={showModal}>
ν
νλ¦Ώ μμ±
</Button>
<Modals
centered
title={
<ModalHeader
title="ν
νλ¦Ώ μ ννκΈ°"
handleOk={handleOk}
handleCloseModal={handleCancel}
/>
}
footer={null}
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
closeIcon={null}
>
<SettingTemplet>
<p className="settingtText">ν
νλ¦Ώ μ€μ νκΈ°</p>
<SelectBox>
<InputBox>
<Select
className="selectbox"
defaultValue="λ¬Έμμ λͺ©"
allowClear
options={[{ value: 'λ¬Έμ', label: 'λ¬Έμμ λͺ©' }]}
/>
<Search
className="searchBox"
type="text"
placeholder="input search text"
onSearch={onSearch}
onFocus={handleSearchFocus}
value={inputText}
onChange={handleChangeText}
/>
</InputBox>
// λ³κ²½ν
{PROCEDURE_MAPPER[procedure]}
</SelectBox>
</SettingTemplet>
</Modals>
</>
);
};
πλ°©λ² 1
πμ²μ νμ΄μ§ mount μ μλ²μμ λͺ¨λ λ°μ΄ν°λ₯Ό κ°μ Έμ€λ apiλ₯Ό νΈμΆν νλ‘ νΈμμ filter μ²λ¦¬ν κ²°κ³Όκ° λ
ΈμΆ
-> ν΄λΉ λ°©λ²μΌλ‘ κΈ°λ₯ ꡬνμ μκΈ°λ λ¬Έμ μ = λ°μ΄ν° λ³κ²½μ΄ λ§μ΄ μμ κ²½μ° νλ‘ νΈμμ filter μ²λ¦¬λ₯Ό νλ©΄ μ΅μ μΌλ‘ λ°μλλ λ°μ΄ν°λ₯Ό κ°μ Έμ€μ§ λͺ»νλ λ¬Έμ μ μ μκ°νμμ΅λλ€.
λν λ°μ΄ν°κ° λ§μμλ‘ λͺ¨λ λ°μ΄ν°λ₯Ό λ°μμ€λκ²μ μ±λ₯ μ μΌλ‘λ μ’μ§ μμκ² κ°λ€κ³ νλ¨νμμ΅λλ€.
πλ°©λ² 2
πμλ²μμ μ
λ ₯κ°μ λν΄ νν°λ§λ λ°μ΄ν°μ λν api λ₯Ό μ¬μ©νμ¬ κ²°κ³Όκ° λ
ΈμΆ
-> 첫λ²μ§Έ λ°©μμμμ λ¬Έμ μ μ μκ°νμ¬ λλ²μ§Έ λ°©μμΌλ‘ κ²μ νμ΄μ§λ₯Ό ꡬννμμ΅λλ€. λ°λΌ μλ²μμ μ
λ ₯κ°μ λν΄ νν°λ§λ apiλ₯Ό μμ±ν ν΄λΉ apiλ₯Ό μ΄μ©νμ¬ κ²μ νμ΄μ§λ₯Ό ꡬννμμ΅λλ€.
export const SelecteSearchTemplate: React.FC<Props> = ({ keyword }) => {
const [product, setProductInfo] = useState<ProductItem[]>([]);
const [filteredResults, setFilteredResults] = useState<ProductItem[]>([]);
useEffect(() => {
const getData = async () => {
try {
const response = await fetch(
`${import.meta.env.VITE_SERVER_BASE_URL}/${keyword}`,
);
if (response.ok) {
const data = await response.json();
setProductInfo([...product, ...data]);
} else {
console.error('Response not OK:', response);
}
} catch (error) {
console.error('Error while fetching data:', error);
}
};
getData();
}, [keyword, product]);
useEffect(() => {
const filteredResults = product.filter((item) =>
item.title.toLowerCase().includes(keyword),
);
setFilteredResults(filteredResults);
}, [keyword, product]);
return (
<>
<SeleteContainer>
<h3>κ²μκ²°κ³Ό</h3>
<ResultBox>
{filteredResults.map((item) => (
<ResultTemBox key={item.id}>{item.title}</ResultTemBox>
))}
</ResultBox>
<BestTemplate />
</SeleteContainer>
</>
);
};
export const SelecteSearchTemplate: React.FC<Props> = ({ inputText }) => {
const [debouncedInputValue, setDebouncedInputValue] = useState('');
const [products, setProducts] = useState<ProductItem[]>([]);
useEffect(() => {
// μ
λ ₯κ°μ΄ λ³κ²½λ λλ§λ€ debounceλ κ°μ μ
λ°μ΄νΈ.
const debounceTimer = setTimeout(() => {
setDebouncedInputValue(inputText);
}, 300); // 300 λ°λ¦¬μ΄(0.3μ΄) λλ°μ΄μ€ μκ°
return () => {
// μ΄μ νμ΄λ¨Έλ₯Ό ν΄λ¦¬μ΄.
clearTimeout(debounceTimer);
};
}, [inputText]);
useEffect(() => {
if (debouncedInputValue) {
const getData = async () => {
try {
const response = await axios.get(`${import.meta.env.VITE_SERVER_BASE_URL}`, {
params: {
search: debouncedInputValue,
},
});
const data = response.data.data.list;
setProducts([...data]);
} catch (error) {
console.error('API νΈμΆ μλ¬:', error);
}
};
getData();
} else {
setProducts([]);
}
}, [debouncedInputValue]);
return (
<>
<SeleteContainer>
<h3>{templateText.inputResult}</h3>
<ResultBox>
{products.map((item) => (
<ResultTemBox key={item.templateId}>
{item.templateTitle} <br />
{item.templateDescription}
</ResultTemBox>
))}
</ResultBox>
<BestTemplate PERSONAL={''} />
</SeleteContainer>
</>
);
};
-> μλ²μμ user μ λ ₯κ°μ λν΄ filter μ²λ¦¬λ₯Ό νκ³ , debounce λ₯Ό μ¬μ©νμ¬ κΈ°λ₯ ꡬννμμ΅λλ€.
-> μΌμ μκ° λμ μ°μμ μΌλ‘ λ°μνλ μ΄λ²€νΈλ€ μ€ λ§μ§λ§λ§ μ€νμμΌ κ³Όλ€ν νΈμΆμ΄λ λ λλ₯Ό λ§μ μ΅μ ννλ κΈ°μ μ λλ€.
λ°λΌ μ¬μ©μκ° κ²μμ°½μ νμ΄ν ν λλ§λ€ Apiκ° νΈμΆλλκ²μ΄ μλ , debounce λ₯Ό μ¬μ©νμ¬ λ§μ§λ§μ νμ΄ν μ λ ₯ν λ Apiκ° νΈμΆλλλ‘ κΈ°λ₯ ꡬνμ νμμ΅λλ€.
π3. antd button components μ¬μ©μ νκ°μ λ²νΌλ§ μ νλλκ² μλ, μ¬λ¬ λ²νΌ μ νλ¨ ( λ€μ μ²΄ν¬ λ²νΌ ν΄λ¦μ μ΄μ ν΄λ¦λ μ²΄ν¬ λ²νΌμ μμ΄μ ΈμΌν¨)
-> radio λ²νΌ ν΄λ¦μ handleRadioChange ν¨μ μ€ν.
<Radio
onChange={() => handleRadioChange(item)}
/>
-> radio λ²νΌμ μμ±κ° checkedλ₯Ό μ΄μ©νμ¬ μ ννν νλ¦Ώμ μμ΄λμ , λ ΈμΆλ ν νλ¦Ώμ μμ΄λκ° κ°μ trueμ΄ λμ΄μΌ 체ν¬κ° λλλ‘ μ‘°κ±΄μμ μΆκ° νμμ΅λλ€.
<Radio
onChange={() => handleRadioChange(item)}
checked={
selectedTemplate &&
selectedTemplate.templateId === item.templateId
}
/>
-> νκ°μ λ²νΌλ§ μ νλ©λλ€.
미리보기 νμ΄μ§ ꡬνμ μν΄ μνκ΄λ¦¬ λΌμ΄λΈλ¬λ¦¬ zustand λ₯Ό μ¬μ©ν΄ Radio button ν΄λ¦μ ν΄λΉ λ°μ΄ν°κ° storeμ μ μ₯νλλ‘ κ΅¬ννμμ΅λλ€.
-> κ° νμ΄μ§ λ§λ€ ν¨νλ¦Ώ μμ radioλ²νΌμ μ νν μ μκ² λκ³ , μ νμ ν΄λΉ id,title, description μ΄ μ μκ΄λ¦¬ μν store μ μ₯λ¨.
πstore.ts -> store μ type μμ±
type TemplateState = {
selectedTemplate: {
category: string;
id: string;
title: string;
description: string;
};
setSelectedTemplate: (template: {
category: string;
id: string;
title: string;
description: string;
}) => void;
};
export const useTemplateStore = create<TemplateState>((set) => ({
selectedTemplate: {
category: '',
id: '',
title: '',
description: '',
},
setSelectedTemplate: (template) =>
set({ selectedTemplate: template }),
}));
πRecommendInner.ts -> λ§λ€μ΄μ§ storeμ μ νν ν νλ¦Ώ λ°μ΄ν° μ μ₯
const { setSelectedTemplate } = useTemplateStore();
const handleRadioChange = (item: TemplateData, status: boolean) => {
const param = {
category: PERSONAL,
id: item.id,
title: item.title,
description: item.description,
};
console.log(item);
console.log(status);
setSelectedTemplate(param);
};
return(
<Radio
onChange={(e) => handleRadioChange(item, e.target.checked)}
/>
)
-> storeμ λ§λ€μ΄μ§ setSelectedTemplate λ₯Ό μ΄μ©ν΄μ λ°μ΄ν° μ μ₯
λ¬Έμ μ : ν
νλ¦Ώμ μλ radio button ν΄λ¦μ ν΄λΉ λ°μ΄ν°λ₯Ό storeμ μ μ₯νκ³ storeμ ꡬλ
νκ³ μλ wallcomponentμ ν΄λΉ λ°μ΄ν°κ° λ°λ‘ λνλ΄λ λ¬Έμ μ μ΄ μκ²Όμ΅λλ€.
radio button ν΄λ¦μ λ°λ‘ λ±λ‘λ ν
νλ¦Ώμ΄ λ³΄μ΄λκ² μλ, radio button ν΄λ¦ν "μλ£" λ²νΌμ λλ¬μΌ λͺ¨λ¬μ°½μ΄ λ«νκ³Ό λμμ wallcomponentμ λ±λ‘λ ν
νλ¦Ώμ΄ λ³΄μ¬μΌ ν©λλ€.
ν΄κ²° λ°©λ² -> true, false μνκ°μ λν 쑰건μμ μΆκ°ν΄μ radio button ν΄λ¦μμλ μνκ° false μ΄κ³ , "νμΈ" λ²νΌ ν΄λ¦μμλ true. true μΌλλ§ ν νλ¦Ώ λ±λ‘μ΄ λλ λ‘μ§μΌλ‘ ꡬννμμ΅λλ€.
μμ μ½λ πstore.ts
export const useTemplateStore = create<TemplateState>((set) => ({
selectedTemplate: {
category: '',
templateId: '',
templateTitle: '',
templateDescription: '',
},
setSelectedTemplate: (template) => set({ selectedTemplate: template }),
// μλ‘μ΄ μνκ° μΆκ°
newStatus: false,
setNewStatus: (newStatus) => set({ newStatus }),
}));
πRecommedInner.tsx
const handleRadioChange = (item: TemplateData) => {
const param = {
category: PERSONAL,
templateId: item.templateId,
templateTitle: item.templateTitle,
templateDescription: item.templateDescription,
};
setSelectedTemplate(param);
// radio λ²νΌ ν΄λ¦μ Status false
setNewStatus(false);
};
πTemplateModal.tsx
const { selectedTemplate, newStatus } = useTemplateStore();
const [templateHistory, setTemplateHistory] = useState<Array<TemplateItem>>(
[],
);
useEffect(() => {
// μνκ° μ‘°κ±΄μμ ν΅νμ¬ μ μ₯λ ν
νλ¦Ώμ 보μ¬μ€.
if (newStatus) {
setTemplateHistory((prevHistory) => [...prevHistory, selectedTemplate]);
}
}, [newStatus, selectedTemplate]);
return(
<BlockContainer blockName="templateBlock">
<div
className={`
${isEdit && 'px-[8px] pb-[8px] pt-[30px]'}
gap-4 grid sm:grid-cols-2 grid-cols-1
`}
>
{templateBlockSubData?.map((template) => (
<SingleTemplate
key={template.templateBlockUUID}
templateTitle={template.templateTitle}
templateDescription={template.templateDescription}
/>
))}
{isEdit && (
<>
<BlockContainer blockName="template">
<div className="sm:h-[210px] h-[115px] flex flex-col items-center justify-center gap-[8px] dm-16" ref={templateAddButtonRef}>
<ModalOpen />
</div>
</BlockContainer>
{templateHistory.map((template, index) => (
<BlockContainer key={index} blockName="template">
<div className="sm:h-[210px] h-[115px] p-block">
<div className="flex items-center justify-between mb-[12px]">
<h4 className="db-18 sm:db-20">{template.templateTitle}</h4>
</div>
<div className="flex sm:gap-[8px] gap-[6px]">
<p className="dm-16 text-gray88">
{template.templateDescription}
</p>
</div>
</div>
</BlockContainer>
))}
)
μμ ν μκΈ΄ 2μ°¨ λ¬Έμ μ
π₯λ¬Έμ μ : μλ μ΄λ―Έμ§ μ²λΌ μΆκ° ν λλ§λ€ μμ ν
νλ¦Ώμ΄ μΆκ°λ λλ§λ€ ν
νλ¦Ώ μμ±μ λΈλμ΄ μμ°μ€λ½κ² λ°λ €λμΌ νλλ°, μμ λ°©λ²λλ‘ κ΅¬ννλ©΄ ν
νλ¦Ώμ΄ μΆκ°λλ ν
νλ¦Ώ μμ±μ λΈλμ μμΉλ κ·Έλλ‘ μλ
λΆμμ°μ€λ¬μ΄ λͺ¨μ΅μ΄ 보μ
λλ€.
κΈ°λ₯ ꡬνλͺ¨μ΅
μ€λ₯ ν΄κ²°λ°©λ² -> νμ¬ storeμ μ μ₯λ λ°μ΄ν°λ‘ wall λ°μ΄ν°λ‘ μ μμ μΌλ‘ μ°κ³ μλ€.
store.tsx
export const useWallStore = create<WallStoreType>((set) => ({
isEdit: false,
setIsEdit: (bool) => set(() => ({ isEdit: bool })),
isPreview: false,
setIsPreview: (bool) => set(() => ({ isPreview: bool })),
getWall: async () => {
const response = await fetch('http://localhost:3000/wall');
if (response.ok) {
set({ wall: await response.json() });
}
},
wall: {} as WallType,
setWall: (states: object) =>
set((state) => ({ wall: { ...state.wall, ...states } })),
}));
μμ ν νλ¦Ώ λΈλ‘μ λν μ»΄ν¬λνΈ μ½λλ μλμ κ°λ€.
SingleTemplate.tsx
리μ‘νΈμμλ stateμ λΆλ³μ±μ μ§μΌμΌ ν©λλ€.
import { useState } from 'react';
export default function App() {
const [cat, setCat] = useState({
name: 'howoo',
age: 6,
});
const handleChangeCatName = () => {
cat.name = 'mango';
setCat(cat);
};
console.log(cat); //{ name: 'mango', age: 6 }
return (
<div style={{ textAlign: 'center' }}>
<div>κ³ μμ΄ μ΄λ¦ : {cat.name}</div>
<button onClick={handleChangeCatName}>μ΄λ¦λ³κ²½</button>
</div>
);
}
λ²νΌμ λλ₯΄λ©΄ console.log(cat)μ ν΅ν΄ μ€μ¬ cat.nameμ λ³κ²½μ΄ λκ²μ νμΈν μ μμ§λ§ cat
μ μ°Έμ‘°κ°μ κ·Έλλ‘μ΄κΈ° λλ¬Έμ μ¬λλλ§μ΄ λ°μνμ§ μμ΅λλ€.
λΆλ³μ±μ μ§μΌμΌνλ€λ μλ―Έλ μμ λΉκ΅λ₯Ό νλ 리μ‘νΈμ νΉμ±μ μ°Έμ‘°ν λ°μ΄ν°μ μλ³Έμ λ³νμ§ μκ²
μ μ§ν΄μΌνκ³ μ¬λλλ§μ μν΄ μλ‘μ΄ μ°Έμ‘°κ°μ setν΄μΌ ν¨μ μλ―Έ ν©λλ€.
λ³Έ νλ‘μ νΈμμλ wall(곡μ©νμ΄μ§μμ 보μ¬μ§λ λͺ¨λ μ 보) κ°μ²΄κ° μμ΅λλ€.
const wall = {
category: 'personal',
memberId: 1,
spaceId: 1,
shareURL: 'howooking',
wallInfoBlock: {
wallInfoBlockId: 9,
wallInfoTitle: 'μ΄νΈμ°',
wallInfoDescription: 'μλ
νμΈμ. κ³ μμ΄ κ°λ°μ μ΄νΈμ°μ
λλ€.',
wallInfoImgURL: 'https://avatars.githubusercontent.com/u/87072568?v=4',
backgroundImgURL:
'https://images.unsplash.com/photo-1696251143046-2d32fb985b59?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2670&q=80',
},
blocks: [
{
blockUUID: '1108fff1-0106-4340-b505-280e15626ecc',
blockType: 'listBlock',
subData: [
{
listBlockId: 33,
listLabel: 'νλ ₯/κ²½λ ₯',
listTitle: 'νλ ₯',
listDescription: 'μμΈλνκ΅',
isLink: false,
},
],
},
... μλ΅
-> 곡μ νμ΄μ§μμ λ°μνλ λͺ¨λ onChange μ΄λ²€νΈλ wall λ΄λΆ κ°λ€μ μ€μκ°μ λ³κ²½μμΌμΌ ν©λλ€.
μλ₯Ό λ€μ΄ wall.wallInfoBlock.wallInfoTitle
κ°μ μλ‘μ΄ κ°μΌλ‘ λ³κ²½νκΈ° μν΄μλ λ€μκ³Ό κ°μ΄ ν΄μΌ ν©λλ€.
setWall({
...wall,
wallInfoBlock: { ...wall.wallInfoBlock, wallInfoTitle: 'μλ‘μ΄ κ°' },
});
μμ κ°μ΄ wall κ°μ²΄μ κΉμ΄κ° μμ κ²½μ°λ μ΄λ ΅μ§ μκ² λΆλ³μ±μ μ§ν¬ μ μμΌλ κΉμ΄κ° κΉμ΄μ§μ λ°λΌ λΆλ³μ±μ μ§ν€λ κ²μ λΆκ°λ₯μ κ°κΉμ μ§λλ€.
-> μ΄ λ¬Έμ λ₯Ό ν΄κ²°ν΄μ£Όλ λΌμ΄λΈλ¬λ¦¬κ° 'IMMER' μ
λλ€.
λ¬Έμ μ μ λν ν΄κ²° λ°©λ²μ μ°Ύκ³ ν΄λΉ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ°Ύμ μ μ©νκΈ°κΉμ§ λ§μ μκ°μ΄ κ±Έλ Έμ΅λλ€.
μ΄μ μλ react μ μ₯μ λ§ κ²½ννλ λΆλΆκ³Όλ λ€λ₯΄κ², ν΄λΉ λ¬Έμ λ₯Ό κ²ͺμΌλ©΄μ react μ λ¨μ λ νμ°νκ² λλμ μκ² λ κ²½νμ΄μμ΅λλ€.
μ¬μ©νλ κΈ°μ μ€νμ λν΄ μ₯,λ¨μ μ λͺ¨λ κΉ¨λ«μ νμ ν΄κ²° λ°©μμ μ°Ύλ λμ€ reactμ λ¨μ μ μ΅μν ν μ μκ³ , λ λμ μ½λ κ°μ μ μν λΌμ΄λΈλ¬λ¦¬ `IMMER'μ μ ννκ² λμμ΅λλ€.
IMMER
λ₯Ό μ¬μ©νλ©΄ κΈ°μ‘΄μ κ°μ²΄μ κ°λ₯Ό λ€λ£¨λ λ¬Έλ²μ μ¬μ©νμ¬ stateλ₯Ό μ
λ°μ΄νΈ μμΌμ€ μ μμ΅λλ€.
import { produce } from 'immer';
setWall(
produce(wall, (draft) => {
draft.wallInfoBlock.wallInfoTitle = 'μλ‘μ΄ κ°';
}),
);
μν€ν
μ³(Architecture) |
---|
κ°μ²΄-κ΄κ³ λͺ¨λΈ (ERD) |
π API λͺ μΈμ π
API λͺ
μΈμ |
---|
π Front-end
μ΄μ μ°(νμ₯) (Front-end) |
κΉνμ (Front-end) |
λ°©λ―Έμ (Front-end) |
μ΄λ―Έμ°(νμ₯) (Back-end) |
μ μμ (Back-end) |
μμν (Back-end) |
κΉν¬ν (Back-end) |
μ€νμ§ (Back-end) |