import React from 'react'
import { useState } from 'react'
import { useEffect } from 'react'
export default function App() {
const [resourceType, setResourceType] = useState("Posts")
useEffect(()=>{
console.log("useEffect 실행")
},[resourceType])
return (
<div>
<button
onClick={
()=>{setResourceType("posts")}
}
>Posts</button>
<button
onClick={
()=>{setResourceType("users")}
}
>Users</button>
<button
onClick={
()=>{setResourceType("comments")}
}
>Comments</button>
<hr/>
{resourceType}
</div>
)
}
useState와 useEffect를 사용하여 상태를 변경하고, 필요한 부분만 상태가 변경될 수 있도록 useEffect를 사용했다.
백엔드 프로그램을 사용하지 않고, 프론트엔드에서 데이터를 관리할 수 있는 방법은 json파일을 이용하는 것이다. 우리가 여기에서 쉽게 데이터를 가져올 수 있는 방법은 fetch()라는 함수를 사용하는 것이다. 즉, fetch()는 외부 데이터를 가지고 올 때 사용할 수 있는 함수이다! 또한 데이터를 가져오는 것 뿐만 아니라 데이터를 사용할 수 있도록 return하는 기능도 가지고 있다.
fetch('외부경로')의 형태로 사용하면 되는데, json 파일을 자바스크립트가 사용할 수 있도록 파싱하는 것이 필요하다.
그러나 fetch()를 작성한 다음에 바로 parsing code를 써버리게 되면 데이터가 다 많은 경우 데이터를 다 가지고 오지 않은 상태가 되더라도 parsing이 진행되어버릴 것이다.
따라서 parsing은 데이터를 다 가지고 온 다음에 처리하는 것이 좋다. 즉, 동기식 처리가 필요하다는 것이다.
그렇다면 '~가 끝난 다음에' 진행시켜줘 라는 문장이 필요한데 이 부분은 '.then'이라고 작성한다. . 앞에 있는 문장이 종료될 때까지 기다렸다가 문장이 실행되게 해주는 메소드이다. then은 앞의 문장이 끝날때까지 "기다려"야 하기 때문에 인자를 콜백함수로 받는다. then(()=>{})의 형태로 사용된다.
A가 끝난 다음에 B를 실행시키고 B가 끝난 다음에 C가 실행되는 등 여러 문장을 사용하고 싶으면 then을 여러번 사용하면 된다.
여러가지 문장들 사이에서의 오류를 찾아내고 싶다면 .catch를 사용하는 것 처럼 어떤 속성에 무조건 붙여야 하는것이 아니라 상황에 맞게 사용하면 된다.
then은 then앞에 있는 문장에서 어떤 결과값을 반환하게 된다면 then의 첫번째 인자로 받도록 약속되어있다. 결과를 반환하지 않는 문장이면 앞 문장이 끝난 뒤에 실행해야 할 문장만 내용을 적으면 된다.
작업을 하기 전에 dummy data를 받아오도록 했다.
dummy data는 실제로 사용되지 않는 가상의 데이터를 말한다.
tip) public 파일 안에 json 파일을 만들어두고 경로를 index.html 기준으로 app.js(데이터를 불러올 파일) 안에 담으면 된다.
json 파일의 특징!
{} 객체 안에 데이터가 담겨있는데 { "키" : "값" } 처럼 키가 변수 형태가 아닌 문자 형태로 담겨있다.
따라서 json 파일을 사용하기 위해서는 parsing이 필요한데, 키를 변수형태로 바꿔주어야 한다.
useEffect(()=>{
console.log("useEffect 실행")
fetch('https://jsonplaceholder.typicode.com/posts')
.then( response => response.json() )
.then( json => console.log("json") )
},[resourceType])
response.json()은 자바스크립트에 내장된 기능으로, json파일을 자바스크립트가 읽을 수 있는 파일 형식으로 변환해준다. (parsing)
(최종 데이터 파일을 불러오는 코드)
import React from 'react'
import { useState } from 'react'
import { useEffect } from 'react'
export default function App() {
const [resourceType, setResourceType] = useState("Posts")
const [items, setItems] = useState([])
useEffect(()=>{
console.log("useEffect 실행")
fetch(`https://jsonplaceholder.typicode.com/${resourceType}`)
.then( response => response.json() )
.then( json => //console.log({resourceType},json)
setItems(json)
)
},[resourceType])
return (
<div>
<button
onClick={
()=>{setResourceType("posts")}
}
>Posts</button>
<button
onClick={
()=>{setResourceType("users")}
}
>Users</button>
<button
onClick={
()=>{setResourceType("comments")}
}
>Comments</button>
<hr/>
<h3>{resourceType}</h3>
{items.map( item => <pre>{JSON.stringify(item)}</pre> )}
</div>
)
}
데이터를 출력할 때 <pre/> 를 사용한 이유: 공백, 줄바꿈을 그대로 출력하기 위해!
JSON 객체
따라서
{items.map( item => <pre>{JSON.stringify(item, null, 2)}</pre> )}
는 item을 JSON 문자열로 변환한 후 줄바꿈과 공백이 유지된 채로 포맷팅된 텍스트로 출력하게 된다.
이 방법은 JSON 데이터를 화면에 읽기 쉽게 표시할 수 있다.
fetch() .then() 사용 방법 정리!

(또 다른 예시)
import React from 'react'
import { useState } from 'react'
import { useEffect } from 'react'
export default function App() {
const [data,setData] = useState('posts')
const [items, setItems] = useState([])
//1
/*fetch('데이터 파일 경로')
//json 데이터를 객체 형식의 데이터로 변환(parsing)
.then( ( fetch로 전달받은 데이터 )=> { 데이터 형변환 } )
.then( (바로 위 then의 리턴값) => {사용할 변수에 리턴값 대입} )
.catch((발생한 에러)=>{ 에러처리 })*/
//2
/*async function logJSONData(){
const responce = await fetch("데이터 파일 경로")
const jsonData = await responce.json() //데이터 형변환
console.log(jsonData) //console.log(예시) 대신 사용할 변수에 리턴값 대입
}*/
useEffect(
()=>{
fetch(`https://jsonplaceholder.typicode.com/${data}`)
.then(res=>res.json())
.then(res=>{setItems(res)})
},
[data]
)
return (
<div>
<button
onClick={
()=>{setData("posts")}
}
>Posts</button>
<button
onClick={
()=>{setData("users")}
}
>Users</button>
<button
onClick={
()=>{setData("comments")}
}
>Comments</button>
<hr/>
<h3>{data}(total : {items.length})</h3>
<pre>{JSON.stringify(items, null,2)}</pre>
</div>
)
}
처음 예시에서는 map()을 사용하여 데이터를 재배열하여 보여주었는데
위의 코드는 map()을 사용하지 않고 json의 data를 불러오고 있다.
또한 total : 에 데이터의 개수를 출력하기 위해 items.length로 그 갯수를 나타내고 있다.
(결과)


비동기식으로 함수를 만들어 외부 데이터를 불러오는 방법도 있다!

결과는 똑같으나 방법을 다르게 쓴 것!
데이터를 불러올 때 기존의 방식이 조금 더 빠르지만 이러한 방법도 있다는 것을 알고 넘어감!
//app.js
import React from 'react'
import useFetch from './components/useFetch'
export default function App() {
const fetchURL = 'https://jsonplaceholder.typicode.com'
const {data,fetchData} = useFetch(fetchURL,'posts')
return (
<div>
<button
onClick={
()=>{fetchData("posts")}
}
>Posts</button>
<button
onClick={
()=>{fetchData("users")}
}
>Users</button>
<button
onClick={
()=>{fetchData("comments")}
}
>Comments</button>
<hr/>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
}
//useFetch.js
import React from 'react'
import { useState } from 'react'
import { useEffect } from 'react'
export default function useFetch(fetchURL, initialType) {
const [data,setData] = useState('posts')
const fetchData = (resourceType)=>{
//fetch(`https://jsonplaceholder.typicode.com/${data}`)
fetch(fetchURL + '/' + resourceType)
.then(res=>res.json())
.then(res=>setData(res))
}
useEffect(
()=>{
fetchData(initialType)
},
[]
)
return {data, fetchData}
}
useFetch() 라는 사용자 정의 훅을 생성 후 fetch에 적용되는 프로토콜 주소와 주소 뒤에 변경되는 내용을 담아 주소가 변경될 때마다 그에 맞는 json 파일이 불러와지도록 했다!
//app.js
import React from 'react'
import useFetch from './components/useFetch'
export default function App() {
const fetchURL = 'https://jsonplaceholder.typicode.com'
const {data : postData} = useFetch(fetchURL,'posts')
const {data : userData} = useFetch(fetchURL,'users')
return (
<div>
<h2>Posts Data</h2>
<pre>{postData && JSON.stringify(postData[0],null,2)}</pre>
<hr/>
<h2>userData</h2>
<pre>{userData && JSON.stringify(userData[0],null,2)}</pre>
</div>
)
}
//useFetch.js
import React from 'react'
import { useState } from 'react'
import { useEffect } from 'react'
export default function useFetch(fetchURL, initialType) {
const [data,setData] = useState('posts')
const fetchData = (resourceType)=>{
//fetch(`https://jsonplaceholder.typicode.com/${data}`)
fetch(fetchURL + '/' + resourceType)
.then(res=>res.json())
.then(res=>setData(res))
}
useEffect(
()=>{
fetchData(initialType)
},
[]
)
return {data, fetchData}
}
//app.js
import React from 'react'
import Nav from './components/nav'
export default function useFetch() {
return (
<div>
<h2>WEB</h2>
<Nav></Nav>
<article>
<h3>Welcome</h3>
<p>React Ajax</p>
</article>
</div>
)
}
//nav.js
import React from 'react'
export default function Nav() {
return (
<nav>
<ul>
<li>
<a href="1">HTML</a>
</li>
<li>
<a href="2">CSS</a>
</li>
<li>
<a href="3">JAVASCRIPT</a>
</li>
</ul>
</nav>
)
}


개발자 도구 > NetWork > fetch
파일명을 클릭하면 데이터 내용도 볼 수 있다.
//App.js
import React from 'react'
import Nav from './components/nav'
export default function useFetch() {
return (
<div>
<h2>WEB</h2>
<Nav></Nav>
<article>
<h3>Welcome</h3>
<p>React Ajax</p>
</article>
</div>
)
}
//nav.js
import React from 'react'
import { useState, useEffect } from 'react'
export default function Nav() {
const [ list, setList ] = useState([])
useEffect( ()=>{
fetch('./data/list.json')
.then( res => res.json() )
.then( res => setList(res) )
}, [])
var listTag = []
for(let i = 0; i < list.length; i++){
listTag.push(<li key={list[i].id}><a href={list[i].id}>{list[i].title}</a></li>)
}
return (
<nav>
<ul>
{/*} <li>
<a href="1">HTML</a>
</li>
<li>
<a href="2">CSS</a>
</li>
<li>
<a href="3">JAVASCRIPT</a>
</li> */}
{listTag}
</ul>
</nav>
)
}
//list.json
[
{"id":1, "title":"HTML"},
{"id":2, "title":"CSS"},
{"id":3, "title":"JAVASCRIPT"}
]

//nav.js
import React from 'react'
import { useState, useEffect } from 'react'
export default function Nav() {
const [ list, setList ] = useState([])
useEffect( ()=>{
fetch('./data/list.json')
.then( res => res.json() )
.then( res => setList(res) )
}, [])
return (
<nav>
<ul>
{
list.map( item => (<li key={item.id}><a href={item.id}>{item.title}</a></li>) )
}
</ul>
</nav>
)
}
tip)
map의 앞에는 무조건 배열
return할 문장이 1개면 중괄호 생략 할 수 있으며, html 문장을 return 하기 위해서는 소괄호 () 안에 담아야 한다.
app.js는 변화하지 않고, nav.js만 수정해서 사용했다.
(결과)

<Nav
onChangeList={
id =>
//console.log("Get id : ", id)
fetch('./data/'+id+'.json')
}
></Nav>
json파일이 잘 불러와지는지 확인

최종코드
//app.js
import React from 'react'
import { useState } from 'react'
import Nav from './components/nav'
import Article from './components/article'
export default function App() {
const [article, setArticle] = useState(
{
title: "Welcome",
content: "React Ajax"
})
return (
<div>
<h2>WEB</h2>
<Nav
onChangeList={
id =>
//console.log("Get id : ", id)
fetch('./data/'+id+'.json')
.then(res=>res.json())
.then(res=>setArticle(
{
title: res.title,
content: res.content
}
))
}
></Nav>
<Article title={article.title} content={article.content}></Article>
</div>
)
}
//article.js
import React from 'react'
export default function Article({title,content}) {
return (
<article>
<h3>{title}</h3>
<p>{content}</p>
</article>
)
}
//nav.jsimport React from 'react'
import { useState, useEffect } from 'react'
export default function Nav({onChangeList}) {
const [ list, setList ] = useState([])
useEffect( ()=>{
fetch('./data/list.json')
.then( res => res.json() )
.then( res => setList(res) )
}, [])
return (
<nav>
<ul>
{
list.map( item => (
<li key={item.id}>
<a href={item.id}
onClick={e=>{
e.preventDefault()
onChangeList( item.id )
}}
>
{item.title}</a>
</li>) )
}
</ul>
</nav>
)
}
import React from 'react'
import { useState, useEffect } from 'react'
export default function Nav({onChangeList}) {
const [ list, setList ] = useState([])
useEffect( ()=>{
fetch('./data/list.json')
.then( res => res.json() )
.then( res => setList(res) )
}, [])
return (
<nav>
<ul>
{
list.map( item => (
<li key={item.id}>
<a href={item.id}
onClick={e=>{
e.preventDefault()
onChangeList( item.id )
}}
>
{item.title}</a>
</li>) )
}
</ul>
</nav>
)
}
(결과)

방법은 모두 다르지만 결과는 모두 같음을 알 수 있다.
링크를 클릭하면 NetWork에 json 파일 Name이 출력되면 json 파일이 잘 불러와지고 있음을 알 수 있다!
component에는 종류가 크게 2가지가 있다.
1. presentation (화면에 보여질 내용들이 담겨있는 컴포넌트)
2. container (기능을 가지고 있는 컴포넌트)
//app.js
import React from 'react'
import { useState } from 'react'
import { useEffect } from 'react'
import Nav from './components/nav'
import Article from './components/article'
export default function App() {
const [article, setArticle] = useState(
{
title: "Welcome",
content: "React Ajax"
})
const [ list, setList ] = useState([])
useEffect( ()=>{
fetch('./data/list.json')
.then( res => res.json() )
.then( res => setList(res) )
}, [])
return (
<div>
<h2>WEB</h2>
<Nav
list={list}
onChangeList={
id =>
//console.log("Get id : ", id)
fetch('./data/'+id+'.json')
.then(res=>res.json())
.then(res=>setArticle(
{
title: res.title,
content: res.content
}
))
}
></Nav>
<Article title={article.title} content={article.content}></Article>
</div>
)
}
//nav.js
import React from 'react'
export default function Nav({list, onChangeList}) {
return (
<nav>
<ul>
{
list.map( item => (
<li key={item.id}>
<a href={item.id}
onClick={e=>{
e.preventDefault()
onChangeList( item.id )
}}
>
{item.title}</a>
</li>) )
}
</ul>
</nav>
)
}
//article.js
import React from 'react'
export default function Article({title,content}) {
return (
<article>
<h3>{title}</h3>
<p>{content}</p>
</article>
)
}
컴포넌트의 기능과 역할을 나눈것 뿐이다.
결과는 위의 예제들과 모두 같다!
페이지가 완전히 로딩되지 않았을 때 임시의 컨텐츠를 보여주는 기능
예를 들어 유튜브에서 화면이 완전히 렌더링 되지 않은 경우에도 볼 수 있고, 로딩창도 만들어 볼 수 있다.
//app.js
import React from 'react'
import { useState } from 'react'
import { useEffect } from 'react'
import Nav from './components/nav'
import Article from './components/article'
import NowLoading from './components/NowLoading'
export default function App() {
const [article, setArticle] = useState(
{
items: {
title:"Welcome",
content: "React Ajax"
},
isLoading: false
})
const [ list, setList ] = useState({
items:[],
isLoading: false
})
useEffect( ()=>{
let newList = Object.assign({},list,{isLoading: true}) //loading
setList(newList) //loading
fetch('./data/list.json')
.then( res => res.json() )
.then( res => setList({
items: res,
isLoading: false
}) )
}, [])
return (
<div>
<h2>WEB</h2>
{/*
//loading
조건 ? <NowLoading /> : <Article title={article.title} content={article.content}></Article>
//{조건?로딩:보여질컴포넌트} << 기본문장
*/
}
{
//loading
article.isLoading?<NowLoading />:<Article title={article.items.title} content={article.items.content}></Article>
}
{
//loading
list.isLoading?<NowLoading />:
<Nav
list={list.items}
onChangeList={
id =>{let newList = Object.assign({},article,{isLoading: true}) //loading
setArticle(newList) //loading}
//console.log("Get id : ", id)
fetch('./data/'+id+'.json')
.then(res=>res.json())
.then(res=>setArticle(
{
items: {
title:res.title,
content: res.content
},
isLoading: false
}
))
}}
></Nav>
}
</div>
)
}
//NowLodaing.js
import React from 'react'
export default function NowLoading() {
return (
<div>
Now Loading...
</div>
)
}
//useFetch.js
import React from 'react'
import { useState } from 'react'
import { useEffect } from 'react'
export default function useFetch(fetchURL, initialType) {
const [data,setData] = useState('posts')
const fetchData = (resourceType)=>{
//fetch(`https://jsonplaceholder.typicode.com/${data}`)
fetch(fetchURL + '/' + resourceType)
.then(res=>res.json())
.then(res=>setData(res))
}
useEffect(
()=>{
fetchData(initialType)
},
[]
)
return {data, fetchData}
}
//nav.js
import React from 'react'
export default function Nav({list, onChangeList}) {
return (
<nav>
<ul>
{
list.map( item => (
<li key={item.id}>
<a href={item.id}
onClick={e=>{
e.preventDefault()
onChangeList( item.id )
}}
>
{item.title}</a>
</li>) )
}
</ul>
</nav>
)
}
//article.js
import React from 'react'
export default function Article({title,content}) {
return (
<article>
<h3>{title}</h3>
<p>{content}</p>
</article>
)
}
데이터가 적기 때문에 Now Loading..을 제대로 확인하려면 NetWork에서 인터넷 속도를 slow로 변경해야 확인할 수 있다.
이 부분도 위의 component 기능 분리와 마찬가지로 app.js에 대부분의 기능을 넣었고, 특히 Now Loading.js 컴포넌트는 보여지는 부분만 코드를 작성했다.
(결과)
