JSX 知识准备
JSX 并不是一门全新的语言,仅仅是一个语法糖,允许开发者在javascript中编写XML语言。这样使用JavaScript来构建组件以及组件之间关系的应用,在代码层面显得更加清晰,而不再是使用JavaScript操作DOM来创建组件以及组件之间的嵌套关系。
作为是React的核心部分,JSX使用XML标记的方式直接声明页面。在JavaScript代码里直接写XML的语法,每一个XML标签都会被JSX转换工具转换成纯JavaScript代码。HTML直接写到JavaScript中不加任何分号,这就是JSX的语法奥义
JSX环境准备
JSX必须借助ReactJS环境才能运行。在编写JSX代码之前,需要先加载ReactJS文件,比react.js.光有ReactJs还不行,还需要加载JSX解析器。下面大家一起准备好一个最基础的ReactJS环境
创建一个test.html文件
-
在
<head>
标签中引入如下文件<script src="http://cdn.bootcss.com/react/15.2.0/react.js"></script> <script src="http://cdn.bootcss.com/react/15.2.0/react-dom.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
这样环境就搭建好了,然后按照惯例,双手奉上一个Hello World 程序。
<body>
<div id="example"></div>
<script type="text/babel">
var HelloComponent = React.createClass({
render: function(){
return <span>Hello World !</span>
}
});
ReactDOM.render(
<HelloComponent />,
document.getElementById('example')
);
</script>
</body>
运行结果
如上面代码,我们在JavaScript中书写HTML标签时,不再像以前那样作为字符串用引号引起来,而是想在XML文件中书写一样,直接写即可。
除了<span>Hello World !</span>
这种直接使用的HTML标签外,还有一个<HelloComponent />
标签。这个是ReactJS创建的组件类标签,通过这种方式,把创建的HelloComponent组件引用进来。ReactJS约定,自定义的组件标签首字母一定要大些,用来区分是组件标签还是HTML标签。
注意几点
- script 标签的 type 属性为 text/babel,这是React 独有的 JSX 语法,跟 JavaScript 不兼容。凡是在页面中直接使用 JSX 的地方,都要加上 type="text/babel"。
- 一共用了三个库: react.js 、react-dom.js 和 browser.min.js ,它们必须首先加载。其中,react.js 是 React 的核心库,react-dom.js 是提供与 DOM 相关的功能, browser.min.js的作用是将 JSX 语法转为 JavaScript 语法。
- 将 JSX 语法转为 JavaScript 语法,这一步很消耗时间。现在前端项目,都会使用前端工程化,不会直接在html页面中直接写js代码,写好的js代码都会使用工具进行编译压缩等。这样的话,我们的jsx也会通过编译直接转化成js语法,让浏览器直接使用。
JSX执行JavaScript表达式
JSX使用{}来执行JavaScript表达式。
var arr = [
<h1>Hello world!</h1>,
<h2>React is awesome</h2>,
];
ReactDOM.render(
<div>{arr}</div>,
document.getElementById('example')
);
JSX定义属性&&样式使用
HTML中可以通过标签上的属性来改变当前元素的样式,当然,这在JSX中也是可以的,只不过JSX在使用行内样式的时候是有缺陷的。。使用{{}}而不是引号的方式,如下
React.render(
<div style={{color:'red',marginTop:'20px'}}>
xxxxx
</div>,
document.body
);
注:直接在标签上使用style属性时,要写成style={{}}是两个大括号,外层大括号是告知jsx这里是js语法,和真实DOM不同的是,属性值不能是字符串而必须为对象,需要注意的是属性名同样需要驼峰命名法。即margin-top要写成marginTop,属性之间以逗号间隔。还有class 属性需要写成 className ,for 属性需要写成 htmlFor ,这是因为 class 和 for 是 JavaScript 的保留字。
JSX中的延展属性
在JSX中我们可以使用ES6中的最新语法...
来遍历对象
var HelloMessage = React.createClass({
render: function() {
return <h1>Hello {this.props.name} get {this.props.votes} votes</h1>;
}
});
var Lucy = {
name: "feng",
votes: "23"
}
ReactDOM.render(
<HelloMessage {...Lucy} />,
document.getElementById('example')
);
JSX中的事件绑定
JSX支持事件的绑定,语法类似于HTML中事件的绑定,不同的是这里事件的名称必须按照驼峰式,下面给个例子大家参考一下。
var HelloComponent = React.createClass({
testClick: function (str) {
alert('hello ' + str)
},
render: function() {
return (
<p
onClick={this.testClick.bind(this, 'feng')} style={{olor:'#ff6600',width:'200px', height:'100p'}}
>
Click me
</p>
)
}
})
ReactDOM.render(
<HelloComponent />,
document.getElementById('example')
);
知晓了JSX的基本使用之后,开始介绍ReactJS的组件的概念和使用方法
组件初步
ReactJS的基础就是实例化,即按功能封装成一个又一个的组件,各个组件维护自己的状态和UI,当状态改变时,会自动重新渲染整个组件,多个组件一起协作构成了整个ReactJS应用。
以一个简单的Hello World开始
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="http://cdn.bootcss.com/react/15.2.0/react.js"></script>
<script src="http://cdn.bootcss.com/react/15.2.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
<head>
<body>
<div id="example"></div>
<script type="text/babel">
var HelloMessage = React.createClass({
render: function() {
return <div>Hello,{this.props.name}</div>
}
})
ReactDOM.render(<HelloMessage name="World" />, document.getElementById('example'))
</script>
</body>
运行结果:
审查元素发现DOM结构如下:
上面的代码中,我们调用React提供的工厂方法来创建组件,其中:
- React是全局对象,ReactJS的所有顶级API都挂在这个对象下。
- React.createClass是ReactJS用来创建组件类的方法,这个对象必须包含一个render方法和若干可选的生命周期(详情请见下文)方法,其中
- render是一个组件级的API,是React.createClass内部最常用的API,该方法主要用来返回组件结构
- React.render(这里我用的是ReactDOM.render)是最常用的方法,用于将模板转换成HTML语言,并将转换后的DOM结构插入到制定的DOM节点上
- 简而言之就是:先通过React.createClass来创建一个组件类,调用组件的render方法输出组件的DOM结构,最后调用React.render将组建插入到id为example的节点上
这里我们需要保证render函数是纯函数:即同样的输入始终返回相同的输出,并且执行过程中没有副作用(和DOM交互或者发Ajax请求)。但一个组件要和DOM交互或者发Ajax请求需求是很正常的,那么就要用到其他生命周期方法了。
组件的状态与属性
组件本质上是状态机,输入确定,输出一定确定。组件把状态与结果一一对应起来,组件中有state与prop(状态与属性)。
- 属性(props)是由父组件传递给子组件的;
- 状态(state)是子组件内部维护的数据,当状态发生变化的同时,组件也会进行更新。当状态发生转换时会触发不同的钩子函数(详见下文中生命周期),从而让开发者有机会做出相应。
props属性的用法
props是一个对象,是组建用来接收外面传来的参数,组件内部是不允许修改自己的props属性的,只能够通过父组件来修改。具体的使用方法如下
-
接收键值对数据
<HelloWorld name= ? />
字符串:"XiaoFeng"
求值表达式 {123}、{"XiaoFeng"}
数组{[1,2,3]}
变量{variable}
-
函数求值表达式{function}(不推荐,如果需要函数可以单独把函数提取出来然后单独调用函数)
var HelloWorld =React.createClass({ render:function () { console.log(this.props) return <p>Hello,{this.props.name ? this.props.name : " World"}</p>; }, }); var HelloUniverse = React.createClass({ getInitialState:function () { return { name:'' }; }, handleChange: function (event) { this.setState({name: event.target.value}); }, render: function () { return (<div> <HelloWorld name={this.state.name}></HelloWorld> <br/> <input type="text" onChange={this.handleChange} /> </div> ); } }); ReactDOM.render(<HelloUniverse />,example);
-
setProps
已过时,即将废弃
-
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属性就通不过验证了。控制台会显示一行错误信息
-
getDefaultProps
getDefaultProps 方法可以用来设置组件属性的默认值
var MyTitle = React.createClass({ getDefaultProps : function () { return { title : 'Hello World' }; }, render: function() { return <h1> {this.props.title} </h1>; } }); ReactDOM.render(<MyTitle />,document.body);
-
this.props.children
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>,example );
上面代码的 NoteList 组件有两个 span 子节点,它们都可以通过 this.props.children 读取。这里需要注意, this.props.children 的值有三种可能:
- 如果当前组件没有子节点,它就是 undefined;
- 如果有一个子节点,数据类型是 object;
- 如果有多个子节点,数据类型就是 array
React 提供一个工具方法 React.Children 来处理 this.props.children 。我们可以用 React.Children.map 来遍历子节点,而不用担心 this.props.children 的数据类型是 undefined 还是 object。
state属性的用法
React把组件当成状态机,一旦用户交互导致状态发生变化,就会触发重新渲染UI。this.state会随着用户的交互而发生变化。
-
工作原理
常用的通知 React 数据变化的方法是调用 setState(data, callback)。这个方法会合并(merge) data 到 this.state,并重新渲染组件。渲染完成后,调用可选的 callback 回调。大部分情况下不需要提供 callback,因为 React 会负责把界面更新到最新状态。
-
getInitialState
object getInitialState()
getInitialState 方法用于定义初始状态,也就是一个对象,这个对象可以通过 this.state 属性读取。在组件挂载之前调用一次。返回值将会作为 this.state 的初始值。
-
setState
setState(object nextState[, function callback])
this.setState 方法用于修改状态值,每次修改以后,自动调用 this.render 方法,再次渲染组件。
注意:
- 绝对不要直接改变 this.state,因为在之后调用 setState() 可能会替换掉你做的更改。把 this.state 当做不可变的。
- setState()不会立刻改变 this.state,而是创建一个即将处理的state转变。在调用该方法之后获取 this.state 的值可能会得到现有的值,而不是最新设置的值。
- 不保证 setState()调用的同步性,为了提升性能,可能会批量执行state转变和DOM 渲染。
- setState ( ) 将总是触发一次重绘,除非在 shouldComponentUpdate()中实现了条件渲染逻辑。如果使用可变的对象,但是又不能在shouldComponentUpdate()中实现这种逻辑,仅在新 state 和之前的 state 存在差异的时候调用 setState()可以避免不必要的重新渲染。
常用的模式是创建多个只负责渲染数据的无状态(stateless)组件,在它们的上层创建一个有状态(stateful)组件并把它的状态通过 props 传给子级。这个有状态的组件封装了所有用户的交互逻辑,而这些无状态组件则负责声明式地渲染数据。
组件的生命周期
生命周期各阶段
在整个ReactJS的声明周期中,主要会经历这四个阶段:创建阶段、实例化阶段、更新阶段、销毁阶段。下面通过一段代码,来深入了解组件各个环节的运作流程
var OneComponent = React.createClass({
//1.创建阶段
getDefaultProps: function() {
//在创建类的时候被调用
console.log('getDefaultProps');
return {}
},
//2.实例化阶段
getInitialState: function() {
//获取this.state的默认值
console.log('getInitialState');
return {};
},
componentWillMount: function() {
//在render之前调用此方法
//业务逻辑的处理都应该放在这里,比如对state的操作等
console.log('componentWillMount')
},
render: function() {
//渲染并返回一个虚拟DOM
console.log('render');
return (
<div> hello <b> {this.props.name} </b></div>
)
},
componentDidMount: function() {
//该方法发生在render方法之后
//在该方法中,ReactJS会使用render方法返回的虚拟DOM对象来创建真实DOM结构
console.log('componentDidMount');
},
//3.个更新阶段
componentWillReceieveProps: function() {
//该方法发生在this.props被修改或富组件调用setProps()方法之后
console.log('componentWillRecieveProps');
},
shouldComponentUpdate: function() {
//是否需要更新
console.log('shouldComponentUpdate');
return true;
},
componentWillUpadate: function() {
//将要更新
console.log('componentWillUpadate');
},
componentDidUpdate: function() {
//更新完毕
console.log('componentDidUpdate');
},
//4.销毁阶段
componentWillUnmout: function() {
//销毁时被调用
console.log('componentWillUnmout');
}
})
ReactDOM.render(
<OneComponent name="World "/>,
document.getElementById('example')
);
创建阶段:
该阶段主要发生在创建组件类的时候,即在调用React.createClass的时候。这个阶段只会触发一个getDefaultProps方法,该方法会返回一个对象,并缓存下来。然后与富组件制定的props对象合并,最后赋值给this.props作为该组件的默认属性。
实例化阶段
该阶段主要发生在实例化组件类的时候,也就是组件类被调用的时候。
ReactDOM.render(
<OneComponent name="World "/>,
document.getElementById('example')
);
该组件被调用的时候,这个阶段会触发一系列的流程,具体的执行顺序如下所示。
getInitialState。初始化组件的state的值,其返回值会赋值给组件的this.state属性。
-
componentWillMount 在渲染(挂载)之前调用一次。
此时this.refs对象为空。如果在此时调用this.setState()则会更新this.state对象,而render只会调用一次。
render 根据state的值,生成页面需要的虚拟DOM结构,并返回该结构
-
componentDidMount 在渲染之后调用一次。根据虚拟DOM结构而生成的真实DOM进行相应的处理,组件内部可以通过this.getDOMNode()来获取当前组件的节点,然后就可以像在web开发中那样操作里面的DOM元素了。
componentDidMount () { const textbox = React.findDOMNode(this.refs.text) if (this.props.autoFocus) textbox.focus() }
更新阶段
主要发生在用户操作或者组件有更新的时候,此时会根据用户的操作行为进行相应的页面结构的调整。该阶段也会触发一系列的流程,具体的执行顺序如下所示。
-
componentWillReceiveProps 在将要接收props时调用。在该函数中,通常可以调用this.setState方法来完成对state的修改。
props是父组件传递给资组建的。父组件发生render的时候子组件就会调用componentWillReceiveProps(不管props有没有更新,也不管父子组件之间有没有数据交换)。
componentWillReceiveProps (nextProps) { if (this.props.disabled !== nextProps.disabled) { // disabled这个属性改变了 } }
-
shouldComponentUpdate:该方法用来拦截新的props或state,然后根据事先设定好的判断逻辑,返回值决定组件是否需要update。
组件挂载之后,每次调用setState后都会调用shouldComponentUpdate判断是否需要重新渲染组件。默认返回true,需要重新render。在比较复杂的应用里,有一些数据的改变并不影响界面展示,可以在这里做判断,优化渲染效率。
shouldComponentUpdate (nextProps, nextState) { // 比较props或者states,返回true则更新照常,返回false则取消更新,且不会调用下面的两个生命周期函数 }
componentWillUpdate :组件更新之前调用一次。当上一部中shouldComponentUpdate方法返回true时候,就可以在该方法中做一些更新之前的操作。
render 根据一系列的diff算法,声称需要更心动额虚拟DOM数据。实际表明,在render中,最好只做数据和模板的结合,而不进行state等逻辑的修改,这样组件结构更清晰。
-
componentDidUpdate 组件的更新已经同步到DOM中后出发。
除了首次render之后调用componentDidMount,其它render结束之后都是调用componentDidUpdate。
销毁阶段
- componentWillUnmount 会在组件即将从挂载点移去时调用,用来取出去除即将被销毁的DOM节点的引用,或者是清除计时器,取消监听的时间等等。
注意:
绝对不要在componentWillUpdate和componentDidUpdate中调用this.setState方法,否则将导致无限循环调用。
componentWillMount、componentDidMount和componentWillUpdate、componentDidUpdate可以对应起来。区别在于,前者只有在挂载的时候会被调用;而后者在以后的每次更新渲染之后都会被调用。
组件生命周期流程图
组件之间的通信
先创建一个父类组件Parent,它内部调用一个叫做Child的子组件,并将接收到的外部参数name传递给子组件Child。
var Parent = React.createClass({
handleClick: function() {
this.refs.myTextInput.focus();
},
render: function() {
return (
<div onClick={this.handleClick}>
<input type="text" ref="myTextInput" />
Parent is:
<Child name={this.props.name}></Child>
</div>
)
}
});
在创建一个子类组件Child
var Child = React.createClass({
render: function() {
return <span>{this.props.name}</span>
}
});
自后通过React.render方法将组件渲染到页面上
ReactDOM.render(
<Parent name="React" />,document.getElementById('example')
);
运行结果
整个应用的功能是父组件接收传入的用户名称,并将用于名称传给子组件,最后再由子组件渲染显示到页面上。这些组件直接按是怎么通讯的呢?下面通过两方面介绍一下
- 子组件调用父组件的方法。从上面的例子可以看出,子组件要拿到父组件的属性,需要通过this.props方法。所以,如果子组件想要调用父组件的方法,只需要父组件把要被调用的方法以属性的方式放在子组件上,子组件内部便可通过"this.props.被调用的方法"这样的方式拿到name属性的。然后,每次父组件修改了传入的name属性,子组件便会得到通知,然后自动获取新的name属性
- 父组件调用子组件的方法。子组件调用父组件是通过prop属性,而反过来,父组件调用子组件通过的就是 ref 属性。子组件被设置了ref属性之后,父组件便可以通过this.ref.xxx来获取到子组件了,其中xx为子组件的ref值。
组件实践
1. 静态组件
var MyComponent=React.createClass({
render: function() {
return <h1 className="my-h1" style={{color:'red'}}>Hello world!</h1>;
}
});
ReactDOM.render(
<MyComponent />,
document.getElementById('example')
);
运行结果
2. 动态组件
var Counter = React.createClass({
incrementCount: function(){
this.setState({
count: this.state.count + 1
});
},
getInitialState: function(){
return {
count: 0
}
},
render: function(){
return (
<div className="my-component">
<h1>Count: {this.state.count}</h1>
<button type="button" onClick={this.incrementCount}>{this.props.name}</button>
</div>
);
}
});
ReactDOM.render(
<Counter name="递增按钮" />,
document.getElementById('mount-point')
);
运行结果:
参考内容
- React入门实例教程
- React Native入门与实践
- React 生命周期小结