Upload
kim-hunmin
View
17.923
Download
2
Embed Size (px)
Citation preview
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 : [email protected] blog : http://huns.me facebook : https://www.facebook.com/jeokrang
Facebook Developer Group https://www.facebook.com/groups/reactist/
감사합니다.