Chapter1. React 데이터 흐름
1-1. React 데이터 흐름
1-2. State 끌어올리기 (Lifting State Up)
Chapter2. Effect Hook
2-1. Side Effect
2-2. Effect Hook 기본
2-3. Effect Hook 조건부 실행
2-4. 컴포넌트 내에서의 AJAX 요청
React에는 두 가지 데이터 “모델”인 props와 state가 있음
props
부모가 자식에게 데이터를 넘겨줄 때 사용할 수 있는 방법
state
오직 상호작용을 위해, 즉 시간이 지남에 따라 데이터가 바뀌는 것에 사용함
<FormattedDate date={this.state.date} />
자식인 FormattedDate
컴포넌트는 date를 자신의 props로 받을 것이고 이것이 Clock
(예시에서 부모 컴포넌트)의 state로부터 왔는지, Clock
의 props에서 왔는지, 수동으로 입력한 것인지 알지 못합니다.
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
상태를 변경시키는 함수(handler)를 하위 컴포넌트에 props로 전달해서 해결할 수 있음
https://ko.reactjs.org/docs/state-and-lifecycle.html
데이터는 아래로 흐릅니다
하위 컴포넌트에서의 어떤 이벤트로 인해 상위 컴포넌트의 상태가 바뀌는 것은 마치 "역방향 데이터 흐름"과 같이 조금 이상하게 들릴 수 있긴 하나 아래의 solution 에 의해 React의 단방향 데이터 흐름의 원칙은 항상 부합함.
상위 컴포넌트의 "상태를 변경하는 함수" 그 자체를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트가 실행한다
컴포넌트 간 데이터 교환을 원활하게 할 수 있음
앱이 힙해짐
state 끌어올리기 하는 방법
상태 갱신 함수는 setValue, 상태를 변경하는 함수는 handleChangeValue
-> props로 handleChangeValue를 자식 컴포넌트에 전달할 것임
-> props의 이름은 handelButtonClick
로 지어줘
-> 얘
를 자식 컴포넌트 {인자} 에 넣고
-> 자식 컴포넌트 내에 얘
를 실행시킬 함수블록 내에 넣어줘
handelButtonClick를 총 3번
작성함
부모 컴포넌트
function ParentComponent() {
const [value, setValue] = useState("날 바꿔줘!");
const handleChangeValue = () => {
setValue("보여줄게 완전히 달라진 값");
};
return (
<div>
<div>값은 {value} 입니다</div>
<ChildComponent handleButtonClick={handleChangeValue} />
</div>
);
}
자식 컴포넌트
function ChildComponent({ handleButtonClick }) {
const handleClick = () => {
// Q. 이 버튼을 눌러서 부모의 상태를 바꿀 순 없을까?
// A. 인자로 받은 상태 변경 함수를 실행하자!
handleButtonClick()
}
return (
<button onClick={handleClick}>값 변경</button>
)
}
부모 컴퍼넌트
import React, { useState } from "react";
import "./styles.css";
const currentUser = "김코딩";
function Twittler() {
const [tweets, setTweets] = useState([
{
uuid: 1,
writer: "김코딩",
date: "2020-10-10",
content: "안녕 리액트"
},
{
uuid: 2,
writer: "박해커",
date: "2020-10-12",
content: "좋아 코드스테이츠!"
}
]);
const addNewTweet = (newTweet) => {
setTweets([...tweets, newTweet]);
}; // 이 상태 변경 함수가 NewTweetForm에 의해 실행되어야 합니다.
return (
<div>
<div>작성자: {currentUser}</div>
<NewTweetForm onButtonClick={addNewTweet}/> // props로 전달
<ul id="tweets">
{tweets.map((t) => (
<SingleTweet key={t.uuid} writer={t.writer} date={t.date}>
{t.content}
</SingleTweet>
))}
</ul>
</div>
);
}
자식 컴포넌트
function NewTweetForm({ onButtonClick }) {
const [newTweetContent, setNewTweetContent] = useState("");
const onTextChange = (e) => {
setNewTweetContent(e.target.value);
};
const onClickSubmit = () => {
let newTweet = {
uuid: Math.floor(Math.random() * 10000),
writer: currentUser,
date: new Date().toISOString().substring(0, 10),
content: newTweetContent
};
onButtonClick(newTweet);
// TDOO: 여기서 newTweet이 addNewTweet에 전달되어야 합니다.
};
return (
<div id="writing-area">
<textarea id="new-tweet-content" onChange={onTextChange}></textarea>
<button id="submit-new-tweet" onClick={onClickSubmit}>
새 글 쓰기
</button>
</div>
);
}
자식 컴포넌트
function SingleTweet({ writer, date, children }) {
return (
<li className="tweet">
<div className="writer">{writer}</div>
<div className="date">{date}</div>
<div>{children}</div>
</li>
);
}
export default Twittler;
//전역 변수 foo를 bar라는 함수가 수정하는 예제
let foo = 'hello';
function bar() {
foo = 'world';
}
bar(); // bar는 Side Effect를 발생시킵니다!
function upper(str) {
return str.toUpperCase(); // toUpperCase 메소드는 원본을 수정하지 않습니다 (Immutable)
}
upper('hello') // 'HELLO'
Math.random()은 순수 함수가 아닙니다. 왜일까요?
어떤 함수가 fetch API를 이용해 AJAX 요청을 한다고 가정해 봅시다. 이 함수는 순수 함수가 아닙니다. 왜일까요?
React의 함수 컴포넌트는, props가 입력으로, JSX Element가 출력으로 나갑니다. 여기에는 그 어떤 Side Effect도 없으며, 순수 함수로 작동합니다.
useEffect
는 컴포넌트 내에서 Side effect를 실행할 수 있게 하는 Hook
첫 번째 인자는 함수로, 해당 함수 내에서 side effect를 실행하면 됨
useEffect(()=>{
console.log(몇 번 호출될까요?)
})
컴포넌트가 처음 생성되거나, props가 업데이트 되거나, state가 업데이트 될 때마다 실행
useEffect(()=>{
console.log(몇 번 호출될까요?)
}, [count])
컴포넌트가 최초 마운트될때(화면에 나타날때), state인 count가 업데이트 될 때마다 실행됩니다.
배열 = Dependency Array
useEffect(()=>{
console.log(몇 번 호출될까요?)
}, [])
컴포넌트가 처음 생성될 때만 단 한 번만, effect 함수가 실행됩니다.
Effect Hook을 컴포넌트 내에서 최초 1회만 호출하고 싶을 때 사용
ex) 대표적으로 처음 단 한 번, 외부 API를 통해 리소스를 받아오고 더 이상 API 호출이 필요하지 않을 때에 사용할
매번 새롭게 컴포넌트가 렌더링 될 때 Effect Hook이 실행됨
useEffect(함수, [종속성1, 종속성2, ...])
useEffect
의 두 번째 인자는 배열이고 이 배열은 조건을 담고 있음
useEffect
의 두 번째 인자는 종속성 배열입니다.
목록 내 필터링을 구현하는 방법 2가지
처음 단 한 번, 외부 API로부터 명언 목록을 받아오고, filter 함수를 이용
검색어가 바뀔 때마다, 외부 API를 호출합니다.
방식 | 장점 | 단점 |
---|---|---|
컴포넌트 내부에서 처리 | HTTP 요청의 빈도를 줄일 수 있다 | 브라우저(클라이언트)의 메모리 상에 많은 데이터를 갖게 되므로, 클라이언트의 부담이 늘어난다 |
컴포넌트 외부에서 처리 | 클라이언트가 필터링 구현을 생각하지 않아도 된다 | 빈번한 HTTP 요청이 일어나게 되며, 서버가 필터링을 처리하므로 서버가 부담을 가져간다 |
http://서버주소/proverbs
라고 가정useEffect(() => {
fetch(`http://서버주소/proverbs?q=${filter}`)
.then(resp => resp.json())
.then(result => {
setProverbs(result);
});
}, [filter]);
네트워크 요청이 항상 즉각적인 응답을 가져다주는 것은 아닙니다. 외부 API 접속이 느릴 경우를 고려하여, 로딩 화면(loading indicator)의 구현은 필수적임
여기에도 상태 처리가 필요함
const [isLoading, setIsLoading] = useState(true);
// 생략, LoadingIndicator 컴포넌트는 별도로 구현했음을 가정합니다
return {isLoading ? <LoadingIndicator /> : <div>로딩 완료 화면</div>}
fetch 요청의 전후로 setIsLoading을 설정해 주어 보다 나은 UX를 구현할 수 있음
useEffect(() => {
setIsLoading(true); ⭐️
fetch(`http://서버주소/proverbs?q=${filter}`)
.then(resp => resp.json())
.then(result => {
setProverbs(result);
setIsLoading(false); ⭐️
});
}, [filter]);
사이드 이펙트는 유즈이펙트에 넣어줘야한다.(외우세요)
유즈이펙트 첫번재인자함수에 = 사이드이펙트넣어줘야한다
ajax요청 = 네트워크요청 = 사이드이펙트임 = >유즈이펙트안에넣어라
리턴해서 프로미스 객체를 생성, getFlight는 프로미스 객체가 된대
리졸브안에 필털드라는 변수를
fillterby ={] 디폴트값을 정해주는것임
unction func1(filteredBy = {}) {
if (filteredBy.num) return filteredBy.num;
if (filteredBy.str) return filteredBy.str;
}
let obj1 = {};
func1(obj1); // undefined
let obj2 = {num : 1};
func1(obj2); // 1
let obj3 = {str:'문자'}
func1(obj3); // '문자'
2-5 로딩화면
종합퀴즈 3번 a 외부에서 내부에 영향을 미치는 것도 비순수
c 객체를 매개변수로 받아와서 수정만 안하면 순수함
클로저는 무조건 비순수함수래...