오늘은 프로젝트에 상품을 추가할 수 있는 기능을 배웠다. 아직 적용은 안 됐지만 나중에 상품을 추가하려면 이미지나 파일을 보내는 기능이 필요하다. 0.2 버전을 업데이트해보자!
이번에도 역시 서버와 데이터베이스가 연동되어서 실행되기 때문에 코드와 이미지로만 올리겠다.
(이거진짜 코드 샌드박스로 안되는건가..)
0.1 버전에서는 상품이미지를 클릭해서 들어가는 ProductPage에서 url파라미터를 useParams로 받아서
이미지 좌표만 바뀌게 해 줬는데 이번에는 클릭하고 들어갔을 때 url파라미터를 서버에서 받아서
특정데이터를 조회해서 응답으로 보내주게 만들어준다.
서버 측 index.js
app.get("/products/:id",(req,res)=>{
const params = req.params;
const {id} = params;
conn.query(`select * from products where p_id=${id}`,function(error,result,fields){
res.send(result); //url에 들어간 번호파라미터로 mysql레코드
});
})
요청한 주소의 params가 req에 담기게 되고 그 값을 id에 할당해서 select 문의 where 조건절에
조건으로 넣어주었다.
sql에서 조회된 결과는 res.send로 응답데이터로 클라이언트에게 응답해 준다.
Product / index.js
import axios from 'axios';
import React from 'react';
import { useParams } from 'react-router-dom';
import useAsync from '../customHook/useAsync';
import './index.css'
async function productFetch(id){
const response = await axios.get(`http://localhost:8080/products/${id}`);
return response.data
}
const ProductPage = () => {
const {p_id} = useParams();
const state = useAsync(()=>productFetch(p_id),[]);
const {loading,error,data} = state;
if (loading) return <div>로딩중</div>
if (error) return <div>에러발생</div>
if (!data) return null
const [Product] = data
return (
<div className='productDetail'>
<h2>기초스킨케어 세트</h2>
<div className='productImg'>
<img src={`../${Product.p_img}`} alt="" />
</div>
<div>
<p>{Product.p_desc}</p>
<p>{Product.p_name}</p>
<p>가격 : {Product.p_price}</p>
<p>무료배송</p>
</div>
</div>
);
};
export default ProductPage;
product폴더에 있는 index.js폴더에서 axios로 서버에 product경로로 get요청을 보내고
useAsync 커스텀훅에서 처리해서 state를 반환 해준다.
받아온 state에서 data로 상품이름과 이미지 좌표 가격을 띄워준다.
(받아온 데이터는 배열의 형태로 저장되어 있어서 배열의 인덱스로 사용하거나
배열 구조분해할당으로 변수에 할당해서 사용해야 한다 )
[{p_id: 4, p_name: '기초스킨케어4', p_price: 70000, p_desc: '인기있는 상품 입니다.', p_quantity: 60, …}]
이런 형식으로 저장되어 있어서 data에서 바로 값으로 접근할 수는 없다. data[0].p_id / Product.p_id
제품 등록하기 )
제품을 등록하기 위해서는 클라이언트에서 보내주는 제품정보를 서버에서 받아서
데이터베이스에 저장해 주는 과정이 필요하다.
제품정보는 Post요청 으로 클라이언트에서 보내주고 Server에서도 Post로 받아서
데이터베이스에 저장한다.
서버 측에 Post를 받는 구문을 추가해 준다.
Node.js > index.js
1
2
3
4
5
6
7
8
|
app.post("/addProduct",async (req,res)=>{ //req 가 가고 res 가 바로오는게 아니기때문에 async 붙여줘야함
const {p_name,p_price,p_desc,p_img,p_quantity} = req.body;
conn.query("insert into products (p_name,p_price,p_desc,p_img,p_quantity) values(?,?,?,?,?)",
[p_name,p_price,p_desc,p_img,p_quantity],
(error,result,fields)=>{
res.send("ok");
})
})
|
cs |
req.body에 클라이언트가 보내주는 데이터가 담겨있다. 구조분해 할당으로 각 변수에 할당해 준다.
데이터베이스에 insert문으로 받아온 데이터를 추가해 준다.
쿼리문 작성형식
conn.query("쿼리문insert value(?,?,?)",[들어갈값],함수()=>{})
데이터베이스에 정상적으로 데이터가 입력되면 응답으로 ok를 보내준다.
인풋태그에 데이터를 모두 입력하고 form이 submit 될 때 데이터를 addProduct경로로
보내주도록 만들어준다.
클라이언트 측
upload / index.js
import axios from 'axios';
import React,{useState} from 'react';
import { useNavigate } from 'react-router-dom';
import './index.css'
const UploadPage = () => {
const navigate=useNavigate()
const [ formData,setFormData] = useState({
p_name:"",
p_price:"",
p_img:"",
p_desc:"",
p_quantity:""
})
const onChange = (e)=>{
const {name,value} = e.target;
setFormData({
...formData,
[name]:value
})
}
const onReset =()=>{
setFormData({
p_name:"",
p_price:"",
p_img:"",
p_desc:"",
p_quantity:""
})
}
const onSubmit = (e)=>{
//form에 연결된 이벤트를 제거
e.preventDefault()
// 입력이 다 되어있는지 체크
if(formData.p_name && formData.p_price && formData.p_img && formData.p_desc && formData.p_quantity){
insertProduct();
}
}
function insertProduct(){
// 서버에 post요청
axios.post("http://localhost:8080/addProduct",formData)
.then(res=>{
console.log(res); //콘솔에 응답출력
navigate('/'); //메인화면으로 이동
})
.catch(e=>{
console.log(e)
})
}
return (
<div className='upload'>
<h2>제품등록하기</h2>
<form onSubmit={onSubmit}>
<table>
<thead></thead>
<tbody>
<tr>
<td>상품이름</td>
<td>
<input type="text" name="p_name" value={formData.p_name} onChange={onChange}/>
</td>
</tr>
<tr>
<td>이미지</td>
<td>
<input type="text" name="p_img" value={formData.p_img} onChange={onChange}/>
</td>
</tr>
<tr>
<td>상품가격</td>
<td>
<input type="number" name="p_price" value={formData.p_price} onChange={onChange}/>
</td>
</tr>
<tr>
<td>상품수량</td>
<td>
<input type="number" name="p_quantity" value={formData.p_quantity} onChange={onChange}/>
</td>
</tr>
<tr>
<td>상세설명</td>
<td>
<textarea name="p_desc" onChange={onChange} value={formData.p_desc}>
{formData.p_desc}
</textarea>
</td>
</tr>
<tr>
<td colSpan={2}>
<button type='submit'>등록</button>
<button type='reset' onClick={onReset}>취소</button>
</td>
</tr>
</tbody>
<tfoot></tfoot>
</table>
</form>
</div>
);
};
export default UploadPage ;
각 input태그의 value값을 state로 관리하고 값이 입력돼서 변경되면 onChange={onChange} 함수가 실행돼서 상태값이 변경되게 된다. 아래에 취소버튼을 누르면 상태값이 다 공백으로 바뀌는 onReset함수 도 만들어 줬다.
등록 버튼을 누르면 onSubmit이벤트가 실행되는데 기본적으로 실행되는 onSubmit이벤트가 아니고
따로 함수를 만들어줄 거 기 때문에 preventDefault( )로 기본적으로 실행되는 이벤트를 막아준다
인풋태그에 데이터가 전부 입력되어 있으면 insertProduct( ) 함수를 실행시켜 준다.
onSubmit
const onSubmit = (e)=>{
//form에 연결된 이벤트를 제거
e.preventDefault()
// 입력이 다 되어있는지 체크
if(formData.p_name && formData.p_price && formData.p_img && formData.p_desc && formData.p_quantity){
insertProduct();
}
}
insertProduction
function insertProduct(){
// 서버에 post요청
axios.post("http://localhost:8080/addProduct",formData)
.then(res=>{
console.log(res); //콘솔에 응답출력
navigate('/'); //메인화면으로 이동
})
.catch(e=>{
console.log(e)
})
}
insertProduct 함수가 실행되면 axios.post로 경로에 입력된 상태값 데이터(formData)를 보내주게 되고.
응답을 받으면 콘솔에 응답을 출력해 주고 navigate로 홈화면으로 이동한다.
서버에 이미지 업로드하기)
이미지파일을 클라이언트가 가지고 있을게 아니라
서버에서 이미지파일을 가지고 있다가 보내줘야 하기 때문에 업로드 할때
이미지파일도 같이 업로드 해주어야 한다.
클라이언트부터 만들어주자. 아직 upload에 넣을게 아니고 테스트만 해보는 거라서
ImageForm.js를 components폴더 안에 만들어 주었다.
components/ImageForm.js
import axios from 'axios';
import React,{useState} from 'react';
const ImageForm = () => {
const [imageURL,setimageURL] = useState(null)
const onChangeImage = (e)=>{
const{name} = e.target;
//<form>태그생성
const imageFormData = new FormData();
//<form>태그에 속성 추가하기
imageFormData.append(name,e.target.files[0]);
axios.post('http://localhost:8080/upload',imageFormData,{
Headers:{'content-type':'multipart/formdata'},
}).then(res=>{
console.log(res);
setimageURL(res.data.imageURL);
}).catch(e=>{
console.log(e)
})
}
return (
<div>
<table>
<tr>
<td>file</td>
<td>
<input type="file" name="file" encType="multipart/form-data" onChange={onChangeImage}/>
{
imageURL ? <img src={`./images/${imageURL}`} alt="" width='200px' height='200px'/>:
(<div id="upload-img-bg">
<img src="images/cameraicons.png" alt="" width='200px' height='200px'/>
</div>)
}
</td>
</tr>
</table>
</div>
);
};
export default ImageForm;
파일선택 버튼을 누르고 이미지 파일을 넣으면 onChange 이벤트가 실행되면서
onChangeImage함수가 실행된다.
const onChangeImage = (e)=>{
const{name} = e.target; //파일형식을 받음
//<form>태그생성
const imageFormData = new FormData();
//<form>태그에 속성 추가하기
imageFormData.append(name,e.target.files[0]);
axios.post('http://localhost:8080/upload',imageFormData,{
Headers:{'content-type':'multipart/formdata'},
}).then(res=>{
console.log(res);
setimageURL(res.data.imageURL);
}).catch(e=>{
console.log(e)
})
}
input태그에서 바로 전송을 해주기 때문에 Form이 없어서
Form을 만들어서 거기에 데이터를 넣어서 보내준다.
const imageFormData = new FormData();
//<form>태그에 속성 추가하기
imageFormData.append(name,e.target.files[0]);
이미지를 axios.post로 보내주고 Header에 콘텐츠의 타입을 정의해 준다. 이미지를 보내면
서버에서 응답으로 {imageURL:파일이름}을 보내준다.
응답을 받아서 이미지태그에 응답으로 받은 이미지이름 넣어준다.
{
imageURL ? <img src={`./images/${imageURL}`} alt="" width='200px' height='200px'/>:
(<div id="upload-img-bg">
<img src="images/cameraicons.png" alt="" width='200px' height='200px'/>
</div>)
}
이제 서버 측도 수정해줘야 한다 파일을 받아주려면 multer API를 설치해줘야 한다
multer 설치하기
npm install --save multer
multer 불러오기
const multer = require("multer");
storage 생성하기
//diskstorage()--> 파일을 저장할때에 모든 제어 기능을 제공
const storage = multer.diskStorage({
destination:(req,file,cb)=>{
cb(null,'upload/');
},
filename:(req,file,cb)=>{
const newFilename = file.originalname;
cb(null,newFilename);
}
})
파일을 저장할 경로와 저장할 파일의 이름을 설정해 줄 수 있다.
저장경로는 upload폴더 이고
파일이름은 파일의 원래이름값인 file.originalname으로 해주었다.
upload객체 생성하기
const upload = multer({ storage : storage }); // {dest:storage} => {storage:storage}
app.post('/upload',upload.single('file'),async (req,res)=>{
res.send({
imageURL:req.file.filename
})
});
이제 upload경로로 Post요청을 보내면 upload객체가 실행되면서 파일을 받아서 서버에서 저장해 준다.
클라이언트에서 파일을 올리면 서버로 보내져서 upload폴더에 저장되고 서버에서 응답으로 이미지의 이름을 보내주게 되고 클라이언트는 받은 이름으로 클라이언트자신의 images폴더에 접근해서 해당하는 이미지를 띄워준다.
일단은 테스트기 때문에 css도 없고 사실 파일도 클라이언트의 img폴더가 아니라 클라이언트가 이미지도 보내줘야하는 게 아닌가 싶긴한데 내일 학원가면 이어서 마저 가르쳐주실테니 일단 여기서 만족한다 multer를 쓰는방법은 링크의 공식문서를 읽어보면 있으니 찾아서 하면될것같고 요즘 하면서 점점느끼는게 외우는 걸 포기하게 되는거다.. ㅋㅋㅋ 어떻게 다외우나 싶어서 선생님께 여쭤봤더니 외우면서 하는게 아니라 그때그때 잘 찾아서 써야 한다고 한다. 이제 외우려고 노력하지 말고 열심히 블로그에 기록하고 다른 블로그에 좋은 포스팅이 있으면 저장해 뒀다가 잘 베껴서 써야겠다 코딩은 정말 엄청 두꺼운 책으로 오픈북 시험을 치는 느낌이다.. ㅠ
'프론트엔드 정복기 > 리액트' 카테고리의 다른 글
[React] react-redux part2 (todos추가) (0) | 2023.01.27 |
---|---|
[React] 24 상태관리 3 (react-redux) (1) | 2023.01.27 |
[React]22 리액트쇼핑몰만들기ver0.1 (서버에서 상품정보받아오기) (0) | 2023.01.20 |
[React]21 서버만들기(node.js/express프레임워크/node + mysql) (0) | 2023.01.19 |
[React] 20 리액트 라우터 라이브러리 (react-router v6) (0) | 2023.01.19 |