React 개발자가 실수하기 쉬운 몇 가지 (2)

제가 개발 중인 JavaScript 정적 분석 도구 DeepScan은 JavaScript의 일반적인 오류 외에도 최근 핫한 React를 잘 지원하려는 목표를 갖고 있습니다.

ESLint나 주변 React 개발자들의 피드백을 통해 십여 종의 React 검증 규칙을 개발해 왔는데, 이 중에서 React를 처음 배우는 개발자들이 실수하기 쉬운 내용을 추려 시리즈로 연재합니다.

지난 글에서 오픈소스 wp-calypso의 사례로 다음과 같은 React의 실수 패턴을 알아봤습니다.

  • React API의 오타
  • render 함수에서 잘못된 값을 반환
  • 이벤트 핸들러 함수를 잘못 지정한 경우
  • DOM element에서 잘못된 속성 지정

오늘은 다른 오픈소스의 사례로 더 다양한 실수 패턴을 알아보도록 하겠습니다.

render 함수의 반환 값을 직접 사용

ReactDOM.render 함수에서 반환하는 ReactComponent 인스턴스를 사용하는 경우가 있습니다.

이 방법은 권장되지 않는데, 그 이유는 향후 React 버전에서 렌더링이 비동기적으로 일어날 수 있고 그 결과 반환 값을 즉시 사용하는 것으로는 ReactComponent 인스턴스를 얻을 수가 없기 때문입니다.

아래 오픈소스 react-starter-kit에서 render 함수의 반환 값을 그대로 사용하는데 이 값이 undefined가 되어 원하는 초기화가 수행되지 않을 수 있습니다.

import ReactDOM from 'react-dom';

let appInstance;

async function onLocationChange(location, action) {
    appInstance = ReactDOM.render(
      <App context={context}>{route.component}</App>,
      container,
      () => onRenderComplete(route, location),
    );
}

if (appInstance) {
  // Force-update the whole tree, including components that refuse to update
  deepForceUpdate(appInstance);
}

src/client.js

이 경우는 ref 콜백 함수를 통해 인스턴스를 얻어야 합니다.

function cb(instance) {
}

async function onLocationChange(location, action) {
    ReactDOM.render(
      <App context={context} ref={cb}>{route.component}</App>,
      container,
      () => onRenderComplete(route, location),
    );
}

DeepScan Rule

DeepScan의 ASYNC_RENDER_RETURN_VALUE 규칙은 render 함수의 반환 값을 직접 사용하는 경우를 찾아 개발자의 실수를 방지할 수 있습니다.

Consider not using 'ReactDOM.render()' result because future versions of React may render components asynchronously.
appInstance = ReactDOM.render(

이벤트 핸들러 함수에서 이벤트 전파가 중단되지 않는 경우

HTML에서는 이벤트 핸들러 함수에서 false를 반환하면 이벤트 전파 및 기본 동작이 중단됩니다.

하지만 React는 별도의 이벤트 시스템을 사용하고, 이벤트 전파나 기본 동작을 중단하려면 인자로 받은 React 이벤트 객체의 stopPropagation()이나 preventDefault()를 명시적으로 호출해야 합니다.

아래 오픈소스 browser-laptop의 예를 보면 onMaximizeClick 이벤트 핸들러에서 return false 같이 기존 방식대로 사용하고 있어 이벤트 전파가 중단되지 않습니다.

const React = require('react')

class WindowCaptionButtons extends ImmutableComponent {
  onMaximizeClick (e) {
    if (isFullScreen()) {
      // If full screen, toggle full screen status and restore window (make smaller)
      windowActions.shouldExitFullScreen(getCurrentWindowId())
      if (isMaximized()) windowActions.shouldUnmaximize(getCurrentWindowId())
      return false
    }
    return (!isMaximized()) ? windowActions.shouldMaximize(getCurrentWindowId()) : windowActions.shouldUnmaximize(getCurrentWindowId())
  }

  render () {
    const props = { tabIndex: -1 }

    return <div>
      <div className='container'>
        <button
          {...props}
          onClick={this.onMaximizeClick}
          title={locale.translation(this.maximizeTitle)}>
        </button>
      </div>
    </div>
  }
}

navigation/windowCaptionButtons.js

DeepScan Rule

DeepScan의 BAD_EVENT_HANDLER_RETURN_FALSE 규칙은 이벤트 핸들러로 사용되는 함수에서 false를 반환하는 경우를 찾아 개발자의 실수를 방지할 수 있습니다.

In React, returning false from an event handler has no effect. Consider using 'preventDefault()' or 'stopPropagation()' of the event object.
return false

JSX에서 JavaScript 주석을 사용하는 경우

JSX에서 ///* */ 같은 JavaScript 주석을 사용할 경우 주의가 필요합니다.

주석이 JSX의 컴포넌트 하위 텍스트 노드로 인식되면 주석 내용이 브라우저 화면에 보이기 때문인데, 주석은 {/* */} 같이 중괄호로 감싸야 합니다.

아래 오픈소스 belle에서는 onUpdate should not be called for valueLink라는 주석이 그대로 화면에 노출되는 문제가 있습니다.

import React from 'react';

export default React.createClass({
  render() {
    return (
      <div>
        <h2>Rating</h2>

        <Card>

          <Button onClick={ this._updateRatingToThree }>Update Rating to value 3</Button>

          //onUpdate should not be called for valueLink
          <h3>ValueLink</h3>

        </Card>

      </div>
    );
  }
});

components/RatingPlayground.js

이 경우는 다음과 같이 써야 합니다.

{/* onUpdate should not be called for valueLink */}
<h3>ValueLink</h3>

반대로 <h3>// is a double slash.</h3> 같이 실제로 “//”를 써야 하는 경우 <h3>{"// is a double slash."}</h3>처럼 주석이 아닌 문자열임이 드러나도록 명확하게 쓰는 것이 좋은 습관으로 알려져 있습니다.

DeepScan Rule

DeepScan의 BAD_JSX_COMMENT 규칙은 JSX 내에서 JavaScript 주석을 사용하는 경우를 찾아 개발자의 실수를 방지할 수 있습니다.

This text is recognized as a JSX child instead of a comment. Consider using JavaScript comment inside braces.
//onUpdate should not be called for valueLink

length 속성 검사

React에서 자식 엘리먼트(child element)를 undefined, null, true 또는 false로 지정한 경우 렌더링에서 제외됩니다. 따라서 선택적인 렌더링을 할 때 cond && <div>...</div> 같은 JSX 구문을 많이 사용합니다.

그런데 숫자 값 0에 대해선 이렇게 적용되지 않고 0이 그대로 화면에 출력됩니다. 즉 array.length && <div>...</div>에서 array가 비어 있다면 0이 출력되므로, 이 경우 length 속성을 직접 검사하는 대신 다음과 같이 사용해야 합니다.

  • array.length > 0 같은 비교 구문으로 사용
  • array.length && <div>...</div> || '' 같이 끝에 빈 문자열을 OR 조건으로 추가

아래 오픈소스 react-native-macoslength 속성을 직접 검사하기 때문에 this.props.params가 비어 있다면 0이 출력됩니다.

var React = require('React');

var Method = React.createClass({
  render: function() {
    return (
      <div className="prop">
        <Header level={4} className="methodTitle" toSlug={this.props.name}>
          {this.props.modifiers && this.props.modifiers.length && <span className="methodType">
            {this.props.modifiers.join(' ') + ' '}
          </span> || ''}
          {this.props.name}
          <span className="methodType">
            ({this.props.params && this.props.params.length && this.props.params
              .map((param) => {
                var res = param.name;
                res += param.optional ? '?' : '';
                return res;
              })
              .join(', ')})
              {this.props.returns && ': ' + this.renderTypehint(this.props.returns.type)}
          </span>
        </Header>
      </div>
    );
  }
});

layout/AutodocsLayout.js

커밋을 보면 위에서 얘기한 것 같이 || '' 조건을 붙여 수정한 것을 볼 수 있습니다.

DeepScan Rule

DeepScan의 BAD_LENGTH_CHECK 규칙은 length 속성을 직접 검사하는 경우를 찾아 개발자의 실수를 방지할 수 있습니다.

Numeric value 0 can be rendered because 'this.props.params.length' itself is checked. Consider checking 'this.props.params.length > 0' instead.
({this.props.params && this.props.params.length && this.props.params

Wrap-Up

위에 제시된 코드들은 데모 페이지에서 바로 붙여넣어 체크해 볼 수 있습니다.

오늘 살펴본 React 코드 오류는 다음과 같습니다.

  • render 함수의 반환 값을 직접 사용
  • 이벤트 핸들러 함수에서 이벤트 전파가 중단되지 않는 경우
  • JSX에서 JavaScript 주석을 사용하는 경우
  • length 속성 검사

특히 React를 처음 배우는 분들에게 도움이 되면 좋겠습니다.

Related Posts

comments powered by Disqus