介绍
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读取,运行结果如下。
这里需要注意, 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`.
思考
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等其他框架,所以直接给出了完整的空框架,可以直接下载使用。
项目结构如图
实例解析
UI给出静态页面后,开发的第一个工作就是页面的分解,按照一定的规律把界面分为不同的(非常大可能有嵌套的关系)的组件,界面的分层一般都对应了数据的层次结构,甚至来说必须如此,这样数据在传递给组件时很容易操作,另外如果搭配redux使用,数据管理也更容易。关于Redux的信息,请参考Redux教程
静态页面的分解
如下面的示例界面
相应的界面代码如下
<!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"></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"></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"></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"></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"></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"></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"></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"></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:''
},
{
title: '联系人',
url: 'contactsMain',
subTitle:'总共20个',
type:0,
icon:''
}
],
salesManagement: [
{
title: '活动记录',
url: '/actionList',
subTitle:'今天有20个详情',
type:1,
icon:''
},
{
title: '数据看板',
url: '/dataBoard',
subTitle:'查看详情',
type:1,
icon:''
}
],
salesSupportLine1: [
{
title: '快速委托',
url: '/FastDelegate',
subTitle:'查看详情',
type:2,
icon:''
},
{
title: '通讯录',
url: '/addressList',
subTitle:'财拓电商',
type:2,
icon:''
}
],
salesSupportLine2: [
{
title: '任务',
url: '/taskList',
subTitle:'即将过期2个',
type:2,
icon:''
},
{
title: '客户调查',
url: 'surveyList',
subTitle:'查看详情',
type:2,
icon:''
}
],
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