[React] Hello World 안내서 정리 (2/2)

전체내용 : Hello World - React

5. 이벤트 처리

  • React의 이벤트는 camelCase 사용
  • JSX를 사용, 함수로 이벤트 핸들러 전달
  • 기본동작을 방지하기 위해 preventDefault를 명시적으로 호출해야 함

    function ActionLink() {
    	function handleClick(e) { // e: 합성이벤트
    		e.preventDefault();
    		consol.log('The Link was clicked.');
    	}
    	return (
    		<a href="#" onClick={handleClick}> 
    			Click me
    		</a>
    	);
    }
  • 엘리먼트가 처음 렌더링될 때 리스너 제공하기

1) 이벤트 핸들러

  • ES6 클래스를 사용할 때, 이벤트 핸들러를 클래스의 메서드로 만든다. 그리고 콜백에서 this가 작동되기 위해 constructor에서 바인딩 해준다.
class Toggle extends React.Component {
	constructor(props) {
		super(props);
		this.state = {isToggleOn: true};

		// 콜백에서 'this'가 작동하려면 아래와 같이 바인딩 해주어야 함
		this.handleclick = this.handleClick.bind(this);
	}

	handleClick() {
		this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);
  • bind() : 새로운 바인딩한 함수를 만듦 - 원본 함수 객체를 감싸는 함수
  • 일반적으로 onClick={this.handleClick}과 같이 메서드 뒤에 ()를 사용하지 않고 참조하는 경우, 해당 메서드를 바인딩 해야 된다. (바인딩 하지 않으면 함수가 호출될 때 this는 undefined가 됨)

(1) 다른방법1 <퍼블릭 필드 문법 (실험적인 방법)>

Create React App의에 기본 설정된 문법

class LoggingButton extends React.Component {
	// 'this'가 handleClick 내에서 바인딩되는 방법
	handleClick = () => {
		console.log('this is:', tis);
	}

	render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}



(2) 다른방법 2 <콜백에 화살표 함수 사용>

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // 이 문법은 `this`가 handleClick 내에서 바인딩되도록 합니다.
		// LoggingButton이 렌더링 될 때마다 다른 콜백이 생기는 문제가 있다.
    return (
      <button onClick={() => this.handleClick()}>
        Click me
      </button>
    );
  }
}



2) 이벤트 핸들러에 인자 전달

// id가 행의 ID일 경우의 예시
// 다음 두 줄은 동등하다.
<button onClick={(e) => this.deleteRow(id, e)}>delete Row</button>
<button onclick={this.deleteRow(id,e)}>Delete Row</button>
  • React 이벤트를 나타내는 e인자가 id 다음 두 번째 인자로 전달된다.
  • 화살표 함수는 명시적으로 인자를 전달해야 되지만, bind를 사용할 경우 추가 인자가 자동으로 전달된다.



6. 조건부 렌더링

  • if 나 조건부 연산자를 이용해서 현재 상태에 맞게 UI를 업데이트 할 수 있다.

    // isLoggedIn값에 따라 return이 다른 Greeting 이라는 함수가 있을 때
    ReactDOM.render(
    	<Greeting isLoggedIn={false} />,
    	document.getElementById('root')
    );
  • 삼항연산자 가능 ~

    <div> User is {isLoggedIn ? 'Online' : 'Offline'} </div>
  • 컴포넌트가 렌더링하는 것 막기

    • 렌더링 결과 출력 대신 null 반환으로 해결!



7. 리스트와 Key

1) 배열을 엘리먼트 리스트로 만들기

const numbers = [1,2,3,4,5];
const listItems = numbers.map((number) => <li>{number}</li>);

map() 함수를 이용해서 배열을 반복 실행. 각 항목에 대해 <li>엘리먼트를 반환.
엘리먼트 배열의 결과를 listItems에 저장

2) Key

  • 엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 함
  • 고유하게 식별할 수 있는 문자열 (String)
// example
<li key={number.toString()}> {number} </li>
<li key={todo.id}> {todo.text} </li>
  • JSX에 map()을 포함시킬 순 있지만, 남발하는건 좋지 않다. 가독성을 위해 변수로 추출할 지 인라인으로 넣을 지는 개발자가 직접 판단해야 한다.



8. 폼 (form)

HTML 폼 엘리먼트는 React의 다른 DOM 엘리먼트와 조금 다르게 동작한다.

1) 제어 컴포넌트(Controlled Component)

state를 신뢰 가능한 단일 출처로 만들어서 두 요소를 결합할 수 있다. 이를 통해 폼을 렌더링하는 React 컴포넌트는 폼에 발생하는 사용자 입력값을 제어한다. 이러한 방식으로 React에 의해 값이 제어되는 입력 폼 엘리먼트를 제어 컴포넌트라고 한다.
제어 컴포넌트로 사용하면, input의 값은 항상 React state에 의해 결정된다.

<form onSubmit={this.handleSubmit}>
  <label>
    Name:
    <input type="text" value={this.state.value} onChange={this.handleChange} />
  </label>
  <input type="submit" value="Submit" />
</form>

value 어트리뷰트는 폼 엘리먼트에 설정되므로 표시되는 값은 항상 this.state.value가 되고, React state는 '신뢰 가능한 단일 출처'가 된다.

제어 컴포넌트에 value prop을 지정하면 의도하지 않은 한 사용자가 변경할 수 없다.
제어 컴포넌트의 대안으로는 비제어 컴포넌트가 있다.


2) 태그

(1) textarea 태그

form이 일부 텍스트를 가진채 시작됨!

<form onSubmit={this.handleSubmit}>
  <label>
    Essay:
    <textarea value={this.state.value} onChange={this.handleChange} />
  </label>
  <input type="submit" value="Submit" />
</form>



(2) select 태그

드롭 다운 목록을 만든다!

<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 태그에 multiple옵션을 허용하면, value 어트리뷰트에 배열을 전달할 수 있다.
<select multiple={true} value={['B', 'C']}>



(3) file input 태그

사용자가 하나 이상의 파일을 서버로 업로드하거나 file API를 통해 JavaScrript로 조작할 수 있다.
값이 읽기 전용이기 때문에 React에서는 비제어 컴포넌트이다.



2) 다중 입력 제어하기

여러 input 엘리먼트를 제어해야할 때, 각 엘리먼트에 name어트리뷰트를 추가하고, event.target.name 값을 통해 핸들러가 어떤 작업을 할 지 선택하게 한다.

constructor(props) {
	super(props);
	this.state = {
		number: 3,
		isGoing: true
	};
}

handleInputChange(event) {
	const target = event.target;
	const name = target.name;
	this.setState({
		[name]: value  // ES6 computed property name 구문
	});
}

render() {
	return (
		<form>
			<label>
			<input name="isGoing" />
			</label>
			<label>
				<input name="number" />
			</label>
		</form>
	);
}



9. State 끌어올리기

동일한 데이터에 대한 변경사항을 여러 컴포넌트에 반영해야 할 때 가장 가까운 공통 조상으로 state를 끌어올린다.

1) 진리의 원천 (Source of Truth)

class TemperatureInput extends React.Component {
	constructor(props) {}
	handleChange(e) {
		this.props.onTemperatureChange(e.target.value);
	}
	render() {
		// 화면에 렌더링되는 내용
	}
} 

class Calculator extends React.Component {
	constructor(props) {
		// 생략 (super, bind)
		this.state = {temperature: '', scale: 'c'};  // 공유될 state
	}

	handleCelsiusChange(temperature) {
		this.setState({scale: 'c', temperature});
	}
	handleFahrenheitChange(temperature) {} // 내용생략

	render() {
		// 변수 할당

		// 아래와 같이 state와 제어메서드를 props로 전달한다.
		return (
			<div>
				<TemperatureInput
					scale="c"
					temperature={celsius}
					onTemperatureChange={this.handleCelsiusChange} />
				<TemperatureInput
          scale="f"
          temperature={fahrenheit}
          onTemperatureChange={this.handleFahrenheitChange} />
        <BoilingVerdict
          celsius={parseFloat(celsius)} />
      </div>
    );
  }
}

TemperatureInput(값을 필요로 하는 컴포넌트)이 개별적으로 가지고 있던 지역 state를 지우고, Calculator(가장 가까운 공통 조상)로 state를 끌어올린다. ⇒ state 끌어올리기

Claculator공유될 state를 소유하고 있으면 이 컴포넌트는 두 입력 필드(TemperatureInput)의 현재 온도에 대한 "진리의 원천"이 된다.

위 예시(코드)에서 두 입력 필드의 값이 동일한 state로부터 계산되기 때문에 이 둘은 항상 동기화된 상태를 유지한다.

props는 읽기 전용이므로, 공통 조상 컴포넌트에서 state를 제어하는 메서드(onTemperatureChange)를 props로 전달한다. ⇒ 부모 컴포넌트는 state와 state제어 메서드를 함께 제공

2) 교훈 (주의사항)

React 애플리케이션 안에서 변경이 일어나는 데이터에 대해서는 진리의 원천을 하나만 두어야 한다.



10. 합성 vs 상속

1) 합성(Composition)

  • 컴포넌트에서 다른 컴포넌트 담기
  • 특수화 : 더 구체적인 컴포넌트가 일반적인 컴포넌트를 렌더링하고 props를 통해 내용을 구성함
  • 클래스로 정의된 컴포넌트에서도 동일하게 적용됨
  • 컴포넌트는 원시 타입의 값, React 엘리먼트 혹은 함수 등 어떠한 props도 받을 수 있다.

2) 상속(Inheritance)

컴포넌트를 상속 계층 구조로 작성을 권장할만한 사례는 아직 찾지 못한 듯 하다.
React는 상속 대신 합성을 사용하여 컴포넌트 간에 코드를 재사용하는 것이 좋다.

UI가 아닌 기능을 여러 컴포넌트에서 재사용하려면 별도의 JavaScript 모듈로 분리하는 것이 좋다.
(상속할 필요 없이 컴포넌트에서 해당 함수, 객체 클래스 등을 import해서 사용)



11. React로 사고하기 (단계별 과정)

디자이너로부터 JSON API와 목업을 받았다고 가정
[기억] React는 항상 컴포넌트 계층구조를 따라 아래로 내려가는 단방향 데이터 흐름을 따른다.

(1) UI를 컴포넌트 계층 구조로 나누기

(2) React로 정적인 버전 만들기

앱을 실제로 구현하기. 데이터 모델을 가지고 UI를 렌더링은 되지만 아무 동작도 없는 버전 만들어보기! 정적 버전을 만드는 것은 생각은 적게 필요하지만 타이핑은 많이 필요로 하기 때문이다. 이때, state는 사용하지 말기!!

(3) UI state에 대한 최소한의 (하지만 완전한) 표현 찾아내기

애플리케이션에서 필요로 하는 변경 가능한 state의 최소 집합을 생각해야한다. 여기서 핵심은 중복배제원칙.
ex) TODO리스트를 만든다면, TODO아이템을 저장하는 배열만 유지하고, 아이템의 개수를 표현하는 state는 별도로 만들지 말자. 아이템의 개수를 렌더링해야 한다면 TODO아이템 배열의 길이를 가져오면 되니까

[state를 결정하기 위한 질문]

  1. 부모로부터 props를 통해 전달됩니까? 그러면 확실히 state가 아닙니다.
  2. 시간이 지나도 변하지 않나요? 그러면 확실히 state가 아닙니다.
  3. 컴포넌트 안의 다른 state나 props를 가지고 계산 가능한가요? 그렇다면 state가 아닙니다.

(4) state가 어디에 있어야 할 지 찾기

[state를 소유할 컴포넌트를 결정하기 위한 과정]
애플리케이션이 가지는 각각의 state에 대해서

  • state를 기반으로 렌더링하는 모든 컴포넌트를 찾으세요.
  • 공통 소유 컴포넌트 (common owner component)를 찾으세요. (계층 구조 내에서 특정 state가 있어야 하는 모든 컴포넌트들의 상위에 있는 하나의 컴포넌트).
  • 공통 혹은 더 상위에 있는 컴포넌트가 state를 가져야 합니다.
  • state를 소유할 적절한 컴포넌트를 찾지 못하였다면, state를 소유하는 컴포넌트를 하나 만들어서 공통 오너 컴포넌트의 상위 계층에 추가하세요.

(5) 역방향 데이터 흐름 추가하기

상위 클래스(변경할 state를 가진) 하위 클래스에 콜백을 넘겨서 state가 업데이트되어야 할 때마다 호출되도록 한다. 이벤트를 사용해서 알림 받기

© 2020 euzl. from JunhoBaik's, Built with Gatsby