第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 wecomponentizedyet/)
第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/templateengine/)
第60页
Living Template
第61页
Living Template 是一种组合技术
String-based Template的头 类似React的身体 DOM-based Template的尾巴
http://leeluolee.github.io/2014/10/10/templateengine/ (http://leeluolee.github.io/2014/10/10/templateengine/)
第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/customelementsregister (https://github.com/regularjs/customelementsregister)
第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、数据源、DataFlow保证了状态本身的可预测性 (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