AirJD 焦点
AirJD

没有录音文件
00:00/00:00
加收藏

漫谈Web前端的『组件化』 by 郑海波@网易

发布者 FEer   简介 前端技术
发布于 1456189805808  浏览 6763 关键词 前端, JavaScript, HTML5 
分享到

第1页

漫谈Web前端的『组件化』

What, Why, How



第2页

Hi, 我叫 郑海波

 @leeluolee(http://github.com/leeluolee)  @拴萝卜的棍子(http://weibo.com/luobolee)  网易杭州 - 前端技术部



第5页

 Agenda

1. 组件 2. 数据驱动的组件框

架 3. 实践经验分享



第6页

什么是组件?



第7页

Component

"AcomponentintheUnifiedModelingLanguage representsamodularpartofasystem,that encapsulatesitscontentandwhosemanifestation isreplaceablewithinitsenvironment.A componentdefinesitsbehaviorintermsof providedandrequiredinterfaces"



第9页

 Tab

Home Profile Messages Settings

Tab页内容...blablabla

<div id="tabs"> <ul> <li><a href="#tabs-1">Nunc tincidunt</a></li> <li><a href="#tabs-2">Proin dolor</a></li> <li><a href="#tabs-3">Aenean lacinia</a></li> </ul> <div id="tabs-1"> tab1 content </div> <div id="tabs-2"> tab2 content </div> <div id="tabs-3"> tab3 content </div>

</div>



第10页

jQuery

$( ".selector" ).tabs( "option", "show", { effect: "blind", duration: 800

});



第11页

NEJ

var tb = p._$$Tab._$allocate({ list: e._$getChildren( e._$get('box')), index:1, onchange:function(_event){ }

}); tb._$go(2);



第12页

YUI

YUI().use('tabview', function(Y) {

var tabview = new Y.TabView({ children: [{ label: 'foo', content: '<p>foo content</p>' }, { label: 'bar', content: '<p>bar content</p>' }, { label: 'baz', content: '<p>baz content</p>' }]

});

tabview.render('#demo');

});



第13页

实现不统一, 使用不统一



第14页

Web Component

 2011年,由Alex Russell提出 



第15页

是系列规范

Custom Element: 自定义HTML元素 shadow DOM: 封装 HTML Imports: 打包一切 HTML Template: Lazy的DOM模板



第16页

Custom Element

<x-foo> Custom Elememnt </x-foo> var xFoo = document.createElement('x-foo');



第17页

定义新元素

var XFooProto = Object.create(HTMLElement.prototype);

// 生命周期相关 XFooProto.readyCallback = function() {

this.textContent = "I'm an x-foo!"; };

// 设置 JS 方法 XFooProto.foo = function() { alert('foo() called'); };

var XFoo = document.register('x-foo', {prototype: XFooProto});



第18页

 VanillaJS+VanillaDOM!



第19页

Shadow Dom



0:00



第20页

 DevTool > Settings > General> show shadow DOM



第21页

h3inshadowRoot

h3 outer

<div><h3>Light DOM</h3></div>

<script> var node = document.querySelector('div');

var root = node.createShadowRoot();

root.innerHTML = '<style>h3{ color: red; }</style>' + '<h3> h3 in shadowRoot </h3>';

</script》



第22页

 AreWeComponentizedYet?(http://jonrimmer.github.io/are­ we­componentized­yet/)



第23页

 Advantage

一致性: Vanlila DOM 可移植性 所写即所得 浏览器内建的生命周期



第24页

 Disadvantage

Not Data Driven Imprective



第25页

Data-Binding

var tabPane = document.createElement('tab-pane'); tabPane.setAttribute('title', 'hello'); tabPane.setAttribute('content', 'hello'); tab.appendChild(tabPane);

VS

tabs.push({ title: 'hello', content: 'hello'

})



第26页

Declarative VS Imprective

«12345»



第27页

<pagination current={current} total={maxCount/20} on-nav={this.nav(1)}></pagination>

<pagination current={current} total={maxCount/20} on-nav={this.nav(2)}></pagination>

VS

<bootstrap-pagination id='pagination'></bootstrap-pagination> <script> // 获取元素 var pagination = document.querySelector('#pagination'); // 绑定事件 pagination.addEventListener('pagination-nav', function(event){

// blablablabal }) // 设置属性 $.ajax('/blogs').then(function( json ){

pagination.setAttribute('current', 0) pagination.setAttribute('total', json.length / 20) })

</script》



第28页

"是数据驱动允许将两个组件通过声明式编程建立内在联系"

Data-Driven是因(gong) Declarative是果(shou)



第29页

视图层本就是由数据所驱动



第33页

矛盾在于DOM操作是碎片的命令式



第34页

 这不是Web Component的错

"Foundation 与 Framework 职责是不同的"



第35页

We Need

 Framework



第36页

v=f(d)

何种框架技术满足这个公式 ?



第37页

答案是 ......

 字符串模板



第38页

{%if login %} <h2>{{user.name}} </h2>

{% endif %}

+

{ login: true, user: { name: '@leeluolee' }

}

===

<h2>@leeluolee</h2>



第39页

Advantage.

Simple Usage ( compile + render ) Natrual Isomorphic( 100% dom 无关 ) Powerful Syntax 100% Stateless "v=f(d)" "High Performance"



第40页

Disadvantage.

Hidden danger ( XSS, invalid tag... etc ) 100% Stateless (jQuery1102048119315954795094_1456189837431) "Low Performance" (???!)



第41页

MDV technology to rescue

ModelDrivenView()



第42页

MDV

AproposalforextendingHTMLandtheDOMAPIs tosupportasensibleseparationbetweentheUI (DOM)ofadocumentorapplicationandits underlyingdata(model).Updatestothemodel arereflectedintheDOManduserinputinto theDOMisimmediatelyassignedtothe model

fromGoogle



第43页

Polymer

Everything is Component



第47页

<link rel="import" href="bower_components/polymer/polymer.html">

<!-- import the iron-input custom element --> <link rel="import"

href="bower_components/iron-input/iron-input.html">

<dom-module id="editable-name-tag">

<template> <p> This is a <strong>{{owner}}</strong>'s editable-name-tag. </p> <!-- iron-input exposes a two-way bindable input value --> <input is="iron-input" bind-value="{{owner}}" placeholder="Your name here...">

</template>

<script> Polymer({ is: "editable-name-tag", properties: { owner: { type: String,



第48页

React

Nothing But Component



第49页

"Idefinitelythinkit’sthewrongprogramming paradigm.Ireallyhopethat[webcomponents]do notsucceed"

from PeteHunt(https://twitter.com/floydophone)



第50页

Share Context Between Logic And View



第52页

Virtual DOM

脏检查发生在View层抽象Virtual DOM上 Diff计算差异并映射到View层, 实现局部更新 View层可以独立实现到各平台 (依赖JS Binding) 不依赖模板, 配合JSX可以有类似书写体验.



第53页

One Way Data-Flow

Two-Way Binding 不再被认为是最佳实践



第54页

Huge

Foudation ( React Native, Flux, Immutable.js..etc) Community ( 3w+ stars , 5000+ issues ) Size... ( 40kb Gzip & without JSX )



第55页

Angular

 The next generation JavaScript language that will kill ALL the frameworks! 



第57页

Feature

Dirty Check(Pull) 100% 的 Plain Object Complex Concepts(directive,service,controller..etc) Huge Community Everything Except Componenet (v1.x) Low Performance (maybe)



第58页

Regularjs

Create data-driven Componentsbased on LivingTemplate (http://leeluolee.github.io/2014/10/10/template­engine/)



第60页

Living Template



第61页

Living Template 是一种组合技术

String-based Template的头 类似React的身体 DOM-based Template的尾巴

http://leeluolee.github.io/2014/10/10/template­engine/ (http://leeluolee.github.io/2014/10/10/template­engine/)



第62页

Define A Component



第63页

Declarative Way

<div class='example-container'> <pophover title='pophover title' placement='bottom'> pophover content </pophover>

</div>



第64页

Imprective Way

let pophover = new Pophover({ data: { title: 'pophover title', placement: 'bottom' }, $body: 'pophover content'

}).$inject('.example-container')



第65页

声明式(标签化)只是『接口』的一种形式



第66页

Feature

Safe 20KB(Gzip) Living Template( String-based + Dom-based ) Dirty Check(性能?) Composite Component( != 标签化 ) Fully Tested( IE6+ ) Widely Used in 我厂( 网易杭州 )



第67页

MDV总结: 框架的Model层

Pull: Regular, Angular 潜在性能问题 Stateless的Plain Object

Push: Polymer(Object.observe), Vue(Object.defineProperty) 高效的模型(MayBe) 非100%的Plain Object, 基于binding.

Pull(view): React



第68页

纯粹的v = f(d)止步于DOM 操作.



第69页

MDV总结: 框架的Foudation

Polymer: 基于Web Component Angular: 基于DOM的解析(innerHTML)+自建的编译(link)流程 React: 自有流程 Regular: 自有流程



第70页

regular-register

将Component 转化为真正的Custom Element !!

https://github.com/regularjs/customelements­register (https://github.com/regularjs/customelements­register)



第71页

 Thank You



第72页

组件实践

 与具体框架无关



第73页

永远不要提前将业务组件化 组件是重构的结果,非提前设计所能及也

组件化前, 共处一个Context 组件化后, 灵活性降低,分处于不同Context,通过事件和Data Flow维系



第74页

组件封装



第75页

组件分类

Non Visual: 只有业务逻辑,没有View 如带翻页列表: 抽象业务逻辑, 一般由其子类实现View 如<polymer-ajax></polymer-ajax>: 纯功能,没有View

Visual: 业务逻辑 + View



第76页

NonVisualComponentisEasy,VisualComponent isHard

组件最大的不稳定性来自于展现层(模板) 继承或混入可以解决成员函数和变量的扩展复用, 但无法解决模板的扩展复 用



第77页

如何解决模板的复用问题?



第78页

组合

A+B=AB



第79页

组合的野生例子: ul + li

属于无序列表的li元素

<ul class="pagination"> <li><a href="">Prev</a></li> <li><a href="">1</a></li> <li><a href="">...</a></li> <li><a href="">10</a></li> <li><a href="">Next</a></li>

</ul>



第80页

组合的野生例子: ol + li

属于有序列表的li元素

<ol class="rank"> <li><a href="">1</a></li> <li><a href="">2</a></li> <li><a href="">3</a></li> <li><a href="">4</a></li> <li><a href="">5</a></li>

</ol>



第81页

组件亦是如此

<dropdown-button title='向上的箭头' style=success dropup on-select={}> <select-item header>我是标题</select-item> <select-item><span class='icon'></span>我是列表项1</select-item> <select-item divider /> <select-item disabled>我是禁止的</select-item> <select-item>我是列表项2</select-item> <select-item divider /> <select-item>我是列表项3</select-item>

</dropdown-button>

select-item的职责: 响应点击、 失效等行为 dropdown-button的职责: 展现样式, 抛出select事件



第82页

组合可以更复杂...

<modal title='筛选器'> <tab> <tab.pane title='列表筛选' selected> <text-search select={select} ref=list ></text-search> </tab.pane> <tab.pane title='文本筛选' > <text-filter match ={match} ref=text ></text-filter> </tab.pane> <tab.pane title='条件筛选' > <condition-filter condition={condition} measures={measures} ref=condition ></c

ondition-filter> </tab.pane> <tab.pane title='高级筛选' > <advanced-filter top={top} measures={measures} ref=advanced ></advanced-filter> </tab.pane>

</tab> </modal>



第83页

组合: 只提供能力

<div class="item {mark.labels.length? 'z-act':''}"> <dropable name='mark.labels' direct=y on-toucheddrop={this.drop('mark.labels', $event)} > <div class="box"> {#list mark.labels as lpill} <dragable target={TARGET_PILL} on-dragend='dragend' > <pill pill={lpill} index={lpill_index} isolate = 1 /> </dragable> {/list} </div>

</dropable> </div>

dropable: 为 包裹区域 提供放置能力 dragable: 为 包裹区域 提供拖拽能力



第84页

DEMO



第85页

One Way Data-Flow



第87页

为何我们需要单向数据流的组件架构

可控性



第88页

Flux

瞬间被后浪排死在沙滩的前浪



第90页

Redux

State Manager, Only 2KB (gzip)

http://rackt.org/redux/(http://rackt.org/redux/)



第91页

流程

1. Store.dispatch(Action) // 派发Action 2. Middleware(Action) // 处理异步逻辑、Log等 3. Reducer(State, Action) 4. View = F(State);



第92页

Example

import { createStore } from 'redux'; // reducer function counter(state = 0, action) {

switch (action.type) { case 'INCREMENT':

return state + 1; case 'DECREMENT':

return state - 1; default:

return state; } } // Its API is { subscribe, dispatch, getState }. let store = createStore(counter);

// You can subscribe to the updates manually, or use bindings to your view layer. store.subscribe(() =>

console.log(store.getState()) );

store.dispatch({ type: 'INCREMENT' });



第93页

 让我们翻译成VanillaJS... state++

先苦后甜, 还是先甜后『狗带』



第94页

 三原则

规定State本身是不变数据类型 规定Reducer为纯函数 规则只能有单个Store(它整合了Dispather)

从数据类型、Mutation、数据源、Data­Flow保证了状态本身的可预测性 (predictable)



第95页

Redux与JS的矛盾点

JS中的Object,Array等是可变数据结构



第96页

可变数据

var a = {selected: true, content: 'hello'}; // action 1 a.selected = false; // action 2 a.selected = true; a === a;

往往你在调试action1时,数据已经被action2改变



第97页

不可变数据

var a = {selected: true, content: 'hello'}; // action1 var a1 = Object.assign({}, a , {

selected: true }) a !== a1



第98页

深层对象修改

deepSet(state, 'blogs[1].title', 'hello')



第99页

当我们把每一步的数据都保存下来

Time Machine!



第100页

如何结合特定的View层框架

store.subcribe(callback)

特别适合基于Pull流程(脏检查)的View层框架



第101页

Regular< ReduxMixin >Redux

var ReduxMixin = { events: { $config: function(){ store.subcribe( function(){

this.mapStateToData( store.getState(), this.data); this.$update()// 触发脏检查

}.bind(this) ) } }, mapStateToData: function(){ throw Error('You need implement mapStateToData') } } ReportApp.implement( ReduxApp );



第102页

 Thank You



支持文件格式:*.pdf
上传最后阶段需要进行在线转换,可能需要1~2分钟,请耐心等待。