React前端组件化开发
2015 年 08 月 05 日
web

    最近重写WAP项目,为了使前端和后端完全分离,需要完全摒弃原始的JSP页面,采用传统的 HTML+CSS+JS(不乏也会涉及部分HTML5,CSS3, 如 StorageLocation等)作为前端基础, 后端仅需提供数据交互API, 也就几乎类似APP + API的交互模式, 这样就能清晰地独立前端和后端项目,独立部署分发。与此同时,前端展现数据将显得力不从心, 没有JSP中丰富的JSTL标签,于是,我们需要一些纯前端的展现,如模板技术,框架技术, 听闻过如AngularJS,但其略显繁重, 在了解到React之后, 决定用其来作为前端主力。

  • React特性

  • 相比其他一些JS框架,React更佳精巧,React眼里一切都是组件,让开发人员使用组件化的思想去构建网页。
  • 仅仅是UI

  • 通常我们会使用React作为MVC中的 VReact能够很轻松地融入项目中, 并不需要很复杂的技术。

  • 虚拟DOM

  • 在HTML中的DOM节点,对于节点的事件处理,UI更新,都需要我们手动监听事件,再在事件中去更新UI或其他操作, 为此React抽象出一层虚拟DOM, 使得节点事件处理和UI更新更佳轻松,而且性能更佳。

  • 数据流

  • React实现了单向数据流,以减少模板的使用,因此比传统的数据绑定更简单。

  • React几个重要概念

  • 组件(Component)

  • 在React中,都以组件为最小单元,比如一个简单的组件:
  • var HelloMessage = React.createClass({
      render: function() {
        return <div>Hello {this.props.name}</div>;
      }
    });
        
  • 上面的代码就是一个简单的React组件,采用JSX语法进行编写, 既然有了组件,我们需要将组件渲染到页面当中(即挂载到某个真实DOM节点):
  • var mountNode = document.getElementById('hello');
    React.render(<HelloMessage name="John" />, mountNode);
        
  • 组件状态(State)

  • React中的每个组件都有自己的状态,即一个Object对象。 可以通过this.state.keyName获取状态变量 keyName的值, 通过this.state.setState({keyName: '...'}) 设置状态变量keyName的值,这将引起组件被重新渲染(render)。
  • 组件规范(Component Specs)

  • render

  • 该方法是必须的。当该方法被调用时, 会检查当前组件的this.propsthis.state, 意味你可以在该方法中开始访问 propsstate。 你不应该在该方法中更新当前组件状态(State),这将导致组件重新被render, 因此,render方法应保持清晰干净,仅仅组装各种组件,设置组件属性即可。

  • getInitialState

  • 该方法在组件被挂载前调用,仅调用一次,返回Object作为组件的初始状态, 可通过this.state.keyName访问这些状态变量, 并且可以通过this.state.setState({keyName: '...'})更新组件状态。

  • getDefaultProps

  • 该方法用于返回默认属性,即当父组件没有传入属性值时,将使用这些默认值, 并且这些默认属性将被缓存,即所有该组件的实例共享该这些默认属性。

  • mixins

  • 该属性用于多个组件公用一些方法,但并不推荐,毕竟React提倡的是组件开发, 使用可见这里

  • statics

  • 顾名思义,statics用于定义组件的静态方法,如

    var MyComponent = React.createClass({
      statics: {
        customMethod: function(foo) {
          return foo === 'bar';
        }
      },
      render: function() {
      }
    });
    
    MyComponent.customMethod('bar');  // true
        
  • displayName

  • 组件名称,可用于调试。

  • 一些主要的组件生命周期方法

  • 在组件的生命周期中,不同的时间点将对应执行不同的方法。
  • componentWillMount

  • 仅调用一次,在初始化render前被执行,因此,若在该方法中调用 setState,那么render将能看到更新的状态, 并且并不会render只会执行一次,尽管执行了setState

  • componentDidMount

  • 仅调用一次,在初始化render后被执行,此时,组件已经有对应的真实DOM节点, 因此可以调用React.findDOMNode(this)获取组件的真实DOM节点, 通常会在该方法中去获取数据。

  • componentWillReceiveProps

  • 当组件被赋值新的属性时被调用,可以在该方法中根据新的属性,做一些处理,如

    componentWillReceiveProps: function(nextProps) {
      this.setState({
        likesIncreasing: nextProps.likeCount > this.props.likeCount
      });
    }
        
  • shouldComponentUpdate

  • 该方法在render前调用,若返回false,render方法不将被执行,可以在该方法做一些判断,确认是否需要render组件。

    shouldComponentUpdate: function(nextProps, nextState) {
      return nextProps.id !== this.props.id;
    }
        
  • componentDidUpdate

  • 该方法在当组件更新后且被刷新到DOM节点后被调用,不会在初始化render后调用, 可以在该方法中操作最新的DOM节点。

  • 一个简单的例子

  • 针对一个简单的登录页面,我们可以将其分为几个组件:
  • 我们将上面的页面分为几个比较细粒度的组件为:
  • <LoginPage>
    	<Header>
    		<HeaderLink icon="icon icon-left-nav" />
            	<HeaderTitle title="快捷登录" />
    	</Header>
    	<Body>
    		<Input type="text" cls="mobile" placeholder="请输入手机号" />
    	        <SendCodeBtn mobile={this.state.mobile}/>
    	        <Input type="text" cls="valid-code" placeholder="请输入您的验证码" />
    	        <Button type="button" cls="btn btn-block btn-warning" >登录</button>
    	</Body>
    </LoginPage>    
        

    可见,即使是简单的input元素,也用Input组件来封装, 也是为了能够进行一些简单的定制化,如样式,事件等。

  • 组件通信

  • 对于父子级的组件之间, 通过属性传递回调等就能较好的通信,如
  • <Button type="button" cls="btn btn-block btn-warning" onTouch={this.onLogin}>登录</button>
        
  • Button组件内部会在onTouchStart时回调其父组件传递的属性onTouch:
  • var Button = React.createClass({
      render: function() {
        return <button className={this.props.cls} onTouchStart={this.props.onTouch}>{this.props.children}</button>;
      }
    });    
        
  • 对于同级别的组件,可以通过其父组件间接通信,即组件1触发事件,调用父组件方法,该方法再调用组件2某方法。
  • 状态更新

  • 当一个页面比较复杂时,会由很多组件组装起来,建议对于业务组件,将状态抽象到上层组件中进行统一管理, 也便于页面中的组件能够通信,因为一旦状态更新,就会render, 状态太混乱,造成不必要的UI渲染,或者错误的UI渲染。在业务组件编写中, 可以尽量用props代替不必要的state, 即让组件无状态,将状态更新交由外部组件去控制。

  • 组件依赖

  • React本身并不支持组件依赖管理, 即不能像RequireJs等模块化管理工具一样,能维护模块之间的依赖关系, 比较简单的解决办法是将业务组件都concat到一个文件中,那么组件就能通过组合来调用其他组件。

  • 使用webpack管理React组件

  • React大多第三方组件是遵循npm规范, 即CommonJS规范, 于是我们可以考虑使用webpack来管理React组件的依赖关系,

  • 对于React组件管理,webpack可能基本配置就是
  • {
        entry:{
            login: ['./app/components/login/index.jsx', './app/components/login/main.jsx']
        },
        output: {
            filename: '[name].js'
        },
        module: {
            loaders: [
                { test: /\.jsx$/, loader: 'babel-loader' }
            ]
        },
        ...
    }
        
  • 既然选择了npm规范,因此我们编写的组件也就必须遵循:
  • var React = require('react');
    // require other components
    
    var Login = React.createClass({
        ...
    });
    
    module.exports = Login;
        
  • 的确React足够简洁,通过状态来自动进行UI渲染,并且通过diff算法对组件进行最小化DOM操作,使得性能可观,并且轻松进行组件化, 开发出高可复用代码,值得一试。
好人,一生平安。