reactjs教程

介绍

React.js是什么

React是由工作在Facebook开发出来的用于开发用户交互界面的JS库。其源码由Facebook和社区优秀的程序员维护,因此其背后有着非常强大的技术团队给予技术支持。React带来了很多新的东西,例如组件化、JSX、虚拟DOM等。其提供的虚拟DOM使得我们渲染组件呈现非常之快,让我们从频繁操作DOM的繁重工作之中解脱。了解React的人都知道,它做的工作更多偏重于MVC中的V层,结合其它如Flux等一起,你可以非常容易构建强大的应用。

为什么使用React.js

React.js教程中一句话简洁的概括了其作用:We built React to solve one problem: building large applications with data that changes over time.就是构建数据随时间改变的大型应用。
构建那些数据会随时间改变的大型应用,做这些,React有两个主要的特点:
简单简单的表述任意时间点你的应用应该是什么样子的,React将会自动的管理UI界面更新当数据发生变化的时候。
声明式在数据发生变化的时候,React从概念上讲与点击了F5一样,实际上它仅仅是更新了变化的一部分而已。React是关于构造可重用组件的,实际上,使用React你做的仅仅是构建组建。通过封装,使得组件代码复用、测试以及关注点分离更加容易。

另外在React官网上,通过《Why did we build React?》为什么我们要建造React的文档中还可以了解到以下四点:
React不是一个MVC框架
React不使用模板
响应式更新非常简单
HTML5仅仅是个开始

原理

Virtual DOM 虚拟DOM
传统的web应用,操作DOM一般是直接更新操作的,但是我们知道DOM更新通常是比较昂贵的。而React为了尽可能减少对DOM的操作,提供了一种不同的而又强大的方式来更新DOM,代替直接的DOM操作。就是Virtual DOM
,一个轻量级的虚拟的DOM,就是React抽象出来的一个对象,描述dom应该什么样子的,应该如何呈现。通过这个Virtual DOM去更新真实的DOM,由这个Virtual DOM管理真实DOM的更新。
为什么通过这多一层的Virtual DOM操作就能更快呢? 这是因为React有个diff算法,更新Virtual DOM并不保证马上影响真实的DOM,React会等到事件循环结束,然后利用这个diff算法,通过当前新的dom表述与之前的作比较,计算出最小的步骤更新真实的DOM。
Components 组件
在DOM树上的节点被称为元素,在这里则不同,Virtual DOM上称为commponent。Virtual DOM的节点就是一个完整抽象的组件,它是由commponents组成。
State 和 Render
React是如何呈现真实的DOM,如何渲染组件,什么时候渲染,怎么同步更新的,这就需要简单了解下State和Render了。state属性包含定义组件所需要的一些数据,当数据发生变化时,将会调用Render重现渲染,这里只能通过提供的setState方法更新数据。

基础

环境

本文的代码是基于ES5,ES6规范编写,使用了Webpack,Gulp。数据流的处理并没有使用Redux等专门的数据流处理框架,一般而言,React.js需要配合Flux之类的数据流处理框架使用,Redux可以看作是Flux思想的一种简化实现。

component

生命周期

React 组件就是一个状态机,它接受两个输入参数: this.props 和 this.state,返回一个虚拟DOM。
创建组建的方式如下:

var NotesList = React.createClass({  
  
getDefaultProps: function() {  
console.log("getDefaultProps");  
return {};  
},  
  
getInitialState: function() {  
console.log("geyInitialState");  
return {};  
},  
  
componentWillMount: function() {  
console.log("componentWillMount");  
},  
  
render: function() {  
console.log("render");  
return (  
<div>hello <strong>{this.props.name}</strong></div>  
);  
},  
  
componentDidMount: function() {  
console.log("componentDidMount");  
},  
  
componentWillRecieveProps: function() {  
console.log("componentWillRecieveProps");  
},  
componentWillUpdate: function() {  
console.log("componentWillUpdate");  
},  
componentDidUpdate: function() {  
console.log("componentDidUpdate");  
},  
  
});  
  
var list1 = React.render(  
<NotesList name='aaa'></NotesList>,  
document.getElementById("div1")  
);  
  
var list2 = React.render(  
<NotesList name='bbb'></NotesList>,  
document.getElementById("div2")  
);  

上述代码的输出是:
getDefaultProps
geyInitialState
componentWillMount
render
componentDidMount
geyInitialState
componentWillMount
render
componentDidMount

createClass

React组件是有 类 和 实例的区别的,通过 React.createClass 创建的是类

实例化

类创建完成之后,就可以进行实例化。
实例化一个类,由如下过程组成:

getInitialState: 获取 this.state 的默认值
componentWillMount: 在render之前调用此方法,在render之前需要做的事情就在这里处理
render: 渲染并返回一个虚拟DOM
componentDidMount: 在render之后,react会使用render返回的虚拟DOM来创建真实DOM,完成之后调用此方法。

其中有几个点需要注意:

1,this.state 只存储原始数据,不要存储计算后的数据
比如 this.state.time = 1433245642536,那么就不要再存一个 this.state.timeString = ‘2015-06-02 19:47:22’ 因为这个是由 time 计算出来的,其实他不是一种state,他只是 this.state.time 的一种展示方式而已。
这个应该放在render中计算出来:
<span>time: {this.formatTime(this.state.time)}</span>

2,componentWillMount 用来处理render之前的逻辑,不要在render中处理业务逻辑。
render就是一个模板的作用,他只处理和展示相关的逻辑,比如格式化时间这样的,如果有业务逻辑,那么要放在 componentWillMount 中执行。
所以render中一定不会出现改变 state 之类的操作。

3,render返回的是虚拟DOM
所谓虚拟DOM,其实就是 React.DOM.div 之类的实例,他就是一个JS对象。render方法完成之后,真实的DOM并不存在。

4,componentDidMount 中处理和真实DOM相关的逻辑
这时候真实的DOM已经渲染出来,可以通过 this.getDOMNode() 方法来使用了。典型的场景就是可以在这里调用jquery插件。

更新

当组件实例化完成,就进入了存在期,这时候一般会响应用户操作和父组件的更新来更新视图。

componentWillRecieveProps: 父组件或者通过组件的实例调用 setProps 改变当前组件的 props 时调用。
shouldComponentUpdate: 是否需要更新,慎用
componentWillUpdate: 调用 render方之前
render:
componentDidUpdate: 真实DOM已经完成更新。

销毁

componentWillUnmount

组合与通信

组合

React组件是无法继承的,即不存在 React.extend 之类的方法可以定义一个子类。
React推崇通过组合的方式来组织大规模的应用。
所以所谓父子组件,就和DOM中的父子元素一样,他们是有从属关系,但没有继承关系。
比如:

var Team = React.createClass({   
 render: function() {   
     return <div>Team onwer is: <People name={this.props.name}></People></div>;   
 }});
var People = React.createClass({  
  render: function() {    
    return <span>{this.props.name}</span>;   
 }});

上述代码创建了两个组建,分别是Team和People,其中Team在render方法中嵌入了People组建,这样,People组建就成了Team的子组件,而Team为父组件。

父子组件通信

组合起来很简单,那么父子组件怎么通信呢。
你可能会想通过事件来通信。React 竟然没有提供一个自定义事件,它的事件仅仅用来处理DOM事件,并没有组件的自定义事件。
比如一个子组件是无法通过 trigger(“hungry”) 之类的事件来通知父组件的。当然,你可以通过mixin之类的方式来给组件提供事件能力。
那么这样,就只有一种方式可以让子组件向父组件发送消息,就是 this.props 属性。

var Team = React.createClass({   
 getSubComponentInfo(){   
     alert(this.refs.subComponent.subComponentInfo()) 
   },     
   parentInfo(){   
     return 'info from parent!' 
   },   
 render: function() {  
      return <div>Team onwer is: <People name={this.props.name} parentInfo={this.parentInfo} ref="subComponent"></People></div>;  
  }});
var People = React.createClass({  
  getParentComponentInfo(){   
     alert(this.props.parentInfo())  
  },   
     subComponentInfo(){   
     return 'info from sub!' 
   },    
    render: function() {  
      return <span>{this.props.name}</span>;  
  }});

上述代码中,父组件把自己的parentInfo方法作为子组件的属性传递给子组件

parentInfo={this.parentInfo}

子组件试图调用父组件的方法,可以直接通过属性拿到此方法调用

this.props.parentInfo()

父组件调用子组件中的方法更简单,直接给子组件设置一个ref属性,然后通过这个ref属性拿到子组件,然后直接调用子组件中的方法即可。

思考

组件是reactjs中对于视图分割的最小单元,每一个界面都是由多层的(当然,也可以是一层)的组件嵌套而成的。

props与state

组件的用法与原生的 HTML 标签完全一致,可以任意加入属性,比如 <HelloMessage name="John">,就是 HelloMessage 组件加入一个 name属性,值为 John。组件的属性可以在组件类的 this.props对象上获取,比如 name属性就可以通过 this.props.name读取,值为John。

添加组件属性,有一个地方需要注意,就是 class属性需要写成 className,for属性需要写成 htmlFor,这是因为 class和 for是 JavaScript 的保留字。

this.props
对象的属性与组件的属性一一对应,但是有一个例外,就是 this.props.children
属性。它表示组件的所有子节点。

var NotesList = React.createClass({
render: function() {
 return ( 
   <ol> {
          React.Children.map(this.props.children, function (child) {
               return <li>{child}</li>; 
           }) 
           } </ol>
      );
 }});
ReactDOM.render( 
  <NotesList>
      <span>hello</span>
      <span>world</span>
   </NotesList>, 
document.body);

上面代码的 NoteList组件有两个 span子节点,它们都可以通过 this.props.children读取,运行结果如下。

bg2015033110.png

这里需要注意, this.props.children的值有三种可能:如果当前组件没有子节点,它就是 undefined;如果有一个子节点,数据类型是 object;如果有多个子节点,数据类型就是 array。所以,处理 this.props.children的时候要小心。React 提供一个工具方法 React.Children 来处理 this.props.children。我们可以用 React.Children.map来遍历子节点,而不用担心 this.props.children的数据类型是 undefined还是 object。更多的 React.Children的方法,请参考官方文档

实际开发中,组件是不可能一成不变的(这基本相当于数据是不可能一成不变的),举一个很简单的例子,一个列表要展示遗传从服务端获取的数据,那么,获取之前列表是为空的,获取数据之后列表是有数据的,那么列表加载数据前后对应两种状态,React 的一大创新,就是将组件看成是一个状态机,一开始有一个初始状态,然后用户互动,导致状态变化,从而触发重新渲染 UI 。

组件的使命周期中有一个getInitialState方法,这个方法要返回一个对象,这个对象就是组件初始化时的初始状态,这个对象中的值可以直接通过this.state[.stateName]的形式直接引用。改变状态可以调用setState方法,此方法必须要传入一个对象,也可以额外传入一个function对象,传入的对象的属性会添加到组件的状态中,需要注意的是,传入的对象并不会覆盖原有对象,如果有同名的属性,则原有属性会被新的值替代。传入的function对象会在状态更新成功后执行。

const ClientMain = React.createClass({
  contextTypes: {
    router: React.PropTypes.object.isRequired
  },
  
  getInitialState(){
    return {name:'jhon',age:20}
  },

  componentDidMount(){
    let self = this
    setTimeout(function () {
      self.setState({name:'tinker'})
    },2000)
  },
  
  render(){
    //...省略代码...
  }
})

上述代码中,组件初始化时状态中保存了两个属性,name和age,值分别是jhon和20,在2秒后,状态被修改了,name变为tinker,age仍然是20.

下面的代码是state在应用中使用的一个简单场景。


const ClientItem = React.createClass({
  contextTypes: {
    router: React.PropTypes.object.isRequired
  },

  onClickItem(){
  },

  handleTime(time){
    if(time.length >= 19){
      return time.substring(5,16)
    }
    return time
  },

  render(){
    let self = this;
    return(
      <div className="twoitem bline" onClick={self.onClickItem}>
        <span className="itemname">{self.props.name}</span>
        <span className="itemsubname">{self.props.time?
('创建时间: ' + self.handleTime(self.props.time)) : ''}</span>
      </div>
    )
  }
})

const ClientMain = React.createClass({
  contextTypes: {
    router: React.PropTypes.object.isRequired
  },

  componentDidMount(){
    this.getgetContactsList()
  },

  // 获取客户列表
  getContactsList(){
    HttpUtil.post(function (result,data) {
      if (result){
        self.setState({clientItems:data.list})
      }
    })
  },
  
  render(){
    let self = this;
    let clientItem = [];
    if (this.state && this.state.clientItems){
      if (this.state.clientItems.length > 0){
        clientItem = this.state.clientItems.map((item,index) =>{
          return(
            <ClientItem saveState={self.saveState} key={index} name={item.customerName}
 time={item.createTime} userId={item.id}/>
          )
        })
      }
    }
    return(
      <div>
        <div className={clientItem.length == 0?"hide":"itemgroup p15"}>
          {clientItem}
        </div>
        <div className={clientItem.length == 0?"unBacklog":"hide"}>
          暂无数据
        </div>
      </div>
    )
  }
})

export default ClientMain;

分析上述代码的执行过程,开始的时候,ClientMain 加载的时候,在第一次加载时,在render方法中,由于本身并没有初始的state,所以this.state && this.state.clientItems为false,则clientItem长度为0,则显示暂无数据,在post请求完成后,如果有有效数据,则
this.state && this.state.clientItems为true,clientItem中被push进有效数据,则数据会在列表中展示。

属性的验证

组件的属性可以接受任意值,字符串、对象、函数等等都可以。有时,我们需要一种机制,验证别人使用组件时,提供的参数是否符合要求。

组件类的PropTypes属性,就是用来验证组件实例的属性是否符合要求

var MyTitle = React.createClass({ 
propTypes: { title: React.PropTypes.string.isRequired, }, 
render: function() {
 return <h1> {this.props.title} </h1>; 
}});

上面的Mytitle组件有一个title属性。PropTypes告诉 React,这个 title属性是必须的,而且它的值必须是字符串。现在,我们设置 title属性的值是一个数值。

var data = 123;
ReactDOM.render(
 <MyTitle title={data} />,
 document.body);

这样一来,title属性就通不过验证了。控制台会显示一行错误信息。

Warning: Failed propType: Invalid prop `title` of type `number` supplied to `MyTitle`, expected `string`.

更多的PropTypes设置,可以查看官方文档网上资源

思考

React组件对象是有setProps方法的,但是官方已经不推荐使用此方法,所以这个方法在未来版本中是有可能废除的。一般来说,React应用的界面设计是多层的,顶层的组件包含状态,而底层的组件则是不包含状态的,它只渲染传进来的props数据,这样所有数据的维护都统一由顶层的组件维护,这就加强了可维护性,数据也更好追踪(但是不使用Redux之类的数据流管理插件,其实还是不容易追踪),而state的设计也是基本和组件的分层相对应的。

mixins

mixins是定义不同的组件使用到的共同的方法而存在的。你可以在mixins里定义一些无关业务逻辑的方法,例如fackbook官方推荐的对于setTimeout的处理

var SetTimeoutMixin = { 
   componentWillMount: function() {   
     this.timeouts = [];  
  },   
 setTimeout: function() {   
     this.timeouts.push(setTimeout.apply(null, arguments));   
 },  
  clearTimeouts: function() {     
   this.timeouts.forEach(clearTimeout);  
  }, 
   componentWillUnmount: function() {  
      this.clearTimeouts(); 
   }};
export default SetTimeoutMixin;

上述代码中的生命周期方法不会覆盖组件中同名的生命周期方法,而是会在组件的同名生命周期方法之前执行,例如,组件在加载时,会先执行mixin中componentWillMount方法,再执行组件本身的componentWillMount方法。示例代码中新建 了一个timeouts数组,setTimeout方法中,在数组里存放所有的要执行的timeout对象,clearTimeouts方法中遍历数组,调用clearTimeout清除已保存的对象,componentWillUnmount方法保证了在组件卸载后之前存放的timeout一定被清除掉了。

自定义组件引入mixin只需要像如下代码一样在createClass时添加到mixins数组里就OK

var SetTimeoutMixin = require('...');
React.createClass({ 
mixins: [SetTimeoutMixin ],
 render: function() { 
return <div className={this.props.className}>foo</div>; 
}});

获取节点

组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM)。只有当它插入文档以后,才会变成真实的 DOM 。根据 React 的设计,所有的 DOM 变动,都先在虚拟 DOM 上发生,然后再将实际发生变动的部分,反映在真实 DOM上,这种算法叫做 DOM diff ,它可以极大提高网页的性能表现。
但是,有时需要从组件获取真实 DOM 的节点,这时就要用到 ref属性。

var MyComponent = React.createClass({
  handleClick: function() {
    this.refs.myTextInput.focus();
  },
  render: function() {
    return (
      <div>
        <input type="text" ref="myTextInput" />
        <input type="button" value="Focus the text input" onClick={this.handleClick} />
      </div>
    );
  }
});

ReactDOM.render(
  <MyComponent />,
  document.getElementById('example')
);

上面代码中,组件 MyComponent的子节点有一个文本输入框,用于获取用户的输入。这时就必须获取真实的 DOM 节点,虚拟 DOM 是拿不到用户输入的。为了做到这一点,文本输入框必须有一个 ref属性,然后 this.refs.[refName]就会返回这个真实的 DOM 节点。需要注意的是,由于 this.refs.[refName]属性获取的是真实 DOM ,所以必须等到虚拟 DOM 插入文档以后,才能使用这个属性,否则会报错。上面代码中,通过为组件指定 Click事件的回调函数,确保了只有等到真实 DOM 发生 Click事件之后,才会读取 this.refs.[refName]属性。
React 组件支持很多事件,除了 Click事件以外,还有 KeyDown、Copy、Scroll等,完整的事件清单请查看官方文档

因为React是可以和其他的JS框架混合使用的,所以可以在React中使用其它插件获取DOM的方式,例如Jquery获取DOM的方式。

项目搭建

建议参考官方入门教程推荐的项目创建方式

项目框架

由于项目用到了webpack,gulp,react-router等其他框架,所以直接给出了完整的空框架,可以直接下载使用
项目结构如图

1.png

实例解析

UI给出静态页面后,开发的第一个工作就是页面的分解,按照一定的规律把界面分为不同的(非常大可能有嵌套的关系)的组件,界面的分层一般都对应了数据的层次结构,甚至来说必须如此,这样数据在传递给组件时很容易操作,另外如果搭配redux使用,数据管理也更容易。关于Redux的信息,请参考Redux教程

静态页面的分解

如下面的示例界面

QQ图片20161129093023.png

相应的界面代码如下

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="format-detection" content="telephone=no">
<meta name="description" content="">
<meta http-equiv="x-dns-prefetch-control" content="on">

<title>CRM</title>
<link href="../css/css.css" rel="stylesheet" type="text/css">
</head>

<body>
<section class="crmhome mt10">
        <h1 class="green">客户关系</h1>
        <div class="mainarea">
              <a href="customer_list.html">
              <div class="left w44 rline p5p">
                  <i class="iconfont left green crmhomeicon">&#xe60a;</i>
                  <div class="left hometext">
                      <span class="hometitle">客户</span>
                      <span class="homesubtitle">总共 50 个</span>
                  </div>
              </div>
              </a>
              <div class="left w44 p5p">
                  <i class="iconfont left green crmhomeicon">&#xe60b;</i>
                  <div class="left hometext">
                      <span class="hometitle">联系人</span>
                      <span class="homesubtitle">总共 150 个</span>
                  </div>
              </div>
        </div>
</section>
<section class="crmhome mt10">
        <h1 class="purple">销售管理</h1>
        <div class="mainarea">
              <div class="left w44 rline p5p">
                  <i class="iconfont left purple crmhomeicon">&#xe60c;</i>
                  <div class="left hometext">
                      <span class="hometitle">活动记录</span>
                      <span class="homesubtitle">今天有20个更新</span>
                  </div>
              </div>
              <div class="left w44 p5p">
                  <i class="iconfont left purple crmhomeicon">&#xe60d;</i>
                  <div class="left hometext">
                      <span class="hometitle">数据看板</span>
                      <span class="homesubtitle">查看详情</span>
                  </div>
              </div>
        </div>
</section>
<section class="crmhome mt10">
        <h1 class="yellow">销售支持</h1>
        <div class="mainarea bline">
              <div class="left w44 rline p5p">
                  <i class="iconfont left yellow crmhomeicon">&#xe611;</i>
                  <div class="left hometext">
                      <span class="hometitle">快速委托</span>
                      <span class="homesubtitle">查看详情</span>
                  </div>
              </div>
              <div class="left w44 p5p">
                  <i class="iconfont left yellow crmhomeicon">&#xe612;</i>
                  <div class="left hometext">
                      <span class="hometitle">通讯录</span>
                      <span class="homesubtitle">财拓电子商务</span>
                  </div>
              </div>
        </div>
        <div class="mainarea">
              <div class="left w44 rline p5p">
                  <i class="iconfont left yellow crmhomeicon">&#xe61d;</i>
                  <div class="left hometext">
                      <span class="hometitle">任务</span>
                      <span class="homesubtitle">即将过期 2 个</span>
                  </div>
              </div>
              <div class="left w44 p5p">
                  <i class="iconfont left yellow crmhomeicon">&#xe613;</i>
                  <div class="left hometext">
                      <span class="hometitle">客户调查</span>
                      <span class="homesubtitle">查看详情</span>
                  </div>
              </div>
        </div>
</section>
</body>
</html>

界面可以看成由三个列表组成,第一个列表和第二个各有两个元素,第三个列表有四个元素。为了减少组件层次,最外层没有设置一个容器存放三个列表的形式,而是把三个列表看成一个整体,最外层组件render方法返回如下

return(
            <div>
                <div style={{height:"10px"}}></div>
                <section className="crmhome">
                    <h1 className="green">客户关系</h1>
                    <div className="mainarea">
                        {cusRel}
                    </div>
                </section>
                <section className="crmhome mt10">
                    <h1 className="purple">销售管理</h1>
                    <div className="mainarea">
                        {salMan}
                    </div>
                </section>
                <section className="crmhome mt10">
                    <h1 className="yellow">销售支持</h1>
                    <div className="mainarea bline">
                        {salSup1}
                    </div>
                    <div className="mainarea">
                        {salSup2}
                    </div>
                </section>
            </div>
        )

其中的cusRel,salMan,salSup1,salSup2是列表元素数组,它们生成的方式如下

let self = this;
        let cusRel;
        cusRel = String.customerRelationship.map(function(item,index){
            return(
                <MainItem key={index} position={index} icon= {item.icon} url={item.url}
                          title={item.title} subTitle={self.getHint(0,index)} type={item.type}/>
            )
        })
        let salMan;
        salMan = String.salesManagement.map(function (item,index) {
            return(
                <MainItem key={index} position={index} icon= {item.icon} url={item.url}
                          title={item.title} subTitle={self.getHint(1,index)} type={item.type}/>
            )
        })
        let salSup1;
        salSup1 = String.salesSupportLine1.map(function (item,index) {
            return(
                <MainItem
                    key={index} position={index} icon= {item.icon} url={item.url}
                    title={item.title} subTitle={self.getHint(2,index)} type={item.type}/>
            )
        })
        let salSup2;
        salSup2 = String.salesSupportLine2.map(function (item,index) {
            return(
                <MainItem key={index} position={index} icon= {item.icon} url={item.url}
                          title={item.title} subTitle={self.getHint(3,index)} type={item.type}/>
            )
        })

代码中的MainItem就是列表中单个元素组件,其中包含一个图标,一个标题和一行文字,String中的数据是每个元素中需要的数据

customerRelationship: [
        {
            title: '客户',
            url: 'client',
            subTitle:'总共50个',
            type:0,
            icon:'&#xe60a;'
        },
        {
            title: '联系人',
            url: 'contactsMain',
            subTitle:'总共20个',
            type:0,
            icon:'&#xe60b;'
        }
    ],

    salesManagement: [
        {
            title: '活动记录',
            url: '/actionList',
            subTitle:'今天有20个详情',
            type:1,
            icon:'&#xe60c;'
        },
        {
            title: '数据看板',
            url: '/dataBoard',
            subTitle:'查看详情',
            type:1,
            icon:'&#xe60d;'
        }
    ],

    salesSupportLine1: [
        {
            title: '快速委托',
            url: '/FastDelegate',
            subTitle:'查看详情',
            type:2,
            icon:'&#xe611;'

        },
        {
            title: '通讯录',
            url: '/addressList',
            subTitle:'财拓电商',
            type:2,
            icon:'&#xe612;'
        }
    ],
    salesSupportLine2: [
        {
            title: '任务',
            url: '/taskList',
            subTitle:'即将过期2个',
            type:2,
            icon:'&#xe61d;'
        },
        {
            title: '客户调查',
            url: 'surveyList',
            subTitle:'查看详情',
            type:2,
            icon:'&#xe613;'
        }
    ],

MainItem的代码如下

const MainItem = React.createClass({
    contextTypes: {
        router: React.PropTypes.object.isRequired
    },

    onClickItem(){
        let self = this;
    },
    getType(){
        if (this.props.type || this.props.type == 0){
            if(this.props.type == 0){
                return 'iconfont left green crmhomeicon'
            }else if (this.props.type == 1){
                return 'iconfont left purple crmhomeicon'
            }else if (this.props.type == 2) {
                return 'iconfont left yellow crmhomeicon'
            }
        }
    },
    render(){
        let remainder = this.props.position % 2;
        return(
            <a onClick={this.onClickItem}>
                <div className={(remainder == 0)?"left w44 rline p5p":"left w44 p5p"}>
                    <i className={this.getType()} dangerouslySetInnerHTML={{__html: this.props.icon}}/>
                    <div className="left hometext">
                        <span className="hometitle">{this.props.title?this.props.title:''}</span>
                        <span className="homesubtitle">{this.props.subTitle?this.props.subTitle:''}</span>
                    </div>
                </div>
            </a>
        )
    }
})

这样页面在加载的时候就会出现所要的效果,最后把真个界面的完整代码奉上

import React from 'react';
import String from '../Helper/Strings';

/**
 * 主页面的每一个列表单元
 *
 * 属性
 *
 *
 * url 要跳转的界面路径
 * key 在列表中的索引
 * title 显示的标题
 * subTitle 显示的子标题
 */
const MainItem = React.createClass({
    contextTypes: {
        router: React.PropTypes.object.isRequired
    },

    onClickItem(){
        let self = this;
    },
    getType(){
        if (this.props.type || this.props.type == 0){
            if(this.props.type == 0){
                return 'iconfont left green crmhomeicon'
            }else if (this.props.type == 1){
                return 'iconfont left purple crmhomeicon'
            }else if (this.props.type == 2) {
                return 'iconfont left yellow crmhomeicon'
            }
        }
    },
    render(){
        let remainder = this.props.position % 2;
        return(
            <a onClick={this.onClickItem}>
                <div className={(remainder == 0)?"left w44 rline p5p":"left w44 p5p"}>
                    <i className={this.getType()} dangerouslySetInnerHTML={{__html: this.props.icon}}/>
                    <div className="left hometext">
                        <span className="hometitle">{this.props.title?this.props.title:''}</span>
                        <span className="homesubtitle">{this.props.subTitle?this.props.subTitle:''}</span>
                    </div>
                </div>
            </a>
        )
    }
})

const Main = React.createClass({

    componentDidMount(){

    },

    getHint(type,index){
        let self = this;
        if (!self.state){
            return ''
        }
        switch (type){
            case 0:
                if (index== 0){
                    if (typeof(self.state.customer) != 'undefined'){
                        return '总共' + self.state.customer + '个';
                    }
                    return '';
                } else if (index == 1){
                    if (typeof(self.state.contacts) != 'undefined'){
                        return '总共' + self.state.contacts + '个';
                    }
                    return '';
                }
                break
            case 1:
                if (index== 0){
                    if (typeof(self.state.activityUpdateCount) != 'undefined'){
                        return '今天有' + self.state.activityUpdateCount + '个更新';
                    }
                    return '';
                } else if (index == 1){
                    return '查看详情';
                }
                break
            case 2:
                if (index== 0){
                    return '查看详情';
                } else if (index == 1){
                    return self.state.companyName;
                }
                break
            case 3:
                if (index== 0){
                    if (typeof(self.state.missionSoonComplate) != 'undefined'){
                        return '待办' + self.state.missionSoonComplate + '个';
                    }
                    return '';
                } else if (index == 1){
                    return '查看详情';
                }
                break
        }
    },

    render(){
        let self = this;
        let cusRel;
        cusRel = String.customerRelationship.map(function(item,index){
            return(
                <MainItem key={index} position={index} icon= {item.icon} url={item.url}
                          title={item.title} subTitle={self.getHint(0,index)} type={item.type}/>
            )
        })
        let salMan;
        salMan = String.salesManagement.map(function (item,index) {
            return(
                <MainItem key={index} position={index} icon= {item.icon} url={item.url}
                          title={item.title} subTitle={self.getHint(1,index)} type={item.type}/>
            )
        })
        let salSup1;
        salSup1 = String.salesSupportLine1.map(function (item,index) {
            return(
                <MainItem
                    key={index} position={index} icon= {item.icon} url={item.url}
                    title={item.title} subTitle={self.getHint(2,index)} type={item.type}/>
            )
        })
        let salSup2;
        salSup2 = String.salesSupportLine2.map(function (item,index) {
            return(
                <MainItem key={index} position={index} icon= {item.icon} url={item.url}
                          title={item.title} subTitle={self.getHint(3,index)} type={item.type}/>
            )
        })
        return(
            <div>
                <div style={{height:"10px"}}></div>
                <section className="crmhome">
                    <h1 className="green">客户关系</h1>
                    <div className="mainarea">
                        {cusRel}
                    </div>
                </section>
                <section className="crmhome mt10">
                    <h1 className="purple">销售管理</h1>
                    <div className="mainarea">
                        {salMan}
                    </div>
                </section>
                <section className="crmhome mt10">
                    <h1 className="yellow">销售支持</h1>
                    <div className="mainarea bline">
                        {salSup1}
                    </div>
                    <div className="mainarea">
                        {salSup2}
                    </div>
                </section>
            </div>
        )
    }
})

export default Main;

路由与界面的跳转

由于项目引入了React-Router,Router控制着界面的跳转,所以,界面的配置只需要在Router里配置就好,项目目录下有一个App.jsx的文件,其中的Router节点就是用来配置路由的

<Router history={hashHistory}>
        <Route path='/' component={App}>
            <IndexRoute component={HomePage}/>
            <Route path='homePage' component={HomePage}/>
            <Route path='todoApp' component={TodoApp}/>
        </Route>
    </Router>

其中的IndexRoute是应用进入时的默认界面,Route节点对应就是应用中的一个界面,代码中,说明应用中只有两个界面,HomePage和TodoApp,其中HomePage是进入应用后第一个展示的界面。访问Route中的path就是访问时页面对应的路径,如代码中TodoApp的path是todoApp,那么在浏览器中打出地址http://localhost:3939/#/todoApp可以访问此界面,代码中的跳转也需要使用配置的path跳转。
代码中跳转的方法很简单,如果从A组件中跳转到下一个界面,那么在A组件中,要引入下面的代码

contextTypes: {    router: React.PropTypes.object.isRequired}

然后在组件中通过如下形式代码就可跳转

this.context.router.push('todoApp')

为完整形式如下

import React from 'react';

const HomePage = React.createClass({
    contextTypes: {
        router: React.PropTypes.object.isRequired
    },

    getInitialState(){
        return {
            info: {
                phoneQtyAll: 2,
                regQtyAll: 3,
                regQtyAllTime: 4,
                tradeWeightAll: 6,
                visitQtyAll: 7
            }
        }
    },

    gotoNextPage(){
        this.context.router.push('todoApp')
    },

    render(){
        return (
            <button onClick={this.gotoNextPage}>跳转按钮</button>
        )
    }
});


export default HomePage;

这样,从HomePage中就可以跳转到TodoApp界面

界面间跳转携带数据

由于项目中并没有使用Redux之类的数据流框架,所以数据需要开发者自己管理了,设想从A界面跳转到B界面,数据的流转方式主要是A存取,B读取,B消费后删除。所以数据存储使用sessionStorage。

参考
http://www.jianshu.com/p/ae482813b791
http://blog.csdn.net/lihongxun945/article/category/5195241
http://www.reactjs.cn/react/
React官方教程
http://www.reactjs.cn/react/docs/getting-started-zh-CN.html

推荐阅读更多精彩内容