국비교육(22-23)

46일차(1)/React(9) : 함수형 컴포넌트 작성 / ajax 요청으로 DB 데이터 출력

서리/Seori 2022. 12. 12. 17:24

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에서 하고 있는 것은 클라이언트 사이드 렌더링!