JSX
정의
- javascript 를 확장한 문법
- React element를 생성
- 컴파일이 끝나면 JSX표현식이 정규 javascript 객체(React element)로 인식됨
표현식
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;
속성 정의
- 속성에 문자열 정의
const element = link ;
- 속성에 javascript 표현식 삽입
const element = <img src={user.avatarUrl}></img>;
!! JSX는 javascript 에 가깝기 때문에 속성(ex-class)을 프로퍼티(ex-className)처럼 camelCase로 정의
주입 공격 방지
- JSX에 삽입된 모든 값을 렌더링 하기전에 escape 처리함
- XSS 공격 방지 가능
Element rendering
- 브라우저 DOM(document 객체) element와 달리 일반 객체임
- 일반적으로 하나의 root DOM 노드가 존재
DOM에 React element rendering
- DOM element를 ReactDom.createRoot() 에 전달
const root = ReactDOM.createRoot(
document.getElementById('root')
);
2. React element를 root.render() 에 전달
const element = <h1>Hello, world</h1>;
root.render(element);
Rendering 된 element update
- React element는 불변객체임
- 생성된 이후에는 자식이나 속성을 변경할 수 없음
- 특정 시점의 UI 를 보여줌
- UI 를 업데이트 하는 유일한 방법은 새로운 element를 생성하고 이를 root.render() 로 전달하는것
변경된 부분만 update
- React DOM 은 해당 element와 그 자식을 이전의 element와 비교하고 필요한 경우에만 DOM을 업데이트함
Components & Props
- component는 javascript 함수와 유사
- props라고 하는 param을 받은 후 화면에 어떻게 표시되는지를 React element 로 반환함
functional component & class component
- functional
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
- class
- es6 class를 사용
- 몇가지 추가 기능이 있음
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
component rendering
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const root = ReactDOM.createRoot(document.getElementById('root'));
const element = <Welcome name="Sara" />;
root.render(element);
- 사용자 정의 component 사용
- 사용자 정의 compoenent로 작성된 React element dml JSX 속성과 자식을 해당 component에 단일 객체(props)로 전달
- props의 이름은 사용될 context가 아닌 component 자체의 관점에서 짓는것을 권장
! component 이름은 항상 대문자로 시작
props
- props를 수정해서는 안됨
- 모든 React component는 자신의 props를 다룰때 반드시 순수 함수처럼 동작해야 함
- 순수 함수?
- input을 바꾸지 않고 동일한 input에 대해 동일한 output을 반환 하는 함수
State & Lifecycle
- state는 props와 유사하지만 비공개이며 component에 의해 완전이 제어됨
class component
- render method는 업데이트가 발생할때마다 호출되지만, 같은 DOM 노드로 rendering 하는 경우 class 의 단일 instance만을 사용하게 됨
- → state와 생명주기 method같은 부가적 기능을 사용할 수 있게 해줌
state
- state를 지정할 수 있는 곳은 constructor가 유일함
- setState() 를 사용하여 값을 재할당
state업데이트는 비동기적일 수 있음
- React 는 성능을 위해 여러 setState() 호출을 단일 업데이트로 한꺼번에 처리할 수 있음
- props, state가 비동기적으로 업데이트 될 수 있기 때문에 값에 의존해선 안됨
- props, state가 참조되는 값의 경우 객체 보다는 함수를 인자로 사용하는 형태로 사용
- state는 update 이전의 상태
- props는 업데이트가 적용된 시점의 props
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
state update는 병합됨
- state는 다양한 독립변수를 포함할 수 있음
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
componentDidMount() {
fetchPosts().then(response => {
// this.state.comment에 영향을 주지 않고 this.state.posts만 완전히 대체됨
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
Event
- JSX를 사용하기 때문에 이벤트 속성명은 camelCase를 사용
- 문자열이 아닌 이벤트 핸들러 함수를 표현식으로 전달
- React event 객체는 합성 event로 브라우저 호환 이슈가 없음
- React event는 브라우저 고유 이벤트와 동일하게 동작하지 않을 수 있음
<button onClick={activateLasers}>
Activate Lasers
</button>
return false로 기본 동작을 방지할 수 없고 preventDefault를 통해 명시적으로 호출
function Form() {
function handleSubmit(e) {
// 명시적으로 호출
e.preventDefault();
console.log('You clicked submit.');
}
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
JSX callback의 this
- class method는 기본적으로 binding 되어있지않아 constructor에서 bind해주지 않으면 this는 undefined
- class method 내부에서 instance scope에서의 this를 사용할때를 위함
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>
);
}
}
class LoggingButton extends React.Component {
// 이 문법은 `this`가 handleClick 내에서 바인딩되도록 합니다.
handleClick = () => {
console.log('this is:', this);
};
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// 이 문법은 `this`가 handleClick 내에서 바인딩되도록 합니다.
return (
<button onClick={() => this.handleClick()}>
Click me
</button>
);
}
}
이벤트 핸들러에 인자 전달
//화살표 함수
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
//Function.prototype.bind
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
- 두 경우 모두 이벤트 객체가 전달됨
- 화살표 함수는 명시적으로 전달해야함
Context
- 컴포넌트 트리 안에서 global 하게 공유될 수 있도록 고안된 방법
- 다양한 레벨에 nesting 된 많은 component에 데이터를 전달 하는것
- ex) - 현재 로그인한 유저, 테마, 선호하는 언어 등
컴포넌트를 재사용하기 어려워질 수 있음
예시
// context를 사용하면 모든 컴포넌트를 일일이 통하지 않고도
// 원하는 값을 컴포넌트 트리 깊숙한 곳까지 보낼 수 있습니다.
// light를 기본값으로 하는 테마 context를 만들어 봅시다.
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// Provider를 이용해 하위 트리에 테마 값을 보내줍니다.
// 아무리 깊숙히 있어도, 모든 컴포넌트가 이 값을 읽을 수 있습니다.
// 아래 예시에서는 dark를 현재 선택된 테마 값으로 보내고 있습니다.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 이젠 중간에 있는 컴포넌트가 일일이 테마를 넘겨줄 필요가 없습니다.
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 현재 선택된 테마 값을 읽기 위해 contextType을 지정합니다.
// React는 가장 가까이 있는 테마 Provider를 찾아 그 값을 사용할 것입니다.
// 이 예시에서 현재 선택된 테마는 dark입니다.
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
context를 사용하기 전에 고려할것
- 컴포넌트 재사용이 어려워질 수 있으므로 컴포넌트 합성으로 대체 가능한지 확인 후 사용
예시
before
<Page user={user} avatarSize={avatarSize} />
// ... 그 아래에 ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... 그 아래에 ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... 그 아래에 ...
<Link href={user.permalink}>
<Avatar user={user} size={avatarSize} />
</Link>
after
function Page(props) {
const user = props.user;
const userLink = (
<Link href={user.permalink}>
<Avatar user={user} size={props.avatarSize} />
</Link>
);
return <PageLayout userLink={userLink} />;
}
// 이제 이렇게 쓸 수 있습니다.
<Page user={user} avatarSize={avatarSize} />
// ... 그 아래에 ...
<PageLayout userLink={...} />
// ... 그 아래에 ...
<NavigationBar userLink={...} />
// ... 그 아래에 ...
{props.userLink}
- 최상단에서 component 자체를 props로 전달하여 사용
provider
- 다른 provider를 하위에 배치하는것도 가능하며, 이 경우 하위 provider의 값이 우선됨
- provider하위에서 context를 구독하는 모든 컴포넌트는 provider의 value prop이 바뀔때마다 다시 렌더링 됨
<MyContext.Provider value={/* 어떤 값 */}>
class.contextType
- context객체를 원하는 class의 contextType 프로퍼티로 지정 가능
- this.context로 render를 포함한 모든 컴포넌트 생명주기 메서드에서 사용 가능
!! 이 API 를 사용하면 하나의 context만 구독 가능
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* MyContext의 값을 이용한 코드 */
}
render() {
let value = this.context;
/* ... */
}
}
MyClass.contextType = MyContext;
Consumer
- context 변화를 구독하는 react 컴포넌트
- 함수 컴포넌트 안에서 context 구독 가능
- consumer 컴포넌트의 자식은 함수여야함
// 기본값이 light인 ThemeContext
const ThemeContext = React.createContext('light');
// 로그인한 유저 정보를 담는 UserContext
const UserContext = React.createContext({
name: 'Guest',
});
class App extends React.Component {
render() {
const {signedInUser, theme} = this.props;
// context 초기값을 제공하는 App 컴포넌트
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}
function Layout() {
return (
<div>
<Sidebar />
<Content />
</div>
);
}
// 여러 context의 값을 받는 컴포넌트
function Content() {
return (
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<ProfilePage user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
displayName
- 개발자도구에서 보여길 context 별명을 문자열로 설정
const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';
<MyContext.Provider> // "MyDisplayName.Provider" in DevTools
<MyContext.Consumer> // "MyDisplayName.Consumer" in DevTools
Ref, DOM
만들어진 이유
- props는 부모와 자식 컴포넌트 사이 유일한 상호작용 수단임
- 자식을 수정하려면 새로운 props를 전달하여 자식을 다시 rendering 해야함
- 일반적인 데이터 flow에서 벗어나 직접 자식을 수정하는경우
- React 컴포넌트 나 DOM 엘리먼트를 직접 수정할때 사용할 수 있는 방법
사용하는 경우
- focus, input, 혹은 미디어의 재생 관리
- 애니메이션을 직접 실행시
- 서드파티 DOM 라이브러리를 React와 같이 사용할때
!! Ref를 남용해선 안됨
Ref 생성
- createRef()를 통해 생성
- JSX 내에서 ref attribute를 통해 element에 부착
- 어느곳에서도 ref접근가능
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
Ref 접근
- ref 어트리뷰트가 HTML 엘리먼트에 쓰였다면, 생성자에서 React.createRef()로 생성된 ref는 자신을 전달받은 DOM 엘리먼트를 current 프로퍼티의 값으로서 받습니다.
- ref 어트리뷰트가 커스텀 클래스 컴포넌트에 쓰였다면, ref 객체는 마운트된 컴포넌트의 인스턴스를 current 프로퍼티의 값으로서 받습니다.
- 함수 컴포넌트는 인스턴스가 없기 때문에 함수 컴포넌트에 ref 어트리뷰트를 사용할 수 없습니다.
- 다만, 함수 컴포넌트 내에서 다른 클래스 컴포넌트나 DOM element에 ref 어트리뷰트를 사용하는것은 가능
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// textInput DOM 엘리먼트를 저장하기 위한 ref를 생성합니다.
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// DOM API를 사용하여 명시적으로 text 타입의 input 엘리먼트를 포커스합니다.
// 주의: 우리는 지금 DOM 노드를 얻기 위해 "current" 프로퍼티에 접근하고 있습니다.
this.textInput.current.focus();
}
render() {
// React에게 우리가 text 타입의 input 엘리먼트를
// 우리가 생성자에서 생성한 `textInput` ref와 연결하고 싶다고 이야기합니다.
return (
<div>
<input
type="text"
ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
콜백 ref
- ref 설정과 해제를 세세하게 제어할 수 있는 방법
- 다른 컴포넌트에 props로 전달가능
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
this.setTextInputRef = element => {
this.textInput = element;
};
this.focusTextInput = () => {
// DOM API를 사용하여 text 타입의 input 엘리먼트를 포커스합니다.
if (this.textInput) this.textInput.focus();
};
}
componentDidMount() {
// 마운트 되었을 때 자동으로 text 타입의 input 엘리먼트를 포커스합니다.
this.focusTextInput();
}
render() {
// text 타입의 input 엘리먼트의 참조를 인스턴스의 프로퍼티
// (예를 들어`this.textInput`)에 저장하기 위해 `ref` 콜백을 사용합니다.
return (
<div>
<input
type="text"
ref={this.setTextInputRef}
/>
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
참고자료
'Front-end > React' 카테고리의 다른 글
Styled-component (0) | 2022.12.05 |
---|---|
React Query (0) | 2022.11.28 |
Redux (0) | 2022.11.23 |
Hook (0) | 2022.11.23 |
Next.js (0) | 2022.11.23 |