-
TIL_200525 (What is React)Today I Learned 2020. 5. 25. 20:41
왜 React를 사용하는가?
웹 페이지는 유저와 수많은 인터렉션이 일어납니다. 그로 인해 많은 상태변화에 대한 관리가 필요합니다.
하나의 의미를 가진 독입적인 단위 모듈인 Coponent를 사용하여 직관적이며 재사용성이 높아진 코드를 작성할 수 있습니다.
ES6의 개념
destructuring, spread operator, rest parameters, default parameters, template literals, arrow function, for-of loop
JSX (자바스크립트의 확장 문법)
자바 스크립트에서 HTML의 태그를 입력하여 반환한다면 바벨이 태그로 구성된 코드를 변환하여 자바스크립트 문법으로 바꾸어 줍니다. 그로 인해 사용하고 읽기 쉬워집니다.
규칙
1. 반드시 하나의 엘리먼트오 감싸야 합니다.
2. 자바스크립트 코드를 적용할 때에는 중괄호( { } ) 안에 작성해야 합니다.
3. JSX내부에서 if문을 사용할 수 없습니다. (삼항연산자를 사용)
4. 엘리먼트의 클래스 이름을 적용할때 className을 사용
특성
DataFlow 단방향의 데이터 흐름을 보여줍니다. 자식 컴포넌트에서 부모 컴포넌트로 데이터를 보낼 수 없습니다.
Props 상위 컴포넌트가 하위 컴포넌트가 내려주는 데이터로써 하위 컴포넌트는 데이터를 변경할 수 없이 사용합니다.
State 컴포넌트가 갖는 상태로써 객체의 형태로 컴포넌트 내에서 보관하고 관리합니다. class 컴포넌트로 작성, 값을 변경 시 setState 메서드를 사용 합니다. 만약에 setState 메서드를 사용하여 state의 값을 변경한다면 reder() 함수가 다시 실행됩니다.
Life Cycle 컴포넌트가 브라우저에 보여질때, 업데이트될 때, 사라질 때 각 단계 전, 후로 특정 메서드가 호출됩니다. class 컴포넌트로 작성해야합니다. functional 컴포넌트로 작성한다면 실행되지 않습니다.
Hello World
ReactDOM.render( <h1>Hello, World!</h1>, document.getElementById('root') );
React는 elements & components로 구성되어 복잡한 앱을 작고 재사용 가능하게 만들 수 있게 됩니다.
Introducing JSX
const element = <h1>Hello, world!</h1>;
위의 코드처럼 문자열도 아니고 html도 아닌 구문을 JSX라고 부릅니다. React에서 사용하게 됩니다. 템플릿 언어를 떠올리지만 자바스크립트로 작동됩니다. JSX는 React의 elements를 생성하고 DOM에 렌더링합니다.
왜 JSX를 사용하는가?
React는 본질적으로 rendering 로직이 이벤트가 어떻게 다뤄지고, state가 어떻게 바뀌며, 데이터가 화면에 어떻게 보여지는지에 대한 UI로직과 결합되어있다는 사실을 받아들입니다.
마크업과 로직이 분리되어 있는 대신에 react는 "components"라고 부르는 느슨한 연결에 그 둘을 넣었습니다.
굳이 JSX를 사용하지 않아도 되지만 Javascript 코드 안에서 UI관련 작업을 할 때 시각적으로 도움이 됩니다.
JSX에 표현식 포함하기
중괄호 안에 javascript의 표현식을 넣을 수 있으며 user.firstName 또는 formatName 등의 표현식을 포함합니다.
const name = 'Josh Perez'; const element = <h1>Hello, {name}</h1>; ReactDOM.render( element, document.getElementById('root') );
function formatName(user) { return user.firstName + ' ' + user.lastName; } const user = { firstName: 'Harper', lastName: 'Perez' }; const element = ( <h1> Hello, {formatName(user)}! </h1> ); ReactDOM.render( element, document.getElementById('root') );
JSX도 표현식입니다.
컴파일이 끝나면 javascrpit 객체로 인식되므로 if구문 또는 for loop 안에 사용, 할당, 반환이 가능합니다.
function getGreeting(user) { if (user) { return <h1>Hello, {formatName(user)}!</h1>; } return <h1>Hello, Stranger.</h1>; }
JSX 속성 정의
속성에 따옴표로 문자열을 정의할 수 있고, 중괄호를 사용하여 표현식을 삽입할 수도 있습니다.
const element = <div tabIndex="0"></div>; const element = <img src={user.avatarUrl}></img>;
JSX자식정의
태그가 비어있다면 " / > "으로 닫아주고, 자식을 포함할 수도 있습니다.
const element = <img src={user.avatarUrl} />; const element = ( <div> <h1>Hello!</h1> <h2>Good to see you here.</h2> </div> );
XSS공격방지
모든 값을 렌더링하기 전에 이스케이프하고, 문자열로 변환되므로 XSS(cross-site-scripting) 공격이 방지됩니다.
JSX 객체표현
const element = ( <h1 className="greeting"> Hello, world! </h1> );
const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!' );
Babel은 JSX를 React.createElement() 호출로 컴파일합니다. 버그가 없는 코드를 작성하는 데 도움이 되도록 몇가지 검사를 수행하며, 기본적으로 다음과 같은 객체를 생성합니다.
// 단순화된 구조입니다 const element = { type: 'h1', props: { className: 'greeting', children: 'Hello, world!' } };
이러한 객체를 "React element"라고 하며 react가 이러한 객체를 읽은 후에 DOM을 구성하고 최신으로 유지합니다.
Rendering Elements
element는 react앱의 가장 작은 단위입니다.
브라우저 DOM element와 다르게 React element는 일반 객체이며 쉽게 생성할 수 있습니다. React DOM이 React element와 일치하도록 DOM을 업데이트 합니다. Element는 Component의 구성요소로써 다른 개념입니다.
<div id="root"></div>
HTML 파일 어딘가에 루트(root)DOM 노드가 있어서 이 안에 들어가는 모든 엘리먼트를 React DOM에서 관리합니다. 일반적으로 하나의 루트DOM노드가 있지만 react를 기존 앱에 통합하려는 경우에는 독립된 루트DOM노드가 있을 수 있습니다.
const element = <h1>Hello, world</h1>; ReactDOM.render(element, document.getElementById('root'));
element를 루트DOM노드에 렌더링하려면 React.render()로 전달합니다.
Element Update
React element는 불변객체입니다. 해당 엘리먼트의 자식이나 속성을 변경할 수 없습니다. 이 상황에서 UI를 업데이트하기 원한다면 새로운 엘리먼트를 생성하고 ReactDOM.render()로 전달하는 기본적인 방법이 있습니다.
setInterval() 을 이용하여 매초마다 ReactDOM.render() 을 호출하는 시계입니다.
function tick() { const element = ( <div> <h1>Hello, world!</h1> <h2>It is {new Date().toLocaleTimeString()}.</h2> </div> ); ReactDOM.render(element, document.getElementById('root')); } setInterval(tick, 1000);
Components and Props
컴포넌트를 통해 UI를 재사용 가능한 개별적인 여러 조각으로 나누고, 각 조각을 개별적으로 살펴볼 수 있습니다.
컴포넌트는 JavaScript 함수와 유사합니다. "props"라는 임의의 입력을 받은 후, 화면에 어떻게 표시되는지를 기술하는 React 엘리먼트를 반환합니다.
functional component, class component
function Welcome(props) { return <h1>Hello, {props.name}</h1>; }
간단하게 함수를 작성하여 정의할 수 있습니다. "props"라는 객체 인자를 받은 후에 React element를 반환하는 유효한 React component입니다. ES6 문법을 사용하여 컴포넌트를 정의할 수도 있습니다.
class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
컴포넌트 렌더링
이전까지 React element를 DOM 태그로 나타냈지만 함수로 정의한 컴포넌트로 나타낼 수 있습니다.
const element = <div />;
const element = <Welcome name="Sara" />;
React가 사용자 정의 컴포넌트를 발견하면 JSX 어트리뷰트와 자식을 해당 컴포넌트에 단일 객체로 전달합니다. 이 객체를 "props"라고 합니다.
function Welcome(props) { return <h1>Hello, {props.name}</h1>; } const element = <Welcome name="Sara" />; ReactDOM.render( element, document.getElementById('root') );
전달되는 과정
1, <Welcome name="Sara" />엘리먼트로 ReactDOM.render()를 호출합니다.
2. React는 {name: 'Sara'}를 props로 하여 Welcome 컴포넌트를 호출합니다.
3. Welcome 컴포넌트는 <h1>Hello, Sara</h1> 엘리먼트를 반환합니다.
4. React DOM은 <h1>Hello, Sara</h1> 엘리먼트와 일치하도록 DOM을 효율적으로 업데이트합니다.
컴포넌트 합성
컴포넌트는 자신의 출력에 다른 컴포넌트를 참조할 수 있습니다.
function Welcome(props) { return <h1>Hello, {props.name}</h1>; } function App() { return ( <div> <Welcome name="Sara" /> <Welcome name="Cahal" /> <Welcome name="Edite" /> </div> ); } ReactDOM.render( <App />, document.getElementById('root') );
일반적으로 새 React 앱은 최상위에 단일 App 컴포넌트를 가지고 있습니다. 하지만 기존 앱에 React를 통합하는 경우 작은 컴포넌트에서 시작하여 점진적으로 올라가면서 작업해야 할 수 있습니다.
컴포넌트 추출
컴포넌트를 여러 개의 작은 컴포넌트로 나누는 것을 두려워하지 마세요.
function Comment(props) { return ( <div className="Comment"> <div className="UserInfo"> <img className="Avatar" src={props.author.avatarUrl} alt={props.author.name} /> <div className="UserInfo-name"> {props.author.name} </div> </div> <div className="Comment-text"> {props.text} </div> <div className="Comment-date"> {formatDate(props.date)} </div> </div> ); }
이런 컴포넌트는 구성요소들이 중첩 구조로 이루어져 있어서 변경하기 어렵고, 구성요소를 재사용하기 어렵습니다. 컴포넌트를 추출하여 단순하게 만들 수 있습니다.
function Avatar(props) { return ( <img className="Avatar" src={props.user.avatarUrl} alt={props.user.name} /> ); }
구성요소인 Avatar를 추출하며 재사용성을 높이기 위해 props의 이름을 일반화하여 user로 변경합니다.
function Comment(props) { return ( <div className="Comment"> <div className="UserInfo"> <Avatar user={props.author} /> <div className="UserInfo-name"> {props.author.name} </div> </div> <div className="Comment-text"> {props.text} </div> <div className="Comment-date"> {formatDate(props.date)} </div> </div> ); }
조금 간단해졌지만 UserInfo 컴포넌트를 추출하여 더욱 간단하게 만들 수 있습니다.
function UserInfo(props) { return ( <div className="UserInfo"> <Avatar user={props.user} /> <div className="UserInfo-name"> {props.user.name} </div> </div> ); }
function Comment(props) { return ( <div className="Comment"> <UserInfo user={props.author} /> <div className="Comment-text"> {props.text} </div> <div className="Comment-date"> {formatDate(props.date)} </div> </div> ); }
재사용 가능한 컴포넌트를 만들어 놓는 일은 큰 앱을 작업할때 유용하게 사용됩니다. 여러번 사용되거나 자체적으로 복잡한 UI의 경우에는 재사용 가능한 컴포넌트를 만드는 것이 좋습니다.
props는 읽기 전용이기 때문에 반드시 모든 React component는 순수 함수처럼 동작해야 합니다.
// 순수함수 function sum(a, b) { return a + b; } // 순수함수가 아님 function withdraw(account, amount) { account.total -= amount; }
State and Lifecycle
째깍거리는 시계를 만들어 봅니다.
function tick() { const element = { <div> <h1>Hello, world!</h1> <h2>It is {new Data().toLocaleTimeString()}.</h2> </div> ); ReactDOM.render( element, document.getElementById('root') ); } setInterval(tick, 1000);
먼저, 캡슐화를 합니다.
function Clock(props) { return ( <div> <h1>Hello, world!</h1> <h2>It is {props.date.toLocaleTimeString()}.</h2> </div> ); } function tick() { ReactDOM.render( <Clock date={new Date()} />, document.getElementById('root') ); } setInterval(tick, 1000);
중요한 요건이 누락되어 있습니다. Clock이 타이머를 설정하고 매초 UI를 업데이트 하는 것이 Clock의 구현 세부사항이 되어야 합니다.
한번만 코드를 작성하고 Clock이 스스로 업데이트하도록 만들려고 합니다. 이를 위해서 Clock 컴포넌트에 "state'를 추가해야합니다.
ReactDOM.render( <Clock />, document.getElementById('root') );
state는 props와 유사하지만 비공개이며 컴포넌트에 의해 완전히 제어됩니다.
함수에서 클래스로 변환하기 다섯단계
1. React.Component를 확장하는 동일한 이름의 ES6 class를 생성합니다.
2. render()라고 불리는 빈 메서드를 추가합니다.
3. 함수의 내용을 render() 메서드 안으로 옮깁니다.
4. render() 내용 안에 있는 props를 this.props로 변경합니다.
5. 남아있는 빈 함수 선언을 삭제합니다.
class Clock extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.props.date.toLocaleTimeString()}.</h2> </div> ); } }
Clock은 클래스로 정의됩니다.
render메서드는 업데이트가 발생할 때마다 호출되지만, 같은 DOM 노드로 <Clock />을 렌더링하는 경우 Clock 클래스의 단일 인스턴스만 사용됩니다.
클래스에 로컬 State 추가하기
세단계에 걸쳐서 date를 props에서 states로 이동합니다.
1. render() 메서드 안에 있는 this.props.date를 this.state.date로 변경합니다.
class Clock extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } }
2. 초기 this.state를 지정하는 class comstructor를 추가합니다. 클래스 컴포넌트는 항상 props로 기본 constructor를 호출해야 합니다.
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } }
3. <Clock /> 요소에서 date prop를 삭제합니다.
ReactDOM.render( <Clock />, document.getElementById('root') );
결과는 다음과 같습니다.
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } } ReactDOM.render( <Clock />, document.getElementById('root') );
생명주기 메서드를 클래스에 추가하기
많은 컴포넌트가 있는 앱에서 컴포넌트가 삭제될때 해당 컴포넌트가 사용 중이던 리소스를 확보하는 것이 중요합니다.
Clock이 처음 DOM에 렌더링 될 때마다 타이머를 설정하려고 합니다. 이것을 React에서 "마운팅"이라고 합니다.
컴포넌트 클래스에서 특별한 메서드를 선언하여 컴포넌트가 마운트되거나 언마운트 될때 일부 코드를 작동할 수 있습니다.
이러한 메서드들은 "생명주기 메서드"라고 불립니다.
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { //컴포넌트 출력물이 DOM에 렌더링 된 후에 실행됩니다. 타이머를 설정하기 좋은 장소입니다. this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount() { clearInterval(this.timerID); } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } }
this.props가 React에 의해 스스로 설정되고 this.state 가 특수한 의미가 있지만, 타이머 ID와 같이 데이터 흐름 안에 포함되지 않는 어떤 항목을 보관할 필요가 있다면 자유롭게 클래스에 수동으로 부가적인 필드를 추가해도 됩니다.
매초 작동해야하는 tick() 메서드를 구현합니다. 컴포넌트 로컬 state를 업데이트하기 위해 this.setState()를 사용합니다.
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount() { clearInterval(this.timerID); } tick() { this.setState({ date: new Date() }); } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } } ReactDOM.render( <Clock />, document.getElementById('root') );
위의 모든 상황을 요약
1. <Clock />가 ReactDOM.render()로 전달되었을 때 React는 Clock 컴포넌트의 constructor를 호출합니다. Clock이 현재 시각을 표시해야 하기 때문에 현재 시각이 포함된 객체로 this.state를 초기화합니다. 나중에 이 states를 업데이트 할 것입니다.
2. React는 Clock 컴포넌트의 render()메서드를 호출합니다. 이를 통해 React는 화면에 표시되어야 할 내용을 알게 됩니다. 그 다음 React는 Clock의 렌더링 출력값을 일치시키기 위해 DOM을 업데이트합니다.
3. Clock 출력값이 DOM에 삽입되면, React는 componentDidMount() 생명주기 메서드를 호출합니다. 그 안에서 Clock 컴포넌트는 매포 컴포넌트의 tick() 메서드를 호출하기 위한 타이머를 설정하도록 브라우저에 요청합니다.
4. 매초 브라우저가 tick() 메서드를 호출합니다. 그 안에서 Clock 컴포넌트는 setState()에 현재 시각을 포함하는 객체를 호출하면서 UI 업데이트를 진행합니다. serState() 호출 덕분에 React는 state가 변경된 것을 인지하고 화면에 표시될 내용을 알아내기 위해 render()메서드를 다시 호출합니다. 이 때 render() 메서드 안의 this.state.date가 달라지고 렌더링 출력값은 업데이트된 시각을 포함합니다. React는 이에 따라 DOM을 업데이트합니다.
5. Clock 컴포넌트가 DOM으로부터 한 번이라도 삭제된 적이 있다면 React는 타이머를 멈추기 위해 componentWillUnmount() 생명주기 메서드를 호출합니다.
State를 올바르게 사용하기
직접 State를 수정하지 마세요. this.state를 지정할 수 있는 유일한 공간은 constructor입니다.
// Wrong this.state.comment = 'Hello'; // Correct this.setState({comment: 'Hello'});
State 업데이트는 비동기적일 수도 있습니다.
this.props와 this.state가 비동기적으로 업데이트될 수 있기 때문에 함수를 인자로 받아 사용하는 다른 형태의 setState()를 사용해야 합니다.
// Wrong this.setState({ counter: this.state.counter + this.props.increment, }); // Correct this.setState((state, props) => ({ counter: state.counter + props.increment }));
State 업데이트는 병합됩니다.
setState()를 호출할 때 React는 제공한 객체를 현재 state로 병합합니다.
state가 포함하고 있는 독립적인 변수를 독립적으로 업데이트할 수 있습니다. 즉, 병합이 일어나지만 얕게 이루어져서 다른 변수에는 영향을 주지 않습니다.
constructor(props) { super(props); this.state = { posts: [], comments: [] }; }
componentDidMount() { fetchPosts().then(response => { this.setState({ posts: response.posts }); }); fetchComments().then(response => { this.setState({ comments: response.comments }); }); }
데이터는 아래로 흐릅니다.
부모 컴포넌트나 자식 컴포넌트 모두 특정 컴포넌트가 유상태인지 또는 무상태인지 알 수 없고, 스들이 함수나 클래스로 정의되었는지에 대해서 관심을 가질 필요가 없습니다. state가 소유하고 설정하는 컴포넌트 이외에는 어떠한 컴포넌트에도 접근할 수 없습니다.
컴포넌트는 자신의 state를 자식 컴포넌트에 props로 전달할 수 있습니다.
'Today I Learned' 카테고리의 다른 글
TIL_200528 (0) 2020.05.29 TIL_200526 (What is React) (0) 2020.05.27 TIL_200520 (0) 2020.05.20 TIL_200427-29 (0) 2020.04.30 TIL_200413 (0) 2020.04.13