1.React 的学习资源
-
react官方首页
https://facebook.github.io/react/ -
React Gitbook 翻译
https://hulufei.gitbooks.io/react-tutorial/content/introduction.html -
react中文导航
http://nav.react-china.org/ -
目前各大公司的使用情况
https://github.com/facebook/react/wiki/Sites-Using-React
2.React
什么是React?
- React 是一个用于构建用户界面的 JAVASCRIPT 库。
- React主要用于构建UI,很多人认为 React 是 MVC 中的 V(视图)。
- React 起源于 Facebook 的内部项目,用来架设 Instagram 的网站,并于 2013 年 5 月开源。
- React 拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。
- 既可以开发浏览器应用,又可以开发移动应用
React 特点
- 1.声明式设计 −React采用声明范式,可以轻松描述应用。
- 2.高效 −React通过对DOM的模拟,最大限度地减少与DOM的交互。
- 3.灵活 −React可以与已知的库或框架很好地配合。
- 4.JSX − JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
- 5.组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
- 6.单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。
什么是JSX?
React 使用 JSX(JavaScript XML) 来替代常规的 JavaScript。JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。我们不需要一定使用 JSX,但它有以下优点:
- JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
- 它是类型安全的,在编译过程中就能发现错误。
- 使用 JSX 编写模板更加简单快速。
3.React的HelloWorld
打开网址:https://facebook.github.io/react/docs/installation.html
官网中有如何搭建React的Helloworld。
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://unpkg.com/react@latest/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@latest/dist/react-dom.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script type="text/babel" src="01.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
01.js:
//React组件化(可以重用),即自定义组件
class TextCompat extends React.Component{
//组件的内容
render(){
return <div>Hello world!!!</div>
}
}
class WrapperText extends React.Component{
render(){
//虚拟DOM(Document Object Model)
//html标签,小写开头
//自定义组件:大写开头
return <p>
<TextCompat></TextCompat>
<span>jarry</span>
</p>
}
}
//绘制到页面中
//ReactDOM.render(<WrapperText></WrapperText>, document.body);
ReactDOM.render(<TextCompat/>, document.getElementById('root'));
流程:
1.编写组件
2.绘制到页面
ReactDOM.render(<TextCompat/>, document.getElementById('root'));
虚拟DOM
DOM:html标签
虚拟DOM即包括DOM又嵌套组件,和普通DOM的区别:html标签是小写,组件是大写开头
虚拟DOM的好处:
- 1.跨平台(讲虚拟DOM翻译成对应平台的语言)
- 2.组件化管理,适合大型项目
缺点:
4.React的状态
State(状态)
React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。
React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。
不需要直接操作DOM,而是通过修改state,自动更新界面
1.初始化状态
2.改变状态
3.获取状态,当状态改变的时候,会重绘页面
注意需要安装React dev tools的插件,来查看错误日志
需要用React.createClass,而不是用继承的方式,因为这个会走生命周期流程。
案例:
点击切换,喜欢不喜欢?
//实例化组件对象过程中,调用getInitialState设置state对象的初始值
var Text = React.createClass({
//设置状态的初始值
getInitialState : function(){
return {isLike:false};
},
//点击事件回调
handleClick : function(){
//改变状态
this.setState({isLike:!this.state.isLike});
},
//当状态改变时,会重新调用render函数,重绘
render : function(){
//获取状态
var text = this.state.isLike ? "喜欢" : "不喜欢";
return <p onClick={this.handleClick}>你{text}吗?</p>
}
});
ReactDOM.render(<Text/>, document.getElementById("myDiv"));
上面例子的加载流程:
1.getInitialState(初始化,设置组件的state的值,给了一个对象)
2.render(不喜欢)
点击之后的流程
1.handleClick(点击回调)
2.setState(改变状态)
3.render(喜欢)
5.Props(属性)
state 和 props 主要的区别在于 props 是不可变的,而 state 可以根据与用户交互来改变。这就是为什么有些容器组件需要定义 state 来更新和修改数据。 而子组件只能通过 props 来传递数据。
可以通过 getDefaultProps() 方法为 props 设置默认值
var Text = React.createClass({
render : function(){
return <p>I love {this.props.name}</p>;
}
});
ReactDOM.render(<Text name="Jason"/>, document.getElementById("myDiv"));
案例:
实现在输入框中,输入内容,上面的文字控件也跟着改变
//子组件
var TextComponent = React.createClass({
render : function(){
return <div>Hello {this.props.text}!</div>;
}
});
//父组件
var WrapperComponent = React.createClass({
//state初始化
getInitialState : function(){
return {text:''};
},
//内容改变回调
handleChange : function(e){
//e 是Event事件对象,target是指事件的目标对象
//改变状态
this.setState({text:e.target.value});
},
render : function(){
return <div>
<TextComponent text={this.state.text}/>
<input type="text" onChange={this.handleChange}/>
</div>;
}
});
ReactDOM.render(<WrapperComponent/>, document.getElementById("myDiv"));
例子中传参流程
1.handleChange(父组件回调)
2.setState(修改父组件的状态)
3.render(父组件重新渲染,子组件也会渲染)
3.把父组件的状态值,作为子组件的属性值传入
4.render(子组件内容改变)
6.React组件生命周期
实例化
首次实例化
- getDefaultProps
- getInitialState
- componentWillMount
- render
- componentDidMount
实例化完成后的更新
- getInitialState
- componentWillMount
- render
- componentDidMount
存在期
组件已存在时的状态改变
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
- render
- componentDidUpdate
销毁&清理期
- componentWillUnmount
7.生命周期共提供了10个不同的API。
1.getDefaultProps
作用于组件类,只调用一次,返回对象用于设置默认的props,对于引用值,会在实例中共享。
2.getInitialState
作用于组件的实例,在实例创建时调用一次,用于初始化每个实例的state,此时可以访问this.props。
3.componentWillMount
在完成首次渲染之前调用,此时仍可以修改组件的state。
4.render
必选的方法,创建虚拟DOM,该方法具有特殊的规则:
- 只能通过this.props和this.state访问数据
- 可以返回null、false或任何React组件
- 只能出现一个顶级组件(不能返回数组)
- 不能改变组件的状态
- 不能修改DOM的输出
5.componentDidMount
真实的DOM被渲染出来后调用,在该方法中可通过this.getDOMNode()访问到真实的DOM元素。此时已可以使用其他类库来操作这个DOM。
在服务端中,该方法不会被调用。
6.componentWillReceiveProps
组件接收到新的props时调用,并将其作为参数nextProps使用,此时可以更改组件props及state。
componentWillReceiveProps: function(nextProps) {
if (nextProps.bool) {
this.setState({
bool: true
});
}
}
7.shouldComponentUpdate
组件是否应当渲染新的props或state,返回false表示跳过后续的生命周期方法,通常不需要使用以避免出现bug。在出现应用的瓶颈时,可通过该方法进行适当的优化。
在首次渲染期间或者调用了forceUpdate方法后,该方法不会被调用
8.componentWillUpdate
接收到新的props或者state后,进行渲染之前调用,此时不允许更新props或state。
9.componentDidUpdate
完成渲染新的props或者state后调用,此时可以访问到新的DOM元素。
10.componentWillUnmount
组件被移除之前被调用,可以用于做一些清理工作,在componentDidMount方法中添加的所有任务都需要在该方法中撤销,比如创建的定时器或添加的事件监听器。
8.传参
通过属性Props可以在父组件中给子组件设置属性值,也就是将父组件的值传递给子组件。但是要解决子组件访问父组件的方法,可以通过回调函数解决,这就和Java的接口一样。
他的主要思路:子组件委托父组件处理。
下面通过一个案例来实现:
- 下拉列表(子组件)的数据发生改变,表单(父组件)能够获取到,用于后面的提交子组件向父组件传递参数
1.下拉选择组件:
//表单的子组件
var ChildGenderSelect = React.createClass({
render : function(){
return <select onChange={this.props.handleSelectChange}>
<option value="1">男</option>
<option value="0">女</option>
</select>;
}
});
2.表单的组件:
//提交数据的两种做法:
//1.直接提交表单,跳转,整个页面刷新(过时的做法)
//2.屏蔽表单的默认提交行为,通过ajax提交数据,服务器响应成功之后在跳转(类似于Android)
var ParentForm = React.createClass({
getInitialState : function(){
return {gender:0};
},
handleChange : function(e){
//保存到state
this.setState({gender:e.target.value});
},
handleSubmit : function(e){
//屏蔽表单的默认提交行为
e.preventDefault();
//ajax发起请求,提交数据
/*var request = new XMLHttpRequest();
request.open("GET","test.json?gender="+this.state.gender);
request.onreadystatechange = handler;
request.responseType = "json"; //服务器响应数据的类型 json
request.setRequestHeader("Accept","application/json");
request.send();
//回调
function handler(){
//4(完成)响应内容解析完成
if(this.readyState !== 4){
return;
}
if(this.status == 200){
//请求成功:显示数据
console.log("ok");
}
}*/
},
render : function () {
return <form onSubmit={this.handleSubmit}>
<ChildGenderSelect handleSelectChange={this.handleChange}></ChildGenderSelect><br/>
<button type="submit">提交</button>
</form>;
}
});
3.将表单组件渲染到页面:
ReactDOM.render(<ParentForm></ParentForm>,document.body);
4.案例的流程:
(1).通过handleSelectChange属性,传入父组件的handleChange函数给子组件。
(2).当子组件的onChange事件触发,调用this.props.handleSelectChange->父组件的handleChange函数。
9.表单提交和函数复用
案例:
- 点击提交按钮,提交输入框输入的内容,通过Ajax请求提交
1.方式一:不可控组件
主要思路:通过ref获取组件
var FormComponent = React.createClass({
//处理表单提交
handleSubmit : function(e){
e.preventDefault();
//ref类似于id,是一个唯一标示 React.findDOMNode
var text = this.refs.input_name.value;
alert(text);
},
render : function(){
return <form onSubmit={this.handleSubmit}>
<input type="text" ref="input_name" defaultValue="Hello..."/><br/>
<button type="submit">提交</button>
</form>;
}
});
ReactDOM.render(<FormComponent></FormComponent>,document.body);
2.方式二:可控组件(和MVVM data binding很类似)
主要思路:把数据存储在状态中(通过事件监听,进行数据绑定),需要时,从状态中获取
var FormComponent = React.createClass({
getInitialState : function () {
return {text:''};
},
handleChange : function(e){
//保存到state
this.setState({text:e.target.value});
},
handleSubmit : function(e){
e.preventDefault();
alert(this.state.text);
},
render : function(){
return <form onSubmit={this.handleSubmit}>
<input type="text" defaultValue="Hello..." onChange={this.handleChange}/><br/>
<button type="submit">提交</button>
</form>;
}
});
ReactDOM.render(<FormComponent></FormComponent>,document.body);
随之产生的问题:多个控件需要多个事件处理函数,如何做到事件处理函数的复用?
3.方式三:事件处理函数的复用方式1
在方式二的基础上进行改进,让子控件可以实现复用,并保存对应的属性值。
思路:bind返回改变了上下文this后的函数
var FormComponent = React.createClass({
getInitialState : function () {
return {
username:'',
gender:0,
agree:true
};
},
//key ->'username' ....
handleChange : function(key,e){
//this->FormComponent对象
//保存到state
var obj = {};
obj[key] = e.target.value;
this.setState(obj);
},
handleSubmit : function(e){
e.preventDefault();
console.log(this.state);
},
//多个控件的事件响应,共用这个函数
render : function() {
return <form onSubmit={this.handleSubmit}>
<label htmlFor="username">输入用户名:</label>
<input id="username" type="text" onChange={this.handleChange.bind(this,'username')}/><br/>
<label htmlFor="gender">请选择性别:</label>
<select id="gender" onChange={this.handleChange.bind(this,'gender')}>
<option value="1">男</option>
<option value="0">女</option>
</select><br/>
<label htmlFor="agree">是否同意:</label>
<input id="agree" type="checkbox" onChange={this.handleChange.bind(this,'agree')} /><br/>
<button type="submit">提交</button>
</form>;
}
});
ReactDOM.render(<FormComponent></FormComponent>,document.body);
注意:this的作用域问题,如果this指定的函数是非箭头函数,会导致作用域不正确,子控件拿不到对应的函数和属性值。
4.方式四:事件处理函数的复用方式2
思路:指定属性,比如说name属性区分不同的组件
var FormComponent = React.createClass({
getInitialState : function () {
return {
username:'',
gender:0,
agree:true
};
},
handleChange : function(e){
var obj = {};
obj[e.target.name] = e.target.value;
this.setState(obj);
},
handleSubmit : function(e){
e.preventDefault();
console.log(this.state);
},
//多个控件的事件响应,共用这个函数
render : function() {
return <form onSubmit={this.handleSubmit}>
<label htmlFor="username">输入用户名:</label>
<input id="username" type="text" name="username" onChange={this.handleChange}/><br/>
<label htmlFor="gender">请选择性别:</label>
<select id="gender" name="gender" onChange={this.handleChange}>
<option value="1">男</option>
<option value="0">女</option>
</select><br/>
<label htmlFor="agree">是否同意:</label>
<input id="agree" name="agree" type="checkbox" onChange={this.handleChange} /><br/>
<button type="submit">提交</button>
</form>;
}
});
ReactDOM.render(<FormComponent></FormComponent>,document.body);
10.动画
案例:让一个文字,在3秒之类从左到右移动一段距离
思路:用定时器,不断改变属性值,并渲染。
var MyComponent = React.createClass({
getDefaultProps : function(){
return {
position:100, //marginLeft目标值,组件实现可配置
time:10
};
},
getInitialState : function () {
return {position:0}; //marginLeft开始值
},
render : function(){
var style = {
color:'red',
marginLeft:this.state.position //左外边距
};
return <div style={style}>This will animated!</div>;
},
//动画函数,不断改变state的position属性
transformComponnent : function () {
if(this.state.position < this.props.position){
this.setState({
position : ++this.state.position
});
}else{
//动画完成,取消定时器
clearInterval(this.timer);
}
console.log("transformComponnent");
},
//渲染完成
componentDidMount : function () {
//开启定时器
this.timer = setInterval(this.transformComponnent,this.props.time);
}
});
ReactDOM.render(<MyComponent position={200} time={20}></MyComponent>,document.body);
流程
1.初始化控件,执行生命周期中的getDefaultProps 和getInitialState ;
2.控件渲染完成,执行componentDidMount ;
3.开启定时器,不断执行transformComponnent ;
4.保存状态setState;
5.不断渲染render,渲染的时候marginLeft拿取状态值;
11.计时器案例
1.模块化
把一些类,函数,变量写到一个文件中,作为一个功能模块,当要使用这个模块,只需要引用。
nodejs不支持,用Bable6转es6为es5(js库,实现模块化),es6模块化(新的语法特性),node和chrome都不支持。
2.创建react工程
支持语法特性,非常便利
npm install -g create-react-app
create-reate-app test
cd test
3.模块引入和导出
创建一个组件
class TimeDisplay extends React.Component{
}
引入:import React form 'react';
导出:export default TimeDisplay;
4.代码
this只有在运行时才确定
转为箭头函数,因为this的作用域是最外层的,比普通的function少了绑定this步骤;
在构造函数中对状态赋初始值,不能调用setState
TimeDisplay.js:
import React from 'react';
import DisplayLog from './DisplayLog';
import Button from './Button';
import formatTime from '../utils/formatTime';
class TimeDisplay extends React.Component{
//在构造函数中对状态赋初始值
constructor(props){
super(props);
//不能调用state
this.state = {
time:0,
on:false,
log:[] //数组
};
}
//开始按钮
handleToggle = ()=>{
//已经开始,取消正在运行的定时器
if(this.state.on){
clearInterval(this.timer);
}else{
//否则开启
//定时器,10ms累加1
this.timer = setInterval(()=>this.setState({time:this.state.time+1}),10);
}
this.setState({on:!this.state.on});
}
//记录时间
handleLogTime = ()=>{
//存储在数组中
this.state.log.push(this.state.time);
}
//清空记录
handleClearLog = ()=>{
this.setState({log:[]});
}
//重置
handleReset = () =>{
this.setState({time:0});
}
render(){
var time = formatTime(this.state.time);
return <div>
<h1 className="display-time">{time}</h1>
<div className="controls">
<Button className={this.state.on ? "danger" : "success"} text={this.state.on ? "暂停" : "开始"} onClick={this.handleToggle}/>
<Button className="warning" onClick={this.handleReset}/>
<Button className="primary" text="记录" onClick={this.handleLogTime}/>
<Button className="undefined" text="清空" onClick={this.handleClearLog}/>
</div>
<DisplayLog log={this.state.log}/>
</div>;
}
//监听键盘事件
componentDidMount(){
//内置对象
window.addEventListener('keydown', e => e.preventDefault());
window.addEventListener('keyup', e => {
e.preventDefault();
switch (e.keyCode){
case 32: //space
this.handleToggle();
break;
case 82:
this.handleReset();
break;
case 13:
this.handleLogTime();
break;
case 67:
this.handleClearLog();
break;
default:
break;
}
});
}
//组件被移除,事件监听取消
componentWillUnmount(){
window.removeEventListener('keydown');
window.removeEventListener('keyup');
}
}
//在外部要使用TimeDisplay
export default TimeDisplay;
Button.js:
button的封装,数据、样式、事件的传递
import React from 'react';
class Button extends React.Component{
//静态属性,给属性赋默认值
static defaultProps = {
onClick : null,
className : '',
text : '默认'
};
render(){
return <button className={`Button ${this.props.className}`} onClick={this.props.onClick}>{this.props.text}</button>;
}
}
export default Button;
DisplayLog.js:
import React from 'react';
import formatTime from '../utils/formatTime';
class DisplayLog extends React.Component{
//log数组长度等于0,返回“空空如也”
renderEmpty = () =>{
return <span className="empty-log">空空如也...</span>;
}
//否则,把元素遍历(时间),得到一系列的<li>
renderLog = () => {
//<li>00</li>
//<li>11</li>
return this.props.log.map(item => {
return <li className="log-item" >{formatTime(item)}</li>;
});
}
render(){
//log数组长度等于0,返回“空空如也”
//否则,把元素遍历(时间),得到一系列的<li>
//<ul>
// <li>00</li>
// <li></li>
//</ul>
const log = this.props.log.length === 0 ? this.renderEmpty() : this.renderLog();
return <ul className="log">
{log}
</ul>;
}
}
export default DisplayLog;
formatTime.js:
//将数组转为字符串,如果数字只有一位,会在第一个位置添加一个0
//9->9->09
const appendZero = n => n.toLocaleString({},{minimumIntegerDigits:2});
export default function(t = 0){
const msec = appendZero(t % 100),
sec = appendZero(parseInt((t/100) % 60)),
min = appendZero(parseInt((t/6000) % 60)),
hour = appendZero(parseInt(t/360000));
return `${hour}:${min}:${sec}.${msec}`;
}
App.css
.App {
text-align: center;
}
ul,li{
list-style: none; /*去掉列表默认样式*/
margin: 0px;
padding: 0px;
}
.display-time{
font-size: 45px;
font-weight: bold;
margin: 60px 0px;
}
.controls{
background-color: #eee;
}
.Button{
width: 130px;
font-size: 20px;
padding: 10px 20px;
margin: 20px;
border: none;
border-radius: 4px;
background-color: lightgreen;
cursor: pointer;
}
.Button:hover{
/*CSS3阴影*/
box-shadow: 0 0 25px rgba(0,0,0,0.2);
}
.success{
background-color: lightgreen;
}
.danger{
background-color: hotpink;
}
.warning{
background-color: gold;
}
.primary{
background-color: skyblue;
}
.undefined{
background-color: #e1e1e1;
}
.log{
text-align: center;
font-size: 30px;
color: #e1e1e1;
}
/*log和empty-log都会应用这个样式*/
.log .empty-log{
display: inline-block;
padding: 50px 10px;
}
/*.log类下的li标签 */
/*.log > li{*/
.log-item{
font-size: 25px;
color: #000;
border-bottom: 1px solid #eee;
padding: 10px 0px;
}
/*伪类*/
/*悬浮*/
.log-item:hover{
background-color: rgba(238, 238, 238, 0.5);
}
App.js
import React from 'react';
import './App.css';
import TimeDisplay from './components/TimeDisplay'
class App extends React.Component {
render() {
return <div className="App">
<TimeDisplay />
</div>;
}
}
export default App;
12.常用的补充点
1.数组
数组的创建
var a = [];
var b = new Array();
数组的元素赋值
a[0] = 2;
a[1] = 10;
a.push(30);
清空
console.log(a);
a = [];
console.log(a);
map 遍历数组的函数
//item是数组的元素,i数组的索引,b是返回的数组
var b = a.map(function(item,i){
return ++item;
});
console.log(b);
2.组件的两种创建方式
1.React.createClass
对应的属性和状态初始化方法:
getDefaultProps
getInitialState
2.extends Component
对应的属性和状态初始化方法:
static defaultProps
constructor
3.css样式
.log-item
网友评论