React是一个用于构建用户界面的Javascript库
和庞大的AngularJS不同,React专注于MVC架构中的V,即视图
reACT 重新造了个轮子: JSX
React引入了 虚拟DOM的概念
开发者操作虚拟DOM,React在必要的时候将它们渲染到真正的 DOM上
虚拟DOM是React的基石
在React中,应用程序在虚拟DOM上操作, React在每次需要渲染时,会先比较当前DOM内容和待渲染内容的差异, 然后再决定如何最优地更新DOM。
除了性能的考虑,React引入虚拟DOM更重要的意义是提供了一种一致的开发方 式来开发服务端应用、Web应用和手机端应用:
因为有了虚拟DOM这一层,所以通过配备不同的渲染器,就可以将虚拟DOM的内容 渲染到不同的平台。而应用开发者,使用JavaScript就可以通吃各个平台了。
相当棒的思路!
- createElement(type,[props],[children...]) - 在虚拟DOM上创建指定的React元素
参数type用来指定要创建的元素类型,可以是一个字符串或一个React组件类型。当使用 字符串时,这个参数应当是标准的HTML标签名称,比如:p、div、canvas等等。
参数props是可选的JSON对象,用来指定元素的附加属性,比如样式、CSS类等等。 我们在示例中简单的设置为null。
从第三个参数children开始的所有参数,都被认为是这个元素的子元素。考虑到 虚拟DOM好歹也是DOM,容易理解React需要通过这些子元素参数,让我们可以构造虚拟DOM树:
var el = React.createElement(
"ul",
null,
React.createElement("li",null,"China"),
React.createElement("li",null,"Japan"),
React.createElement("li",null,"Korea")
);
上面的例子在虚拟DOM中创建了一个具有三个li子元素的ul元素,看起来有点累。不过 想想,造一个轮子,总会付出一些代价的。
在示例中,我们简单地传入了一个文本子元素作为p元素的内容。
- render(element,container,[callback]) - 将虚拟DOM上的对象渲染到真实DOM上
参数element是我们使用createElement()方法创建的React元素,注意,不是HTML元素!
参数container是真实DOM中的HTML元素,作为渲染的目标容器,它的内容将被render()方法 的执行改变。
callback参数是可选的函数,当渲染完成或更新后被执行,通常我们不用它。
React组件
在React中定义一个组件也是相当的容易,组件就是一个 实现预定义接口的JavaScript类:
- React.createClass(meta)
参数meta是一个实现预定义接口的JavaScript对象,用来 对React组件原型进行扩展。
在meta中,至少需要实现一个render()方法,而这个方法, 必须而且只能返回一个有效的React元素。
这意味着,如果你的组件是由多个元素构成的,那么你必须在外边包一个顶层 元素,然后返回这个顶层元素。比如我们创建一个布局组件:
render:function(){
return React.createElement(
"div",null,
React.createElement("div",null,"header"),
React.createElement("div",null,"content"),
React.createElement("div",null,"footer")
);
}
注意 :你的React组件名称的首字母应当大写, 关于大小写的差异你会在后面发现。
在示例代码中,我们实现了一个液晶显示组件EzLedComp(为了更逼真一些, 定义了简单的样式,别忘了翻看一下),你应该会注意到div元素的样式类是用 className而不是class声明的,这是因为class 是JavaScript的保留字,渲染后,真实的DOM还会是:
<div class="ez-led">Hello, React!</div>
组件定义以后,和标准HTML标签一样,可以使用createElement()方法 创建元素,只是这时,第一个参数是我们定义的组件类,而不是标签名字符串:
- React.createElement(EzLedComp);
修改示例代码,定义一个两排字的液晶显示组件
轮子来了:JSX == javascript + XML
<script src="lib/react.min.js"></script>
<script src="lib/JSXTransformer.js"></script>
//JSX-->
<div>
<div className="ez-led">Hello, React!</div>
<div className="ez-led">2015-04-15</div>
</div>;
//<--JSX
- 指定脚本类型
在html文件中引入的JSX脚本,需要指定类型为text/jsx:
- //内联脚本
- <script type="text/jsx">...</script>
- //外部脚本
- <script src="a.js" type="text/jsx"></script>
- 引入JSX语法转换库
在html中使用JSX,还需要引入JSX语法转换库JSXTransform.js。 这个库加载后,将在DOM树构造完成后(通过监听DOMContentLoaded事件)处理 JSX脚本:
- 搜索DOM树中的script节点,如果其类型为text/jsx则进行后续处理
- 读取script节点的内容,将其转化为JavaScript代码
- 构造一个新的script元素,设置其内容为转化结果代码,并追加到DOM树head元素中
JSXTransform.js引入后通过全局对象JSXTransformer提供了API接口, 我们可以使用transform()方法来模拟这个语法自动转换的过程。
在右边的示例代码中,为了避免自动转换,我们将script元素的类型设置为text/jsx2, 同时为了简化DOM元素定位,给它加了一个id。
属性 : props
三种写法
//定义React组件
var EzLampComp = React.createClass({
render : function(){
//取得属性值
var onoff = this.props.onoff;
//返回React元素
if(onoff == "on")
return <span className = "ez-lamp on"></span>; //JSX
else
return <span className = "ez-lamp off"></span>; //JSX
}
});
//渲染React元素
属性
React.render(
< EzLampComp onoff="off" /> ,
document.querySelector("#content"));var myOnoff = "on"; React.render( < EzLampComp onoff={myOnoff} />, document.querySelector("#content"));
varmyOnoff="on"; React.render( React.createElement( EzLampComp, { onoff : myOnoff }), document.querySelector("#content"));
内联样式
在前面的示例中,每当需要设定元素的样式,我们总是使用样式类。但有时我们的确需要 直接在元素上声明内联样式,就像在HTML中一样:
- //HTML
<div style="width:200px;height:200px;"></div>
在React元素中声明样式,需要给出一个JSON对象,其字段对应样式名称,比如要渲染出 上面的HTML片段,需要这样:
var myStyle = {
width:"200px",
height:"200px"
};
//JSX
var e = <div style={myStyle} />;//JavaScript
var e = React.createElement(
"div",{
style : myStyle
});//render
React.render(e,...);注意1 - 对应样式名称的字段,需要使用驼峰式命名
比如:border-radius样式需要使用borderRadius来访问,而background-image 样式需要使用backgroundImage来访问。
- 注意2 - 样式名称中的供应商前缀,除ms外都需要大写首字母
对于供应商前缀(-webkit, -moz, -o, -ms),除了ms,其他都需要将首字母大写。 比如:-webkit-transition应当通过WebkitTransition来访问,然而-ms-transition 则需要通过msTransition来访问。
状态记忆 : state
很多情况下,组件实例的外观及行为通过使用props变量进行定制就可以了。 这样的组件我们称之为无状态/stateless的组件,因为在任何时刻,组件 实例的表现都仅仅取决于外部传入的props属性,与 它自身之前的表现毫无关系,即,它本身没有任何记忆。
让一个组件拥有记忆能力,意味着它不仅能对外界的刺激产生反应(通过props 传入的数据、或用户的交互事件),也能根据自身的状态对同样的刺激做出 不同的反应。
比如示例中的切换开关,它可以响应用户的点击事件,如果当前状态是关,那么它就 切换到开的状态(显示开状态的图片);而如果当前状态是开,那么它就切换到关的 状态(显示关状态的图片):
现在思考一下,使用props可以实现这个切换开关吗?
React的组件的确引入了状态机的概念,通过将组件划分为不同的状态,使组件具有 了一定的记忆能力:
- state - 组件的状态变量
每个React组件实例都有一个state变量,用来保存组件的当前状态。可以在 任何时刻使用this.state读取当前状态。
- getInitialState() - 设置组件初始状态
组件的实现者应当实现一个getInitialState()方法来设置组件的初始状态。getInitialState()方法必须返回一个JSON对象或空值null, 这意味着即使你只需要一个简单的标志作为状态,比如true或false,也要把它放到JSON对象里。
- setState(currentState) - 设置组件当前状态
尽管可以使用this.state来直接设置组件当前状态,但React要求我们使用setState()方法来进行状态设置。这是因为,setState()方法会自动 地重新渲染组件,而这通常是我们所期望的。
参数currentState是一个JSON对象,不必包含状态变量的所有字段,setState()方法会 将这个参数值与当前状态this.sate进行合并,结果作为状态变量的新值。
生命周期
在组件实例的整个周期中,React将在特定的时间点调用以下方法:
- componentWillMount() - 组件实例即将挂接(初次渲染)时被调用
这个方法在整个生命周期中只会被调用一次。
- componentDidMount() - 组件实例挂接(初次渲染)后被调用
这个方法在整个生命周期中只会被调用一次。
- componentWillReceiveProps(nextProps) - 组件实例即将设置新属性时被调用
参数nextProps表示即将应用到组件实例上的新属性值。
这个方法在初次渲染时不会被调用。在此方法内调用setState()不会引起重新渲染。
- shouldComponentUpdate(nextProps, nextState) - 组件实例即将重新渲染时被调用
参数nextProps传入即将应用到组件实例上的新属性值,参数nextState传入组件实例即将被 设置的状态值。如果这个方法返回false,那么组件实例就不会被重新渲染。除非我们明确地 知道,新的属性和状态不需要进行重新渲染,否则这个方法都应该返回true。
这个方法在初次渲染时或通过forceUpdate()方法进行渲染时不会被调用。
- componentWillUpdate(nextProps, nextState) - 组件实例即将重新渲染时被调用
这个方法在初次渲染时不会被调用。注意:不能在此方法内调用setState()。
- componentDidUpdate(prevProps, prevState) - 组件实例重新渲染后被调用
这个方法在初次渲染时不会被调用。
- componentWillUnmount() - 组件实例即将从DOM树移除时被调用
这个方法在整个生命周期中只会被调用一次。
访问DOM
在React中,有时需要_直接访问_React元素对应的DOM对象,比如读取用户的输入。 这需要两个步骤:
- 设置React元素的ref属性
如果需要在代码中访问某个React元素的DOM对象,那么首先需要设置这个React 元素的ref属性。
比如,我们需要读取文本输入框的值,那么首先给这个input元素指定ref属性:
- //JSX
<input type="text" defaultValue="beijing" ref="q"
placeholder="请输入城市拼音,如:beijing"/>
声明了React元素的ref属性之后,可以通过this.refs对象访问 这个组件,比如上面的示例中:this.refs.q指向input组件对象,你应该已经注意到, 我们为React元素设置的ref属性值,在这里被用为this.refs对象的键值。
- 获得DOM对象
在设置了React元素的ref属性后,可以使用React.findDOMNode()方法获得对应的 DOM对象:
- React.findDOMNode(component)
参数component是一个React组件对象,如前所述,我们可以通过this.refs对象获得。
如果React元素已经渲染到DOM树上,findDOMNode()方法将返回组件对象对应的DOM节 点对象,后续就可以使用标准的DOM API操作这个DOM对象了。
右边的示例实现了一个简单的天气查询组件,在文本框中输入城市名称的拼音,点击按钮 就可以获得这个城市的当前天气信息。天气数据实时从openweathermap.org网站读取,所以 可能会慢点,也可能,失效:
表单输入
在React中,表单输入元素如 input, textarea, option等,和其他标准的HTML元素 相比需要特殊的注意:
- 文本输入框
不要使用value属性设置文本输入框元素的初值,应当使用defaultValue:
//JSX
<input type = "text" defaultValue = "demo"/>复选按钮
不要使用checked属性设置复选按钮的初始选中状态,应当使用defaultChecked:
//JSX
<input type = "checkbox" defaultChecked/>单选按钮组
不要使用option元素的selected属性设置单选按钮组的初始选中状态,应当使用 select元素的defaultValue:
-
//JSX
<select defaultValue="A">
<option value="A">China</option>
<option value="B">India</option>
<option value="C">Japan</option>
</select><!DOCTYPE html> <html>
<head>
<meta charset="utf-8">
<title>EzLoginComp</title>
<script src="lib/react.min.js"></script>
<script src="lib/JSXTransformer.js"></script>
</head>
<body>
<div id="content"></div>
<script type="text/jsx">
//组件定义
var EzLoginComp = React.createClass({
auth : function(event){
var account = React.findDOMNode(this.refs.account).value,
pass = React.findDOMNode(this.refs.password).value;
alert([account,pass]);
},
render : function(){
return <div className = "ez-login">
<div className="row title">
<h1>登录</h1>
</div>
<div className="row account">
<label>用户</label>
<input type="text" defaultValue="asasassa" ref="account"/>
</div>
<div className="row pass">
<label>密码</label>
<input type="text" ref="password"/>
</div>
<div className="row remember">
<input type="checkbox" defaultChecked/>
<span>记住密码</span>
</div>
<div className="row button">
<button onClick={this.auth}>登录</button>
</div>
</div>;
}
});
//渲染
React.render(<EzLoginComp/>,document.querySelector("#content"));
</script>
</body>
</html>