走进React
React是一个构建用户界面的JavaScript库,是Facebook公司在2013年5月在github上开源的。其特点如下:
- 高效--React通过对DOM的模拟,最大程度地减少和DOM的交互。
- JSX--它是对JavaScript的扩展,在React中可以不使用JSX,但是我们建议使用之。
- 它主要是用于构建UI,很多人认为React是MVC中的V(视图)。
第一部分:React基本结构
React的基本结构大致如下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello React!</title> <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script> <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script> <script src="http://static.runoob.com/assets/react/browser.min.js"></script> </head> <body> <div id="example"></div> <script type="text/babel"> ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('example') ); </script> </body> </html>
在这个React的基本结构里,我们需要明白三点:
- 无论怎么说,React也是一个js库,这里我们首先要加载三个js文件,其中react.min.js文件时React的核心文件;react-dom.min.js提供了与DOM相关的功能,之前我们也提到了“React通过对DOM的模拟,最大程度地减少和DOM的交互”;browser.min.js 可以将JSX语法转化为Javascript语法。
- 我们可以看到<script type="text/babel">其中的babel替换了我们十分熟悉的javascript,这是因为这里使用的JSX的语法,而不是JavaScript的语法,需格外注意。
- React和Babel他俩不是一回事,其中React是JavaScript的扩展,而Babel是一个编译器。他们两个的关系就是通过Babel我们可以把JSX转化为普通的JavaScript代码。
- 当然,React JSX代码也可以放在一个独立文件中,比如我们创建了一个myReact.js文件用于编写JSX代码,我们只需要这样引用<script type="text/babel" src="./js/myReact.js">。
另外这个例子得到的结果是显示 Hello world! 而ReactDOM.render()就是一个核心方法。React表明这是由React提供的方法,DOM表明此方法与DOM密切相关,render的因为是“渲染”。所以这里的意思就是将<h1>Hello,world!</h1>渲染到html中id为example下。值得注意的是,在JavaScript中对于html标签一定要打引号的,而这里没有使用引号,这也正体现了JSX的特别之处。但是JSX究竟是什么呢?它还有什么特别的用处呢?下面我们就可以对它了解。
第二部分:JSX
JSX也是Facebook团队提出的一个语法方案,它可以在JavaScript的代码中直接使用HMTL标签来编写JavaScript对象。它是一个类似于XML的JavaScript语法扩展(JSX的全称即为JavaScriptXML),这种语法方案需要通过JSXTransformer来进行编译转换成真实可用的JavaScript代码。
React是基于组件的开发思想,它认为组件是一个完全独立的没有任何其他依赖的模块文件,一个组件中可以有自己的样式(Inline Style)和结构(JSX编写的HTML)。
React使用JSX来代替常规的JavaScript,因为使用JSX后执行地更快,用它编写模版也非常简单。
基本语法如下:
1. 使用JSX来创建一个HTML标签,首字母小写:
var link = <href="#">Hello World</a>
注意:这便是JSX语法,如果使用JavaScript,那么link变量值不可能是一个标签!!!
上面的这句代码实际上相当于调用了React.createElement('a',{href:'#'},'Hello World!');于是我们可以看到React.createElement()方法接收三个参数:‘标签的名称’,{属性的名称:属性值},'标签的内容'。(注:显然React.createElement()方法是JavaScript语法,而var link = <href="#">Hello World</a>是JSX语法。具体我们可以这样使用:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>react.js</title> <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script> <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script> <script src="http://static.runoob.com/assets/react/browser.min.js"></script> </head> <body> <div id="example"></div> <script type="text/babel"> var link = <a href="#">Hello World</a> ReactDOM.render( link, document.getElementById('example') ); </script> </body> </html>
关于ReactDOM.render()方法后面会讲到,大致上说它是一个将html元素渲染的方法。
2.使用JSX来创建一个Component(组件,后面会详细讲解),首字母大写:
var HelloWorld = <HelloWorld foo="bar"></HelloWorld>
注意:上面之所以严格要求区分首字母的大小写,这是因为这样JSX可以很简单的通过区分首字母的大小写来判断转换的是HTML标签还是自定义的Component标签。就像var a=5; 这里a显然是Number类型的,而var a = 'hello'; 显然这里的a就是string类型的,这样我们就无需再去判断了,从而更加简便。
3.JSX可以通过{变量名}这种形式来插入一个JavaScript变量。如下所示:
var variable = "World";
<a href="#"> hello {variable}!</a>
于是我们竟然看到JavaScript语法和HTML语法完美结合!
4.刚刚说了可以通过{变量名}来插入一个JavaScript变量,实际上,我们还可以通过{一个一次就能执行完的语句}在JSX中插入一个一次就能执行完成的JavaScript代码。也就是说:我们可以在JSX中使用JavaScript表达式,但表达式一定要写在{}中。如下所示:
var link = <a href="#"> Hello {conditions ? 'zzw':''}! </a>;
由于这是JSX语法,需要编译,编译之后得到的就是如下语法(显然编译得到的JS语法):
var link = React.createElement('a',{href:'#'},if(conditions){zzw});
于是我们可以得到这样的一个结论:JSX的基本语法规则为:遇到HTML标签(以<开头),就用HTML规则解析;遇到代码块(以{开头)就用JavaScript规则解析。
5.1 之前所说的都是单个标签的情况,如果有多个(语句)呢?这时应当注意:无论你的JSX代码有多长,每一段代码都只能有一个根节点,否则编译通过不了,如下所示:
var test = ( <button>点击</button> <h1>错误的语法</h1> ); var test = ( <div> <button>点击</button> <h1>正确的语法</h1> </div> );
5.2 我们更常用的是ReactDOM.render()方法(这个方法也是JSX语法),它接受两个参数:第一个参数时html元素,如<h1>哈哈</h1>;第二个参数是获取一个元素,即要将第一个参数插入的地方,如document.getElementById("#ha");具体举例如下所示:
ReactDOM.render( <div> <h1>JSX学习</h1> <h2>React 学习</h2> <p data-myattribute = "somevalue">这是一个很不错的 JavaScript 库!</p> </div> , document.getElementById('example') );
值得注意的是:这里我们添加了自定义属性data-myattribute,且添加自定义属性时一定要添加data-前缀。
6.因为JSX可以直接将HTML写在JavaScript代码中,所以对于HTML中的属性可能会和JavaScript的关键字冲突,为避免冲突,我们必须做出相应的转变,如HTML的类class需要在JSX中写成className, 而for需要转化成htmlFor。
7. 之前我们说过:React是基于组件的开发思想,它认为组件是一个完全独立的没有任何其他依赖的模块文件,一个组件中可以有自己的样式(Inline Style)和结构(JSX编写的HTML)。
而之前我们已经介绍了使用JSX来直接编写HTML,这里我们将会把样式添加到这里组件中,举例如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>react.js</title>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
<script src="http://static.runoob.com/assets/react/browser.min.js"></script>
</head>
<body>
<div ></div>
<script type="text/babel">
var myStyle = {
fontSize: 100,
color: '#FF0000'
};
ReactDOM.render(
<h1 style = {myStyle}>react学习</h1>,
document.getElementById('example')
);
</script>
</body>
</html>
这里应当知道:mystyle是一个对象,所以var myStyle = 后面用的是花括号。 定义的inline_style对象中的属性名(key)就是样式的属性,如果该属性有“-”,那么我们应当将之转化为驼峰式,如本例中的fontSize,不需要写px,对于数字,会自动加上px,当然直接写成fontSize:100px'; 虽然这里把样式写成了内联的形式(在组件内部)违反了结构、表现相分离的原则,但是它却解决了因为分离带来的组件独立性的问题。
8. JSX支持组件的命名空间。
9.代码风格建议:为了有更好的可读性,无论是单行语句还是多行语句,都建议使用()来包裹JSX语句。
第三部分:React组件
组件是React的核心部分。
React允许将代码封装成组件(component),然后像插入普通HTML标签一样,在网页中插入这个组件。而React.createClass方法就用于生成一个组件类。
先看下面的这个例子:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>react.js</title> <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script> <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script> <script src="http://static.runoob.com/assets/react/browser.min.js"></script> </head> <body> <div id="example"></div> <script type="text/babel"> var MyComponent = React.createClass({ render:function(){ return <h1> hello {this.props.name}! </h1> } }); ReactDOM.render( <MyComponent name="zhenwei"/>, document.getElementById('example') ); </script> </body> </html>
上面的代码中,变量MyComponent就是一个组件类。 模版插入<MyComponent/>(注意这里不需要写成‘开始标签+结束标签’的形式)时,就会自动生成MyComponent的一个实例,且所有的组件类都需要有自己的render方法,用于输出组件(即由render方法确定输入的组件实例的形式)。
而其中的props是指所有的属性的集合(即属性properties的缩写形式),可以看作一个数组对象,本例的name只是props中的其中一个属性,还可以有其他的属性,如下例所示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>react.js</title> <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script> <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script> <script src="http://static.runoob.com/assets/react/browser.min.js"></script> </head> <body> <div id="example"></div> <script type="text/babel"> var MyComponent = React.createClass({ render:function(){ return <h1> hello {this.props.name} , you are {this.props.gender} ! </h1> } }); ReactDOM.render( <div> <MyComponent name="zhenwei" gender="male"/> <MyComponent name="heting" gender="female"/> </div>, document.getElementById('example') ); </script> </body> </html>
最终我们可以看到效果如下:
值得注意的是:在ReactDOM.render()中的两个组件实例必须由div包裹起来,因为在第一个参数中不得有两个根元素,否则会报错,如下面的形式就是错的:
ReactDOM.render( <MyComponent name="zhenwei" gender="male"/> <MyComponent name="heting" gender="female"/> , document.getElementById('example') );
报错如下:
第四部分: React State(状态)
我们可以将React的组件看成是一个状态机,一开始有一个初始状态,这时有一个对应的UI;然后用户和页面互动之后,导致状态发生了改变,从而触发从新渲染UI。如下例所示:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>React 实例</title> <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script> <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script> <script src="http://static.runoob.com/assets/react/browser.min.js"></script> </head> <body> <div id="example"></div> <script type="text/babel"> var LikeButton = React.createClass({ getInitialState: function() { return {liked: false}; }, handleClick: function(event) { this.setState({liked: !this.state.liked}); }, render: function() { var text = this.state.liked ? 'like' : 'haven\'t liked'; return ( <p onClick={this.handleClick}> You {text} this. Click to toggle. </p> ); } }); ReactDOM.render( <LikeButton />, document.getElementById('example') ); </script> </body> </html>
显然,在上面的代码中LikeButton是一个组件,其中getInitialState方法是固有的、React给我们提供的方法,用于定义这个“状态机”的初始状态,这个状态是一个对象,且我们可以通过this.state来改变;而handleClick方法是说当我们点击时,会自动切换两种状态。
这里需要注意的是:
- state即为组件的状态,我们也可以把它和props一样看作是数组对像,其中liked仅仅是这个数组对象中的其中一个属性,也就是说,state下面还可以有更多的属性。
- state和props的区别在于,props表示那些一旦定义就不会再改变的特性;而state表示那些会随着用户互动而改变的特性。
第五部分:this.props.children
该属性不同于this.props,它表示组件的所有子元素节点。并且React提供了一个方法即React.Child.map方法来遍历子元素节点,而不用担心this.props.children是undefined类型(当组件没有元素子节点时)、还是object类型(当组件有一个元素子节点时)、或者是array类型(当组件有两个及以上的元素子节点时)。因为React.Child.map方法可以很好地处理各种情况,举例如下所示:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>React 实例</title> <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script> <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script> <script src="http://static.runoob.com/assets/react/browser.min.js"></script> </head> <body> <div id="example"></div> <script type="text/babel"> var MyComponent = React.createClass({ render:function(){ return ( <ol> { React.Children.map(this.props.children,function(child){ return <li>{child}</li>; }) } {/*凡是出现js代码块的地方,我们都需要在外围添加 {} ,来告诉React如何去解析*/} </ol> ); {/*这里的return是一个语句,所以最后要添加分号*/} } }); ReactDOM.render( <MyComponent> <span>这是react</span> <span>这是JSX</span> <span>这是JavaScript</span> </MyComponent> , document.body ); </script> </body> </html>
注意
- 我们在JSX中使用注释时,必须将注释包裹在{}中,即{/*这是一条注释*/}这样的形式。否则将会报错。
- 在return 之后,因为return的是<ol>这样的html标签,所以后面应当添加()来告诉React如何去解析,否则会报错。
- 由于render: function....这是一种类似函数声明的方式,所以最后是不可加分号的,否则会报错。这里也可以看作是创建的对象,因为该对象中只有一个属性,所以我们不需要在末尾添加符号。
- 遗留问题1:我在return ( 之后添加注释时,总会不错,不清楚是什么问题。
- 遗留问题2:我在render:function()...这个函数声明的末尾也无法成功添加注释。
第六部分:表单
用户在表单中填入内容,这就是用户跟组件的互动,所以不能用this.props读取,因为我们之前说过“this.props表示那些一旦定义,就不能再改变的特性,而this.state是会随着用户互动而产生变化的特性”。所以这里要互动,就得使用this.state来跟组<!DOCT<html>
<head> <meta charset="UTF-8" /> <title>React 实例</title> <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script> <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script> <script src="http://static.runoob.com/assets/react/browser.min.js"></script> </head> <body> <script type="text/babel"> var Input = React.createClass({ getInitialState: function() { return {value: ''}; }, handleChange: function(event) { this.setState({value: event.target.value}); }, render: function () { var value = this.state.value; var myStyle={ width:'500px', height:'50px', fontSize:25, backgroundColor:'#ccc' }; return ( <div> <input type="text" value={value} style={myStyle} onChange={this.handleChange} /> <h1>I will repeat the input:{value}</h1> </div> ); } }); ReactDOM.render(<Input/>, document.body); </script> </body> </html>
React.createClass方法中因为包含的是JavaScript代码,所以需要用{}括起来,我们也能将之看作一个对象,有三个属性,而这三个属性又都是三个方法。
其中getInitialState()方法用来设置初始状态;而handleChange()方法是用来处理change事件的,change事件被触发时,就会将event.target.value设置位当前的状态,而event.target即为触发这个事件的html元素,本例中即为input元素; 而render方法则是必须的,用来告诉浏览器如何将这个组件渲染,最终效果如下所示:
第七部分:组件的声明周期
组件的声明周期可以分为以下三个状态:
- Mounting:以插入真实DOM。 (注:mounting的中文意思即 增加,嵌入)
- Updating: 正在被重新渲染。 (注:updating的中文意思即 更新)
- Unmounting:已移出真实DOM (注:unmounting即和mounting相反)
React为每种状态提供了两种处理函数,will函数在进入状态之前使用,did函数在进入状态之后使用,三种状态可以有五种处理函数,如下所示:
- componentWillMount() 即组件插入时
- componentDidMount() 即组件插入后
- componentWillUpdate() 即组件更新时
- componentDidUpdate() 即组件更新后
- componentWillUnmount() 即组件移出时
另外React还提供了两种特殊状态的处理函数:
- componentWillReceiveProps() 即当已加载组件接受到参数时调用
- shouldComponentUpdate() 即组件判断是否重新渲染时调用
下面是一个与之相关的例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>react.js</title>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
<script src="http://static.runoob.com/assets/react/browser.min.js"></script>
<style>
.my_own_class{
font-size: 25px;
color:red;
}
</style>
</head>
<body>
<div >
<!-- This element's content will be replaced by your component. -->
</div>
<script type="text/babel">
var Hello = React.createClass({
getInitialState:function(){
alert("init");
return {
opacity:1.0,
fontSize:'22px',
color:'red'
};
{/*关键:上面返回的值我们一般可以通过this.state得到,显然,这样得到的是一个对象。*/}
},
render:function(){
return <div style={this.state} > Hello ,{this.props.name} </div>
},
componentWillMount:function(){
alert("will");
},
componentDidMount:function(){
alert('did');
var _self = this;
{/*这里之所以要把this赋值给_self,是因为这之前this指的是组件实例;而在window.setTimeout函数中时,this就指向了global全局环境,所以需要把this赋值给_self*/}
window.setTimeout(function(){
_self.setState({
opacity:0.3,
fontSize:'50px',
color:'blue'
});
},1000);
{/*显然,这一句代码被执行时,就处在了updating阶段*/}
}
});
{/* 注意:props一旦定义,一般不可再变化;然而state还是可以不断变化的。 */}
ReactDOM.render(
<Hello name="zhuzhenwei" />,
document.getElementById("container")
);
</script>
</body>
</html>
这一部分内容较为重要,我们可以参考慕课网视频,点击进入。
第八部分:事件监听、组件复用与获取dom的真实节点
我们通过下面的这个例子来学习这一部分的知识内容,如下所示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>react.js</title> <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script> <script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script> <script src="http://static.runoob.com/assets/react/browser.min.js"></script> <style> </style> </head> <body> <div id="container"> <!-- This element's content will be replaced by your component. --> </div> <script type="text/babel"> var TestClickComponent = React.createClass({ handleClick:function(event){ var tipE=React.findDOMNode(this.refs.tip); {/* 上面这行代码的作用是拿到ref="tip" 的span的真实节点,其中 this.refs.tip只是虚拟节点,只有使用React.findDOMNode()方法才可以得到其真实节点。需要格外注意大小写,因为JSX是严格区分大小写的。*/} if(tipE.style.display==='none'){ tipE.style.display='inline-block'; }else{ tipE.style.display='none'; } event.stopPropagation(); event.preventDefault(); }, render: function(){ return ( <div> <button onClick={this.handleClick}>显示|隐藏</button><span ref="tip">测试点击</span> </div> ); } }); var TestInputComponent = React.createClass({ getInitialState:function(){ return { inputContent:'' }; }, changeHandler:function(event){ this.setState({ inputContent: event.target.value }); {/*注意:this.setState()函数一定是需要在圆括号内添加花括号的,然后再添加需要设置的状态state*/} event.stopPropogation(); event.preventDefault(); }, render: function(){ return ( <div> <input type="text" onChange={this.changeHandler}/> <span>{this.state.inputContent}</span> {/*这里绑定了onChange事件(由于这个虚拟节点,所以和纯粹的html是不同的),注意区分大小写。且一旦发生了onChange事件,就会回调this.changeHandler函数*/} </div> ); } }); React.render(<div><TestClickComponent/><br/><br/><br/><TestInputComponent/></div>,document.getElementById('container')); {/* 注意:第一:React.render()函数中的第一个参数必须只能有一个顶层标签,所以我们需要使用div包裹起来。 第二:这里我们使用了三个<br/>,这是为了在实验时更加地清楚,但是这违背了最佳实践---样式和结构的完全分离,在做项目的过程中,我们千万不能这么做! */} </script> </body> </html>
需要注意的地方在代码的注释中讲述的非常清楚。关于更多如何获取真实的节点可以看这篇文章。
第九部分:JSX的转换
我们知道JSX是js的扩展,需要通过Babel编译之后才能使用。在之前的例子中,我们直接把JSX写在了<script type="text/babel"></script>之间,这样其自身就可以转换了。
当然我们也可以手动转换,然后将JavaScript代码直接写在<script type="text/javascript"></script>之间。举例说明如下:
第一步:将第八部分的JSX代码复制粘贴到 http://babeljs.io/ 中,在右半部分就可以得到js代码了。
第二步:将js代码粘贴到<script type="text/javascript"></script>之间,即可正常使用。
参考资料
http://www.ruanyifeng.com/blog/2015/03/react.html
http://www.runoob.com/react/react-tutorial.html
http://www.tuicool.com/articles/r2IJNr
推荐视频
http://www.imooc.com/u/102030/courses?sort=publish