46일차(1)/React(9) : 함수형 컴포넌트 작성 / ajax 요청으로 DB 데이터 출력
- 지금까지는 클래스 기반으로 component를 만들었는데,
이번부터는 함수 기반으로 만드는 방법을 익혀볼 예정!
* 클래스 기반
- component 를 상속해서 클래스를 정의하고, render 메소드를 사용해 javascript 객체를 기본으로 화면 구성
- App이라는 클래스를 export해주면 클래스가 리턴해주는 js가 화면을 구성해주는 것
* 함수 기반
- App이라는 함수 안에서 화면 구성. jsx를 사용해서 작성하고 함수 안에 있는 내용으로 화면이 구성된다.
- 마지막에는 함수명으로 export 해준다. 이것을 함수 기반 컴포넌트라고 한다.
App07.js
//App07.js
import React from "react";
//함수 기반으로 컴포넌트를 만드는 방법
const App = ()=>{
/*
[ 초기값을 받을 변수(상태값), 상태값을 변경할 함수 ]
*/
const [msg, setMsg]=React.useState('초기값')
const [num, setNum]=React.useState(1)
console.log(msg);
console.log(setMsg);
//버튼을 눌렀을 때 호출되는 함수
const handleClick = ()=>{
alert("버튼을 눌렀네요?")
};
const onChange= (e)=>{
//상태값을 바꿔주는 함수를 이용해서 상태값 바꾸기
let inputMsg=e.target.value;
setMsg(inputMsg);
};
const btnClick= ()=>{
//상태값으로 관리되는 num에 1을 더한 값으로 상태를 변경한다.
setNum(num+1);
};
return(
<div className="container">
<h1>함수기반 컴포넌트</h1>
<button onClick={handleClick} className="btn btn-primary">눌러보셈</button>
<br />
<input type="text" onChange={onChange} />
<p>{msg}</p>
{/* 아래 버튼을 누르면 숫자가 1씩 증가하도록 해보기 */}
<button onClick={btnClick} className="btn btn-primary">{num}</button>
</div>
);
}
export default App;
/*
export default ()=>{
return(
<div className="container">
<h1>함수기반 컴포넌트</h1>
</div>
);
};
*/
- App 에 대입되는 ()=>{} 이 함수가 클래스 형식의 render 함수와 같이 작용한다.
- 더 줄여서 쓰려면 export default () => {} 형태로도 쓸 수 있다.
- App이라는 변수에 담아서 export 하든 직접 만들어서 export 하든 똑같은 것!
- 위와 같이 함수 안에다 함수를 만드는 형태라고 생각하고 만들어주면 된다.
- 이후에 수정될 내용은 아니므로 상수 const로 함수 만들기
- 클릭시 실행되도록 넣어주기
- 그렇다면 state값은 함수기반 컴포넌트에서 어떻게 관리할까?
- 함수 기반 컴포넌트에서 클래스 기반 방식으로 똑같이 state={} 로 입력하면 작동하지 않는다.
- 먼저 React를 import 해준다.
- import 후 React. 점을 찍어보면 useState 라는 함수가 있는 것을 볼 수 있다.
const [msg, setMsg]=React.useState('초기값')
- 초기값을 넣어주고, const [] 배열에 담아준다.
- javascript 문법에서 let [a, b] = [10, 20] 으로 작성하면
알아서 0번 방에 있는 값이 a, 1번 방에 있는 값이 b에 들어간다.
- 배열에 있는 값을 분해해서 할당한다고 생각하면 된다.
→ 이것을 구조분해할당이라고 부른다.(javascript의 문법 중 하나!)
- 위와 같이 문자열, 함수도 담을 수 있다. 각각 string 타입 / function 타입이 들어가있다.
- 이 경우 함수는 () 으로 호출도 가능하다.
- object도 이러한 구조분해 할당 문법으로 작성하는 것이 가능하다.
const [msg, setMsg]=React.useState('초기값')
- 이렇게 담아주면 [x, x] 형태로 배열을 리턴해준다.
- 콘솔 창에는 위와 같이 출력된다.
- 이 msg가 결국 상태값(state)이라고 보면 된다.
- msg 라는 이름의 상태값을 가지게 되는 것이고, 어떤 값을 useState() 안에 넣어주면 msg에 들어갈 초기값이 된다.
- 클래스형에서는 상태값을 바꾸는 this.setState({ msg: yyy }); 을 사용했는데,
함수형에서는 상태값을 바꾸고 싶다면 setMsg('yyy')로 입력하면 된다.
구조가 좀더 단순해진 것!
- p에 출력하는 것으로 적어주면 위와 같이 받아서 출력한다.
const onChange= (e)=>{
//상태값을 바꿔주는 함수를 이용해서 상태값 바꾸기
let inputMsg=e.target.value;
setMsg(inputMsg);
};
- input 안의 값이 바뀌었을때 바로 setMsg 해줄 함수 등록
- useState를 여러번 호출해서 상태값을 여러개 관리할 수 있다.
- object, array, 문자열 등 무엇이든 가능
const btnClick= ()=>{
setNum(num+1);
};
- 버튼 클릭시 안의 숫자가 1씩 증가하도록 하고 싶다면
버튼을 누를때마다 호출되는 함수를 생성해서 그 함수안에서 setNum() 해서 state 값을 수정해줌
- 함수 기반 component에서는 함수를 만들어서 리턴해주고,
이 함수안에서 리턴해주는 내용으로 화면이 구성된다는 것을 기억하기!
App08.js
//App08.js
import React from "react";
const App = ()=>{
//이 함수는 UI가 업데이트될때마다 여러번 호출되는 함수이다.
//상태값으로 관리될 cafeList는 jsx 로 구성된 글 목록을 담고 있는 배열
const [cafeList, setCafeList]=React.useState([]);
//아래에서 리턴한 UI의 초기화 작업(준비작업)이 끝났을 때 원하는 동작이 있으면
//아래의 useEffect() 안에 전달한 함수 안에서 작업을 하면 된다.
React.useEffect(()=>{
//fetch 함수를 이용해서 tomcat 서버로부터 json 문자열을 응답받늗다.
fetch("http://localhost:8888/Step04_Final/cafe/ajax_list.jsp")
.then((res)=>{
//json문자열이 응답되기 때문에 res.json() 을 리턴해준다.
return res.json();
})
.then((data)=>{
//data는 array이다.
console.log(data);
//hint:배열의 map함수를 활용해보세요
let newArray=data.map((item)=>{
return(
<tr key={item.num}>
<td>{item.num}</td>
<td>{item.writer}</td>
<td>{item.title}</td>
<td>{item.viewCount}</td>
<td>{item.regdate}</td>
</tr>
);
});
setCafeList(newArray);
});
},[]);
return(
<div className="container">
<h1>글 목록입니다</h1>
<table>
<thead>
<tr>
<th>글번호</th>
<th>작성자</th>
<th>제목</th>
<th>조회수</th>
<th>작성일</th>
</tr>
</thead>
<tbody>
{cafeList}
</tbody>
</table>
</div>
);
}
export default App;
- DB에 연결해서 DB에 있는 글 목록을 출력하기!
- node.js 서버(react) 와 Tomcat 서버(java)
같은 브라우저에서 요청/응답하게 할 수 있는지?
- node js는 react 개발환경으로, 보이는 페이지도 react 에서 응답을 받은 것
- tomcat 서버는 java 기반으로, oracle DB와 연결되어서 데이터를 가져와 응답해준다.
- 페이지전환없이 ajax로 글목록만 받아올 것이다. json으로 응답하게 한다!
- node로 개발한 것을 tomcat 서버로 넣으면 문제가 없지만,
react 페이지가 아직 개발중이기 때문에 build해서 넣을 수 없다.
- 즉 초기 틀은 node에서, 데이터는 tomcat에서 불러오는 페이지를 만들고자 하는 것.
- 원래는 결과물을 얻어내서(build) tomcat 서버로 넣어야 한다.
- 그러면 node를 거칠 필요 없이 tomcat 서버에서만 요청/응답 이 가능하다.
- 지금 상황에서는 tomcat 서버 쪽으로 요청을 해도 응답을 주지 않는다.
(동일출처정책에 따라 웹페이지를 요청받은 쪽에서만 응답을 줄 수 있게 되어 있다.)
- 원래는 응답을 안해주지만, 서버의 세팅을 바꾸면 서버의 출처가 달라도 응답하게 할 수 있다.
- 이 개발환경에서 서버를 2개 쓰도록 설정할 예정!
- 웹페이지의 기본 컨텐츠는 node js에서 받아오고, 응답 요청(ajax)은 tomcat 서버로 보낸다.
- 이클립스에서 가져오려는 데이터가 들어있는 프로젝트의 WEB-INF의 web.xml에 Cross Origin 설정을 넣어준다.
- 이 설정을 해놓으면 tomcat 서버가 자기가 페이지를 생성하지않은 서버의 ajax 요청도 응답해준다.
- 개발 단계일때 넣어두고 쓰는 것이고, 개발이 끝나고 나면 없애버려도 무방하다.
ajax_list.jsp
<%@page import="test.cafe.dto.CafeDto"%>
<%@page import="test.cafe.dao.CafeDao"%>
<%@page import="java.util.List"%>
<%@ page language="java" contentType="application/json; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
//한 페이지에 몇개씩 표시한 것인지
final int PAGE_ROW_COUNT=5;
//하단 페이지를 몇개씩 표시할 것인지
final int PAGE_DISPLAY_COUNT=5;
//보여줄 페이지의 번호를 일단 1이라고 초기값 지정
int pageNum=1;
//페이지 번호가 파라미터로 전달되는지 읽어와 본다.
String strPageNum=request.getParameter("pageNum");
//만일 페이지 번호가 파라미터로 넘어온다면
if(strPageNum != null){
//숫자로 바꿔서 보여줄 페이지 번호로 지정한다.
pageNum=Integer.parseInt(strPageNum);
}
//보여줄 페이지의 시작 ROWNUM
int startRowNum=1+(pageNum-1)*PAGE_ROW_COUNT;
//보여줄 페이지의 끝 ROWNUM
int endRowNum=pageNum*PAGE_ROW_COUNT;
//하단 시작 페이지 번호
int startPageNum=1+((pageNum-1)/PAGE_DISPLAY_COUNT)*PAGE_DISPLAY_COUNT;
//하단 끝 페이지 번호
int endPageNum=startPageNum+PAGE_DISPLAY_COUNT-1;
//전체 글의 갯수
int totalRow=CafeDao.getInstance().getCount();
//전체 페이지의 갯수 구하기
int totalPageCount=(int)Math.ceil(totalRow/(double)PAGE_ROW_COUNT);
//끝 페이지번호가 이미 전체 페이지 갯수보다 크게 계산되었다면 잘못된 값이다.
if(endPageNum>totalPageCount){
endPageNum=totalPageCount; //끝 페이지번호 값을 보정해준다.
}
//CafeDto 객체를 생성해서
CafeDto dto=new CafeDto();
//위에서 계산된 startRowNum, endRowNum을 담고
dto.setStartNum(startRowNum);
dto.setEndRowNum(endRowNum);
//CafeDto를 인자로 전달해서 글목록 얻어오기
List<CafeDto> list=CafeDao.getInstance().getList(dto);
%>
[
<%for(int i=0;i<list.size(); i++){
CafeDto tmp=list.get(i);%>
{
"num":<%=tmp.getNum() %>,
"writer":"<%=tmp.getWriter() %>",
"title":"<%=tmp.getTitle() %>",
"viewCount":<%=tmp.getViewCount() %>,
"regdate":"<%=tmp.getRegdate() %>"
}
<% if(i != list.size()-1) {%><%-- 배열의 마지막 인덱스가 아니면 , 찍기 --%>
,
<%} %>
<%} %>
]
- 열람하려는 페이지를 ajax_list.jsp 로 생성(이전 list.jsp 복사)
- 상단의 contentType을 appliaction/json 으로 바꾸어준다.
- [{}, {}, {}, ... ] object 형태로 응답. json타입으로 응답해줄 것이다.
- 위와 같은 문자열로 object를 작성해 응답해준다.
- 글 목록만 json형식으로 리턴해주면 되기 때문에 html 부분은 필요없다. 전부 지우기!
- for문 돌면서 내용 출력하기. <%= %> 으로 출력해도 문자열은 "" 로 감싸주어야 한다.
- jsp페이지에서 json문자열을 만들어내기가 쉽지않다... 상당히 귀찮다...
- 실행해 보면 이런모양이다.
- [ {}, {}, .... ] 형식이어야 하는데 , 가 빠져있다.(단 마지막 아이템에는 , 가 있으면 안된다.)
- 확장for문을 사용하면 마지막 인덱스를 확인할 수 없다.
→ 기본 for문으로 돌아야한다.
- 끝에 if문을 넣어 배열의 마지막 방이 아니면 출력한 뒤에 , 를 찍는 것으로 작성
- 그럼 이 요청을 react에서 하려면? → 주소를 복사해서 fetch로 가져오기
//fetch 함수를 이용해서 tomcat 서버로부터 json 문자열을 응답받늗다.
fetch("http://localhost:8888/Step04_Final/cafe/ajax_list.jsp")
.then((res)=>{
return res.json();
})
.then((data)=>{
console.log(data);
});
- data에 넣어 콘솔에 출력되도록 해준다.
- 콘솔창에 배열이 출력된 것을 볼 수 있다.
- cafeList에 <tr>, <td>가 들어가도록 해야 한다.
- useState([ ]) 안에는 현재 빈 배열이 들어가 있다.
- 현재 콘솔창에 찍히는 내용을 클라이언트가 볼 수 있도록 출력하기
- 저 안에 state값을 바꾸는 코딩을 하면 된다.
- map() 함수를 사용해 새 배열을 리턴하고, item으로 tablerow 부분을 만들어낸다.
- row에 tr, td를 리턴해주면서 setCafeList에 바뀐 정보를 담아줌
- 그런데 정상적으로 출력되긴 하는데 콘솔창에 무한루프가 반복됨
React.useEffect(); 를 함수와 빈 배열 구조로 만들어주기
- useEffect의 두번째인자로는 빈 배열 [ ] 을 넣어준다.
- const App 이라는 함수는 UI가 업데이트될 때마다 여러번 호출되는 함수이기 때문에,
페이지 로딩 시점에 이 위치에 UI를 출력하는 작업을 하면 무한루프를 돌게 될 수 있다.
- setCafeList()하면 App함수가 중복으로 호출되는 것이다.
- 아래에서 리턴한 UI의 초기화 작업(준비작업)이 끝났을 때 원하는 동작이 있으면
아래의 useEffect() 안에 전달한 함수 안에서 작업을 하면 된다.
- 그러면 페이지가 초기화되는 시점에 알아서 요청을 하게된다.
- node 서버를 사용하고 있지만 데이터를 요청해서 받아온 것은 tomcat 서버임을 알 수 있다.
- ajax요청, json으로 응답받음. 페이지 전환 없이 데이터를 받아온 것이다!
- 새로운 UI로 업데이트된 것이다.
App07.js - class 기반 컴포넌트에서 tomcat에 데이터 요청해보기
class App extends Component{
//상태값
state={
cafeList:[]
}
//컴포넌트가 준비가 되었을 때 호출되는 함수
componentDidMount = ()=>{
//fetch 함수를 이용해서 tomcat 서버로부터 json 문자열을 응답받늗다.
fetch("http://localhost:8888/Step04_Final/cafe/ajax_list.jsp")
.then((res)=>{
//json문자열이 응답되기 때문에 res.json() 을 리턴해준다.
return res.json();
})
.then((data)=>{
//data는 array이다.
console.log(data);
//hint:배열의 map함수를 활용해보세요
let newArray=data.map((item)=>{
return(
<tr key={item.num}>
<td>{item.num}</td>
<td>{item.writer}</td>
<td>{item.title}</td>
<td>{item.viewCount}</td>
<td>{item.regdate}</td>
</tr>
);
});
this.setState({
cafeList:newArray
});
});
}
render(){
return(
<div className="container">
<h1>글 목록입니다</h1>
<table>
<thead>
<tr>
<th>글번호</th>
<th>작성자</th>
<th>제목</th>
<th>조회수</th>
<th>작성일</th>
</tr>
</thead>
<tbody>
{this.state.cafeList}
</tbody>
</table>
</div>
)
}
}
export default App;
- 클래스 기반으로 바꾼 것
- state 값의 초기값을 설정하고(빈 배열), this.cafeList로 state를 업데이트한다.
- componentDidMount 함수가 있다.
- 이 안은 useEffect 안의 내용과 거의 같다.
- 복사해서 붙여넣어줌
- index.jsp 페이지를 요청하면 서버에서 응답해준다.
- 웹브라우저가 요청에 따라 위에서부터 해석해준다. css, js를 로딩하고, navbar를 만들고, div와 컨텐츠를 출력한다.
- 다른 페이지로 이동해서도 똑같은 작업을 반복한다.
- 누를때마다 새로고침되는데, 로딩되는 자원들 중에서는 중복된 자원들도 많다. 그런데도 처음부터 다시 로딩하는것.
→ 이게 전통적인 방식이다.(서버 side 렌더링)
- 최근에는 페이지 전환 없이 필요한 컨텐츠만 바꾸는 쪽으로 변화하고 있다.
- 페이지의 일부만 업데이트되는 쪽으로!
- 서버에서 전체를 만들어서 보내는가, 데이터만 받아와서 클라이언트 사이드에서 만드는가의 차이!
- 이 경우 서버는 재료가 되는 기본 틀 데이터만 주고(json으로), 읽어서 화면을 구성하는 것은 브라우저이다.
- 문서 안의 마크업을 조립하는 것이 누구인가를 생각하기
- 서버는 기본 틀만 제공하고 UI 출력 / 업데이트 등은 웹브라우저가 담당한다.
이런 방식으로 개발하기 위해서는 js가 많이 필요하다.
- 그런데 순수 js로 개발하기에는 품이 너무 많이 든다.
프로그래머의 자유도가 너무 높아서 누가 개발하느냐에 따라서 차이가 크기 때문에 유지보수가 어렵다
그래서 React/ Vue / Angular js 를 쓰게 되는 것
* SPA (Single Page Application)
- 페이지전환 없이 single page만으로도 웹사이트를 만들 수도 있다.
- 기본 틀은 고정하고 추가로 받아올 필요한 데이터를 서버에 요청함으로써!
- 최근에는 이 방향으로 점점 가고 있다.
ex) 대표적인것은 gmail. 특정 편지함을 클릭해도 화면 전체가 업데이트되지 않고 일부만 바뀐다.
웹브라우저를 사용하지만 이 안에서 앱을 사용하는 느낌
- Vue도 React와 같이 node를 사용해서 구성할 수 있다.
- jsp의 이런 방식은 서버 사이드 렌더링이고,
- React에서 하고 있는 것은 클라이언트 사이드 렌더링!
'국비교육(22-23)' 카테고리의 다른 글
46일차(3)/React(11) : React 에서 만든 결과물 배포하기(build) (0) | 2022.12.12 |
---|---|
46일차(2)/React(10) : import, export 활용 예제(module) (0) | 2022.12.12 |
45일차(2)/React(8) : 모듈화된 CSS 적용하기 / form 요소 활용 (0) | 2022.12.11 |
45일차(1)/React(7) : React 실습예제 / CSS import해서 적용하기 (0) | 2022.12.09 |
44일차(2)/React(6) : jsx 객체 활용, setState 함수 실습 (0) | 2022.12.08 |