简介
在目前看来react毫无疑问是最流行的框架,没有之一。
之前看了阮一峰写的react基础,写的非常好。但这个教程在现在看来,有些用法已经过时了。
所以我打算用ES6语法写一篇这样的文章。
说到ES6,大家都知道目前还不能兼容所有浏览器,因此我们要用到babel。把ES6代码转化为浏览器识别的ES5代码。
在这里我用webpack搭建的环境,没用过webpack的,可以看我之前写的文章。这里就不一一讲解。
一、HTML模板
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<title>react主页</title>
</head>
<body>
<div id="root"></div>
<script src="http://localhost:8080/bundle.js"></script>
</body>
</html>
这里的root是根节点,下文就会用到
二.render
render是react的最基本语法,用于将HTML转化为语言,并插入指定的节点。
用react时我们要引进两个库
react和react-dom,用ES6中的import引进,后面每次都需要引入,不然无法执行代码。
import React,{ component } from 'react';
import { render } from 'react-dom';
render(
<h1>hello world!</h1>,
document.getElementById('root')
)
运行结果如下:
hello world!
三、JSX语法
上一节的代码,HTML语言直接写在javascript语言中,不加任何引号,这就是JSX语法。
它允许HTML和JSX混写。
render(
<div>
{
names.map(function(item,index){
return <h1 key={index}>hello {item}</h1>
})
}
</div>,
document.getElementById('root')
)
上面的代码体现了JSX的基本语法规则:遇到HTML(以<开头),就用HTML解析规则;遇到代码块(以{开头),就用javascript规则解析。
上面的代码运行结果如下:
hello 张三
hello 李四
hello 王五
JSX允许在模板中插入javascript变量,如果这个这个变量是一个数组,会展开数组所有的成员。
var arr = [
<h1>hello 张三</h1>,
<h1>hello 李四</h1>
];
render(
<div>{arr}</div>,
document.getElementById('root')
)
执行结果如下:
hello 张三
hello 李四
四、组件
React允许将代码封装成组件(component),然后像插入普通HTML一样,在网页中插入组件。用class定义一个类组件。
class Hello extends Component{
render(){
return (
<h1>hello {this.props.name}</h1>
)
}
}
render(
<Hello name="张三" />,
document.getElementById('root')
)
上面代码,Hello 就是用ES6的class定义的一个类。模板插入时,就会自动生成Hello的一个实例(下文的“组件”都指组件类的实例)。所有的组件类都有自己的render方法,用于输出组件。
PS:
1.组件类的第一个字母必须大写,否则无法显示内容。比如,Hello不能写成hello。
2.组件类只能包含一个顶层标签,否则会报错。
class Hello extends Component{
render(){
return (
<h1>hello { this.props.name }</h1>
<h1>小</h1>
)
}
}
render(
<Hello name="张三" />,
document.getElementById('root')
)
上面这个会报错。
但这样不会报错,原因正在探索中
class Hello extends Component{
render(){
return (
<h1>hello { this.props.name}</h1>,
<h1>小</h1>
)
}
}
render(
<Hello name="张三" />,
document.getElementById('root')
)
运行的结果为
小
父组件<Hello name="张三" />
中的name属性传递给子组件。this.props.name用于获取父组件传递的值。
组件的用法和原生的HTML标签完全一样,可以加入任意属性。比如,<Hello name="张三" />
就是Hello组件加入的一个name属性,值为张三。组件的属性可以在组件的this.props对象上获取。比如那么属性就可以通过this.props.name属性读取。
添加组件属性,需要注意的地方是class
属性要写成className
,for
属性要写成htmlFor
。这是因为class和for是javascript的保留字。
五、this.props.children
this.props对象的属性与组件的属性一一对应,但是有一个例外就是this.props.children属性,它表示组件的所有子节点。
class NoteList extends Component{
render(){
console.log(this.props.children);
return(
<div>
{
React.Children.map(this.props.children,function(item,index){
return <h1 key={index}>{item}</h1>
})
// this.props.children.map(function(item,index){
// return <h1 key={index}>{item}</h1>
// })
}
</div>
)
}
}
render(
<NoteList>
<span>hello</span>
<span>world</span>
</NoteList>,
document.getElementById('root')
)
上面代码的NoteList 组件有两个span子节点,它们都可以通过this.props.children读取,运行结果如下:
hello
world
这里要注意的是,this.props.chidren的值有3种可能
第一种可能是:没有子节点,这时它的值为undefined
第二种可能是:只有一个子节点,这时它的值是一个对象。
第三种可能是: 有两个及以上子节点时,它的值是一个数组。
如果用下面这种方法遍历时需要注意:
this.props.children.map(function(item,index){
return <h1 key={index}>{item}</h1>
})
遍历的是数组还是对象。于是react提供了一种方法,React.Children.map,不管你是数组还是对象我都可以遍历。
六、propTypes
组件的属性接受任意值,字符串、对象、函数等等都可以。有时我们需要一种机制,验证别人使用组件时,提供的参数是否符合要求。
组件类的Proptypes属性,就是用来验证组件实例的属性是否符合要求。
class Hello extends Component{
constructor(props){
super(props);
}
render(){
return <h1>{this.props.title}</h1>
}
}
Hello.propTypes = {
title: React.PropTypes.string
}
上面的Hello组件有一个title属性。PropTypes告诉React,这个title属性时必须的,而且它的值必须是字符串,我们给它设置一个数字,看会有什么结果?
var data = 123;
render(
<Hello title={data} />,
document.getElementById('root')
)
结果呢?报错了
Warning: Failed prop type: Invalid prop `title` of type `number` supplied to `Hello`, expected `string`.
in Hello
如果写成这样,title属性就不会通过验证了,控制台会会报错。
var data = '123';
此外,defaultProps可以设置组件属性的默认值。
class Hello extends Component{
constructor(props){
super(props);
}
render(){
return <h1>{this.props.title}</h1>
}
}
Hello.defaultProps = {
title: 'hello world'
}
render(
<Hello />,
document.getElementById('root')
)
上面的代码会输出
hello world
七、获取真实的DOM节点
class Hello extends Component{
handleClick(){
this.refs.myTextInput.focus();
console.log(this.refs.myTextInput.focus());
}
render(){
return (
<div>
<input type="text" ref="myTextInput" />
<input type="button" value="Focus the text Input" onClick={this.handleClick.bind(this)} />
</div>
)
}
}
render(
<Hello />,
document.getElementById('root')
)
八、this.state
组件免不了要和用户互动,React的一大创新,就是将组件看成一个状态机,一开始有一个初始状态,然后用户互动,导致状态变化。从而触发重新触发UI。
class Hello extends Component{
constructor(props){
super(props);
this.state = {
liked: false
}
}
handleClick(){
this.setState({liked:!this.state.liked})
}
render(){
var text = this.state.liked ? '喜欢':'不喜欢';
return(
<p onClick={this.handleClick.bind(this)}>
You {text} toggle
</p>
)
}
}
render(
<Hello />,
document.getElementById('root')
)
我们逐个代码块分析
constructor(props){
super(props);
this.state = {
liked: false
}
}
this.state 初始化状态,所以第一次渲染完之后就是不喜欢
handleClick(){
this.setState({liked:!this.state.liked})
}
当我们发生点击事件时,就会取它相反的状态。每次修改状态之后就会调用render方法,就会再次渲染页面。
由于this.props和this.state都用于描述组件的特性,可能会产生混淆。一个简单的区分方法是:this.props表示哪些一旦定义就不在改变的特性,而this.state是随着用户互动而产生变化的特性。
九、表单
用户在表单填入的内容属于用户和组件的互动,所以不能用this.props.
class Hello extends Component{
constructor(props){
super(props);
this.state = {
value: 'Hello!'
}
}
handleClick(){
this.setState({liked:!this.state.liked})
}
handleChange(e){
this.setState({value: e.target.value})
}
render(){
var value = this.state.value;
return(
<div>
<input type="text" value={value} onChange={this.handleChange.bind(this)} />
<p>{value}</p>
</div>
)
}
}
render(
<Hello />,
document.getElementById('root')
)
上面的代码中,文本框中的值不能通过this.props.value读取,要定义onChange的回调函数。通e.target.value读取用户输入的值。textarea元素、select元素、radio元素都属于这种情况。
十、组件的生命周期
组件的生命周期分为3个状态:
Mounting: 已插入真实的DOM
Updating:正在被重新渲染
UnMounting: 已移除真实的DOM
React为每个状态都提供了两种状态函数,will函数在进入状态之前调用,did函数在进入状态之后调用。三种状态共计5种处理函数。
ComponentWillMount()
ComponentDidMount()
componentWillUpdate(object nextProps,object nextState)
componentDidUpdate(object prevProps,object prevState)
componentWillUnMount()
此外,React还提供两种特殊状态的处理函数。
componentWillReceivesProps(object nextProps)已加载组件收到新的参数时调用。
shouldComponentUpdate(object nextProps,object nextState)组件判断是否重新渲染时调用
class Hello extends Component{
constructor(props){
super(props);
this.state = {
opacity: 1.0
}
}
componentDidMount(){
var opacity = this.state.opacity;
this.timer = setInterval(function(){
opacity -= 0.05;
if(opacity < 0.1){
opacity = 1;
}
this.setState({opacity: opacity})
}.bind(this),100)
}
render(){
return (
<h1 style={{opacity:this.state.opacity}}>Hello {this.props.name}</h1>
)
}
}
render(
<Hello name="xiao" />,
document.getElementById('root')
)
十一、 Ajax
组件的数据来源是通过ajax请求从服务器获取,可以使用componentDidMount()方法设置ajax请求,等请求成功之后,再用this.setState方法重新渲染UI。
class Hello extends Component{
constructor(props){
super(props);
this.state = {
username: '',
lastGistUrl: ''
}
}
componentDidMount() {
$.get(this.props.source, function(result) {
var lastGist = result[0];
//if (this.isMounted()) {
this.setState({
username: lastGist.owner.login,
lastGistUrl: lastGist.html_url
});
//}
}.bind(this));
}
render(){
return(
<div>
{this.state.username}
<a href={this.state.lastGistUrl}>here</a>
</div>
)
}
}
render(
<Hello source="https://api.github.com/users/octocat/gists" />,
document.getElementById('root')
)