组件化的web

本文内容为学习http://www.alloyteam.com/2015/11/we-will-be-componentized-web-long-text/  博文的总结

模块化的不足

为了提高项目开发效率和便于后期维护,我们倾向于使用模块化的方式来互相协作,现在大部分稍微大型一点的项目,都会使用requirejs或者seajs来实现JS的模块化,多人分工合作开发,各自定义自己模块的依赖和暴露接口,这些模块-----通过独立拆分且通用的代码单元,组成一个个的页面。
传统模块化代码示例如下:
require([
    'Tmpl!../tmpl/list.html','lib/qqapi','module/position','module/refresh','module/page','module/net'
], function(listTmpl, QQapi, Position, Refresh, Page, NET){
    var foo = '',
        bar = [];
    QQapi.report();
    Position.getLocaiton(function(data){
        //...
    });
    var init = function(){
        bind();
        NET.get('/cgi-bin/xxx/xxx',function(data){
            renderA(data.banner);
            renderB(data.list);
        });
    };
    var processData = function(){
    };
    var bind = function(){
    };
    var renderA = function(){
    };
    var renderB = function(data){
        listTmpl.render('#listContent',processData(data));
    };
    var refresh = function(){
        Page.refresh();
    };
    // app start
    init();
});

以上是具体某个页面的主js,已经封装了功能模块,但页面的主逻辑依旧是”面向过程“的代码结构,即根据页面的渲染过程来编写代码结构。像init -> getData -> processData -> bindevent -> report -> xxx 方法之间线性跳转。面向过过程的代码随着页面逻辑越来越复杂,这条”过程线“也会越来越长,并且越来越绕。加之缺少规范约束,其他项目成员根据各自需要,在”过程线“加插各自逻辑,最终这个页面的逻辑变得难以维护。

页面结构模块化

面对传统模块的面向过程问题,很多公司给出了解决方案,以腾讯为例:
假设页面划分为tabContainer,listContainer和imgsContainer三个模块,则上文介绍的传统模块化是“将所有模块糅合在一起的面向过程的代码”变为“各模块分别顺序执行自己流程的多个小面向过程代码”,如下图所示:

代码示例:
require([
    'Tmpl!../tmpl/list.html','Tmpl!../tmpl/imgs.html','lib/qqapi','module/refresh','module/page'
], function(listTmpl, imgsTmpl, QQapi, Refresh, Page ){
 
    var tabContainer = new RenderModel({
        renderContainer: '#tabWrap',
        data: {},
        renderTmpl: "<li soda-repeat='item in data.tabs'>{{item}}</li>",
        event: function(){
            // tab's event
        }
    });
 
    var listContainer = new ScrollModel({
        scrollEl: $.os.ios ? $('#Page') : window,
        renderContainer: '#listWrap',
        renderTmpl: listTmpl,
        cgiName: '/cgi-bin/index-list?num=1',
        processData: function(data) {
            //...
        },
        event: function(){
            // listElement's event
        },
        error: function(data) {
            Page.show('数据返回异常[' + data.retcode + ']');
        }
    });
 
    var imgsContainer = new renderModel({
        renderContainer: '#imgsWrap',
        renderTmpl: listTmpl,
        cgiName: '/cgi-bin/getPics',
        processData: function(data) {
            //...
        },
        event: function(){
            // imgsElement's event
        },
        complete: function(data) {
           QQapi.report();
        }
    });
    
    var page = new PageModel();
    page.add([tabContainer,listContainer,imgsContainer]);
    page.rock();
 
});
这样我们就我们把这些常用的请求,对返回数据的处理,事件绑定,上报,容错处理等一系列逻辑方法,以页面块为单位封装成一个Model模块。我们可以清晰地看到该页面块,请求的CGI是什么,绑定了什么事件,做了什么上报,出错怎么处理。新增的代码就应该放置在相应的模块上相应的状态方法(preload,process,event,complete…),杜绝了以往的无规则乱增代码的行文。
现在基于Model的页面结构开发,已经带有一点”组件化“的味道

 WebComponents 标准

现阶段的“组件”基本上只能达到是某个功能单元上的集合。他的资源都是松散地分散在三种资源文件中,而且组件作用域暴露在全局作用域下,很容易与其他组件产生冲突,如最简单的css命名冲突。对于这种“组件”,还不如最开始介绍的页面结构模块化。于是W3C按耐不住了,制定一个WebComponents标准,为组件化的未来指引了明路。
WebComponents标准有一下四点:

 <template>模板能力

<template id="datapcikerTmpl">
<div>我是原生的模板</div>
</template>
这样,我们想要使用该模板时候,就可以通过
innerHTML= document.querySelector('#datapcikerTmpl').content;
来调用模板

ShadowDom 封装组件独立的内部结构

var wrap = document.querySelector('#wrap');
var shadow = wrap.createShadowRoot();
shadow.innerHTML = '<p>you can not see me </p>'
ShadowDom可以理解为一份有独立作用域的html片段。在具体dom节点上使用createShadowRoot方法即可生成其ShadowDom。就像在整份Html的屋子里面,新建了一个shadow的房间。房间外的人都不知道房间内有什么,保持shadowDom的独立性。

自定义原生标签

imports解决组件间的依赖

<link rel="import" href="datapciker.html">

即html导入功能
总结来说WebCompoents有以下四部分功能:
  • <template>定义组件的HTML模板能力
  • Shadow Dom封装组件的内部结构,并且保持其独立性
  • Custom Element 对外提供组件的标签,实现自定义标签
  • import解决组件结合和依赖加载

组件化实践方案

我们总结一下一份真正成熟可靠的组件化方案,需要具备的能力:
  • 资源高内聚”—— 组件资源由自身加载控制
  • “作用域独立”—— 内部结构密封,不与全局或其他组件产生影响 
  • 自定义标签
  • 可相互组合”—— 组件正在强大的地方,组件间组装整合
  • 接口规范化”—— 组件接口有统一规范,或者是生命周期的管理、
现阶段WebComponent的支持度还不成熟,不能作为方案的手段。而以高性能虚拟Dom为切入点的组件框架React,在facebook的造势下,社区得到了大力发展。另外一名主角Webpack,负责解决组件资源内聚,同时跟React极度切合形成互补。
所以【Webpack】+【React】将会是这套方案的核心技术。

组件生命周期

React天生就是强制性组件化的,所以可以从根本性上解决面向过程代码所带来的麻烦。React组件自身有生命周期方法,能够满足“接口规范化”能力点。另外react的jsx自带模板功能,把html页面片直接写在render方法内,组件内聚性更加紧密。
react的生命周期有以下三个状态:
  • Mount: 插入Dom
  • Update: 更新Dom
  • Unmount: 拔出Dom
组件状态就是: 插入-> 更新 ->拔出。 
然后每个状态会有一至两个处理函数,will函数和did函数。
  • componentWillMount()  准备插入前
  • componentDidlMount()  插入后
  • componentWillUpdate() 准备更新前
  • componentDidUpdate()  更新后
  • componentWillUnmount() 准备拔出前
另外React另外一个核心:数据模型props和state,对应着也有自个状态方法
  • getInitialState()     获取初始化state。
  • getDefaultProps() 获取默认props。对于那些没有父组件传递的props,通过该方法设置默认的props
  • componentWillReceiveProps()  已插入的组件收到新的props时调用
  • 还有一个特殊状态的处理函数,用于优化处理
  • shouldComponentUpdate():判断组件是否需要update调用
 加上最重要的render方法,React自身带的方法刚刚好10个。(其实getInitialState,componentDidMount,render三个状态方法都能完成大部分组件)。
上文我们提到的面向过程代码,在react中,通过生命周期变成了下图所示
组件的状态方法流中,有两点需要特殊说明:

二次渲染:

由于React的虚拟Dom特性,组件的render函数不需自己触发,根据props和state的改变自个通过差异算法,得出最优的渲染。
请求CGI一般都是异步,所以数据被取回后,必定会通过props和state的改变带来二次渲染。只是空数据渲染的时候,有可能会被React优化掉。当数据回来,通过setState,触发二次render

componentWiillMount与componentDidMount的差别

ajax请求建议在WillMount的方法内执行,而不是组件初始化成功之后的DidMount。这样能在“空数据渲染”阶段之前请求数据,尽早地减少二次渲染的时间。
willMount只会执行一次,非常适合做init的事情。
didMount也只会执行一次,并且这时候真实的Dom已经形成,非常适合事件绑定和complete类的逻辑

JSX很丑,但是组件内聚的关键!

虽然jsx很丑,但是也为我们提供了很多组件化独有的遍历
1.之前的“逻辑视图分离”模式,我们需要去找相应的js文件,相应的event函数体内,找到td-info的class所绑定的事件。
而JSX的高度内聚,所有事件逻辑就是在本身jsx文件内,绑定的就是自身的showInfo方法。组件化的特性能立马体现出来。
<p className="td-info" onClick={this.showInfo}>{obj.info}</p>

2.或许JSX内部有负责繁琐的逻辑样式,可JSX的自定义标签能力,组件的黑盒性立马能体验出来
render: function(){
    return (
      <div>
         <Menus bannerNums={this.state.list.length}></Menus>
         <TableList data={this.state.list}></TableList>
      </div>
   );
}
现在让我们看看在react帮助下,我们组件化能力点的完成情况
资源高内聚”—— (33%)  html与js内聚
作用域独立”—— (50%)  js的作用域独立
自定义标签”—— (100%)jsx
可相互组合”—— (50%)  可组合,但缺乏有效的加载方式
接口规范化”—— (100%)组件生命周期方法

 Webpack 资源组件化

根据webpack的设计理念,所有资源都是“模块”,webpack内部实现了一套资源加载机制,可以把css,图片等资源等有依赖关系的“模块”加载,不只是加载js模块。这跟requirejs大大不同。这套加载机制,通过一个个loader来实现。

组件一体输出

// 加载组件自身css
require('./slider.css');
// 加载组件依赖的模块
var React = require('react');
var Clip = require('../ui/clipitem.jsx');
// 加载图片资源
var spinnerImg = require('./loading.png');
var Slider = React.createClass({
    getInitialState: function() {
        // ...
    },
    componentDidMount: function(){
        // ...
    },
    render: function() {
        return (
            <div>
               <Clip data={this.props.imgs} />
               <img className="loading" src={spinnerImg} />
            </div>
        );
    }
});
module.exports = Slider;

如果说,react使到html和js合为一体。那么加上webpack,两者结合一起的话。js,css,png(base64),html 所有web资源都能合成一个JS文件。这正是这套方案的核心所在: 组件独立一体化。如果要引用一个组件,仅仅require('./slider.js') 即可完成。
加入webpack的模块加载器之后,我们组件的加载问题,内聚问题也都成功地解决掉
“资源高内聚”—— (100%) 所有资源可以一js输出
“可相互组合”—— (100%)  可组合可依赖加载

猜你喜欢

转载自blog.csdn.net/goodgirl1991/article/details/53996161