styled-components
장점
Detail.js
에서 사용했다면 Detail.js
에서만 적용된다는 것!)<style></style>
에 주입해준다.단점
🤔 CSS파일 생성 시 작명
컴포넌트명.module.css
로 하면 해당 컴포넌트에만 종속되는 CSS파일을 생성할 수 있다.
CSS파일 사용 시에는 <button>
을 만들어서 className
을 넣고 CSS파일에서 스타일을 지정해야 한다.
하지만 라이브러리를 사용하면 JS파일에서 전부 해결할 수 있다.
이렇게 생성되는 것은 컴포넌트 이므로 사용 시 <YellowBtn>버튼</YellowBtn>
처럼 컴포넌트를 호출하는 방식으로 사용해야 한다.
let YellowBtn = styled.button`
background: yellow;
color: black;
padding: 10px;
`
컴포넌트를 또 만드는 것이 아니라 props
문법을 사용하면 된다.
let YellowBtn = styled.button`
background: ${props => props.bg};
color: black;
padding: 10px;
`
<YellowBtn bg="Yellow">Yellow</YellowBtn>
<YellowBtn bg="Orange">Orange</YellowBtn>
👉 결과
🤔 참고
- 간단한 프로그래밍도 가능하다.
color: ${props => props.bg == 'blue' ? 'white' : 'black'};
- 기존 스타일도 복사 가능하다.
let NewBtn = styled.button(YellowBtn)` ... `
useEffect()
컴포넌트의 Lifecycle
Mount(페이지에 장착된다.) → Update(가끔 업데이트도 된다.) → Unmount(필요 없으면 제거된다.)
이걸 알아서 뭘 하느냐? → 중간중간 코드를 실행할 수 있다.
useEffect()
useEffect
안에 있는 코드는 렌더링이 다 끝난 후(= HTML을 다 그려준 뒤), 실행된다. 따라서 useEffect
에서는 주로 다음과 같은 작업을 한다.
🤔 clean up function
clean up function은 mount 시 실행 안 되고, unmount 시 실행된다.
clean up을 하지 않을 시, 만일 2초 사이에 재랜더링 된다면useEffect
안에 있는 코드를 또 실행하고, 요청이 끝나기도 전에 또 요청할 것이다. → 버그 발생!useEffect(() => { (서버로 데이터 요청하는 코드(2초 소요)) return () => { (기존 데이터 요청은 제거해 주세요) } })
useEffect
를 사용해서 유저가 <input>
에 숫자 말고 다른 것을 입력하면 "그러지마세요"라는 안내메시지를 출력하자.
// Detail.js
let [input, setInput] = useState("");
...
export default function Detail(props) {
useEffect(() => {
if(isNaN(input)) alert("그러지마세요");
}, [input])
return(
...
<input onChange={(e) => setInput(e.target.value)} />
...
)
}
🤔
isNaN()
- JS의 문자열이 숫자인지 체크하는 함수이다.
isNaN()
에 숫자가 들어오면false
를 반환, 숫자가 아닌 것이 들어오면true
를 반환한다.input
에 들어오는 값은 모두string
으로 인식하므로 위와 이 함수를 사용해서 판단해야 한다.
AJAX
서버에 데이터 요청 시
가 필요하다.
그런데 GET, POST 요청 시 페이지가 새로고침되는데, 이는 AJAX를 사용하면 해결할 수 있다. (새로고침 없이 GET/POST) 요청 가능)
🤔 AJAX 쓰려면 옵션 3개 중 택1
1. XMLHttpRequest
2. fetch()
3. axios
GET
)GET 요청: axios.get('url')
요청결과: axios.get('url').then()
// App.js
<button onClick={() => {
axios.get('https://codingapple1.github.io/shop/data2.json')
.then((res) => {
console.log(res.data);
})
}}>버튼</button>
요청실패: .catch()
<button onClick={() => {
axios.get('https://codingapple1.github.io/sop/data2.json')
.then((res) => {
console.log(res.data);
})
.catch(() => {
console.log("실패");
})
}}>버튼</button>
HTML을 생성하는 것이 아니라 state를 조작하면 된다. (products
에 데이터를 몇 개 추가해 주세요.)
👉 이미 Card
를 데이터 개수만큼 화면에 출력하도록 map
으로 짜놨기 때문에!
// App.js
axios.get('https://codingapple1.github.io/shop/data2.json')
.then((res) => {
let temp = [...products];
temp.push(...res.data);
setProducts(temp);
})
// 더 간편한 코드
axios.get('https://codingapple1.github.io/shop/data2.json')
.then((res) => {
let temp = [...products, ...res.data];
setProducts(temp);
})
POST
)POST 요청: axios.post('URL', {name: 'kim'})
동시에 요청을 여러 개 하려면:
Promise.all([axios.get('url1'), axios.get('url2')])
.then(() => {
})
🤔 원래는 서버와 문자만 주고 받을 수 있다.
object/array 같은 자료형을 주고 받을 수 없다.
그럼 방금 전까지 array 자료를 어떻게 받아온 것인지? 👉 object/array 자료에 따옴표를 쳐놓으면 된다."{"name" : "kim"}"
이를 JSON 이라고 한다.
axios
JSON은 문자 취급을 받으므로 서버와 자유롭게 주고받을 수 있다.
그래서 실제로결과.data
를 출력해보면 따옴표쳐진 JSON이 나와야 한다. 하지만axios
라이브러리는 JSON → object/array 변환작업을 자동으로 해주므로 출력해보면 object/array가 나오게 되는 것이다.
fetch
참고로,fetch
를 이용해도 GET/POST 요청이 가능하지만 그건 JSON → object/array 변환작업을 자동으로 해주지 않아서 직접 바꾸는 작업이 필요하다.fetch('URL').then(결과 => 결과.json()).then((결과) => { console.log(결과) } )
click 횟수를 저장하는 state를 만들어서 관리
처음 클릭했을 때는 데이터 요청을 '~data2'로, 두 번째 클릭에는 '~data3'으로 요청하므로 click+1
`https://codingapple1.github.io/shop/data${click+1}.json`
이 예제에서는 클릭을 두 번 했을 때까지만 상품이 보여질 것이므로 click이 2보다 크면 버튼이 안 보이도록 했다.
{
click < 2
? <button onClick={() => {handleClick()}}>더보기</button>
: null
}
loading state를 만들어서 관리
true
일 때 보여주고, false
일 때 숨기기(통신 시작할 때 true
로 바꾸고, 통신이 끝나면 false
)
// App.js
let [click, setClick] = useState(0);
let [loading, setLoading] = useState(false);
function App() {
...
const handleClick = () => {
setClick(click += 1);
console.log(click);
setLoading(true);
axios.get(`https://codingapple1.github.io/shop/data${click+1}.json`)
.then((res) => {
console.log(res.data);
let temp = [...products, ...res.data];
setProducts(temp);
setLoading(false);
})
.catch(() => {
console.log("실패");
setLoading(false);
})
}
...
return (
<Routes>
<Route path="/" element={
<>
...
{
click < 2
? <button onClick={() => {handleClick()}}>더보기</button>
: null
}
{
loading? <div>loading...</div> : null
}
</>
} />
...
</Routes>
)
}
👉 결과
HTML 안에서는 if문을 사용할 수 없으므로 컴포넌트로 빼서 바깥에서 사용했다.
tab
이라는 state는 Detail
에 있으므로 props를 이용해 TabComponent
로 전송해 주어야 한다.
// Detail.js
export default function Detail(props) {
let [tab, setTab] = useState(0);
...
<Nav fill variant="tabs" defaultActiveKey="link-0">
<Nav.Item>
<Nav.Link onClick={()=>setTab(0)} eventKey="link-0">Detail</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link onClick={()=>setTab(1)} eventKey="link-1">Review</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link onClick={()=>setTab(2)} eventKey="link-2">QnA</Nav.Link>
</Nav.Item>
</Nav>
<TabComponent tab={tab} />
...
}
// return을 해줘야 화면에 출력됨!!!
const TabComponent = (props) => {
if (props.tab === 0)
return <div>상품정보 탭</div>
if (props.tab === 1)
return <div>리뷰 탭</div>
if (props.tab === 2)
return <div>QnA 탭</div>
}
🤔 Tip
위에서 if문 3개를 사용한 코드를 좀 더 간단하게 바꿀 수 있다.const TabComponent = (props) => { return [<div>상품정보 탭</div>, <div>리뷰 탭</div>, <div>QnA 탭</div>][props.tab] }
👉 결과
transition
)🤔 전환 애니메이션 만드는 step
1. 애니메이션 동작 전 className 만들기
2. 애니메이션 동작 후 className 만들기
3. className에 transition 속성 추가
4. 원할 때 2번 className 부착
1, 2번
/* App.css */
.start {
opacity: 0;
}
.end {
opacity: 1;
transition: opacity 0.5s;
}
3, 4번
tab이 클릭될 때(= tab state가 변경될 때), 즉, tab state가 변할 때 end를 뗐다가 부착하면 된다.
그런데 다음처럼 해도 뗐다 붙이는 게 되지 않는다. 👉 Automatic Batching 때문에!
const TabComponent = (props) => {
let [fade, setFade] = useState('');
useEffect(() => {
setFade('end');
return () => {
setFade('');
}
}, [props.tab])
return <div className={`start ${fade}`}>
{ [<div>상품정보 탭</div>, <div>리뷰 탭</div>, <div>QnA 탭</div>][props.tab] }
</div>
}
🤔 Automatic Batching
setFade('end'); setFade('');
위처럼 state 변경함수들이 근처에 있다면 하나로 합쳐서 최종적으로 한 번만 state를 변경하는 것이다.
state변경함수() ← 재렌더링 X state변경함수() ← 재렌더링 X state변경함수() ← 재렌더링 X state변경함수() ← 재렌더링 O
따라서 setTimeout
을 이용해 미세한 시간 차를 두면 나중에 실행하게 할 수 있다.
const TabComponent = (props) => {
let [fade, setFade] = useState('');
useEffect(() => {
setTimeout(() => {
setFade('end');
}, 100);
}, [props.tab])
return <div className={`start ${fade}`}>
{ [<div>상품정보 탭</div>, <div>리뷰 탭</div>, <div>QnA 탭</div>][props.tab] }
</div>
}
👉 결과
Context API
나중에.. 들을 것...