React 起源于Facebook的内部项目,因为该公司对市场上所有JavaScript MVC框架,都不满意,就决定自己写一套,用来架设Instagram(照片交友)的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。
由于React的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却非常简单。所以,越来越多的人开始关注和使用,认为它可能是将来Web开发的主流工具。
React 学习路线图
react-developer-roadmap
.vue
文件,来创建对应的组件
- React是由Facebook前端官方团队进行维护和更新的;因此,React的维护开发团队,技术实力比较雄厚;
- Vue:第一版,主要是有作者尤雨溪专门进行维护的,当Vue更新到2.x版本后,也有了一个以尤雨溪为主导的开源小团队,进行相关的开发和维护;
参考:vue还是react?小孩子才做选择题!
DOM (Document Object Model) 译为文档对象模型,是浏览器中的概念,通过JS对象来表示页面上的元素,并提供了操作DOM对象的APl
HTML DOM 树形结构如下:
用JS对象结构表示 DOM 树的结构
既然有了DOM 为啥还需要VituralDOM?
为了实现页面DOM元素的高效更新
举例:比如我们要实现对日期的排序。
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}
点击按钮之后需要对内存中的对象数组进行排序,然后输出到HTML中
如何将排序后的数组渲染到页面上?
标签,拼接成一个字符串,然后替换掉原先的HTML页面的标签内的内容
- 使用模板引擎 比如ejs, 使用for标签实现,其本质上也是拼接成字符串,只是模板引擎帮我们完成了这些的工作。
-
上面的方式是否存在性能问题?如何才能做到最优的性能?
- 按需更新,只重新渲染更新的数据所对应的页面元素
-
如何做到按需更新?
- 获取内存中的新旧两棵DOM树,进行对比,得到需要被按需更新的DOM元素:
- 一个网页渲染的过程:
- 浏览器请求服务器获取页面HTML代码
- 浏览器要现在内存中,解析DOM结构,并在存览器内存中,渲染出一棵DOM树
- 浏览器把DOM树,呈现到页面上
- 所以我们需要先获取内存的新旧DOM树,比较之后按需更新,最后在把内存中最新的DOM树渲染到页面上。
-
如何获取内存中的新旧两棵DOM树?
-
如何解决上述的问题,如何比较新旧两棵DOM树?
- 聪明的程序员想到了可以通过手工模拟的方式去模拟DOM结构–> 虚拟DOM
-
如何模拟DOM树结构?
- 我们知道通过HTML能描述一个DOM树,除了HTML标签的方式还可以使用什么方式去模拟?—> js对象的方式。相对于 DOM 对象,原生的 JavaScript 对象处理起来更快,而且更简单。DOM 树上的结构、属性信息我们都可以很容易地用 JavaScript 对象表示出来
- 比如我们需要模拟下面这个div标签的DOM节点元素,要怎么用JS对象去描述?
<div id="myDiv" title="标题">
这是我的Div标签
<p>这是一个p标签p>
div>
-
- 通过js对象可以进行如下的描述
{
tagName:"div",
title:"标题",
children:[
"这是我的Div标签",
{
tagName:"p",
children:[
"这是一个p标签"
]
}
]
}
- 当DOM节点元素发生变化时候,就可以通过虚拟DOM去获取到更新的节点元素。
比如上面的div节点变成了如下:
<div id="myDiv" title="标题111">
这是我的Div标签
<p>这是一个p标签1111p>
div>
模拟出来的虚拟DOM表示如下:
{
"tagName": "div",
"title": "标题111",
"children": [
"这是我的Div标签",
{
"tagName": "p",
"children": [
"这是一个p标签111"
]
}
]
}
通过模拟虚拟DOM就可以很容易知道新旧两个DOM树之间更新的部分在哪里。
简单总结虚拟DOM(Vitural DOM)的处理方式
- 用JS对象结构表示DOM树的结构,然后用这个虚拟DOM树构建一个真正的DOM树,插到文档当中
- 当状态变更的时候,重新构造一棵新的虚拟DOM树。然后用新的树和旧的树进行比较,记录两棵树差异
- 把第2步骤所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了
所以React所有操作都是针对虚拟DOM,然后再把这个虚拟DOM变成浏览器中的DOM元素,最后在渲染到页面上。
简单总结虚拟DOM
用JS对象的形式来模拟页面上DOM嵌套关系(虚拟DOM是以JS对象的形式存在的)这就是React 中虚拟DOM的概念
- 本质:用JS对象模拟DOM元素和嵌套关系
- 目的:就是为了实现页面元素的高效更新
Diff算法
通过前面的虚拟DOM我们就可以模拟出浏览器内存中的新旧DOM树,但是要做到高效的更新,还是离不开高效的更新算法,所以我们下面就要提到Diff算法。
当使用React的时候,在某个时间点render()
函数创建了一棵React元素树(虚拟DOM树),
在下一个state或者props更新的时候,render()
函数将创建一棵新的React元素树(虚拟DOM树),
React将对比这两棵树的不同之处,计算出如何高效的更新UI(只更新变化的地方)
了解更多
如何实现一个 Virtual DOM 算法
理解 Virtual DOM
React 入门
环境准备
搭建本地开发环境
npx create-react-app my-app
cd my-app
npm start
参考:https://react.docschina.org/tutorial/tutorial.html#setup-option-2-local-development-environment
开发者工具
Hello React
前面说React 通过读取虚拟DOM(React DOM),然后使用它们来构建 DOM 以及保持随时更新,所以我们第一步就是需要创建虚拟DOM(React DOM)
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
const title = React.createElement("h1", {className: "main"},
"Hello React!!") //Hello React!!
ReactDOM.render(title, document.getElementById('root'))
嵌套子节点,在
标签外套一层标签:
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
const title = React.createElement("h1", {className: "main"},
"Hello React!!")
const div = React.createElement("div", {id:"myDiv"},
"这是个Div",title) //这是个DivHello React!!
ReactDOM.render(div, document.getElementById('root'))
createElement()的问题
说明:createElement()方式,代码编写不友好,太复杂
var dv = React.createElement(
"div",
{ className: "shopping-list" },
React.createElement(
"h1",
null,
"Shopping List for "
),
React.createElement(
"ul",
null,
React.createElement(
"li",
null,
"Instagram"
),
React.createElement(
"li",
null,
"WhatsApp"
)
)
)
// 渲染
ReactDOM.render(dv, document.getElementById('root'))
JSX 的基本使用
- JSX语法,最终会被编译为
createElement()
方法
- 推荐:使用 JSX 的方式创建组件
- JSX - JavaScript XML
- JSX的语法需要通过 babel-preset-react 编译后,才能被解析执行
JSX的注意点
- 如果在 JSX 中给元素添加类, 需要使用 className 代替 class, 因为class是js的关键字
- 在 JSX 中注释语法:{/* 中间是注释的内容 */}
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
ReactDOM.render(
<h1 className="main">Hello, React! {/*这是注释*/}</h1>,
document.getElementById('root')
);
- 在 JSX 中可以直接使用 JS代码,直接在 JSX 中通过 {} 中间写 JS代码即可
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
ReactDOM.render(
<h1 className="main">Hello, React! {alert("Hello World")}</h1>,
document.getElementById('root')
);
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
const a1 = true
const a2 = 100
const a3 = "Hello React"
const a4 = new Date()
const element = <h1>Hello, {a3}</h1> //React元素
const user = {firstName:'Kevin',lastName:'Cai'}
function fun(user){
return user.firstName + ' ' + user.lastName;
}
ReactDOM.render(
<div>
<h1>{a1.toString()}</h1>
<h1>{a2 + 1}</h1>
<h1>{a3}</h1>
<h1>{a4.toLocaleString()}</h1>
<h1>{a1 ? "条件为真" : "条件为假"}</h1>
{element}
{fun(user)}
</div>,
document.getElementById('root')
)
- 在JSX中只有一个根结点
下面这种方式是有问题的
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
ReactDOM.render(
<h1 className="main">Hello, React!</h1>
<h1>Hello World!</h1>,
document.getElementById('root')
);
下面这种方式才是OK的:
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
ReactDOM.render(
<div>
<h1 className="main">Hello, React!</h1>
<h1>Hello World!</h1>
</div>,
document.getElementById('root')
)
- 在JSX中节点元素必须闭合,要符合XML规范
下面这种方式在编译的时候就会报错了
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
ReactDOM.render(
<div>
<h1 className="main">Hello, React!</h1>
<hr>
<h1>Hello World!</h1>
</div>,
document.getElementById('root')
)
节点元素必须要闭合
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
ReactDOM.render(
<div>
<h1 className="main">Hello, React!</h1>
<hr/>
<h1>Hello World!</h1>
</div>,
document.getElementById('root')
)
React组件
React创建组件的两种方式
- 通过 JS函数创建(无状态组件)
- 通过 class创建(有状态组件)
- 定义组件最简单的方式就是编写 JavaScript 函数
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Kevin" />;
ReactDOM.render(
element,
document.getElementById('root')
);
该函数是一个有效的 React 组件,因为它接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素。这类组件被称为“函数组件”,因为它本质上就是 JavaScript 函数。
【注意】
- 函数名称必须为大写字母开头,React通过这个特点来判断是不是一个组件
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
class welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
const element = <welcome name="Kevin" />;
ReactDOM.render(
element,
document.getElementById('root')
);
如果函数名称是小写,React是不会把它当做一个组件,所以页面上是不会显示的。
- 函数必须有返回值,返回值可以是:JSX对象或null
- 返回的JSX,必须有一个根元素
- 组件的返回值使用()包裹,避免换行问题
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
class Welcome extends React.Component {
render() {
return
<h1>Hello, {this.props.name}</h1>;
}
}
const element = <Welcome name="Kevin" />;
ReactDOM.render(
element,
document.getElementById('root')
);
这个时候可以使用()
来包裹,避免换行的问题,建议都加上,避免格式化问题导致出问题。
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
class Welcome extends React.Component {
render() {
return(
<h1>Hello, {this.props.name}</h1>);
}
}
const element = <Welcome name="Kevin" />;
ReactDOM.render(
element,
document.getElementById('root')
);
- 同时还可以使用 ES6 的 class 来定义组件:
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
// react对象继承字React.Component
class Welcome extends React.Component {
// class创建的组件中 必须有rander方法 且显示return一个react对象或者null
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
const element = <Welcome name="Kevin" />;
ReactDOM.render(
element,
document.getElementById('root')
);
上述两个组件在 React 里是等效的。
函数式组件 和 class 组件的使用场景
- 如果一个组件仅仅是为了展示数据,那么此时就可以使用 函数组件
- 如果一个组件中有一定业务逻辑,需要操作数据,那么就需要使用 class 创建组件,因为,此时需要使用 state
给组件传递数据
- 组件中有一个 只读的对象 叫做 props,无法给props添加属性
- 获取方式:函数参数 props
- 作用:将传递给组件的属性转化为 props 对象中的属性
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
function Welcome(props) {
// props ---> { name: 'Kevin', age: 20 }
return (
<div>
<div>Welcome React</div>
<h3>姓名:{props.name}----年龄是:{props.age}</h3>
</div>
)
}
//传递 props:name 和 age(如果你想要传递number类型数据 就需要向下面这样)
const element = <Welcome name="Kevin" age={20}/>
ReactDOM.render(
element,
document.getElementById('root')
)
封装组件到独立的文件中
Vue.js是使用.vue
文件来声明一个组件,同样React也可以把我们的自定义组件封装到一个独立的文件中。
// 1. 引入React模块
// 由于 JSX 编译后会调用 React.createElement 方法,所以在你的 JSX 代码中必须首先拿到React。
import React from 'react'
import '../index.css'
// 2. 使用function构造函数创建组件
function Welcome(props) {
return (
<div>
<div className="main">Welcome React</div>
<h3>姓名:{props.name}----年龄是:{props.age}</h3>
</div>
)
}
// 3. 导出组件
export default Welcome
使用import Welcome from './component/Welcome'
来导入组件
import React from 'react'
import ReactDOM from 'react-dom'
import Welcome from './component/Welcome'
//传递 props:username 和 age(如果你想要传递number类型数据 就需要向下面这样)
const element = <Welcome name="Kevin" age={20}/>
ReactDOM.render(
element,
document.getElementById('root')
)
props和state
props
- 作用:给组件传递数据,一般用在父子组件之间
- 说明:React把传递给组件的属性转化为一个对象并交给 props
- 特点:props是只读的,无法给props添加或修改属性
// props 是一个包含数据的对象参数,不要试图修改 props 参数
// 返回值:react元素
function Welcome(props) {
// 返回的 react元素中必须只有一个根元素
return <div>hello, {props.name}</div>
}
class Welcome extends React.Component {
constructor(props) {
super(props)
}
render() {
return <h1>Hello, {this.props.name}</h1>
}
}
state
状态即数据
- 作用:用来给组件提供组件内部使用的数据
- 注意:只有通过class创建的组件才具有状态
- 注意:状态是私有的,完全由组件来控制
import React from 'react'
import ReactDOM from 'react-dom'
class Welcome extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'Kevin'
}
}
render() {
return <h1>Hello, {this.state.name}</h1>
}
}
const element = <Welcome/>
ReactDOM.render(
element,
document.getElementById('root')
)
组件的生命周期
简单说:一个组件从开始到最后消亡所经历的各种状态,就是一个组件的生命周期
组件生命周期函数的定义:从组件被创建,到组件挂载到页面上运行,再到页面关闭组件被卸载,这三个阶段总是伴随着组件各种各样的事件,那么这些事件,统称为组件的生命周期函数!
组件生命周期函数总览
组件的生命周期包含三个阶段:创建阶段(Mounting)、运行和交互阶段(Updating)、卸载阶段(Unmounting)
组件生命周期 - 创建阶段(Mounting)
- constructor()
- 作用:获取props和初始化state
- 说明:通过 constructor() 的参数props获取
- 设置state和props
import React from 'react'
import ReactDOM from 'react-dom'
class Welcome extends React.Component {
constructor(props) {
super(props) //通过 constructor() 的参数props获取
this.state = { //设置state和props
name: props.name
}
}
render() {
return <h1>Hello, {this.state.name}</h1>
}
}
const element = <Welcome name="Kevin"/>
ReactDOM.render(
element,
document.getElementById('root')
)
- componentWillMount()
- 说明:组件被挂载到页面之前调用,其在render()之前被调用,因此在这方法里同步地设置状态将不会触发重渲染
- 注意:无法获取页面中的DOM对象
- 注意:可以调用 setState() 方法来改变状态值
- 用途:发送ajax请求获取数据
import React from 'react'
import ReactDOM from 'react-dom'
class Welcome extends React.Component {
constructor(props) {
super(props) //通过 constructor() 的参数props获取
this.state = { //设置state和props
name: props.name
}
}
//组件被挂载到页面之前调用,其在render()之前被调用,因此在这方法里同步地设置状态将不会触发重渲染
componentWillMount() {
console.warn(document.getElementById('hello')) // null
this.setState({
name: "Kevin Cai"
})
}
render() {
return <h1 id="hello">Hello, {this.state.name}</h1>
}
}
const element = <Welcome name="Kevin"/>
ReactDOM.render(
element,
document.getElementById('root')
)
![在这里插入图片描述](http://img.e-com-net.com/image/info8/9efb16a4660042dda1265616198ba376.jpg)
- render()
- 作用:渲染组件到页面中,无法获取页面中的DOM对象
- 注意:不要在render方法中调用 setState() 方法,否则会递归渲染
- 原因说明:状态改变会重新调用render(),render()又重新改变状态
import React from 'react'
import ReactDOM from 'react-dom'
class Welcome extends React.Component {
constructor(props) {
super(props) //通过 constructor() 的参数props获取
this.state = { //设置state和props
name: props.name
}
}
//渲染组件到页面中,无法获取页面中的DOM对象
render() {
console.warn(document.getElementById('hello')) // null
return <h1 id="hello">Hello, {this.state.name}</h1>
}
}
const element = <Welcome name="Kevin"/>
ReactDOM.render(
element,
document.getElementById('root')
)
不要再render方法中更新state,状态改变会重新调用render(),render()又重新改变状态,会导致无限递归
import React from 'react'
import ReactDOM from 'react-dom'
class Welcome extends React.Component {
constructor(props) {
super(props) //通过 constructor() 的参数props获取
this.state = { //设置state和props
name: props.name
}
}
//渲染组件到页面中,无法获取页面中的DOM对象
render() {
console.warn(document.getElementById('hello')) // null
this.setState({
name: "Kevin Cai"
})
return <h1 id="hello">Hello, {this.state.name}</h1>
}
}
const element = <Welcome name="Kevin"/>
ReactDOM.render(
element,
document.getElementById('root')
)
- componentDidMount()
- 组件已经挂载到页面中
- 可以进行DOM操作,比如:获取到组件内部的DOM对象
- 可以发送请求获取数据
- 可以通过 setState() 修改状态的值
- 注意:在这里修改状态会重新渲染
import React from 'react'
import ReactDOM from 'react-dom'
class Welcome extends React.Component {
constructor(props) {
super(props) //通过 constructor() 的参数props获取
this.state = { //设置state和props
name: props.name
}
}
componentDidMount() {
// 此时,就可以获取到组件内部的DOM对象
console.warn(document.getElementById('hello'))
}
render() {
return <h1 id="hello">Hello, {this.state.name}</h1>
}
}
const element = <Welcome name="Kevin"/>
ReactDOM.render(
element,
document.getElementById('root')
)
组件生命周期 - 运行阶段(Updating)
- 特点:该阶段的函数执行多次
- 说明:每当组件的props或者state改变的时候,都会触发运行阶段的函数
- componentWillReceiveProps()
- 说明:组件接受到新的props前触发这个方法,props改变
- 参数:当前组件props值
- 可以通过 this.props 获取到上一次的值
- 使用:若你需要响应属性的改变,可以通过对比this.props和nextProps并在该方法中使用
this.setState()
处理状态改变
- 注意:修改state不会触发该方法
通过如下的代码来演示props的改变:
import React from 'react'
import ReactDOM from 'react-dom'
class Parent extends React.Component {
constructor(props) {
super(props) //通过 constructor() 的参数props获取
this.state = { //设置state和props
name: props.name
}
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState({
name: "Kevin Cai"
})
}
render() {
return <button id="hello" onClick={this.handleClick}>
Parent - Hello {this.state.name}, <Child name={this.state.name}/>
</button>
}
}
class Child extends React.Component {
constructor(props) {
super(props) //通过 constructor() 的参数props获取
}
componentWillReceiveProps(nextProps) {
console.log("Child - componentWillReceiveProps")
console.log(`this.props-->${JSON.stringify(this.props)}`)
console.log(`nextProps-->${JSON.stringify(nextProps)}`)
}
render() {
return <h3>Child - Hello, {this.props.name}</h3>
}
}
const element = <Parent name="Kevin"/>
ReactDOM.render(
element,
document.getElementById('root')
)
- shouldComponentUpdate()
- 作用:根据这个方法的返回值决定是否重新渲染组件,返回true重新渲染,否则不渲染
- 优势:通过某个条件渲染组件,降低组件渲染频率,提升组件性能
- 说明:如果返回值为false,那么,后续render()方法不会被调用
- 注意:这个方法必须返回布尔值!!!
- 场景:根据随机数决定是否渲染组件,或者比如进度条的更新。
import React from 'react'
import ReactDOM from 'react-dom'
class Counter extends React.Component {
constructor(props) {
super(props) //通过 constructor() 的参数props获取
this.state = {
count: 0
}
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState(state => ({
count: state.count + 1
}))
}
// - 参数:
// - 第一个参数:最新属性对象
// - 第二个参数:最新状态对象
shouldComponentUpdate(nextProps, nextState) {
console.log('shouldComponentUpdate')
console.log(`nextProps-->${JSON.stringify(nextProps)}`)
console.log(`nextState-->${JSON.stringify(nextState)}`)
return nextState.count % 2 === 0
}
render() {
return <button onClick={this.handleClick}>{this.state.count}
</button>
}
}
const element = <Counter/>
ReactDOM.render(
element,
document.getElementById('root')
)
- componentWillUpdate()
- 作用:组件将要更新
- 参数:最新的属性和状态对象
- 注意:不能修改状态 否则会循环渲染
import React from 'react'
import ReactDOM from 'react-dom'
class Counter extends React.Component {
constructor(props) {
super(props) //通过 constructor() 的参数props获取
this.state = {
count: 0
}
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState(state => ({
count: state.count + 1
}))
}
shouldComponentUpdate(nextProps, nextState) {
console.log('shouldComponentUpdate')
console.log(`nextProps-->${JSON.stringify(nextProps)}`)
console.log(`nextState-->${JSON.stringify(nextState)}`)
return nextState.count % 2 === 0
}
componentWillUpdate(nextProps, nextState) {
console.log('componentWillUpdate')
console.log(`nextProps-->${JSON.stringify(nextProps)}`)
console.log(`nextState-->${JSON.stringify(nextState)}`)
}
render() {
return <button onClick={this.handleClick}>{this.state.count}
</button>
}
}
const element = <Counter/>
ReactDOM.render(
element,
document.getElementById('root')
)
- render() 渲染
- 作用:重新渲染组件,与Mounting阶段的render是同一个函数
- 注意:这个函数能够执行多次,只要组件的属性或状态改变了,这个方法就会重新执行
import React from 'react'
import ReactDOM from 'react-dom'
class Counter extends React.Component {
constructor(props) {
super(props) //通过 constructor() 的参数props获取
this.state = {
count: 0
}
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState(state => ({
count: state.count + 1
}))
}
shouldComponentUpdate(nextProps, nextState) {
console.log('shouldComponentUpdate')
console.log(`nextProps-->${JSON.stringify(nextProps)}`)
console.log(`nextState-->${JSON.stringify(nextState)}`)
return nextState.count % 2 === 0
}
componentWillUpdate(nextProps, nextState) {
console.log('componentWillUpdate')
console.log(`nextProps-->${JSON.stringify(nextProps)}`)
console.log(`nextState-->${JSON.stringify(nextState)}`)
}
render() {
console.log("render.....")
return <button onClick={this.handleClick}>{this.state.count}
</button>
}
}
const element = <Counter/>
ReactDOM.render(
element,
document.getElementById('root')
)
- componentDidUpdate()
- 作用:组件已经被更新
- 参数:旧的属性和状态对象
import React from 'react'
import ReactDOM from 'react-dom'
class Counter extends React.Component {
constructor(props) {
super(props) //通过 constructor() 的参数props获取
this.state = {
count: 0
}
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState(state => ({
count: state.count + 1
}))
}
shouldComponentUpdate(nextProps, nextState) {
console.log('shouldComponentUpdate')
console.log(`nextProps-->${JSON.stringify(nextProps)}`)
console.log(`nextState-->${JSON.stringify(nextState)}`)
return nextState.count % 2 === 0
}
componentWillUpdate(nextProps, nextState) {
console.log('componentWillUpdate')
console.log(`nextProps-->${JSON.stringify(nextProps)}`)
console.log(`nextState-->${JSON.stringify(nextState)}`)
}
render() {
console.log("render.....")
return <button onClick={this.handleClick}>{this.state.count}
</button>
}
componentDidUpdate(prevProps, prevState) {
console.log('componentDidUpdate')
console.log(`prevProps-->${JSON.stringify(prevProps)}`)
console.log(`prevState-->${JSON.stringify(prevState)}`)
}
}
const element = <Counter/>
ReactDOM.render(
element,
document.getElementById('root')
)
组件生命周期 - 卸载阶段(Unmounting)
- 组件销毁阶段:组件卸载期间,函数比较单一,只有一个函数,这个函数也有一个显著的特点:组件一辈子只能执行依次!
- 使用说明:只要组件不再被渲染到页面中,那么这个方法就会被调用( 渲染到页面中 -> 不再渲染到页面中 )
- componentWillUnmount()
- 作用:在卸载组件的时候,执行清理工作,比如
- 清除定时器
- 清除componentDidMount创建的DOM对象
了解更多
React生命周期的图解
React生命周期图解
生命周期图谱
state和setState
- 注意:使用 setState() 方法修改状态,状态改变后,React会重新渲染组件
- 注意:不要直接修改state属性的值,这样不会重新渲染组件!!!
import React from 'react'
import ReactDOM from 'react-dom'
class Counter extends React.Component {
constructor(props) {
super(props) //通过 constructor() 的参数props获取
this.state = {
count: 0
}
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.state.count = 1 //这样方式,不会重新渲染组件
}
render() {
return <button onClick={this.handleClick}>{this.state.count}</button>
}
}
const element = <Counter/>
ReactDOM.render(
element,
document.getElementById('root')
)
- 使用:
- 初始化state
- setState修改state
使用的正确姿势:
- 修改state的方式一
import React from 'react'
import ReactDOM from 'react-dom'
class Counter extends React.Component {
constructor(props) {
super(props) //通过 constructor() 的参数props获取
this.state = { //初始化 state
count: 0
}
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
// 修改 state 的值
// 方式一:
this.setState({
count: this.state.count + 1
})
}
render() {
return <button onClick={this.handleClick}>{this.state.count}</button>
}
}
const element = <Counter/>
ReactDOM.render(
element,
document.getElementById('root')
)
setState()
是异步操作
import React from 'react'
import ReactDOM from 'react-dom'
class Counter extends React.Component {
constructor(props) {
super(props) //通过 constructor() 的参数props获取
this.state = { //初始化 state
count: 0
}
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
// 修改 state 的值
// 方式一:
this.setState({
count: this.state.count + 1
})
console.log(this.state.count) //输出当前的this.state.count
}
render() {
return <button onClick={this.handleClick}>{this.state.count}</button>
}
}
const element = <Counter/>
ReactDOM.render(
element,
document.getElementById('root')
)
可以看到输出的结果并不是最新的,说明setState方法并不是同步的,而是异步的。
那如果我们就想要拿到最新的count的结果呢? 要怎么做?
import React from 'react'
import ReactDOM from 'react-dom'
class Counter extends React.Component {
constructor(props) {
super(props) //通过 constructor() 的参数props获取
this.state = { //初始化 state
count: 0
}
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
// 修改 state 的值
// 方式一:
this.setState({
count: this.state.count + 1
},function(){
// 由于 setState() 是异步操作,所以,如果想立即获取修改后的state
// 需要在回调函数中获取
console.log(this.state.count) //输出当前的this.state.count
})
}
render() {
return <button onClick={this.handleClick}>{this.state.count}</button>
}
}
const element = <Counter/>
ReactDOM.render(
element,
document.getElementById('root')
)
- 修改state的方式二
import React from 'react'
import ReactDOM from 'react-dom'
class Counter extends React.Component {
constructor(props) {
super(props) //通过 constructor() 的参数props获取
this.state = { //初始化 state
count: 0
}
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
// 修改 state 的值
// 方式二:
this.setState(function(prevState, props) {
console.log("prevState:",prevState)
console.log("props:",props)
return {
count: prevState.count + props.increment
}
})
}
render() {
return <button onClick={this.handleClick}>{this.state.count}</button>
}
}
const element = <Counter increment = {1}/>
ReactDOM.render(
element,
document.getElementById('root')
)
或者还可以写成如下:
import React from 'react'
import ReactDOM from 'react-dom'
class Counter extends React.Component {
constructor(props) {
super(props) //通过 constructor() 的参数props获取
this.state = { //初始化 state
count: 0
}
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
// 修改 state 的值
// 方式二:
// 或者 - 注意: => 后面需要带有小括号,因为返回的是一个对象
this.setState((prevState, props) => ({
count: prevState.count + props.increment
}))
}
render() {
return <button onClick={this.handleClick}>{this.state.count}</button>
}
}
const element = <Counter increment = {1}/>
ReactDOM.render(
element,
document.getElementById('root')
)
组件绑定事件
React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
- React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
- 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
例如,传统的 HTML:
<button onclick="activateLasers()">
Activate Lasers
button>
在 React 中略微不同:
<button onClick={activateLasers}>
Activate Lasers
button>
在 React 中另一个不同点是你不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault
。例如,传统的 HTML 中阻止链接默认打开一个新页面,你可以这样写:
<a href="#" onclick="console.log('The link was clicked.'); return false">
Click me
a>
在 React 中,可能是这样的:
import React from 'react'
import ReactDOM from 'react-dom'
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
const element = <ActionLink/>
ReactDOM.render(
element,
document.getElementById('root')
)
事件绑定中的this
必须谨慎对待 JSX 回调函数中的 this,在 JavaScript 中,class
的方法默认不会绑定 this。如果你忘记绑定 this.handleClick
并把它传入了 onClick
,当你调用这个函数的时候 this 的值为 undefined
。
import React from 'react'
import ReactDOM from 'react-dom'
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
}
handleClick() {
console.log(this)
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
通过bind绑定
- 原理:bind能够调用函数,改变函数内部this的指向,并返回一个新函数
- 说明:bind第一个参数为返回函数中this的指向,后面的参数为传给返回函数的参数
import React from 'react'
import ReactDOM from 'react-dom'
class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {msg : "button"}
}
handleClick() {
console.log(this)
this.setState({
msg: '点击事件修改state的值'
})
}
render() {
return (
<button onClick={this.handleClick.bind(this)}>
{this.state.msg}
</button>
)
}
}
ReactDOM.render(
<MyButton />,
document.getElementById('root')
)
import React from 'react'
import ReactDOM from 'react-dom'
class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {msg : "button"}
}
handleClick(arg1, arg2) {
console.log(this)
this.setState({
msg: '点击事件修改state的值' + arg1 + arg2
})
}
render() {
return (
// 有参数
<button onClick={this.handleClick.bind(this, 'abc', [1, 2])}>
{this.state.msg}
</button>
)
}
}
ReactDOM.render(
<MyButton />,
document.getElementById('root')
)
- 在构造函数中使用bind
import React from 'react'
import ReactDOM from 'react-dom'
class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {msg : "button"}
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
console.log(this)
this.setState({
msg: '点击事件修改state的值'
})
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.msg}
</button>
)
}
}
ReactDOM.render(
<MyButton />,
document.getElementById('root')
)
通过箭头函数绑定
- 原理:箭头函数中的this由所处的环境决定,自身不绑定this
import React from 'react'
import ReactDOM from 'react-dom'
class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {msg : "button"}
}
handleClick(arg1, arg2) {
console.log(this)
this.setState({
msg: '点击事件修改state的值' + arg1 + arg2
})
}
render() {
return (
<button onClick={() => { this.handleClick('参数1', '参数2') }}>
{this.state.msg}
</button>
)
}
}
ReactDOM.render(
<MyButton />,
document.getElementById('root')
)
条件渲染
React 中的条件渲染和 JavaScript 中的一样,使用 JavaScript 运算符 if 或者条件运算符去创建元素来表现当前的状态,然后让 React 根据它们来更新 UI。
import React from 'react'
import ReactDOM from 'react-dom'
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
ReactDOM.render(
// Try changing to isLoggedIn={true}:
<Greeting isLoggedIn={false} />,
document.getElementById('root')
);
- 与运算符 &&
通过花括号包裹代码,你可以在 JSX 中嵌入任何表达式。这也包括 JavaScript 中的逻辑与 (&&) 运算符。它可以很方便地进行元素的条件渲染。
import React from 'react'
import ReactDOM from 'react-dom'
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
return isLoggedIn && <UserGreeting />;
}
ReactDOM.render(
// Try changing to isLoggedIn={true}:
<Greeting isLoggedIn={true} />,
document.getElementById('root')
);
true && expression 总是会返回 expression, 而 false && expression 总是会返回 false。
因此,如果条件是 true,&& 右侧的元素就会被渲染,如果是 false,React 会忽略并跳过它。
- 三目运算符
另一种内联条件渲染的方法是使用 JavaScript 中的三目运算符 condition ? true : false。
import React from 'react'
import ReactDOM from 'react-dom'
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
//三目运算符
return isLoggedIn ? <UserGreeting /> : <GuestGreeting />;
}
ReactDOM.render(
// Try changing to isLoggedIn={true}:
<Greeting isLoggedIn={false} />,
document.getElementById('root')
);
列表 & Key
import React from 'react'
import ReactDOM from 'react-dom'
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li>{number}</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
当我们运行这段代码,将会看到一个警告 Warning: Each child in a list should have a unique “key” prop.,意思是当你创建一个元素时,必须包括一个特殊的 key 属性。
key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
import React from 'react'
import ReactDOM from 'react-dom'
function NumberList(props) {
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用数据中的 id 来作为元素的 key
当元素没有确定 id 的时候,万不得已你可以使用元素索引 index 作为 key
import React from 'react'
import ReactDOM from 'react-dom'
function NumberList(props) {
const listItems = props.numbers.map((number, index) =>
<li key={index}>
{number}
</li>
)
return (
<ul>{listItems}</ul>
)
}
const numbers = [1, 2, 3, 4, 5]
ReactDOM.render(
<NumberList numbers={numbers}/>,
document.getElementById('root')
)
受控组件
在HTML当中,像input,textarea和select这类表单元素会维持自身状态,并根据用户输入进行更新。
在React中,可变的状态通常保存在组件的state中,并且只能用 setState() 方法进行更新.
React根据初始状态渲染表单组件,接受用户后续输入,改变表单组件内部的状态。
因此,将那些值由React控制的表单元素称为:受控组件。
import React from 'react'
import ReactDOM from 'react-dom'
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('提交的名字: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="提交" />
</form>
);
}
}
ReactDOM.render(
<NameForm/>,
document.getElementById('root')
)
import React from 'react'
import ReactDOM from 'react-dom'
class EssayForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '请撰写一篇关于你喜欢的 DOM 元素的文章.'
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('提交的文章: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
文章:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="提交" />
</form>
);
}
}
ReactDOM.render(
<EssayForm/>,
document.getElementById('root')
)
import React from 'react'
import ReactDOM from 'react-dom'
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: '葡萄柚'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('你喜欢的风味是: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
选择你喜欢的风味:
<select value={this.state.value} onChange={this.handleChange}>
<option value="葡萄柚">葡萄柚</option>
<option value="酸橙">酸橙</option>
<option value="椰子">椰子</option>
<option value="芒果">芒果</option>
</select>
</label>
<input type="submit" value="提交" />
</form>
);
}
}
ReactDOM.render(
<FlavorForm/>,
document.getElementById('root')
)
学习参考
React入门看这篇就够了
React官网文档
React 菜鸟教程
你可能感兴趣的:(React.js)