React学习笔记

React简介

React另辟蹊径,通过引入虚拟DOM、状态、单向数据流等设计理念,形成以组件为核心,用组件搭建UI的开发模式,理顺了UI的开发过程,完美地将数据、组件状态和UI映射到一起,极大地提高了开发大型Web应用的效率。

React基础

JSX简介

JSX是一种用于描述UI的JavaScript扩展语法

,React使用这种语法描述组件的UI。

使用类似HTML的标签描述组件的UI,让UI结构直观清晰,同时因为JSX本质上仍然是JavaScript,所以可以使用更多的JS语法,构建更加复杂的UI结构。

JSX语法

JSX的基本语法和XML语法相同,都是使用成对的标签构成一个树状结构的数据

标签类型

DOM类型的标签(div、span等)和React组件类型的标签

  1. 使用DOM类型的标签时,标签的首字母必须小写;
  2. 当使用React组件类型的标签时,组件名称的首字母必须大写。
  3. React正是通过首字母的大小写判断渲染的是一个DOM类型的标签还是一个React组件类型的标签。

JavaScript表达式

  1. JSX本质上仍然是JavaScript。
  2. 在JSX中使用JavaScript表达式需要将表达式用大括号“{}”包起来。
  3. 表达式在JSX中的使用场景主要有两个:通过表达式给标签属性赋值和通过表达式定义子组件。
  4. JSX中可以使用三目运算符或逻辑与(&&)运算符代替if语句的作用。

标签属性

  1. 当JSX标签是DOM类型的标签时,对应DOM标签支持的属性JSX也支持,例如id、class、style、onclick等。
  2. 部分属性的名称会有所改变,主要的变化有:class要写成className,事件属性名采用驼峰格式,例如onclick要写成onClick。原因是,class是JavaScript的关键字,所以改成className;
  3. React对DOM标签支持的事件重新做了封装,封装时采用了更常用的驼峰命名法命名事件。
  4. 当JSX标签是React组件类型时,可以任意自定义标签的属性名
  5. JSX中的注释需要用大括号“{}”将/**/包裹起来。

JSX不是必需的

JSX语法只是React.createElement(component, props, ...children)的语法糖,所有的JSX语法最终都会被转换成对这个方法的调用。

组件

定义一个组件有两种方式,使用ES 6 class(类组件)和使用函数(函数组件)。

使用class定义组件需要满足两个条件:

(1)class继承自React.Component。

(2)class内部必须定义render方法,render方法返回代表该组件UI的React元素。

挂载到页面的DOM节点上,需要使用ReactDOM.render()完成

组件的props

组件的props用于把父组件中的数据或方法传递给子组件,供子组件使用。

props是一个简单结构的对象,它包含的属性正是由组件作为JSX标签使用时的属性组成。

React学习笔记_javascript

组件的state

  1. 组件的state是组件内部的状态,state的变化最终将反映到组件UI的变化上。
  2. 在组件的构造方法constructor中通过this.state定义组件的初始状态
  3. 通过调用this.setState方法改变组件状态(也是改变组件状态的唯一方式),进而组件UI也会随之重新渲染。

有三个需要注意的地方:

(1)在组件的构造方法constructor内,首先要调用super(props),这一步实际上是调用了React.Component这个class的constructor方法,用来完成React组件的初始化工作。

(2)在constructor中,通过this.state定义了组件的状态。

(3)在render方法中,我们为标签定义了处理点击事件的响应函数,在响应函数内部会调用this.setState更新组件的状态。

总结

组件的props和state都会直接影响组件的UI。事实上,React组件可以看作一个函数,函数的输入是props和state,函数的输出是组件的UI。

UI = Component (props, state)

  1. React组件正是由props和state两种类型的数据驱动渲染出组件UI。
  2. props是组件对外的接口,组件通过props接收外部传入的数据(包括方法)
  3. state是组件对内的接口,组件内部状态的变化通过state来反映。
  4. props是只读的,你不能在组件内部修改props;
  5. state是可变的,组件状态的变化通过修改state来实现。

有状态组件和无状态组件

state用来反映组件内部状态的变化,如果一个组件的内部状态是不变的,当然就用不到state,这样的组件称之为无状态组件

定义无状态组件除了使用ES 6 class的方式外,还可以使用函数定义:

一个函数组件接收props作为参数,返回代表这个组件UI的React元素结构。

  1. 尽可能多地使用无状态组件,无状态组件不用关心状态的变化,只聚焦于UI的展示,因而更容易被复用。
  2. React应用组件设计的一般思路是,通过定义少数的有状态组件管理整个应用的状态变化,并且将状态通过props传递给其余的无状态组件,由无状态组件完成页面绝大部分UI的渲染工作。
  3. 总之,有状态组件主要关注处理状态变化的业务逻辑,无状态组件主要关注组件UI的渲染。

属性校验和默认属性

React提供了PropTypes这个对象,用于校验组件属性的类型。PropTypes包含组件属性所有可能的类型,我们通过定义一个对象(对象的key是组件的属性名,value是对应属性的类型)实现组件属性类型的校验。

组件属性类型和PropTypes属性的对应关系

React学习笔记_javascript_02

当使用PropTypes.object或PropTypes.array校验属性类型时,我们只知道这个属性是一个对象或一个数组,至于对象的结构或数组元素的类型是什么样的,依然无从得知。

这种情况下,更好的做法是使用PropTypes.shape或PropTypes.arrayOf。

React学习笔记_生命周期_03

React还提供了为组件属性指定默认值的特性,这个特性通过组件的defaultProps实现。当组件属性未被赋值时,组件会使用defaultProps定义的默认属性。

React学习笔记_表单_04

组件样式

为组件添加样式的方法主要有两种:外部CSS样式表和内联样式。

外部CSS样式表

CSS样式表中根据HTML标签类型、ID、class等选择器定义元素的样式。唯一的区别是,React元素要使用className来代替class作为选择器。

样式表的引入方式有两种,一种是在使用组件的HTML页面中通过标签引入:

  1. 一种是在使用组件的HTML页面中通过标签引入,引入样式表的方式常用于该样式表文件作用于整个应用的所有组件(一般是基础样式表)
  2. 第二种引入样式表的方式常用于该样式表作用于某个组件(相当于组件的私有样式)

内联样式

内联样式实际上是一种CSS in JS的写法:将CSS样式写到JS文件中,用JS对象表示CSS样式,然后通过DOM类型节点的style属性引用相应样式对象。

function Welcome (props) {
return (
<h1
style={ {
width: "100号" ,
height: "50px",
backgroundColor: "blue",
fontSize :
"20px"
}}
>
He11o,{props . name }
</h1>
);
}
  1. 第一个大括号表示style的值是一个JavaScript表达式
  2. 第二个大括号表示这个JavaScript表达式是一个对象。

组件和元素

React元素是一个普通的JavaScript对象,这个对象通过DOM节点或React组件描述界面是什么样子的。JSX语法就是用来创建React元素的

React组件是一个class或函数,它接收一些属性作为输入,返回一个React元素。React组件是由若干React元素组建而成的。

组件的生命周期

组件从被创建到被销毁的过程称为组件的生命周期

组件的生命周期可以被分为三个阶段:挂载阶段、更新阶段、卸载阶段。

挂载阶段

这个阶段组件被创建,执行初始化,并被挂载到DOM中,完成组件的第一次渲染。

依次调用的生命周期方法有:

(1)constructor

  • 这个构造方法接收一个props参数,props是从父组件中传入的属性对象,如果父组件中没有传入属性而组件自身定义了默认属性,那么这个props指向的就是组件的默认属性。
  • 你必须在这个方法中首先调用super(props)才能保证props被传入组件中。
  • constructor通常用于初始化组件的state以及绑定事件处理方法等工作。

(2)componentWillMount

这个方法在组件被挂载到DOM前调用,且只会被调用一次

(3)render

  • 在这个方法中,根据组件的props和state返回一个React元素,用于描述组件的UI,通常React元素使用JSX语法定义。
  • 需要注意的是,render并不负责组件的实际渲染工作,它只是返回一个UI的描述,真正的渲染出页面DOM的工作由React自身负责。
  • render是一个纯函数,在这个方法中不能执行任何有副作用的操作,所以不能在render中调用this.setState,这会改变组件的状态。

(4)componentDidMount

  • 在组件被挂载到DOM后调用,且只会被调用一次。
  • 这时候已经可以获取到DOM结构,因此依赖DOM节点的操作可以放到这个方法中。
  • 这个方法通常还会用于向服务器端请求数据。在这个方法中调用this.setState会引起组件的重新渲染

更新阶段

  1. 组件被挂载到DOM后,组件的props或state可以引起组件更新。
  2. props引起的组件更新,本质上是由渲染该组件的父组件引起的,也就是当父组件的render方法被调用时,组件会发生更新过程,这个时候,组件props的值可能发生改变,也可能没有改变,因为父组件可以使用相同的对象或值为组件的props赋值。但是,无论props是否改变,父组件render方法每一次调用,都会导致组件更新。
  3. State引起的组件更新,是通过调用this.setState修改组件state来触发的。

组件更新阶段,依次调用的生命周期方法有:

(1)componentWillReceiveProps(nextProps)

  • 这个方法只在props引起的组件更新过程中,才会被调用。
  • State引起的组件更新并不会触发该方法的执行。
  • 方法的参数nextProps是父组件传递给当前组件的新的props。
  • 父组件render方法的调用并不能保证传递给子组件的props发生变化,也就是说nextProps的值可能和子组件当前props的值相等,因此往往需要比较nextProps和this.props来决定是否执行props发生变化后的逻辑,比如根据新的props调用this.setState触发组件的重新渲染。

注意:

  1. 在componentWillReceiveProps中调用setState,只有在组件render及其之后的方法中,this.state指向的才是更新后的state。在render之前的方法shouldComponentUpdate、componentWillUpdate中,this.state依然指向的是更新前的state。
  2. 通过调用setState更新组件状态并不会触发componentWillReceiveProps的调用,否则可能会进入一个死循环,componentWillReceiveProps→this.setState→componentWillReceiveProps→this.setState……

(2)shouldComponentUpdate(nextProps, nextState)

  • 这个方法决定组件是否继续执行更新过程。
  • 当方法返回true时(true也是这个方法的默认返回值),组件会继续更新过程;
  • 当方法返回false时,组件的更新过程停止,后续的componentWillUpdate、render、componentDidUpdate也不会再被调用。
  • 一般通过比较nextProps、nextState和组件当前的props、state决定这个方法的返回结果。这个方法可以用来减少组件不必要的渲染,从而优化组件的性能。

(3)componentWillUpdate(nextProps, nextState)

这个方法在组件render调用前执行,可以作为组件更新发生前执行某些工作的地方,一般也很少用到。

(4)render

(5)componentDidUpdate(prevProps, prevState)

组件更新后被调用,可以作为操作更新后的DOM的地方。这个方法的两个参数prevProps、prevState代表组件更新前的props和state。

卸载阶段组件

从DOM中被卸载的过程,这个过程中只有一个生命周期方法:componentDidMount

  1. 这个方法在组件被卸载前调用,可以在这里执行一些清理工作,比如清除组件中使用的定时器,清除componentDidMount中手动创建的DOM元素等,以避免引起内存泄漏。
  2. 只有类组件才具有生命周期方法,函数组件是没有生命周期方法的,因此永远不要在函数组件中使用生命周期方法。

事件处理

在React元素中绑定事件有两点需要注意:

(1)在React中,事件的命名采用驼峰命名方式,而不是DOM元素中的小写字母命名方式。例如,onclick要写成onClick,onchange要写成onChange等。

(2)处理事件的响应函数要以对象的形式赋值给事件属性,而不是DOM中的字符串形式

  • React中的事件是合成事件,并不是原生的DOM事件。
  • 在DOM事件中,可以通过处理函数返回false来阻止事件的默认行为,但在React事件中,必须显式地调用事件对象的preventDefault方法来阻止事件的默认行为。
  • 除了这一点外,DOM事件和React事件在使用上并无差别。如果在某些场景下必须使用DOM提供的原生事件,可以通过React事件对象的nativeEvent属性获取。
  • 在React组件中处理事件最容易出错的地方是事件处理函数中this的指向问题,因为ES 6 class并不会为方法自动绑定this到当前对象。

React事件处理函数的写法主要有三种方式,不同的写法解决this指向问题的方式也不同:

  1. 使用箭头函数:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = { number: 0 }
}
render() {
return (
<button
onClick={(event) => {
console.log(this.state.number)
}}
>
Click
</button>
)
}
}
  • 因为箭头函数中的this指向的是函数定义时的对象,所以可以保证this总是指向当前组件的实例对象。当事件处理逻辑比较复杂时,如果把所有的逻辑直接写在onClick的大括号内,就会导致render函数变得臃肿,不容易直观地看出组件的UI结构,代码可读性也不好。这时,可以把逻辑封装成组件的一个方法,然后在箭头函数中调用这个方法。

2.使用组件方法:

//代码3
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {number: 0};
this.handleClick = this.handleClick.bind(this); // 手动绑定this
}

handleClick() {
this.setState({
number: ++this.state.number
});
}

render() {
return (
<div>
<div>{this.state.number}</div>
<button onClick={this.handleClick}>
Click
</button>
</div>
);
}
}

点击Button的事件响应函数是组件的方法:handleClick。这种方式的好处是:每次render方法的调用,不会重新创建一个新的事件响应函数,没有额外的性能损失。但是,使用这种方式要在构造函数中为作为事件响应的方法(handleClick),手动绑定this: this.handleClick = this.handleClick.bind(this),这是因为ES6 语法的缘故,ES6 Class 的方法默认不会把this绑定到当前的实例对象上,需要我们手动绑定。

使用bind会创建一个新的函数,因此这种写法依然存在每次render都会创建一个新函数的问题。但在需要为处理函数传递额外参数时,这种写法就有了用武之地。

3.属性初始化语法(property initializer syntax)

//代码4
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {number: 0};
}
// ES7的属性初始化语法,实际上也是使用了箭头函数
handleClick = () => {
this.setState({
number: ++this.state.number
});
}

render() {
return (
<div>
<div>{this.state.number}</div>
<button onClick={this.handleClick}>
Click
</button>
</div>
);
}
}

表单

受控组件

  1. 如果一个表单元素的值是由React来管理的,那么它就是一个受控组件。
  2. React组件渲染表单元素,并在用户和表单元素发生交互时控制表单元素的行为,从而保证组件的state成为界面上所有元素状态的唯一来源。
  • 文本框文本框包含类型为text的input元素和textarea元素。它们受控的主要原理是:通过表单元素的value属性设置表单元素的值,通过表单元素的o'nChange事件监听值的变化,并将变化同步到React组件的state中。
  • 下拉列表select。在React中,通过在select上定义value属性来决定哪一个option元素处于选中状态,不需要关注option元素。
  • 复选框和单选框
    类型为checkbox的input元素和类型为radio的input元素。通常复选框和单选框的值是不变的,需要改变的是checked状态。因此React控制的属性不再是value,而是checked。
import React,{Component} from 'react';

export default class Demo1 extends Component{

constructor(props){

super(props);

this.state = {name:'',password:''};

handleChange(event){ //监听用户名和密码两个input值的变化

const target = event.target;

this.setState({[target.name]: target.value});

}

handleSubmit(event){ //表单提交的响应函数

console.log('login successfully');

event.preventDefault();

}

}

render(){

return(

<form onSubmit={ this.handleSubmit }>

<label>

用户名:<input type="text" name="name" value={this.state.name} onChange={this.handleChange}/>

</label>

<label>

密码:<input type="password" name="password" value={this.state.password} onChange={this.handleChange}/>

</label>

<input type="submit" value="登陆"/>

</form>

)

}

}