React.js框架 + TSX 知识点归纳
- 用了几年
Vue
,但对于React
还处在懵懂的状态,趁着最近工作不忙,赶紧把React
的知识补上 - 强烈推荐哔哩哔哩上的张天禹老师的教程:尚硅谷React教程(2022加更,B站超火react教程)
- 有
vue
基础的上手特别快,有需要学习React
的伙伴可以观看一下
什么是 React
React
是一个简单的 javascript UI
库,用于构建高效、快速的用户界面。它是一个轻量级库,因此很受欢迎。它遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效。它使用 虚拟DOM
来有效地操作 DOM
。它遵循从高阶组件到低阶组件的单向数据流。
React
和 Vue
的区别
核心思想不同
Vue
的核心思想是尽可能的降低前端开发的门槛,是一个灵活易用的渐进式双向绑定的MVVM
框架。React
的核心思想是声明式渲染和组件化、单向数据流,React
既不属于MVC
也不属于MVVM
架构。声明式是什么意思?
声明式与之相对应的是命令式,命令式指的是通过DOM操作一步步把网页变成想要的样子,而声明式则是只需要通过状态去形容最后的网页长什么样子即可。
组件化是什么意思?
组件化指的是尽可能的将页面拆分成一个个较小的、可以复用的组件,这样让我们的代码更加方便组织和管理,并且拓展性页更强。
如何理解React的单向数据流?
React
的单向数据流指的是数据主要从父节点通过props
传递到子节点,如果顶层某个props
改变了,React
会重新渲染所有的子节点,但是单向数据流并非单向绑定,React
想要从一个组件去更新另一个组件的状态,需要进行状态提升,即将状态提升到他们最近的祖先组件中,触发父组件的状态变更,从而影响另一个组件的显示。单向数据流的好处是能够保证状态改变的可追溯性,假如,父组件维护了一个状态,子组件如果能够随意更改父组件的状态,那么各组件的状态改变就会变得难以追溯。
组件写法上不同
Vue
的组件写法是通过template
的单文件组件格式。React
的组件写法是JSX+inline style
,也就是吧HTML
和CSS
全部写进JavaScript
中。
diff
算法不同
vue
对比节点,如果节点元素类型相同,但是className
不同,认为是不同类型的元素,会进行删除重建,但是react
则会认为是同类型的节点,只会修改节点属性。vue
的列表比对采用的是首尾指针法,而react
采用的是从左到右依次比对的方式,当一个集合只是把最后一个节点移动到了第一个,react
会把前面的节点依次移动,而vue
只会把最后一个节点移动到最后一个,从这点上来说vue
的对比方式更加高效。
响应式原理不同
React
主要是通过setState()
方法来更新状态,状态更新之后,组件也会重新渲染。vue
会遍历data
数据对象,使用Object.definedProperty()
将每个属性都转换为getter
和setter
,每个Vue组件实例
都有一个对应的watcher
实例,在组件初次渲染的时候会记录组件用到了那些数据,当数据发生改变的时候,会触发setter
方法,并通知所有依赖这个数据的watcher
实例调用update
方法去触发组件的compile
渲染方法,进行渲染数据。
什么是 JSX
?
JSX
是 javascript
的语法扩展。它就像一个拥有 javascript
全部功能的模板语言。它生成 React
元素,这些元素将在 DOM中
呈现。React
建议在组件使用 JSX
。在 JSX
中,我们结合了 javascript
和 HTML
,并生成了可以在 DOM中
呈现的 react
元素。
函数式组件和类组件
什么是函数式组件(无状态)
函数或无状态组件是一个纯函数,它可接受接受参数,并返回 react
元素。这些都是没有任何副作用的纯函数。这些组件没有状态或生命周期方法
1 | import { FC } from 'react'; // 其中 FC 是 FunctionComponent 的缩写 |
什么是类组件(有状态)
类或有状态组件具有状态和生命周期方可能通过 setState()
方法更改组件的状态。类组件是通过扩展 React
创建的。它在构造函数中初始化,也可能有子组件。
1 | import { Component } from 'react'; |
组件三大核心属性之一: State
state
是React
组件对象中最重要的属性,值是对象(可以包含多个key-value的组合)。- 组件被称为
状态机
,通过更新组件中的state
来更新对应的页面显示(重新渲染组件)。 - 我们可以理解为
Vue
中的data
。 state
不能直接更改,要通过setState()
方法更改组件的状态。
初始化 state
1 | import { Component } from 'react'; |
事件绑定和更改 State
状态
1 | import { Component } from 'react'; |
state
的状态不能直接改变,需要通过setState()
方法去更改- 事件绑定名称需要使用驼峰法,例如:
onClick
、onChange
、onBlur
等 - 事件的回调是一个函数,事件的方法要写成箭头函数的形式,让
this
指向类的实例
组件三大核心属性之一: Props
Props
是只读属性,传递给组件以呈现UI和状态。
1 | import { Component } from 'react'; |
PropTypes
为组件提供类型检查
1 | import { Component } from 'react'; |
组件三大核心属性之一: Refs
何时使用 Refs
- 管理焦点,文本选择或媒体播放。
- 触发强制动画。
- 集成第三方 DOM 库。
创建 Refs
使用
React.createRef()
创建1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import React,{ Component } from 'react';
export default class App extends Component<any,any> {
// 创建 Refs
textInput = React.createRef()
focusTextInput = () => {
// 直接使用原生 API 使 text 输入框获得焦点
// 注意:我们通过 "current" 来访问 DOM 节点
this.textInput.current.focus();
}
render() {
return (
<div>
<h1 onClick={this.focusTextInput}>Holle React!</h1>
<input type="text" ref={this.textInput}/>
</div>
)
}
}回调
Refs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import { Component } from 'react';
export default class App extends Component<any,any> {
// 创建 Refs
textInput = null
setTextInputRef = (e) => {
// 绑定 Refs
this.textInput = e
}
componentDidMount(){
// 组件挂载后,让文本框自动获得焦点
this.textInput && this.textInput.focus()
}
render() {
return <input type="text" ref={this.setTextInputRef}/>
}
}过时
API
:String Refs
1
2
3
4
5
6
7
8
9
10
11import { Component } from 'react';
export default class App extends Component<any,any> {
componentDidMount(){
// 组件挂载后,让文本框自动获得焦点
this.refs.textInput.focus()
}
render() {
return <input type="text" ref="textInput"/>
}
}如果你目前还在使用
this.refs.textInput
这种方式访问refs
,我们建议用回调函数或 createRef API 的方式代替。
React 生命周期
什么是生命周期?
- 生命周期就是组件从挂载到销毁的一个过程,每个特定的阶段都会有对应的
API
去完成对应的操作 React
组件的生命周期可以分为3个状态:- Mounting(挂载):已插入真实 DOM
- Updating(更新):正在被重新渲染
- Unmounting(卸载):已移出真实 DOM
Mounting(挂载)
当组件实例被创建并插入 DOM
中时,其生命周期调用顺序如下:
constructor()
: 在React
组件挂载之前,会调用它的构造函数。getDerivedStateFromProps()
: 在调用render
方法之前调用,并且在初始挂载及后续更新时都会被调用。render()
:render()
方法是class
组件中唯一必须实现的方法。componentDidMount()
: 在组件挂载后(插入DOM
树中)立即调用。
render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。
Updating(更新)
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
getDerivedStateFromProps()
: 在调用render
方法之前调用,并且在初始挂载及后续更新时都会被调用。根据shouldComponentUpdate()
的返回值,判断React
组件的输出是否受当前state
或props
更改的影响。shouldComponentUpdate()
:当props
或state
发生变化时,shouldComponentUpdate()
会在渲染执行之前被调用。render()
:render()
方法是class
组件中唯一必须实现的方法。getSnapshotBeforeUpdate()
: 在最近一次渲染输出(提交到DOM
节点)之前调用。componentDidUpdate()
: 在更新后会被立即调用。
render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。
Unmounting(卸载)
componentDidUpdate()
: 在更新后会被立即调用。
声明周期实例:
1 | import { Component } from 'react'; |
Fragments
React
中的一个常见模式是一个组件返回多个元素。Fragments
允许你将子列表分组,而无需向 DOM
添加额外节点。
1 | render() { |
短语法
1 | class Columns extends React.Component { |
组件之间通信
⽗组件向⼦组件通讯
⽗组件可以通过向⼦组件传 props
的⽅式来实现父到子的通讯。
1 | import { Component } from 'react'; |
⼦组件向⽗组件通讯
当⽗组件向⼦组件传递 props
进⾏通讯时,可在该 props
中传递一个回调函数,当⼦组件调⽤该函数时,可将⼦组件中想要传递给父组件的信息作为参数传递给该函数。由于 props
中的函数作⽤域为⽗组件⾃身,因此可以通过该函数内的 setState
更新到⽗组件上。
1 | import { Component } from 'react'; |
兄弟组件中通信
- 可以利用父子组件中的
props
,例如 A 是父组件,B 和 C 都是 A 种的子组件且是兄弟组件,B 可以通过props
回调传递给 A,然后A再通过props
传递给 C。 - 可以考虑下面的通信方式
events 自定义事件通信
和消息订阅与发布机制
events
自定义事件通信
- 安装
events
1
2yarn add events
npm i events - 创建一个 events.js 文件,在该文件中生成事件总线:
1
2import { EventEmitter } from 'events';
export default new EventEmitter(); - 在负责接收数据的组件中,给事件总线身上添加事件:4. 在负责传递数据的组件中,去调用事件总线身上的事件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31import { Component } from 'react'
import emitter from './utils/event'
import Child from './Child'
export default class App extends Component {
state = { message: '' }
eventEmitter: any = null
getData = (message: string) => {
this.setState({
message,
});
}
// 组件挂载完成
componentDidMount() {
// 组件装载完成以后声明一个自定义事件
this.eventEmitter = emitter.addListener('childMsg', this.getData);
}
// 记得在 componentWillUnmount 钩子中移除 addListener
componentWillUnmount() {
emitter.removeListener('childMsg', this.getData);
}
render() {
return (
<div>
<h1>子组件B:负责接收数据</h1>
<p>接收到的数据:{this.state.message}</p>
<Child />
</div>
)
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import emitter from './utils/event'
import { Component } from 'react'
export default class ChildA extends Component {
state = {
message: '张三'
}
sendData = () => {
emitter.emit('childMsg', this.state.message)
}
render() {
return (
<div>
<h1>子组件A:负责传递数据</h1>
<button onClick={this.sendData}>按钮</button>
</div>
)
}
}消息订阅与发布机制
- 安装
pubsub-js
1
2yarn add pubsub-js
npm i pubsub-js - 在组件中发布消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import PubSub from 'pubsub-js'
import { Component } from 'react'
export default class ChildA extends Component {
state = {
message: '张三'
}
sendData = () => {
// 发布消息
PubSub.publish('childMsg', this.state.message)
}
render() {
return (
<div>
<h1>子组件A:负责传递数据</h1>
<button onClick={this.sendData}>按钮</button>
</div>
)
}
} - 组件中订阅消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26import { FC, useState, useEffect } from 'react'
import PubSub from 'pubsub-js'
import Child from './Child'
const App: FC = () => {
const [msg, setMsg] = useState('')
let PubSubEmitter: any = null
useEffect(() => {
// 订阅消息
PubSubEmitter = PubSub.subscribe('childMsg', (msg, data) => {
setMsg(data)
})
// 组件卸载前取消消息订阅
return () => {
PubSub.unsubscribe(PubSubEmitter)
}
}, [])
return (
<div>
<h1>子组件B:负责接收数据</h1>
<p>接收到的数据:{msg}</p>
<Child />
</div>
)
}
export default App
HOOK
什么是 Hook?
Hook
是一些可以让你在函数组件里“钩入” React state
及生命周期等特性的函数。Hook
不能在 class
组件中使用 —— 这使得你不使用 class
也能使用 React
。
State Hook
在 useState()
中,它接受状态的初始值作为参数,即上例中计数的初始值,它返回一个数组,其中数组第一项为一个变量,指向状态的当前值。类似 this.state
,第二项是一个函数,用来更新状态,类似 setState
。
1 | import { useState,FC } from 'react'; |
Effect Hook
它可以用来更好的处理副作用,如异步请求等;可以把 useEffect Hook
看做 componentDidMount
,componentDidUpdate
和 componentWillUnmount
这三个函数的组合。
1 | import { useState, useEffect, FC } from 'react'; |
使用useEffect模拟react三个生命周期
- 实现
componentDidMount
1
2
3
4
5
6
7
8
9
10
11
12
13
14import { useState, useEffect, FC } from 'react';
const App: FC = () => {
const [count, setCount] = useState(0);
useEffect(() => {
getList() // 调用方法发起异步请求
}, []);
return (
<div>
hello world
</div>
)
} - 实现
componentDidUpdate
1
2
3
4
5
6
7
8
9
10
11
12
13
14import { useState, useEffect, FC } from 'react';
const App: FC = () => {
const [count, setCount] = useState(0);
useEffect(() => {
getList() // 仅在count更改时更新
}, [count]);
return (
<div>
hello world
</div>
)
} - 实现
componentWillUnmount
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import { useState, useEffect, FC } from 'react';
const App: FC = () => {
useEffect(() => {
getList();
return () => {
console.log('组件注销, 实现componentWillUnmount');
};
}, []);
return (
<div>
hello world
</div>
)
}useEffect的第二个参数集中形式:
- 不传: useEffect 会在第一次渲染以及每次更新渲染后都执行。
(不建议)
- 空数组: useEffect 会在第一次渲染后执行一次。
- 由基本类型或者引用类型组成的数组: useEffect 会在第一次渲染以及每次更新渲染后都执行。
Todo
- 不传: useEffect 会在第一次渲染以及每次更新渲染后都执行。