React组件化

来源:互联网 发布:三国志11 mac 10.12 编辑:程序博客网 时间:2024/05/16 09:05

传统的组件

传统组件是结构,样式和交互分离的,分别对应html,css和js,以一个常见的tab组件为例,我们会先构建组件的基本结构

   <div>       <ul>           <li>Tab1</li>           <li>Tab2</li>           <li>Tab3</li>       </ul>   </div>   <div class="content">       <div>           tab1内容...       </div>       <div>           tab2内容...       </div>       <div>           tab3内容...       </div>   </div>

然后通过一个js的tab组件操作dom

   class Tab {      static defaultOptions = {          classPrefix: 'tabs',          activeIndex: 0,      };   }   constructor(options) {       this.options = Object.assign(Tabs.defaultOptions, options);       // 从options中定义组件属性       this.element = this.options...       // 各种dom操作事件       this._initElement();       this._initTabs();       this._bindTabs();   }   _initElement() { ... }   _initTabs() { ... }   _bindTabs() { ... }   destroy() { ... }

初始化过程十分简单,传入几个参数就可以赋予交互

   const tab = new Tabs({      element: ...,      tabs:  ...   })

组件封装的基本思路就是面向对象思想,交互基本上以dom为主,逻辑上是结构(html)上需要操作哪里,我们就操作哪里

  • 基本的封装性 通过原型继承来实现了组件的封装
  • 简单的生命周期 最明显的两个方法constructor和destroy,代表了组件的挂载和卸载过程,但其他的过程,如组件更新的生命周期并没有体现
  • 明确的数据流动 这里的数据指的是调用组件的参数,一旦确定参数的值,就会解析传进来的参数,根据参数的不同作出不同的响应,然后反映到视图上
    传统组件的主要问题是逻辑一旦复杂,就存在大量的DOM操作,开发和维护成本很高。后面前端又出现了MVC的架构,View只关心怎么输出变量,于是诞生了各种模板语言,让模板本身能承载逻辑,减轻了在js中操作DOM的逻辑,不过这种组件化实现的还是字符串拼接级别的组件化。

React的组件化

Web Component通过自定义元素的方式实现组件化,React的组件元素被描述成纯粹的JSON对象,由三部分组成——属性(props),状态(state)以及生命周期方法。

React组件构建方法

React组件有三种构建方法
- React.createClass
这种方法兼容性最好

   const Button = React.createClass({       getDefaultProps(){           return {              color: 'blue',              text: ''           }       },       render(){           const { color, text } = this.props;           return (           // 虚拟节点              <button className = {'btn-${color}'}>                 <em>text</em>              </button>           )       }   })

当另一个组件需要调用Button,就和new一个对象差不多,只需要写< Button />就会被解析成React.createElement(Button)方法来创建Button实例,这意味者在应用中调用几次Button,就会创建几次Button实例
- ES6 classes
ES6 classes的写法是通过ES6标准的类语法的方式来构建方法:

   import React, { Component } from 'react';   class Button extends Component {       constructor(props){           super(props);       }       static defaultProps = {           color: 'blue',           text: ''       }       render(){          return (             <button className={btn-${color}}>                 <em>{text}</em>             </button>          )       }   }

如果我们学过面向对象的知识,就知道继承与组合的不同,他们可以用IS-A和HAS-A来区别,在实际应用React的过程中,我们极少让子类去继承功能组件。试想在UI层面小的修改就会影响到整体交互或样式,用继承来抽象太死板了。所以在React组件开发中,常用的方式是将组件拆分到合理的粒度,用组合的方式合成业务组件。
- stateless function
使用无状态函数构建的组件称为无状态组件,只传入了props和context两个参数,也就是说它不存在state,也没有生命周期方法,无状态组件不像上述两种方法在调用时会创建新实例。

  function Button({ color = 'blue', text = 'Confirm'}){     return (        <button className={ btn-$color }>           <em>{text}</em>        </button>     )  }

用React实现Tabs组件

首先,用上面第二种es6 classes简洁的方法来初始化Tabs组件的骨架

   import React, { Component, PropTypes } from 'react';   class Tabs extends Component {      constructor(props){         super(props)      }      ...      render() {         return <div className="ui-tabs"></div>      }   }

state

在使用React之前,常见的MVC框架也非常容易实现交互界面的状态管理,比如Backbone。它们将View中与界面交互的状态解耦,一般将状态放在Model中管理。当组件内部使用库内置的setState方法时,最大的表现行为是该组件会尝试重新渲染。
值得注意的是,setState是一个异步方法,一个生命周期内所有的setState方法会合并操作。
我们再来看Tabs组件的state,我们需要维护两个可能的内部状态activeIndex和prevIndex,它们分别代表当前选中tab的索引和前一次tab选中的索引。针对这点我们有两个不同的视角
- activeIndex在内部更新 当我们切换标签的时候,可以看作组件内部的交互行为,被选择后通过回调函数返回具体选择 的索引。
- activeIndex在外部更新 当我们切换tab标签时候,可以看作是组件外部在传入具体的索引,而组件就像木偶一样被操控着。
这两种情形在React组件的设计中非常常见,第一种组件写法叫做智能组件(smart component)和木偶组件(dumb component)
我们来看下Tabs组件中初始化时的实现部分

   constructor(props){      super(props);      const currProps = this.props;      let activeIndex = 0;      // 来源核心判断      if('activeIndex' in currProps){         activeIndex = currProps.activeIndex      } else if('defaultActiveIndex' in currProps){         activeIndex = currProps.defaultActiveIndex;      }      this.state = {         activeIndex,         prevIndex: activeIndex      }   }

对于activeIndex来说,既可能来源于使用内部更新的defaultActiveIndex prop,即我们不需要外组件控制组件状态,也可能来源于需要外部更新的activeIndex prop(比如一个input选择框)

props

props是React用来让组件互相联系的一种机制,通俗说就像方法的参数一样。React的单项数据流,主要的流动管道就是props。props本身是不可变的,当我们试图改变props的原始值时,React会报出类型错误的警告,组件的props一定来自于默认属性或通过父组件传递而来,如果要渲染对props加工后的值,最简单的方法就是使用局部变量(在组件中定义的)或者直接在JSX中计算结果。
再一次仔细观察Tabs组件在Web界面的特征,会看到两个区域:切换区域和内容区域,那么我们就定义两个子组件,其中TabNav组件对应切换区域,TabContent组件对应内容区域。在Tabs组件中只显示定义内容区域的子组件集合,头部区域对应内部区域每一个TabPane组件的props,让其在TabNav组件内拼装

   <Tabs classfix={'tabs'} defaultActiveIndex={0}>      <TabPane key={0} tab={'Tab 1'}>第一个Tab里的内容</TabPane>      <TabPane key={1} tab={'Tab 2'}>第二个Tab里的内容</TabPane>      <TabPane key={2} tab={'Tab 3'}>第三个Tab里的内容</TabPane>   </Tabs>

基本的结构确定之后,只有两个props放在Tabs组件上,而其他参数直接放到TabPane组件中,由它的父组件TabContent隐式对TabPane组件拼装。渲染TabPane组件的方法如下:

   getTabPanes(){       const { classPrefix, activeIndex, panels, isActive } = this.props;       return React.children.map(panels, (child)=>{           if(!child) { return; }           //将字符串转成10进制数字           const order = parseInt(child.props.order, 10);           const isActive = activeIndex === order;           return React.cloneElement(child, {               classPrefix,               isActive,               children: child.props.children,               key: 'tabpane-${order}',           })       })   }

上述代码讲述了子组件集合是怎么渲染的,通过React.Children.map方法遍历子组件,将order(渲染顺序),isActive(是否激活tab),children(Tabs组件中传入的children)和key利用React的cloneElement方法克隆到TabPane组件中,最后返回这个TabPane组件集合。
其中React.children是React官方提供的一系列children的方法,就像js中提供给数组的方法一样。
最后,TabContent组件的render方法只需要调用getTabPanes方法即可渲染。
在TabPane组件上,除了可以传递字符串,还可以直接传入DOM节点

   <TabPane     order="0"     tab={<span><i className=""></i></span>}     第一个Tab里的内容   </TabPane>
0 0
原创粉丝点击