- 用了几年 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(); - 在负责接收数据的组件中,给事件总线身上添加事件:
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>
)
}
}4. 在负责传递数据的组件中,去调用事件总线身上的事件:
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 会在第一次渲染以及每次更新渲染后都执行。