장기영 (Thomas Jang)

CHEQUER FrontEnd Director

 

tom@chequer.io

github.com/thomasjang

axisj.com, jsdev.kr, 구구너드.com

발표자료설명글 : https://goo.gl/YscrRr

액시스제이

구구너드.com

React datagrid component

with ES6, React, TypeScript

React datagrid component

with ES6, React, TypeScript

목차

  • 데이터 그리드 란?
  • 데이터 그리드를 만드는 이유
  • 설계
  • 주요코드 살펴보기
  • 성능비교
  • 후기

데이터 그리드란?

datagrid

데이터 그리드란?

웹에서 엑셀과 비슷한 경험을 주는 컴포넌트로 웹에서 많은 데이터를 표현하기 위해 사용

 

기존에는 grid라 불렀는데 datagrid로 이름 변경

ax5ui-grid

많은 데이터를

편리하게 보여줘

통일된 UI사용으로

개발편의성과 사용자경험 개선

데이터 그리드의 주요기능

  • 원하는 영역 선택 후 클립보드에 저장하기
  • 데이터 정렬 / 필터링
  • 특정 데이터를 간단하게 수정하기 (inline edit)
  • 셀의 데이터를 서식에 맞게 보여주기 (formatter)
  • click, keyDown 이벤트 등에 따라 특정 작업 수행하기
  • 멀티컬럼헤더(이건 한국인들만 원하는 게 아닐까..)
  • 특정열 또는 행까지 틀 고정
  • 컬럼의 너비 조정
  • 데이터 그룹화 기능

가볍고, 빠르고, 쉽고,

멋지고 ...

데이터 그리드를 만드는 이유

AXISJ - AXGrid

이런 것도 만들었습니다. 2013년 (dev.axisj.com)

AX5UI - ax5ui-grid

이것도 만들었습니다. 2016년 (ax5.io)

새로운 코드가 필요한 시대

React로 개발하고 싶은 욕망

새로운 데이터 그리드 UI가 필요해

ES6, Webpack, React, TypeScript 몽땅 써야겠어.

설계

DOM 구조설계

SCROLL-CONTAINER

왜 이런 복잡한 구조가 필요할까?

왜 이런 복잡한 구조가 필요할까?

aside

left

top

스크롤 포지션

// scrollTop(ST)
// scrollBarTop(BT)
// scrollContainerHeight(SCH)
// scrollHeight(SH)
// scrollBarContainerHeight(BCH)
// scrollBarHeight(BH)


ST : SH - SCH = BT : BH - BCH
BT = ST * (BH - BCH) / (SH - SCH)
ST = (SH - SCH) * BT / (BH - BCH)

프로젝트 폴더 구성

Gulp 활용

요즘 대세는 NPM Script이지만 아직은 Gulp가 익숙합니다.

 

'gulp-shell'을 활용하면 못하는게 없습니다.

SRC 폴더 구성

  • 같은 종류의 파일이 한 개 이상이 되면 폴더를 만든다.
  • 하나의 파일로 만들 수 있는 경우라도 가능한 한 잘게 나누고 폴더에 index로 export 한다.
  • 파일의 내용이 짧다면 파일로도 충분하다.

주요 코드 살펴보기

Thinking in React

jQuery

  1. 마우스다운 이벤트 발생
  2. 마우스이동 이벤트 발생
  3. 마우스이동량 계산
  4. scrollBar 엘리먼트 찾기
  5. scrollBar.style.top 조절
  6. scrollContent 엘리먼트 찾기
  7. scrollContent.style.top 조절
  8. 마우스업 이벤트 발생
  9. 상황종료

Thinking in React

React

  1. 마우스다운 이벤트 발생
  2. 마우스이동 이벤트 발생
  3. 마우스이동량 계산
  4. Grid.setState({scrollTop: y});
  5. 마우스업 이벤트 발생
  6. 상황종료

Container Dimension

public componentDidMount() {
  this.gridRootNode = ReactDOM.findDOMNode( 
    this.refs.gridRoot 
  );
}

Container Dimension

public componentDidMount() {
  this.gridRootNode = ReactDOM.findDOMNode( this.refs.gridRoot );

  this.throttled_updateDimensions = throttle( 
    this.updateDimensions.bind( this ), 
    100 
  );

  window.addEventListener( 
    'resize', this.throttled_updateDimensions 
  );

  this.setState( { mounted: true } );
}

Container Dimension

public componentWillUnmount() {
  window.removeEventListener( 
    'resize', 
    this.throttled_updateDimensions 
  );
}

// 이벤트 제거

Convert Columns

Convert Columns

export function makeHeaderTable( _columns, _options ) {
  const columns = fromJS( _columns ).toJS();
  let table = {
    rows: []
  };

  return table;
}

Convert Columns

export function makeHeaderTable( _columns, _options ) {
  const columns = fromJS( _columns ).toJS();
  let table = {
    rows: []
  };
  let colIndex = 0;

  function maekRows( 
    _columns: iAXDataGridColumns[], 
     depth: number, 
     parentField?: any ): number 
    {
    // https://github.com/axui/datagrid/blob/master/src/util/_makeData.ts#L18
  }
  
  maekRows( columns, 0 );

  return table;
}

  maekRows( columns, 0 );

  // set rowspan
  table.rows.forEach( ( row, ri ) => {
    row.cols.forEach( ( col ) => {
      if ( !('columns' in col) ) {
        col.rowspan = table.rows.length - ri;
      }
    } );
  } );

  return table;
}
export function propsToState( props, state ) {
  let dividedObj;

  // state 계산영역 시작
  state.headerTable = makeHeaderTable( props.columns, state.options );
  state.bodyRowTable = makeBodyRowTable( props.columns, state.options );
  state.bodyRowMap = makeBodyRowMap( state.bodyRowTable, state.options );

  dividedObj = divideTableByFrozenColumnIndex( 
    state.headerTable, 
    state.options.frozenColumnIndex, 
    state.options 
  );
  state.asideHeaderData = dividedObj.asideData;
  state.asideColGroup = dividedObj.asideColGroup; 
  state.leftHeaderData = dividedObj.leftData;
  state.headerData = dividedObj.rightData;
  // https://github.com/axui/datagrid/blob/master/src/util/_props.ts#L20
}

Virtual Scroll

Virtual Scroll

const sRowIndex = 
    Math.floor( -scrollTop / bodyTrHeight ) + frozenRowIndex;

let bodyScrollConfig: iAXDataGridBodyPanelScrollConfig = {
      frozenRowIndex: frozenRowIndex,
      sRowIndex: sRowIndex,
      eRowIndex: sRowIndex + Math.ceil( bodyHeight / bodyTrHeight ) + 1
    };

성능비교

jQuery

React

후기

webpack.config.js

tsconfig.json

.babelrc

tslint.json

 

어렵다. 영어공부가 필요하다.

React

만족스럽다.

TypeScript

생각보다는 쉽다.

감사합니다.