组件化小结

397 阅读3分钟

引入

前端编码中需要一种方式来封装已有功能,要求如下:

  • 符合开闭原则,在开发新功能时不需要修改原有代码:包括html,css,js
  • 良好的封装,不依赖上下文:不会有冲突如(js重名,classname重名)

本文认为组件化是一种符合上述要求的良好实践:

  • 流行的框架(前端:vue,react,angular;动画: cocos-creator)都采用了组件化

组件化

组件基本结构

  • 内部状态: state,prop,attr
  • 对外接口: setAttribute,addEventListener,mount(el)

组件树

组件树

  • 实例化一个组件
    • create 函数: h(Component,attr,...children) 返回组件实例
      • 在内部创建Component的实例
      • 设置组件attr
      • 组合children 与 当前实例
  • 组件树的实例化
          <Parent attr1={val1} attr2={val2}>
            <Child1 />
            <Child2 />
          </Parent>
    
    • 先create末端节点,如Child1,Child2,再create 父节点

      问:与react子组件拿到父组件的数据

      矛盾:先创建子组件,此时父组件还没创建,应该拿不到父组件数据

      答:不矛盾:

      class CompA extends BaseComponent{
        fn1(){
      
        }
        render(){
          return <CompNode1>
            <CompNode2 onClick={()=>this.fn1())}>
            </CompNode1>
        }
      }
      
      • react 中 子组件拿到"父"组件,其中父指当前实例所在组件树上下文对应的组件,例如 CompNode2的父组件指其所在树 CompNode1>CompNode2 所在上下文对应的 CompA 组件
      • 而不是说 组件树 CompNode1>CompNode2 中 CompNode2的父节点对应的组件 CompNode1

      注意:区分父组件与组件树中组件的父节点

      每个组件都可以是组件树的上下文
      • 不是只有一个树
        //入口文件中将jsx 树mount 到#app节点上
            (<Comp1>
                <Comp2>Hello world</Comp2>
            </Comp1>).mount(document.getElementByElement('app'))
        
      • 而是每个组件都可以包含一个组件树(例如react的render 函数返回一个jsx 组件树),例如
        • Comp1
            class Comp1 extends BaseComponent{
              render(){
                const {children} = this;
                return <div class="beautiful">
                    <span>~~~</span>
                    {children}
                    <span>~~~</span>
                </div>
              }
            }
        
        • Comp2
            class Comp2 extends BaseComponent{
              render(){
                const {children} = this;
                return <span class="goodFont">
                    {children}
                </span>
              }
            }
        
        • 生成的html如下
        <div class="beautiful">
            <span>~~~</span>
            <span class="goodFont">Hello world</span>
            <span>~~~</span>
        </div>
        

组件树实现

  • jsx,例如react
    • 以jsx格式书写组件树:简单
    • 配置babel转换jsx的 plugin:@babel/plugin-transform-react-jsx
    • 实现 create 函数
  • dsl ,例如vue
    • 自定义格式:功能强大 可以定义表达式
    • 实现自定义格式的parse
    • 实例化组件并构成组件树

组件与组件通信

  • 全局的eventHub,
  • 基于dom的事件传递
    • 子组件emit事件
    • 父组件

组件封装

html的封装

  • html标签能解析为组件实例,组件实例负责创建对应dom
      //Wrapper组件负责代理一个 dom对象
      class Wrapper extends BaseComponent {
        constuctor(tag){
          ...
          this.tag = tag
        }
        created(){
          this.el = document.createElement(this.tag);
        }
        setAttribute(name,val){
          if(name === 'class'){
            for(let aClassName of val.split(\/s+\).filter(s=>s!==''))
            this.el.classList.add(aClassName);
            return;
          }
          this.el.setAttribute(name,val);
        }
      }
    
    
    • 在create函数中创建Wrapper组件实例来产生实际的dom
    function create(component,attribute,...children){
      let compInstance;
      if(isHtmlTag(component)){
        compInstance = new Wrapper(component);
      }
      ...
    }
    
    • 注意:此方式会创建额外的组件对象,不适用于实际生产场景

css 的封装

  • 全局:不封装

    • 使用全局css, 组件中设置对应的css 类.
      • 优点: 简单粗暴
      • 缺点:
        • css 没有封装到组件中。使用组件时也要引用对应的css
        • 修改样式时,可能需要修改对应组件
        • css 可能会与上下文冲突
  • 将css 转成 键值对,例如react

    //css
    .shop{border:1px solid #0f0;}
    //jsx
    <div class={shop}>...</div>
    
    //生成的代码
    <div style="border:1px solid #0f0;">...</div>
    
    • 优点: 封装性好~不会有样式冲突
    • 缺点: 本质是内联样式,不能使用 伪类,伪元素
  • vue的<style scoped>方式

    • 每个组件生成唯一的id
    • 利用组件id 防止样式冲突
    //css
    .shop{border:1px solid #0f0;}
    //jsx
    <div class="shop">...</div>
    
    //生成的代码
    //css
    .shop[comp-id='uniqId']{border:1px solid #0f0;}
    //dom
    <div class="shop" comp-id='uniqId'>...</div>
    
    • 优点: 封装性好~不会有样式冲突
    • 缺点: 暂无

附录