리액트 레퍼런스를 참고하여 리액트 주요 개념을 정리해보고자 한다
정확한 것은 리액트 레퍼런스 가서 확인하면 되고(이렇게 잘 번역되어있는 레퍼런스도 처음 봤다 번역해주신 분 압도적 감사!)
난 내 쪼대로 이해해보려고 한다


JSX 소개

  • javascript를 확장한 문법(javascript의 문법 모두 포함)
  • JSX 중괄호 안에서 Javscript표현식을 넣어서 사용한다 (뭔가 대충 jsp 느낌적 느낌인 것 같다)
  • 속성에 따옴표로 문자열 리터럴 정의
  • 중괄호로 attribute에 Javascript 표현식 삽입 이 때 따옴표 넣으면 안됨
  • JSX에서 class는 className이고, tabindex는 tabIndex가 되는데 이건 javascript에 비슷한 명령어가 있어서
  • JSX는 주입공격을 방지하는데 JSX에 삽입된 모든 항목이 렌더링 전에 문자열로 변환됨(XSS 방지)
  • React.createElement()는 버그가 없는 코드를 작성하는데 도움이 되도록 몇 가지 검사를 수행하며 다음과 같은 객체를 생성한다
  • 이러한 객체를 React Element 라고 하며 화면에서 보고 싶은 것을 나타내는 표현이다
  • React는 이 객체를 읽어서 DOM을 구성하고 최신 상태로 유지한다
//셋이 똑같다 

1)
const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

2)
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

3)
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

엘리먼트 렌더링

  • 브라우저 DOM 엘리먼트와 달리 React 엘리먼트는 일반 객체이며 쉽게 생성할 수 있다(React DOM은 React 엘리먼트와 일치하도록 DOM을 업데이트 한다)
  • <div id = "root"></div>에 들어가는 모든 엘리먼트를 React DOM에서 관리하며 이를 Root DOM 이라고 부른다
  • ReactDOM.render()로 React 엘리먼트를 루트 DOM 노드에 렌더링한다
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
  • React 엘리먼트는 불변객체이며, 특정 시점 UI를 보여준다
  • UI를 업데이트하는 유일한 방법은 새로운 엘리먼트를 생성하고 ReactDOM.render()로 전달하는 것 뿐
  • 전체를 변경하도록 해도 ReactDOM은 해당 엘리먼트와 그 자식 엘리먼트를 이전의 엘리먼트와 비교하고 필요한 부분에 대해서만 DOM을 업데이트한다(일부분만 교체하면 일부분만 업데이트한다)

Components와 Props

  • Component를 통해 UI를 재사용 가능한 개별적인 여러 조각으로 나누고 각 조각을 개별적으로 살펴볼 수 있다(ex login page를 LoginBox Component, InputComponent, Button Component 이런 식으로 나눌 수 있다 난 모듈 같은 개념으로 이해함)
  • component를 정의하는 가장 간단한 방법은 Javascript 함수를 작성하는 것, 혹은 ES6 class 이용
  • 사용자 정의 컴포넌트로 엘리먼트의 JSX attribute와 자식을 props라는 단일 객체로 전달한다
  • 컴포넌트는 다른 컴포넌트를 출력할 수 있다(그래서 컴포넌트 안에 여러 컴포넌트들을 배치해서 만든다)

State and LifeCycle

  • state : props와 유사하지만 비공개이며, 컴포넌트에 의해 완전히 제어된다
  • 이전에 학원에서 배웠는데 props는 고정이고 state는 고정이 아니었던 듯(props를 사용할 때 한번 css가 바뀌면 그 이후에는 못바꿨던 기억이 난다)
  • 함수 컴포넌트를 class 로 변환할 수 있다
    1) React.Component를 확장하는 동일한 이름의 ES6 class를 생성한다
    2) render()라고 불리는 빈 메서드를 추가한다
    3) 함수의 내용을 render() 메서드 안으로 옮긴다
    4) render() 내용 안에 있는 props를 this.props로 변경한다
    5) 남아있는 빈 함수 선언을 삭제한다
function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

-> 

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

  • render 메서드는 업데이트 발생시마다 호출되지만 같은 DOM 노드로 렌더링하는 경우 클래스의 단일 인스턴스만 사용된다
  • 이것은 state와 생명주기 메서드와 같은 부가적인 기능을 사용할 수 있게 해준다
  • 클래스에 로컬 state를 추가하는 방법은 다음과 같다
    1) render() 메서드 안에 있는 this.props.date를 this.state.date로 변경한다
    2) 초기 this.state를 지정하는 class constructor을 추가한다
    3) 컴포넌트 요소에서 prop 들을 삭제한다 (여기서는 <Clock/>)
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')
);

/*
참고로 원래는 이거였음(전달하는 props 존재)
ReactDOM.render(
  <Clock date={new Date()} />,
  document.getElementById('root')
);
*/

  • 많은 컴포넌트가 있는 애플리케이션에서 컴포넌트를 삭제할 때 해당 컴포넌트가 사용중이던 리소스를 확보하는 것이 중요하기 때문에 생명주기 메서드를 클래스에 추가해야한다
  • 생명주기 메서드 : 컴포넌트 클래스에서 특별한 메서드를 선언하여 컴포넌트가 마운트되거나 언마운트 될 때 일부 코드를 작동할 수 있다 (ex Rendering 될 때 설정 : 마운트, DOM 삭제 시 해제 : 언마운트)
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) 처음 렌더링 될 때 state를 new Date()로 초기화하여 Clock 만듦
2) 이후 Clock이 DOM에 삽입되면 componentDidMount를 호출하고, 이 함수에서 timer을 설정한다(1초마다 tick()함수 호출)
3) tick함수에서 setState 함수를 사용해 state 값을 바꾸고, setState 함수를 호출해서 React는 state가 바뀐 것을 알고 화면에 표시될 내용을 알기 위해 render()을 다시 호출함 -> DOM 업데이트
4) Clock 컴포넌트가 DOM으로부터 삭제된 적이 있다면, React는 타이머를 멈추기 위해 componentWillUnmount() 함수를 호출한다


setState를 사용할 때 알아야 할 것들이 있다
1) 직접 State를 수정해선 안된다(그럼 컴포넌트를 다시 렌더링하지 않음)
-> setState함수를 사용, this.state를 지정할 수 있는 장소는 오직 constructor 뿐
2) State 업데이트는 비동기적일 수 있다
-> this.props와 this.state가 비동기적으로 업데이트 될 수 있기 때문에 state를 계산할 때 해당 값에 의존하면 안된다
-> 객체보다는 함수를 인자로 사용하는 다른 형태의 setState()를 사용함

// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});
	
// Correct
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));    

// Correct
this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment
  };
});

3) State 업데이트는 병합된다
-> setState()를 호출할 때 React는 제공한 객체를 현재 state로 병합한다

// 이렇게 독립적인 다양한 변수(posts, comments 등)을 포함할 수 있다 
  constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
  }
  
  // 별도의 setState()호출로 이러한 변수를 독립적으로 업데이트 할 수 있다(얕은 병합)
  componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts
      });
    });

    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });
  }

  • 데이터는 단방향으로 흐른다
  • state가 소유하고 설정한 컴포넌트 외에는 어떠한 컴포넌트도 접근할 수 없다(로컬/캡슐화)
  • 컴포넌트는 자신의 state를 자식 컴포넌트에 props로 전달할 수 있다🎈
  • 이를 하향식, 단방향식 데이터 흐름이라고 한다(오직 트리구조에서 자신의 아래 있는 컴포넌트에만 영향을 미친다)
<FormattedDate date={this.state.date} />

-> 받음 
function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

이벤트 처리

  • 이벤트는 소문자 대신 카멜케이스를 사용한다
  • JSX를 사용해 문자열이 아닌 함수로 이벤트 핸들러를 전달한다
<button onClick = {activateLasers}>
  Activate Lasers
</button>

  • React에서는 false를 반환해도 기본 동작을 방지할 수 없고, preventDefault를 명시적으로 호출해야한다
function Form(){
	function handleSubmit(e){
    	e.preventDefault();
        console.log('You clicked Submit');
    }
    
    return (
    	<form onSubmit = {handleSubmit}>
        	<button type = "submit>Submit</button>
        </form>
        	
    )
}
  • e는 합성 이벤트이기 때문에 브라우저 호환성에 대해 걱정할 필요 없으며, 브라우저 고유 이벤트와 동일하게 동작하지는 않는다
  • React를 사용할 때 DOM 엘리먼트가 생성된 후 리스너를 추가하기 위해 addEventListener을 호출할 필요가 없다
  • 엘리멘트가 처음 렌더링 될 때 리스너를 제공하면 된다
  • ES6클래스를 사용해 컴포넌트를 정의할 때 일반적으로 이벤트 핸들러를 클래스 메서드로 만든다
class Toggle extends React.Component{

	constructor(props){
    	super(props);
        this.state = {isToggleOn : true};
        
        //콜백에서 this가 동작하려면 아래와 같이 바인딩 해주어야 한다 
        this.handleClick = this.handleClick.bind(this);      	
    }
    
    handleClick(){
    	this.setState(prevState => ({
        	isToggleOn: !prevState.isToggleOn
        }));
    }
    
    render() {
    	return(
            <button onClick = {this.handleClick}>
            	{this.state.isToggleOn ? 'ON' : 'OFF'}
            </button>
        );
    }
    
    ReactDOM.render(
    	<Toggle/>,
        document.getElementById('root')
    );
}
  • javascript에서 클래스 메서드는 기본적으로 바인딩 되어있지 않으며 bind 호출이 귀찮다면 다음과 같은 방법들을 사용한다
  1. 퍼블릭 클래스 필드 문법(실험적이므로 주의할 것)
Class LoggingButton extends React.Component{
	
handleClick = () => {
	console.log('this is:' , this);
}

render(){
	return(
    	<button onClick = {this.handleClick}>
        	Click me 
        </button>
    );
  }
}
  1. 클래스 필드 문법을 사용하고 있지 않다면 콜백에 화살표 함수를 사용하는 방법도 있다
class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // 이 문법은 `this`가 handleClick 내에서 바인딩되도록 합니다.
    return (
      <button onClick={() => this.handleClick()}>
        Click me
      </button>
    );
  }
}
  • 이 경우 LoggingButton이 렌더링될 때마다 다른 콜백이 생성된다는 문제가 있다
  • 콜백이 하위 컴포넌트에 props로서 전달된다면 그 컴포넌트들은 추가로 다시 렌더링을 할 수 있다
때문에 생성자 안에서 바인딩하거나 클래스 필드 문법을 사용하는 것을 권장한다
  • 루프 내부에서는 이벤트 핸들러에 추가적인 매개 변수를 전달하는 것이 일반적이다
<button onClick = {(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick = {this.deleteRow.bind(this, id)}>Delete Row</button>
  • 화살표 함수를 사용하면 e 인자가 두번째 인자로 전달되고, 명시적으로 인자를 전달해야한다
  • bind 함수를 사용하면 추가 인자가 자동으로 전달된다

조건부 렌더링

  • 사용자 조건에 따라 다른 컴포넌트 보여줌
function UserGreeting(props){
	return <h1>Welcome Back!</h1>
}

function GuestGreeting(props){
	return <h1>Please sign up</h1>
}

function Greeting(props){
	const isLoggedIn = props.isLoggedIn;
    if (isLoggedIn){
    	return <UserGreeting/>
    }
    return <GuestGreeting/>
}

ReactDom.render(
	<Greeting isLoggedIn = {false}/>,
    document.getElementIdById('root')
);
  • 논리 && 연산자로도 표현식을 포함할 수 있다
function Mailbox(props){
	const unreadMessages = props.unreadMessages;
    return (
    	<div>
            <h1>Hello</h1>
            {unreadMessages.length > 0 &&
            <h2>
            	You have {unreadMessages.length} unread messages.
            </h2>
            }
        </div>	
    );
    
    const messages = ['React', 'Re:React', 'Re:Re: React']
    ReactDOM.render(
    	<Mailbox unreadMessages = {message}/>,
        document.getElementById('root')
    );
}
  • 조건이 true일 경우에는 && 뒤 출력, false이면 &&뒤 출력을 하지 않으며 false일 경우 뒤의 표현식은 건너뛰지만 앞의 표현식이 반환될 수 있다
render(){
	const count = 0;
    return(
    	<div>	
        	{count && <h1>Messages : {count}</h1>}
        </div>
    )
}
위의 경우 <div>0</div>가 반환된다
  • 조건부 연산자인 condition ? true:false를 이용해 조건부 렌더링을 할 수 있다
render(){
	const isLoggedIn = this.state.isLoggedIn;
    return(
    	<div>
        	The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> Logged in.
        </div>
    );
}

혹은 

render() {
	const isLoggedIn = this.state.isLoggedIn;
    return (
    	<div>
        	{
            	isLoggedIn
                ? <LogoutButton onClick = {this.handleLogoutClick}/>
                : <LoginButton onClick = {this.handleLoginClick}/>
            }
        </div>
    );
}
    
  • 가끔 다른 컴포넌트에 의해 렌더링 될 때 컴포넌트 자체를 숨기고 싶을 때가 있다
  • 그러면 렌더링 결과를 출력하는 대신 null을 반환한다
  • null을 반환하는 것은 생명주기 메소드 호출에 영향을 주지 않는다
    ex) componentDidUpdate는 계속 호출되게 된다
function WarningBanner(props){

if(!props.warn){
	return null;
}

return (
    <div className = "warning">
    Warning!
    </div>
);

}

class Page extends React.Component{
	constructor(props){
    	super(props);
        this.state = {showWarning : true};
        this.handleToggleClick = this.handleToggleClick.bind(this);
    }
    
    handleToggleClick(){
    	this.setState(state => ({
        showWarning : !state.showWarning
        }));
    }
    
    render(){
    	return(
        	<div>
            	<WarningBanner warn = {this.handleToggleClick}/>
                <button onClick = {this.handleToggleClick}>
                 {this.state.showWarning ? 'Hide' : 'Show'}
                </button>  
            </div>
        );
    }
   
}

ReactDOM.render(
	<Page/>,
    document.getElementById('root');
);

리스트와 KEY

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
// [ 2, 4, 6, 8, 10]
  • map 함수를 이용해 배열 반복 실행이 가능하다
// 1~5 숫자로 이루어진 리스트를 보여준다 
cosnt numbers = [1, 2, 3, 4, 5]
const listItems =  numbers.map((numbers) =>
<li>{numbers}</li>
);

ReactDOM.render(
	<ul>{listItems}</ul>
    document.getElementById('root')
);
  • key는 React가 어떤 항목을 추가, 변경, 삭제할 지 식별하는 것을 도와주며 배열 내부의 엘리먼트에 지정해야한다
  • Key를 선택하는 가장 좋은 방법은 리스트의 다른 항목들 사이에서 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것이다
    ex) 데이터의 ID를 사용하는 경우가 대부분이며, 렌더링 한 항목에 대한 안정적인 ID 가 없다면 최후의 수단으로 index를 사용할 수 있다
  • 항목의 순서가 바뀔 수 있는 경우 key에 인덱스를 사용하면 성능 저하가 일어나거나 컴포넌트 state 관련 문제가 일어날 수 있기 때문에 권장하지 않는다
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((numbers) => 
	<li key = {number.toString()}>
    	{number}
    </li>
);

const todoItems = todos.map((todo) => 
	<li key = {todo.id}>
       {todo.text}
    </li>
);

const todoItems = todos.map((todo, index) => 
<li key = {index}>
	{todo.text}
</li>
);
  • key는 주변 배열의 context에만 의미가 있다
  • ListItem 컴포넌트를 추출한 경우 ListItem 안에 있는 <li>엘리먼트가 아니라 <ListItem/>엘리먼트가 key를 가져야한다
  • map() 함수 안에 있는 엘리먼트에 key를 넣어주는 것이 좋다
function ListItem(props){
	return <li>{props.value}</li>
}

function NumberList(props){
	const numbers= props.numbers;
    const listItems = numbers.map((number) => 
    	// 배열 안에 key를 지정해야함 
        <ListItem key = {number.toString()} value = {number} />
    );
    
    return(
    	<ul>
        {listItems}
        </ul>
    );
    
}

const numbers= [1, 2, 3, 4, 5];
ReactDOM.render(
	<NumberList numbers={numbers}/>,
    document.getElementById('root')
);

  • keysms 형제 사이에서 고유해야하고, 전체 범위에서는 고유할 필요는 없다
  • 두개의 다른 배열을 만들 때 동일한 key를 사용할 수 있다
const content = posts.map((post) =>
    <Post
    	key = {post.id}
        id = {post.id}
        title = {post.title}
    />
);
  • React에서는 key는 힌트를 제공하지만 컴포넌트로 전달하지는 않는다
  • 컴포넌트에서 key와 동일한 값이 필요하면 다른 이름의 prop로 명시적으로 전달한다
  • 위 예제에서는 Post 컴포넌트는 props.id는 읽을 수 있지만, props.key는 읽을 수 없다
  • map의 함수 결과는 인라인으로 처리할 수 있지만, 가독성을 위해 변수로 추출하는 것이 좋을지는 잘 판단해야한다

  • input, textarea, select 등의 폼 엘리먼트는 일반적으로 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트 한다
  • React 컴포넌트는 폼에 발생하는 사용자 입력값을 제어하며 이러한 방식으로 값이 제어되는 입력 폼 엘리먼트를 제어 컴포넌트라고 한다
class NameForm extends React.Component{
	constructor(props){
    	super(props);
        this.state = {value : ' '};
        
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }
    
    handleChange(event){
    	this.setState({value : event.target.value});
    }
    
    handleSubmit(event){
    	alert('A name was submitted :' + this.state.value);
        event.preventDefault();
    }

render(){
	return (
    	<form onSumit = {this.handleSubmit}>
            <label>
            	Name:
                <input type = "text" value = {this.state.value} onChange= {this.handleChange} />
            </label>
            <input type = "submit" value = "submit" />
        </form>
    );
}
}
  • React에서 textarea는 value 어트리뷰트를 대신 사용한다
  • textarea를 사용하는 폼은 한 줄 입력을 사용하는 폼과 비슷하게 작성할 수 있다
class EssayForm extends React.Component{
	constructor(props){
    	super(props);
        this.state = {
        	value : 'Please write an essay about your favorite DOM element'
        };
        
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }
    
    handleChange(event){
    	this.setState({value : event.target.value});
    }
    
    handleSubmit(event){
    	event.preventDefault();
    }
	
    render(){
    	<form onSubmit = {this.handleSubmit}>
        	<label>
            	Essay : 
                <textarea value = {this.state.value} onChange= {this.handleChange} />
            </label>
            <input type = "submit" value ="Submit"/>
        </form>
    }

}
  • selected 대신 select 태그에 value 어트리뷰트를 사용한다
class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('Your favorite flavor is: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Pick your favorite flavor:
          <select value={this.state.value} onChange={this.handleChange}>
            <option value="grapefruit">Grapefruit</option>
            <option value="lime">Lime</option>
            <option value="coconut">Coconut</option>
            <option value="mango">Mango</option>
          </select>
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}
  • select 태그에 멀티플 옵션을 허용한다면 value 어트리뷰트에 배열을 전달할 수 있다
<select multiple={true} value={['B', 'C']}>
  • type을 file로 정하면 file input을 할 수 있다
<input type = "file"/>
  • 다중 입력이 필요하면 name 어트리뷰트를 추가하고 event.target.name 값을 통해 핸들러가 어떤 작업을 할 지 선택할 수 있게 해준다
class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

	//es6의 computed property name 구문 
    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          Is going:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}
  • 제어 컴포넌트에 value prop을 지정하면 의도하지 않는 한 사용자가 변경할 수 없다
  • value를 설정했는데 여전히 수정할 수 있다면 value를 undefined나 null로 설정했을수도 있다

State 끌어올리기

  • 종종 동일한 데이터에 대한 변경사항을 여러 컴포넌트에 반영해야 할 필요가 있을 수 있다
  • 이럴 땐 가장 가까운 공통 조상으로 state를 끌어올리는 것이 좋다
  • 두 입력 필드의 값이 동일한 state로부터 계산되기 때문에 이 둘은 항상 동기화 된다
const scaleNames = {
  c: 'Celsius',
  f: 'Fahrenheit'
};

function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}

function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}

function tryConvert(temperature, convert) {
  const input = parseFloat(temperature);
  if (Number.isNaN(input)) {
    return '';
  }
  const output = convert(input);
  const rounded = Math.round(output * 1000) / 1000;
  return rounded.toString();
}

function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>The water would boil.</p>;
  }
  return <p>The water would not boil.</p>;
}

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(e) {
    this.props.onTemperatureChange(e.target.value);
  }

  render() {
    const temperature = this.props.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
    this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
    this.state = {temperature: '', scale: 'c'};
  }

  handleCelsiusChange(temperature) {
    this.setState({scale: 'c', temperature});
  }

  handleFahrenheitChange(temperature) {
    this.setState({scale: 'f', temperature});
  }

  render() {
    const scale = this.state.scale;
    const temperature = this.state.temperature;
    const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
    const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

    return (
      <div>
        <TemperatureInput
          scale="c"
          temperature={celsius}
          onTemperatureChange={this.handleCelsiusChange} />
        <TemperatureInput
          scale="f"
          temperature={fahrenheit}
          onTemperatureChange={this.handleFahrenheitChange} />
        <BoilingVerdict
          celsius={parseFloat(celsius)} />
      </div>
    );
  }
}

ReactDOM.render(
  <Calculator />,
  document.getElementById('root')
);
  • 다른 입력 필드의 값은 항상 그 값들에 기반해서 render() 메서드 안에서 계산될 수 있다
  • 이를 통해 사용자 입력값을 정밀도를 유지한 채 다른 필드의 입력값에 반올림을 지우거나 적용할 수 있다
  • UI에서 무언가 잘못된 부분이 있을 경우 React Developer Tools를 이용해 props를 검사하고 state를 갱신할 책임이 있는 컴포넌트를 찾을 때까지 트리를 따라 탐색하며 버그를 추적할 수 있다

합성(Composition) vs 상속(Inheritance)

  • React에서는 상속 대신 합성을 이용해 컴포넌트간 간에 코드를 재사용 하는 것이 좋다
  • 어떤 컴포넌트 들은 어떤 자식 엘리먼트가 들어올 지 미리 예상할 수 없는 경우가 있다 이럴 땐 children prop을 사용해 자식 엘리먼트를 출력에 그대로 전달하는 것이 좋다
function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}
  • 컴포넌트에 여러 개의 구멍이 필요할 수도 있는데 이런 경우에는 children 대신 자신만의 고유한 방식을 적용할 수 있다
  • React Element는 객체이기 때문에 다른 데이터처럼 prop으로 전달할 수 있다
function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}
  • 특수한 경우인 컴포넌트를 고려해야 할 경우 합성을 통해 할 수 있따
  • 더 구체적인 컴포넌트가 일반적인 컴포넌트를 렌더링 하고 props를 통해 내용을 구성한다
  • 합성은 클래스로 정의된 컴포넌트에서도 동일하게 적용된다
function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
      {props.children}
    </FancyBorder>
  );
}

class SignUpDialog extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.handleSignUp = this.handleSignUp.bind(this);
    this.state = {login: ''};
  }

  render() {
    return (
      <Dialog title="Mars Exploration Program"
              message="How should we refer to you?">
        <input value={this.state.login}
               onChange={this.handleChange} />
        <button onClick={this.handleSignUp}>
          Sign Me Up!
        </button>
      </Dialog>
    );
  }

  handleChange(e) {
    this.setState({login: e.target.value});
  }

  handleSignUp() {
    alert(`Welcome aboard, ${this.state.login}!`);
  }
}

React적 사고

  1. 목업으로 시작하기
  • UI를 컴포넌트 계층 구조로 나누기(단일 책임 원칙)
  1. React로 정적인 버전 만들기
  • state는 오로지 상호작용을 위해, 시간이 지남에 따라 데이터가 바뀌는 것에 사용한다
  • 상향식 혹은 하향식으로 만들 수 있다
  1. UI state에 대한 최소한의(하지만 완전한) 표현 찾아내기
  • 중복 배제의 원칙으로 가장 최소한의 state를 찾고 나머지는 필요에 따라 그때그때 계산되도록 만든다

  • state ? props ?

1. 부모로부터 props를 통해 전달됩니까? 그러면 확실히 state가 아닙니다.
2. 시간이 지나도 변하지 않나요? 그러면 확실히 state가 아닙니다.
3. 컴포넌트 안의 다른 state나 props를 가지고 계산 가능한가요? 그렇다면 state가 아닙니다.
  1. State가 어디에 있어야 할 지 찾기
  • 어떤 컴포넌트가 state를 변경하거나 소유할 지를 찾아야한다
  • React는 단방향 데이터 흐름을 따른다
  • 공통, 상위에 있는 컴포넌트가 state를 가져야한다
  • 만일 적절한 컴포넌트가 없으면 state 소유 컴포넌트를 하나 만들어서 공통 오너 컴포넌트의 상위 계층에 추가한다
  1. 역방향 데이터 흐름 추가하기
  • 하단 컴포넌트에서 상단 컴포넌트의 state를 업데이트 할 수 있어야 한다
  • 상단 -> 하단으로 콜백을 넘겨서 state가 업데이트되어야 할 때마다 호출되도록 할 것이다
  • 콜백은 setState()를 호출하고 앱이 업데이트 될 것이다
profile
나는야 누워있는 개발머신

0개의 댓글