本博客pdf链接: https://pan.baidu.com/s/1E_E4RqZUPgQiby1T7Afj8Q 提取码: h77d
React进阶之路系列学习笔记:React进阶之路系列学习笔记
github源码:React_learning
2.1 JSX
2.1.1JSX简介
JSX是一种用于描述UI的JavaScript扩展语法,React使用这种语法描述组件的UI。
2.1.2JSX语法
1.基本语法
JSX的基本语法和XML语法相同,都是使用成对的标签构成一个树状结构的数据。
const element = (
<div>
<h1>Hello, world!</h1>
</div>
)
2.标签类型
两种标签类型:
(1)DOM类型的标签(div,span等), 使用时,标签首字母必须小写
(2)React组件类型的标签,使用时,组件名称的首字母必须大写
React通过首字母的大小写判断渲染的是DOM类型标签还是React组件类型标签。
3.JavaScript表达式
JSX的本质是JavaScript。在JSX中使用JSX表达式需要用{}将表达式包起来。
表达式使用主要场景:
- 通过表达式给标签属性赋值
const element = <MyComponent foo={ 1 + 2 }/>
- 通过表达式定义子组件
const todos = ['item1', 'item2', 'item3'];
const element = {
<ul>
{todos.map(message => <Item key={message} message={message} />)}
</ul>
};
=》是ES6中定义函数的方式。map是一种数组遍历方式,message是箭头函数的参数,key是索引,message是对应todos的值。这里将数组todos依次遍历在HTML代码中展示。
另外,JSX中只能使用JS表达式,不能使用多行JS语句。JSX可以使用三目运算符或者逻辑与(&&)运算符代替if语句。
4.标签属性
1.当JSX标签是DOM类型的标签时,对应的DOM标签支持的属性JSX也支持,部分属性名称有所变化,例如class写成className,之所以改变是因为React对DOM标签支持的事件重新做了封装。事件属性名称采用驼峰格式。
2.当JSX标签是React组件类型时,可以自定义标签的属性名。
5.注释
JSX的注释需要大括号”{}”将/**/包裹起来。
2.1.3 JSX是必需的
在《React进阶之路》一书中提到JSX不是必需的。
2.2 组件
2.2.1组件定义
定义组件的两种方式:使用ES6 class(类组件)和使用函数(函数组件)。
使用class定义组件满足的两个条件:
- class继承自React.Component
- class内部必须自定义render方法,render方法返回代表该组件UI的React元素。
使用ReactDom.render()将PostList挂载到页面的DOM节点上,在使用ReactDOM.render()需要先导入react-dom库,这个库会完成组件所代表的虚拟DOM节点到浏览器的DOM节点的转换。
import React from "react";
import ReactDOM from "react-dom";
import PostList from "./PostList";
ReactDOM.render(<PostList />, document.getElementById("root"));
2.2.2组件的props
组件的props用于把父组件中的数据或方法传递给子组件,供子组件使用。
Props是一个简单结构的对象,它包含的属性正是由组件作为JSX标签使用时的属性组成。 下面是一个使用User组件作为JSX作为JSX标签的声明:
<User name='React' age='4' address='America' >
props = {
name: 'React',
age: '4',
address: 'America'
}
{data.map(item =>
<PostItem
title={item.title}
date={item.date}
author={item.author}
/>
)}
这是类组件PostList中使用PostItem的代码,对data数组中的元素遍历一遍,然后返回大小相同的数组(其中包含了title,author,date属性),最终在UI展示。
2.2.3组件的state
组件的state是组件内部的状态,state的变化最终将反映在组件UI的变化上。在构造方法constructor中通过this.state定义组件的初始状态,并调用this.setState方法改变组件的状态。
constructor(props){
super(props);
this.state = {
vote: 0
};
}
///处理点赞逻辑
handleClick(){
let vote = this.state.vote;
vote++;
this.setState({
vote:vote
});
}
- constructor内首先调用super(props),这一步实际上是调用React.Component这个class的constructor方法来完成React组件的初始化;
- constructor通过this.state定义组件的初始状态;
- render方法中定义了处理点击事件的响应函数,响应函数内部会调用this.setState更新组件点赞数。
UI = Component(props,state)
React组件是通过props和state两中数据驱动渲染出组件UI。Props是组件对外的接口,它是只读的,组件通过props接受外部传入的数据;state是组件对内的接口,它是可变的,组件内部状态通过state来反映。
2.2.4有状态组件和无状态组件
state是用来反映组件内部状态的变化,如果一个组件的内部状态是不变的,这样的组件称为无状态组件;反之称为有状态组件。
定义无状态组件除了使用ES6 class方式外,还可以用函数定义。一个函数组件接收props作为参数,返回代表这个组件UI的React元素结构。
function Welcome(props){
return <h1>Hello, {props.name}</h1>;
}
有状态组件主要关注状态业务变化的业务逻辑,无状态组件主要关注组件UI的渲染。
2.2.5属性校验和默认属性
React提供了ProTypes这个对象,用于校验组件属性的类型。ProTypes包含组件属性所有可能的类型,通过定义一个对象(对象的key是组件属性名,val是对应属性类型)实现组件属性类型的校验。
想要知道一个对象的结构或数据元素的类型,比较好的做法是使用ProTypes.shape或Protypes.arrayof。 如果属性是组件的必需属性,也就是使用某个组件时必须传入的属性,就要爱ProTypes的类型属性上调用isRequired。
PostItem.propTypes = {
post: PropTypes.shape({
id: PropTypes.number,
title: PropTypes.string,
author: PropTypes.string,
date: PropTypes.string,
vote: PropTypes.number
}).isRequired,
onVote: PropTypes.func.isRequired
}
如上面代码,在PostItem组件中post和onVote是必需属性。
React还提供了为组件属性指定默认值的特性,这个特性通过组件的defaultProps实现。
2.2.6组件样式
1. 外部CSS样式表【首选】
此方式和平时开发Web应用时使用外部CSS文件相同,CSS样式表中根据HTML标签类型、ID、class等选择器定义元素样式。唯一区别:React要用className代替class作为选择器。
样式表的引入方式:
- 使用组件的HTML页面中通过标签引入
- 把样式表文件当做一个模块,在使用该样式表的组件中,像导入其他组件一样导入样式表文件
注意class名称冲突
2. 内联样式
2.2.7组件和元素
React元素是一个普通的JS对象,这个对象通过DOM节点或React组件描述界面是什么样子的。JSX语法就是用来创建React元素的。
<div className='foo'>
<Button color='blue'>
OK
</Button>
</div>
Button是一个自定义的React组件。
React组件是一个class或函数,它接收一些属性作为输入,返回一个React元素。React组件是由若干元素组件而成。下面的例子可以解释React组件与React元素的关系:
class Button extends React.Component{
render(){
return (<button>OK</button>);
}
}
//在JSX中使用组件Button,button表示组件Button的一个react元素
const button = <Button />;
//在组件中使用React元素button
class Page extends React.Component{
render(){
return (
<div>
{button}
</div>
);
}
}
2.3 组件的生命周期
2.3.1挂载阶段
这个阶段组件被创建,执行初始化,并被挂载到DOM中,完成组件的第一次渲染。依次调用的生命周期方法有:constructor,componentWillMount,render,componentDidMount。
- constructor
这是ES6class的构造方法,组件被创建时,会先调用组件的构造方法。如果父组件没有传入属性而组件自身定义了默认属性,那么参数props指向的是组件的默认属性。Constructo通常用于初始化组件的state以及绑定时间处理方法等工作。
- componentWillMount
这个方法在组件被挂载到DOM前调用,且只会被调用一次。在实际项目中很少会用到,因为可以将该方法提前到constructor中执行。在这个方法中调用this.setState不会引起组件的重新渲染。
- render
这是定义组件时唯一必要的方法(组件的其他生命周期方法都可以省略)。在这个方法中,根据组件的props和state返回一个React元素,用于描述组件的UI,通常React元素使用JSX语法定义。注意:render并不负责组件的实际渲染工作,它只是返回一个UI的描述,真正的渲染出页面DOM的工作由React自身负责。Render是一个纯函数,在这个方法中不能执行任何有副作用的操作。
- componentDidMount
在组件被挂载到DOM后调用,且只会被调用一次。这时候已经可以获取到DOM结构了。这个方法通常还会用于向服务器端请求数据。在这个方法中调用this.setState会引起组件的重新渲染。
2.3.2更新阶段
组件被挂载到DOM后,组件的props或state可以引起组件更新。props引起的组件更新,本质上是由渲染该组件的父组件引起的,也就是父组件的render方法被调用时,组件会发生更新过程,这个过程无论props是否改变,父组件render方法每一次调用,都会导致组件更新。State引起的组件更新,是通过调用this.setState修改组件 state触发的。组件更新阶段依次调用的声明周期方法有:compoentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render和componentDidUpdate。
- compoentWillReceiveProps(nextProps)
这个方法只有在props引起的组件更新过程中,才会被调用。State引起的组件更新并不会触发该方法的执行。方法的参数nextProps是父组件传递给当前组件的新的props。因此往往需要比较nextProps和this.props来决定是否执行props发生变化后的逻辑,比如根据新的props调用this.setState触发组件的重新渲染。
- shouldComponentUpdate(nextProps,nextState)
这个方法决定组件是否继续执行更新过程。方法返回true时,组件继续更新过程;返回false时,更新过程停止。后续的方法也不会再被调用给你。一般通过比较nextProps、nextState和当前组件的props、state决定这个方法的返回结果。这个方法可以用来减少组价不必要的渲染从而优化组件性能。
- componentWillUpdate(nextProps,nextState)
这个方法在组件render调用前执行,可以作为组件更新发生前执行,某些工作的地方,一般也很少用到。
- componentDidUpdate(prevProps,prevState)
组件更新后被调用,可以作为操作更新下后DOM的地方。这个方法的两个参数分别代表组件更新前的props和state。
2.3.3卸载阶段
组件从DOM中被卸载的过程,这个过程只有一个生命周期方法:componentWillUnMount。 这个方法在组件被卸载之前调用,可以在这里执行一些清理工作,比如清理组件中使用的定时器等等,避免引起内存泄露。
最后注意:只有类组件具有生命周期方法,函数组件是没有生命周期方法的。
2.4 列表和keys
①当列表数据为多组时,调用数据运行时为提示警告(浏览器F12->Console):” Each child in an array or iterator should have a unique ‘ key ’ prop ” 说明如果没有给每条数据添加一个’ key ‘ 的话,当数据组数过多时,不同组数有相同的数据,调用就可能会出现错误。因此这里的’ key ’ 就相当于数据库里的主键,给每组数据调取都有一个参照。
<div>
<h2>帖子列表</h2>
<ul>
{this.state.posts.map(item =>
<PostItem
key = {item.id}
post = {item}
onVote = {this.handleVote}
/>
)}
</ul>
</div>
②虽然列表元素的key不能重复,但这个唯一性仅限至于在当前列表中,而不是全局唯一。
③不推荐使用索引作为key,因为一旦列表中的数据发生重排,数据的索引也会发生变化,不利于React的渲染优化。
2.5 事件处理
1.React元素绑定事件有两点注意事项:
①在React中,事件的命名采用驼峰的形式(两个或多个单词组成的事件第一个单词首字母小写,其余的都要大写),而不是DOM元素中的小写字母命名方式。如:JavaScript里的onclick要改为onClick、onchange要改为onChange。
②处理事件的响应函数要以对象的形式赋值给事件属性,而不是DOM中的字符串形式。
如:在DOM中绑定一个点击事件是这样(传统的js写法):
<button onclick="clickButton()">
Click
</button>
而在React元素中绑定一个点击事件变成这种形式:
class App extends Component {
clickButton(){}; //clickButton函数
render() {
return (
<div>
<button
onclick={ this.clickButton }> //clickButton是一个函数
Click
</button>
</div>
)
}
}
2.React事件处理函数的三种写法:
①使用箭头函数(以下几种情况不使用箭头函数
https://www.jianshu.com/p/1357112985ef )
class ...... {
constructor(props){
super(props);
this.state = { number:0 }
}
......
<button onClick={(event)=>{console.log(this.state.number)}>Click</button>
}
②使用组件方法(直接将组件的方法赋值给元素的事件属性,同时在类的构造中,将这个方法的this绑定到当前对象)
class ...... {
constructor(props){
super(props);
this.state = { number:0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick(event){
const number = ++this.state.number;
this.setState({
number:number;
})
}
}
......
<div>{ this.state.number }</div>
<button onClick={ this.handleClick }>Click</button>
}
③属性初始化语法(property initializer syntax) (使用ES7的property initializers会自动为class中定义的方法绑定this)
handleClick = (event) => {
const number = ++this.state.number;
this.setState({
number:number
})
}
三种方法进行比较:第一种每次调用render时,会重新创建一个新的事件处理函数,带来额外的性能开销;第二种构造函数中需要为事件处理函数绑定this,多个处理事件时,代码就会显得繁琐;第三种为推荐使用方法,这种方式既不需要在构造函数中手动绑定this,也不需要担心组件重复渲染导致的函数重复创建问题。
2.6表单
2.6.1 受控组件
如果一个表单元素的值是由React来管理的,那它就是一个受控组件对于不同的表单元素,React的控制方式略有不同。下面展示一下三种长用的表单元素的控制方式。
1.文本框 文本框包含类型为text的input元素和textarea元素。它们受控的主要原理是:通过表单元素的value属性设置表单元素的值,通过表单元素的o'nChange事件监听值的变化,并将变化同步到React组件的state中。
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>
)
}
}
用户名和密码两个表单元素的值是从组件的state中获取的,当用户更改表单元素的值时,onChange事件会被触发,对应的handleChange处理函数会把变化同步到组件的state,新的state又会触发表单元素重新渲染,从而实现表单元素状态的控制。
- 列表 列表select元素时最复杂的表单元素,他可以用来创建一个下拉表
在React中,通过在select上定义value属性来决定哪一个option元素处于选中状态。这样,对select的控制只需要在select这一个元素上修改即可,不需要再关注option元素。 - 复选框和单选框 复选框的类型为checkbox的input元素,单选框为radio的input元素。复和单选框的值时不变的,需要改变的是他们的checked状态。React控制的属性变为checked
P48 代码类图及关系的描述
PostItem和PostList都继承于Component,两个类之间相互关联
PostList渲染的是
{this.state.posts.map(item =>
<PostItem key = {item.id} post= {item} onVote={this.handleVote} onSave={this.handleSave} /> )}
onVote这里用到自己定义的方法handleVote(id),根据帖子的id进行过滤,找到待修改vote属性的帖子,返回新的posts对象,然后更新state。
onSave这里用到自己定义的方法handleSave(post),但用到PostItem中的属性prop,根据post的id,过滤出当前要更新的post
PostItem渲染的是标题(通过handleTitleChange方法处理<textarea>值的变化)
点赞图片实现函数绑在了onClick(即handleVote) handleEditPost来处理保存编辑按钮点击后的逻辑
2.6.2 非受控组件
非受控组件看似简化了操作表单元素的过程,但这种方式破坏了React对组件状态管理的一致性,往往出现不容易排查的问题,因此不建议使用。
2.7 本章小结
本章详细介绍了React的主要特性及其用法。React通过JSX语法声明界面UI,将界面UI和它的逻辑封装在同一个JS文件中。组件是React的核心,根据组件的外部接口props和内部接口state完成自身的渲染。使用组件需要理解它的生命周期,借助不同的生命周期方法,组件可以实现复杂逻辑。渲染时,注意key的使用,事件处理时,注意事件名称和事件处理函数的写法。最后介绍表单元素的用法,使用方式分受控组价和非受控组件。另外,在工程项目中,注意有状态组件和无状态组件的划分从而妥善运用props和state。