Facebook은 React를 왜 만들었을까?

Post on 16-Apr-2017

17.923 views 2 download

Transcript of Facebook은 React를 왜 만들었을까?

Facebook은 React를 왜 만들었을까?

NAVER Corp. Blog & Post Development Lab

김훈민

[명사] 1. 사물ㆍ현상이 놓여 있는 모양이나 형편.

#1 상태(state)

UI가 놓여있는 모습이나 형편

UI의 상태

상태 관리 -> 데이터 관리 상태가 복잡하다 -> 관리할 데이터가 많다

우리가 보는 UI는 특정 시점의 데이터를 표현

http://info.cern.ch/hypertext/WWW/TheProject.html

http://info.cern.ch/hypertext/WWW/TheProject.html

정적(static)인 상태User’s Action

Page

하이퍼 링크로 문서와 문서를 연결하는 아주 단순한 웹

http://www.infinitezest.com/articles/xmlhttprequest-and-ajax-on-google-suggest.aspx

JavaScript 비중 증가 심각한 브라우저 파편화 개발 난도(難度) 상승

동적(Dynamic)인 상태User’s Action

Page

크로스 브라우징, XHR 처리, DOM 셀렉팅, DOM 노드 탐색, 엘리먼트 상태/스타일 변경 등…

브라우저 호환성, 편리한 사용성, 간결한 인터페이스

update: function( el ) { if( !el.offsetWidth ) { return; }

var $el = $( el ),

height = $el.outerHeight(), initialOffset = $el.data( S.keys.offset ), startTop = $el.data( S.keys.startTop ), scroll = S.getScrollTop(), isAlreadyOn = $el.is( '.' + S.classes.active ), toggle = function( turnOn ) {

$el[ turnOn ? 'addClass' : 'removeClass' ]( S.classes.active ) [ !turnOn ? 'addClass' : 'removeClass' ]( S.classes.inactive );

}, viewportHeight = $( window ).height(), position = $el.data( S.keys.position ), skipSettingToFixed, elTop, elBottom, $parent = $el.parent(), parentOffset = $parent.offset().top, parentHeight = $parent.outerHeight();

if( initialOffset === undefined ) {

initialOffset = $el.offset().top + startTop; $el.data( S.keys.offset, initialOffset ); $el.after( $( '<div>' ).addClass( S.classes.clone ).height( height ) );

}

if( !position ) { // Some browsers require fixed/absolute to report accurate top/left values. skipSettingToFixed = $el.css( 'top' ) !== 'auto' || $el.css( 'bottom' ) !== 'auto';

if( !skipSettingToFixed ) {

$el.css( 'position', 'fixed' ); }

position = {

top: $el.css( 'top' ) !== 'auto', bottom: $el.css( 'bottom' ) !== 'auto'

};

if( !skipSettingToFixed ) { $el.css( 'position', '' );

}

$el.data( S.keys.position, position ); }

function isFixedToTop() {

var offsetTop = scroll + elTop;

관심사 분리나 OOP 지원 같은 설계 관점 부재 - Smart UI 양산 - 상태 관리 전략 모호 - 잦은 DOM 셀렉팅으로 인한 성능 저하

HTML + JavaScript + CSS가 강하게 결합 - 코드 유연성 저하 - 테스트 곤란

복잡도 증가

웹 프론트엔드에 불기 시작한 새로운 바람

이렇게 된 이상

MVC로 간다!

대표적인 SPA Framework 웹 프론트엔드 환경에 맞게 MVC를 재해석 도메인 모델 + REST API

Backbone.js

ServerClient

대표적인 SPA Framework 웹 프론트엔드 환경에 맞게 MVC를 재해석 도메인 모델 + REST API

Backbone.js

View Model Server

initialize : function(){ this.listenTo(this.model, "add", this._onAddFriend, this); this.listenTo(this.model, "change:display", this._onChangeDisplay, this); this.model.fetch();

}, addFriend : function(friend){

this.model.add(friend); }, hideFriend : function(){

this.model.set("display", false); }, _onAddFriend : function(friend){

var friendView = new FriendView({ model : friend, oGroupModel : this.model

});

this.$el.append(friendView.render()); }, _onChangeDisplay : function(isDisplay){

if(isDisplay){ this.$elFriendList.show();

} else { this.$elFriendList.hide();

} }

표현 로직과 업무 로직 분리에 초점 데이터 동기화, UI 상태 관리는 여전히 개발자의 몫

동기화는 “님”들이 알아서

View Model

동기화는 누가?동기화는 누가?

View의 상태는?

동기화를 프레임워크가 책임진다면 정말 좋겠네

View의 상태와 Model의 데이터를 바인딩하여 한 쪽의 상태 변경을 다른 쪽에 실시간으로 반영하는 프로세스

데이터 바인딩(Data Binding)

Ember.js KVO(Key-Value Observing) 프레임워크에 종속적인 코드 스타일을 강제 엔터프라이즈에 강점

Model과 View를 알려주면

내가 동기화 해줄게!

App.GravatarImageComponent = Ember.Component.extend({ size: 200, email: '', gravatarUrl: Ember.computed('email', 'size', function() { var email = this.get('email').toLowerCase(), size = this.get('size'); return 'http://www.gravatar.com/avatar/' + md5(email) + '?s=' + size; }) });

<img src={{gravatarUrl}}> <div class="email-input"> {{input type="email" value=email placeholder="Enter your Gravatar e-mail"}}</div>

user = Ember.Object.create({ fullName: 'Kara Gates'}); UserComponent = Ember.Component.extend({ userName: Ember.computed.oneWay('user.fullName') }); userComponent = UserComponent.create({ user: user}); // Changing the name of the user object changes// the value on the view.user.set(‘fullName', 'Krang Gates'); // userComponent.userName will become "Krang Gates"// ...but changes to the view don't make it back to// the object.userComponent.set('userName', 'Truckasaurus Gates'); user.get(‘fullName'); // "Krang Gates"

Angular.js 양방향(Two-Way) 데이터 바인딩 POJO(Plain Old JavaScript Object) + Dirty Checking

그냥, 다 확인하지 뭐

Template View Model

<div ng-controller="Controller"> Date format: <input ng-model="format"> <hr/> Current time is: <span my-current-time="format"></span> </div>

angular.module('docsTimeDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.format = 'M/d/yy h:mm:ss a'; }]) .directive('myCurrentTime', ['$interval', 'dateFilter', function($interval, dateFilter) { function link(scope, element, attrs) { var format, timeoutId; function updateTime() { element.text(dateFilter(new Date(), format)); } scope.$watch(attrs.myCurrentTime, function(value) { format = value; updateTime(); }); element.on('$destroy', function() { $interval.cancel(timeoutId); }); // start the UI update process; save the timeoutId for canceling timeoutId = $interval(function() { updateTime(); // update DOM }, 1000); } return { link: link }; }]);

#2 React Begins못 살겠다, 바꿔보자

디버깅의 어려움 높은 학습비용 고질적인 성능 이슈

아직도 배가 고프다

No magical data binding No model dirty checking No more explicit DOM operations

조금만 더

단순할 수는 없을까?User’s Action

Page

상태 변경 -> 렌더링 DOM은 그저 특정 시점의 상태를 표현할 뿐

Make it Reactive!

var Timer = React.createClass({ displayName: "Timer", getInitialState: function getInitialState() { return { secondsElapsed: 0 }; }, tick: function tick() { this.setState({ secondsElapsed: this.state.secondsElapsed + 1 }); }, componentDidMount: function componentDidMount() { this.interval = setInterval(this.tick, 1000); }, componentWillUnmount: function componentWillUnmount() { clearInterval(this.interval); }, render: function render() { return React.createElement( "div", null, "Seconds Elapsed: ", this.state.secondsElapsed ); } }); ReactDOM.render(React.createElement(Timer, null), mountNode);

JavaScript로 DOM 엘리먼트 생성하는

코드 넘 구린 듯!

차라리 템플릿이 나은 듯!

JavaScript로 DOM 엘리먼트 생성하는

코드 넘 구린 듯!

차라리 템플릿이 나은 듯!

인정!

JSX 쓰면 어떰?

var Timer = React.createClass({ getInitialState: function() { return {secondsElapsed: 0}; }, tick: function() { this.setState({secondsElapsed: this.state.secondsElapsed + 1}); }, componentDidMount: function() { this.interval = setInterval(this.tick, 1000); }, componentWillUnmount: function() { clearInterval(this.interval); }, render: function() { return ( <div>Seconds Elapsed: {this.state.secondsElapsed}</div> ); } }); ReactDOM.render(<Timer />, mountNode);

XML 스타일의 코드를 작성할 수 있게 JavaScript를 확장한 문법 ECMAScript 표준 스펙과는 별개 트랜스파일하여 ECMAScript로 변환

JSXvar dropdown = <Dropdown> A dropdown list <Menu> <MenuItem>Do Something</MenuItem> <MenuItem>Do Something Fun!</MenuItem> <MenuItem>Do Something Else</MenuItem> </Menu> </Dropdown>; render(dropdown);

JavaScript로 DOM 엘리먼트 생성하는

코드 넘 구린 듯!

차라리 템플릿이 나은 듯!

인정!

JSX 쓰면 어떰?

으악! 저게 머임!

님 관심사 분리 모름?

누가 요즘 JavaScript 코드 안에 HTML 씀?

JavaScript로 DOM 엘리먼트 생성하는

코드 넘 구린 듯!

차라리 템플릿이 나은 듯!

인정!

JSX 쓰면 어떰?

으악! 저게 머임!

님 관심사 분리 모름?

누가 요즘 JavaScript 코드 안에 HTML 씀?

HTML이랑 JavaScript를 분리한다고

관심사 분리가 됨?

복잡성을 덜기 위해 관심사를 분리 HTML과 JavaScript의 분리는 관심사가 아닌 기술의 분리

관심사란

객체가 맡고 있는 책임

어떤 입력을 처리하고, 무엇을 보여줄 것인가?

재사용할 수 있는 대상은 주로 컴포넌트

진짜 관심사는, UI 인터랙션

export default class NaverMain extends React.Component { constructor(){ super(); } render() { return ( <div> <SearchBar/> <TabMenu /> <NewsHeadLineList /> <RealTimeSearchPanel /> <HotNewsSlide /> </div> ); } }

JavaScript는 HTML을 객체화 한 DOM을 조작 HTML과 JavaScript는 논리적으로 강하게 결합

기능(JavaScript)과

구조(HTML)를 분리하면 의존성을 줄일 수 있을까?

<!DOCTYPE html> <html> <head> <script> function myFunction() { document.getElementById("demo").innerHTML = "Paragraph changed."; } </script> </head> <body> <h1>My Web Page</h1> <p id="demo">A Paragraph</p> <button type="button" onclick="myFunction()">Try it</button> </body> </html>

function addElement () { var newDiv = document.createElement("div"); var newContent = document.createTextNode("Hi there and greetings!"); newDiv.appendChild(newContent); var currentDiv = document.getElementById("div1"); document.body.insertBefore(newDiv, currentDiv); }

var html=""; html += "<div class=\"content\" data-jiis=\"cc\" id=\"main\"><span class=\"ctr-p\">"; html += " <div style=\"height:233px;margin-top:89px\" id=\"lga\">"; html += " <div style=\"padding-top:109px\">"; html += " <div title=\"Google\" align=\"left\" id=\"hplogo\">"; html += " <div nowrap=\"\">한국<\/div>"; html += " <\/div>"; html += " <\/div>"; html += " <\/div>"; html += " <div style=\"height:118px\"><\/div>"; html += " <div data-jibp=\"h\" data-jiis=\"uc\" id=\"prm-pt\"><\/div>"; html += " <div class=\"ctr-p\" id=\"footer\">"; html += " <div data-jibp=\"h\" data-jiis=\"uc\" id=\"fbarcnt\" ><\/div>"; html += " <\/div>"; html += " <div data-jibp=\"h\" data-jiis=\"uc\" id=\"footc\">"; html += " <div id=\"xfoot\">"; html += " <div id=\"xjsd\">"; html += " <\/div>"; html += " <div id=\"xjsi\">"; html += " <\/div>"; html += " <\/div>"; html += " <\/div>"; html += "<\/div>"; document.body.innerHTML = html;

<div class="post"> {{> userMessage tagName="h1" }} <h1>Comments</h1> {{#each comments}} {{> userMessage tagName="h2" }} {{/each}} </div>

DSL(특정 도메인 전용 논리 언어) 추가 학습비용 DSL의 낮은 표현력

템플릿(Template)도 조금 부족해

<ul> {{#each items}} <li>{{agree_button}}</li> {{/each}} </ul>

var context = { items: [ {name: "Handlebars", emotion: "love"}, {name: "Mustache", emotion: "enjoy"}, {name: "Ember", emotion: "want to learn"} ] }; Handlebars.registerHelper('agree_button', function() { var emotion = Handlebars.escapeExpression(this.emotion), name = Handlebars.escapeExpression(this.name); return new Handlebars.SafeString( "<button>I agree. I " + emotion + " " + name + "</button>" ); });

<ul> <li><button>I agree. I love Handlebars</button></li> <li><button>I agree. I enjoy Mustache</button></li> <li><button>I agree. I want to learn Ember</button></li> </ul>

매번 다시 그리는 건 낭비 아님?

성능에 문제 없음?

바뀐 부분만 다시 그릴 거임!

매번 다시 그리는 건 낭비 아님?

성능에 문제 없음?

님, 장난함?

DOM 상태를 비교한다고?

DOM API 호출하는 비용 무시함?

매번 다시 그리는 건 낭비 아님?

성능에 문제 없음?

바뀐 부분만 다시 그릴 거임!

님, ㄴㄴㄴ

가상의 DOM 객체를 만들어서

비교한 후에 실제 바뀐 부분만 다시 그려주면 됨!

매번 다시 그리는 건 낭비 아님?

성능에 문제 없음?

님, 장난함?

DOM 상태를 비교한다고?

DOM API 호출하는 비용 무시함?

바뀐 부분만 다시 그릴 거임!

UI 상태에 대한 정보를 가지고 있는 가상의 표현 객체 ReactElement ReactDOMTextComponent(ReactText)

Virtual DOMvar HelloMessage = React.createClass({ render: function() { return <div>Hello {this.props.name}</div>; } }); ReactDOM.render(<HelloMessage name="John" />, mountNode);

var HelloMessage = React.createClass({ render: function() { return <div>Hello {this.props.name}</div>; } }); ReactDOM.render(<HelloMessage name="John" />, mountNode);

<div> ReactElement

<span> ReactText

“Hello”

<span> ReactText

“John”

https://github.com/facebook/react/blob/master/src/isomorphic/classic/element/ReactElement.js

https://github.com/facebook/react/blob/master/src/renderers/dom/shared/ReactDOMTextComponent.js

#3 React Rises이제는 조금 더 빨라져야 할 시간

하나의 트리를 다른 트리로 변환하는가장 빠른 알고리즘

시간 복잡도는 O(n^3)

불필요한 비교 단계를 생략하는 백트레킹(Backtracking)과 유사한 전략

휴리스틱하게

O(n)으로 만들면 되지

비교 범위를

동일한 레벨의 노드로 한정

Tree A Tree B

자식 컴포넌트 리스트 비교

List A List B

리스트의 중간에

새로운 컴포넌트를 하나 추가하면?

List A List B

컴포넌트에 Key를 할당하여 맵핑함으로써 동일한 노드를 추적

Key를 이용한 리스트 비교

List A List B

A

B

C

D

A

B

C

D

E

List A List B

A

B

C

D

A

B

C

D

E

컴포넌트에 Key를 할당하여 맵핑함으로써 동일한 노드를 추적

Key를 이용한 리스트 비교

Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of `CardThumbnailSlide`. See https://

fb.me/react-warning-keys for more information.

동일한 컴포넌트 클래스만 세부 비교 진행

Tree A Tree B

클래스가 다르다면 비교할 이유가 없지!

Tree A Tree B

동일한 컴포넌트 클래스만 세부 비교 진행클래스가 다르다면 비교할 이유가 없지!

Tree A Tree B

동일한 컴포넌트 클래스만 세부 비교 진행클래스가 다르다면 비교할 이유가 없지!

Tree A Tree B

동일한 컴포넌트 클래스만 세부 비교 진행클래스가 다르다면 비교할 이유가 없지!

배치 처리 선택적 서브 트리 렌더링

렌더링 최적화로 좀 더 빠르게

setState를 호출하면 이벤트 루프 동안에 상태를 변경해야 하는 컴포넌트를 찾아서 더티 체크(dirty check)

더티 체크

setState

이벤트 루프가 끝나면 더티 체크한 컴포넌트의 render 함수를 일괄 호출

배치 처리

setState render

setState를 호출하면 모든 자식 컴포넌트의 render를 호출

서브 트리 렌더링

setState render

What…?

최상위 노드에서 setState를 호출할 일은 많지 않다

대부분의 변경은 일부 영역에서 발생

render

shouldComponentUpdate를 오버라이딩

선택적 서브 트리 렌더링으로 방어 처리 가능

setState shouldComponentUpdate: function(nextProps, nextState) { return false; }

setState

shouldComponentUpdate를 오버라이딩

선택적 서브 트리 렌더링으로 방어 처리 가능

CPU를 많이 점유하는 코드 작성은 지양! 컴포넌트 트리의 깊이를 적당(?)한 수준으로 유지할 것!

shouldComponentUpdate는

아주 자주 불리는 메서드

자동 이벤트 위임 처리 크로스 브라우징 이벤트 객체 풀링(Pooling) 메모리 소비 감소

Event Delegation

import React from 'react'; import { Button, Glyphicon } from 'react-bootstrap'; import CardActions from '../actions/CardActions.js'; export default class CardInsertButton extends React.Component { constructor(){ super(); } render() { return ( <div className="card-thumbnail-slide__card-insert-btn"> <Button onClick={ this._handleClick }> <Glyphicon glyph="plus" /> </Button> </div> ); } _handleClick(){ CardActions.add({ id : Date.now(), src : "https://unsplash.it/400/400?random&bs=" + Date.now() }); } }

Virtual DOM이나 컴포넌트 단위 사고 방식은 좀 더 쉽게 문제를 풀려다가 곁가지로 나온 해결책

React의 본질은 단순함

결국 도구일 뿐이고 장점과 단점은 동전의 앞면과 뒷면 문제를 먼저 살피고 도구를 선택하는 것이 현명

그래서,

React 써야 하나요?

• World Wide Web - http://info.cern.ch/hypertext/WWW/TheProject.html

• XMLHttpRequest and AJAX on Google Suggest - http://www.infinitezest.com/articles/xmlhttprequest-and-ajax-on-

google-suggest.aspx

• Be Predictable, Not Correct. - https://www.youtube.com/watch?v=h3KksH8gfcQ

• React:Rethinking Best Practices - http://www.slideshare.net/floydophone/react-preso-v2

• React’s diff algorithm - http://calendar.perfplanet.com/2013/diff/

• Model-View-Controller(MVC) Its Past and Present - http://heim.ifi.uio.no/~trygver/2003/javazone-jaoo/MVC_pattern.pdf

참고자료

김코딩 email : jeokrang@gmail.com blog : http://huns.me facebook : https://www.facebook.com/jeokrang

Facebook Developer Group https://www.facebook.com/groups/reactist/

감사합니다.