본문 바로가기

Front-end/React

React core

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

  1. 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>
    );
  }
}

 

 

참고자료

https://ko.reactjs.org/docs/introducing-jsx.html

'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