React组件化

传统的组件

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

   <div>
       <ul>
           <li>Tab1li>
           <li>Tab2li>
           <li>Tab3li>
       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,就和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={><i className="">i>span>}
     第一个Tab里的内容
   TabPane>

你可能感兴趣的:(React,html,class,javascript,React)