耗时一个月,React 知识点万字大总结(超全超基础)

文章目录

React特点React 与 Vue 对比React 与 Angular 对比JSX语法state状态props属性refs 属性事件处理render函数虚拟DOM生命周期脚手架工具ReduxReact-reduxRedux-thunk中间件Redux-saga中间件React Router DomFragmentsErrorBoundaryPortalsContextLazyLoadMemoReact HooksHOC高阶组件性能优化React16 的架构以及 React17改动

React特点

React是一个将数据渲染为 HTML 视图 的 js 库

简单看来,React 框架主要功能体现在前端 UI 页面的渲染,包括性能优化以及操作简化等等方面。站在 mvc 框架的角度来看,React 操作 view 层的功能实现。

采用组件化模式、声明式编码、函数式编程,提高开发效率和组件复用性它遵循从高阶组件到低阶组件的单向数据流。在 React Native 中可以用 react 预发进行安卓、ios 移动端开发使用虚拟 dom 和 diff 算法,尽量减少与真实 dom 的交互,提高性能

React 与 Vue 对比

相同点:

都使用 Virtural DOM都使用组件化思想,流程基本一致都是响应式,推崇单向数据流都有配套框架,Vue 有 Vue-router 和 Vuex,而 React 有 React-router 和 React-Redux都有成熟的社区,都支持服务端渲染

不同点:

模版语法不同

Vue 推荐编写近似常规 HTML 的模板进行渲染,而 React 推荐 JSX 的书写方式。

核心思想不同

Vue推崇灵活易用(渐进式开发体验),数据可变,双向数据绑定(依赖收集)。

React推崇函数式编程(纯组件),数据不可变以及单向数据流。

组件实现不同

Vue源码实现是把options挂载到Vue核心类上,然后再new Vue({options})拿到实例。

React内部使用了四大组件类包装VNode,不同类型的 VNode 使用相应的组件类处理,职责划分清晰明了。

React 类组件都是继承自 React.Component 类,其 this 指向用户自定义的类,对用户来说是透明的。

响应式原理不同

Vue依赖收集,自动优化,数据可变。递归监听 data 的所有属性,直接修改。当数据改变时,自动找到引用组件重新渲染。

React基于状态机,手动优化,数据不可变,需要 setState 驱动新的 State 替换老的 State。当数据改变时,以组件为根目录,默认全部重新渲染。

diff 算法不同

两者流程思维上是类似的,都是基于两个假设(使得算法复杂度降为 O (n)):

不同的组件产生不同的 DOM 结构。当 type 不相同时,对应 DOM 操作就是直接销毁老的 DOM,创建新的 DOM。同一层次的一组子节点,可以通过唯一的 key 区分。

但两者源码实现上有区别:

Vue 基于 snabbdom 库,它有较好的速度以及模块机制。Vue Diff使用双向链表,边对比,边更新DOM。

React主要使用 diff 队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。

事件机制不同

Vue原生事件使用标准Web事件。Vue 组件自定义事件机制,是父子组件通信基础。

React原生事件被包装,所有事件都冒泡到顶层 document 监听,然后在这里合成事件下发。基于这套,可以跨端使用事件机制,而不是和 Web DOM 强绑定。

React 组件上无事件,父子组件通信使用 props。

React 与 Angular 对比

Angular 是一个成熟的 MVC 框架,带有很多特定的特性,比如服务、指令、模板、模块、解析器等等。

React 是一个非常轻量级的库,它只关注 MVC 的视图部分。

Angular 遵循两个方向的数据流,而 React 遵循从上到下的单向数据流。

React 在开发特性时给了开发人员很大的自由,例如,调用 API 的方式、路由等等。

我们不需要包括路由器库,除非我们需要它在我们的项目。

JSX语法

JSX 是 javascript 的语法扩展。它就像一个拥有 javascript 全部功能的模板语言。它生成 React 元素,这些元素将在 DOM 中呈现。React 建议在组件使用 JSX。在 JSX 中,我们结合了 javascript 和 HTML,并生成了可以在 DOM 中呈现的 react 元素。

定义虚拟 dom 时不要用引号

标签中引入 js 表达式要用 {}

如果在 jsx 要写行内样式需要使用 style={{coler:red}} 形式

样式的类名指定不能写 class,要写 className;

只有一个根标签

标签必须闭合

标签首字母 ①若小写字母开头,则会将该标签转为 html 同名标签,如果没找到,则会报错; ②若大写字母开头,则会认为是组件,它就会去找对应的组件,如果没找到,就会报组件未定义的错误;

实质:JSX 通过 babel 编译,而 babel 实际上把 JSX 编译给 React.createElement() 调用

React.createElement()即 h 函数,返回 vnode第一个参数可能是组件也可能是 html tag ,如果是组件,首字母必须大写

const imgElem =

some text

// 经过 babel 编译后

var imgElem =

React.createElement("div",null,

React.createElement("p",null,"some text"),React.createElement("img",{src:imgUrl}));

state状态

state 是组件实例对象最重要的属性,必须是对象的形式

组件被称为状态机,通过更改 state 的值来达到更新页面显示(重新渲染组件)

组件 render 中的 this 指的是组件实例对象

组件自定义方法中的 this 为 undefined,怎么解决?

①将自定义函数改为表达式 + 箭头函数的形式(推荐)

②在构造器中用 bind()强制绑定 this

状态数据不能直接赋值,需要用 setState()

setState()有同步有异步,基本上都是异步更新,自己定义的DOM事件里setState()是同步的

同步异步原理:看是否能命中 batchUpdate 机制,就是判断 isBatchingUpdates,true 为同步,false 为异步

class ListDemo extends React.component{

constructor(props){...}

render(){...}

increase = () =>{

// 开始:处于 batchUpdate

// isBatchingUpdates = true

this.setState({

count : this.state.count + 1

})

// 结束

// isBatchingUpdates = false

}

}

class ListDemo extends React.component{

constructor(props){...}

render(){...}

increase = () =>{

// 开始:处于 batchUpdate

// isBatchingUpdates = true

setTimeout(() => {

// 由于异步,所以此时 isBatchingUpdates 是 false

this.setState({

count : this.state.count + 1

})

})

// 结束

// isBatchingUpdates = false

}

}

能命中 batchUpdate 机制:生命周期(和它调用的函数)、React 中注册的事件(和它调用的函数),其实就是 React 可以“管理”的入口

不能命中 batchUpdate 机制:setTimeout/setInterval等(和它调用的函数)、自定义 DOM 事件(和它调用的函数),其实就是 React “管不到”的入口,因为不是在 React 中注册的

state 异步更新的话,更新前会被合并:setState()传入对象会被合并(类似于Object.assgin),传入函数不会被合并

// 传入对象会被合并,每次+1

this.setState({

count:this.state.count + 1

})

this.setState({

count:this.state.count + 1

})

this.setState({

count:this.state.count + 1

})

// 传入函数不会被合并,每次+3

this.setState((prevState,proprs) => {

return{

count:prevState.count + 1

}

})

this.setState((prevState,proprs) => {

return{

count:prevState.count + 1

}

})

this.setState((prevState,proprs) => {

return{

count:prevState.count + 1

}

})

props属性

props 就是在调用组件的时候在组件中添加属性传到组件内部去使用

每个组件都会有 props 属性组件标签的所有属性都保存在 props组件内部不能改变外部传进来的 props 属性值

接下来如果想对传入的 props 属性进行一些必传、默认值、类型的校验,就需要用到一个 prop-types 库

下载:npm i prop-types --save 引入:import PropTypes from ‘prop-types’

class Person extends React.Component{

//对标签属性进行类型、必要性的限制

static propTypes = {

name:PropTypes.string.isRequired,//限制name必传,且为字符串

sex:PropTypes.string,//限制sex为字符串

age:PropTypes.number,//限制age为数字

speak:PropTypes.func,//限制speak为函数

}

//指定默认标签属性值

static defaultProps = {

sex:'男', //sex默认值为男

age:18 //age默认值为18

}

}

refs 属性

字符串形式的 ref(这种方式已过时,不推荐使用,因为效率低)

refs 是组件实例对象中的属性,它专门用来收集那些打了 ref 标签的 dom 元素

比方说,组件中的 input 添加了一个 ref=“input1”

那么组件实例中的 refs 就 ={input1:input(真实 dom)}

这样就可以通过 this.refs.input1 拿到 input 标签 dom 了

就不需要想原生 js 那样通过添加属性 id,

然后通过 document.getElementById (“id”) 的方式拿

回调函数

class Demo extends React.Component{

showData = () => {

const {input1} = this

alert(input1.value)

}

render(){

return{

this.input1 = c} type="text" />

}

}

}

直接让 ref 属性 = 一个回调函数,为什么这里说是回调函数呢?

因为这个函数是我们定义的,但不是我们调用的

是 react 在执行 render 的时候,看到 ref 属性后跟的是函数,他帮我们调用了

然后把当前 dom 标签当成形参传入

所以就相当于把当前节点 dom 赋值给了 this.input1,那这个 this 指的是谁呢?

不难理解,这里是箭头函数,本身没有 this 指向,所以这个 this 得看外层的

该函数外层是 render 函数体内,所以 this 就是组件实例对象

所以 ref={c=>this.input1=c} 意思就是给组件实例对象添加一个 input1

最后要取对应节点 dom 也直接从 this(组件实例中取)

createRef

createRef() 方法是 React 中的 API,它会返回一个容器,存放被 ref 标记的节点,但该容器是专人专用的,就是一个容器只能存放一个节点;

当 react 执行到 div 中第一行时,发现 input 节点写了一个 ref 属性,又发线在上面创建了 myRef 容器,所以它就会把当前的节点存到组件实例的 myRef 容器中

注意:如果你只创建了一个 ref 容器,但多个节点使用了同一个 ref 容器,则最后的会覆盖掉前面的节点,所以,你通过 this.ref 容器.current 拿到的那个节点是最后一个节点。

class Demo extends React.Component{

//React.createRef调用后可以返回一个容器,该容器可以存储被ref所标示的节点,该容器是专人专用的

myRef = React.createRef()

//展示输入框的数据

showData = () => {

alert(this.myRef.current.value)

}

render(){

return{

}

}

}

事件处理

通过 onXxxx 属性指定事件处理函数(小驼峰形式)通过 event.target 可以得到发生事件的 dom 元素使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。

在原生 DOM 中,我们可以通过返回 false 来阻止默认行为,但是这在 React 中是行不通的,在 React 中需要明确使用 preventDefault() 来阻止默认行为。

事件回调函数里的 event 是经过 React 特殊处理过的(遵循 W3C 标准),所以可以放心地使用它,而不用担心跨浏览器的兼容性问题。

注意:在使用事件回调函数的时候,需要特别注意 this 的指向问题。

因为在 React 里,除了构造函数和生命周期钩子函数里会自动绑定 this 为当前组件外,其他的都不会自动绑定 this 的指向为当前组件,因此需要我们自己注意好 this 的绑定问题。

通常而言,在一个类方式声明的组件里事件回调,需要在组件的 constructor 里绑定回调方法的 this 指向。

render函数

当组件的 state 或者 props 发生改变的时候,render 函数就会重新执行

当父组件的 render 函数重新执行时,子组件的 render 函数也会重新执行

虚拟DOM

本质上其实就是一个 object 对象;虚拟 dom 上的属性比较少,真实 dom 属性多,因为虚拟 dom 只在 react 内部使用,用不到那么多的属性虚拟 dom 最终会被 react 转换成真实 dom,呈现再页面上

大致过程:

state数据

jsx模版

数据+模版 结合,生成虚拟DOM

(虚拟DOM就是一个JS对象,用它来描述真实的DOM)(损耗了性能)

用虚拟DOM的结构生成真实的DOM来显示

state发生改变

数据+模版 生成新的虚拟DOM(极大提升了性能)

比较原始虚拟DOM和新的虚拟DOM的区别,找差异(极大提升了性能)

直接操作DOM,改变内容

优点:

性能提升

使得跨端应用得以实现

虚拟 DOM 中的 key 的作用:

当状态中的数据发生改变时,react 会根据新数据生成新虚拟 DOM

随后 react 会进行新虚拟 DOM和旧虚拟 DOM的 diff 算法比较

若旧 DOM中找到了与新 DOM相同的 key,则会进一步判断两者的内容是否相同

如果也一样,则直接使用之前的真实 DOM,如果内容不一样,则会生成新的真实 DOM,替换掉原先的真实 DOM

若旧 DOM中没找到与新 DOM相同的 key,则直接生成新的真实 DOM,然后渲染到页面

不用 index 作为 key 的原因:

若对数据进行逆序添加、逆序删除等破坏顺序的操作时会产生不必要的真实 DOM 更新,造成效率低下

如果结构中还包含输入类的 dom,会产生错误 dom 更新,出现界面异常

diff算法

在 react 中如果某个组件的状态发生改变,react 会把此组件以及此组件的所有后代组件重新渲染

不过重新渲染并不代表会全部丢弃上一次的渲染结果,react 还是会通过 diff 去比较两次的虚拟 dom 最后 patch 到真实的 dom 上

diff 算法只比较同一层级,不跨级比较

tag 不相同则直接删掉重建,不再深度比较;tag 和 key 两者都相同,则认为是相同节点,也不再深度比较

虽然如此,如果组件树过大,diff 其实还是会有一部分的开销

因此react 内部通过 fiber 优化 diff 算法,外部建议开发者使用 SCU 和pureComponent

生命周期

componentWillMount() //在组件即将被挂载到页面的时候自动执行

componentDidMount() //在组件被挂载到页面之后,自动被执行

shouldComponentUpdate(nextProps,nextState){ //组件被更新之前自动被执行,默认返回true

if(nextState.count !== this.state.count){

return true //可以渲染

}

return false // 不可以渲染

}

// shouldComponentUpdate 返回true则执行,返回false不执行

componentWillUpdate() //在组件被更新之前,自动执行

componentDidUpdate() //在组件更新完成之后,自动执行

componentWillReceiveProps() //一个组件要冲父组件接受参数

//只要父组件的render函数被重新执行了,子组件的这个生命周期函数就会被执行

componentWillUnmount() //当这个组件即将被从页面中剔除的时候会被执行

挂载时:

先执行构造器(constructor)

组件将要挂载(componentWillMount)

组件挂载渲染(render)

组件挂载完成(componentDidMount)

组件销毁(componentWillUnmount)

组件内部状态更新:

组件是否应该更新(shouldComponentUpdate)

组件将要更新(componentWillUpdate)

组件更新渲染(render)

组件更新完成(componentDidUpdate)

父组件重新 render:

调用组件将要接收新 props(componentWillReceiveProps)

组件是否应该更新(shouldComponentUpdate)

组件将要更新(componentWillUpdate)

组件更新渲染(render)

组件更新完成(componentDidUpdate)

注:只有在父组件状态发生改变了,重新调用 render 时才会调用子组件的 componentWillReceiveProps 函数,父组件第一次引用子组件不会调用

脚手架工具

使用 create-react-app(脚手架工具)创建一个初始化项目

1、下载脚手架工具:npm i -g create-react-app

2、创建引用:create-react-app my-app

3、运行应用:cd my-app(进入应用文件夹),npm start(启动应用)

如果控制台报了You are running create-react-app 5.0.0, which is behind the latest release (5.0.1).

那么先查一下npm 的版本,如果大于 5.2 ,就使用 npx create-react-app@latest train-ticket

然后是运行 npm run eject:

因为在 package.json 中,只有三个依赖,分别是 react,react-dom,react-scripts

依赖为什么这么少?是因为像 webpack,babel 等等都是被 creat react app 封装到了 react-scripts 这个项目当中,包括基本启动命令,都是通过调用 react-scripts 这个依赖下面的命令进行启动的。

creat react app 搭建出来的项目默认支持这 4 种命令:start 以开发模式启动项目,build 将整个项目进行构建,test 进行测试,eject会将原本 creat react app 对 webpack,babel 等相关配置的封装弹射出来。

如果我们要将 creat react app 配置文件进行修改,现有目录下是没有地方修改的,此时,我们就可以通过 eject 命令将原本被封装到脚手架当中的命令弹射出来,然后就可以在项目的目录下看到很多配置文件。

npm run eject 是个单向的操作,一旦 eject ,npm run eject 的操作是不可逆的。

Redux

Redux = Reducer + Flux

Redux 是 React 的一个状态管理库,它基于 flux。

Redux 简化了 React 中的单向数据流。 Redux 将状态管理完全从 React 中抽象出来。

它是专门做状态管理的 js 库,不是 react 插件库

作用:集中式管理 react 应用中多个组件共享的状态

需求场景:

某个组件的状态需要让其他组件也能拿到

一个组件需要改变另一个组件的状态(通信)

Redux设计和使用的三项原则:

store是唯一的、

只有store能够改变自己的内容、

Reducer必须是纯函数。

纯函数指的是,给定固定的输入,就一定会有固定的输出,而且不会有任何副作用。

redux流程原理

在 React 中,组件连接到 redux ,如果要访问 redux,需要派出一个包含 id 和负载 (payload) 的 action。action 中的 payload 是可选的,action 将其转发给 Reducer。

当 reducer 收到 action 时,通过 switch...case 语法比较 action 中 type。 匹配时,更新对应的内容返回新的 state。

当 Redux 状态更改时,连接到 Redux 的组件将接收新的状态作为 props。当组件接收到这些 props 时,它将进入更新阶段并重新渲染 UI。

reducer可以接受state,但是绝不能修改state。

ActionTypes拆分:

通过constants创建常量,用于检测定位bug位置。

如果是直接使用字符串,如果写错不报异常,没有办法定位错误位置。

使用ActionCreator统一创建action:

提升代码可读性和方便前端自动化测试。

无状态组件

当定义一个UI组件,只负责渲染,没有任何逻辑操作的时候,

建议使用无状态函数,性能提升,因为不用有生命周期函数这种。

React-redux

react-redux 将 react 组件划分为容器组件和展示组件

展示组件:只是负责展示 UI,不涉及到逻辑的处理,数据来自父组件的 props;容器组件:负责逻辑、数据交互,将 state 里面的数据传递给展示组件进行 UI 呈现

Provider连接store ,它内部的组件都可以获取到store

connect方法让子组件和store连接

TodoList是UI组件,connect将业务逻辑和UI组件结合,返回一个容器组件

//在TodoList.js里

export default connect(mapStateToProps,null)(TodoList);

mapStateToProps:此函数将 state 映射到 props 上,因此只要 state 发生变化,新 state 会重新映射到 props。 这是订阅 store 的方式。

mapDispatchToProps:此函数用于将 actionCreators 绑定 props 。

怎么做连接,就是用mapStateToProps做映射

//在TodoList.js里

const mapStateToProps = (state) => {

return {

inputValue: state.inputValue

}

}

mapDispatchToProps,解决组件如何对store里的数据做修改

可以让props里的方法调用store.dispatch去操作store里的数据

//store.dispatch , props

const mapDispatchToProps = (dispatch) => {

return {

changeInputValue(e){

const action = {

type:'change_input_value',

value:e.target.value

}

dispatch(action);

}

}

}

export default connect(mapStateToProps,mapDispatchToProps)(TodoList);

Redux-thunk中间件

redux 里,action 仅仅是携带了数据的普通 js 对象。

actionCreator 返回的值是这个 action 类型的对象。

然后通过 store.dispatch 进行分发,同步的情况下一切都很完美,但是 reducer 无法处理异步的情况。

那么我们就需要在 action 和 reducer 中间架起一座桥梁来处理异步。

这就是 middleware 中间件,就是指在 action 和 store 之间。

使得可以在action里面写异步的代码。

其实就是对store的dispatch方法升级,本来只能接受一个对象,现在也可以接受一个函数。

redux 开发者工具

下载开发者工具 Redux DevTools

下载完后右上方的插件图标还是不会亮的,因为它还识别不了你写的 redux,所以还需要下载一个库(redux-devtools-extension)

然后在 store 文件中引入该库文件 import {composeWithDevTools} from redux-devtools-extension

然后在createStore()第二个参数位置调用 composeWithDevTools(),将之前的中间件传到该方法中export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

Redux-saga中间件

redux-saga 提供了一些辅助函数,用来在一些特定的 action 被发起到 Store 时派生任务,先来看一下两个辅助函数:takeEvery 和 takeLatest

import { takeEvery } from 'redux-saga'

// Generator生成器函数

function* watchFetchData() {

yield takeEvery("FETCH_REQUESTED", fetchData)

}

takeEvery 函数可以使用下面的写法替换

function* watchFetchData() {

while(true){

yield take('FETCH_REQUESTED');

yield fork(fetchData);

}

}

takeEvery 允许多个 fetchData 实例同时启动。在某个特定时刻,我们可以启动一个新的 fetchData 任务, 尽管之前还有一个或多个 fetchData 尚未结束。

如果我们只想得到最新请求的响应(例如,始终显示最新版本的数据),我们可以使用 takeLatest 辅助函数

import { takeLatest } from 'redux-saga'

function* watchFetchData() {

yield takeLatest('FETCH_REQUESTED', fetchData)

}

和 takeEvery 不同,在任何时刻 takeLatest 只允许执行一个 fetchData 任务,并且这个任务是最后被启动的那个,如果之前已经有一个任务在执行,那之前的这个任务会自动被取消。

redux-saga 框架提供了一些创建 effect 的函数,大概介绍几个常用的:

take(pattern)put(action)call(fn, …args)fork(fn, …args)

take 函数可以理解为监听未来的 action,它创建了一个命令对象,告诉 middleware 等待一个特定的 action, Generator 会暂停,直到一个与 pattern 匹配的 action 被发起,才会继续执行下面的语句,也就是说,take 是一个阻塞的 effect

put 函数是用来发送 action 的 effect,可以简单的把它理解成为 redux 框架中的 dispatch 函数,当 put 一个 action 后,reducer 中就会计算新的 state 并返回。 注意:put 也是阻塞 effect

call 函数就是可以调用其他函数的函数,它命令 middleware 来调用 fn 函数, args 为函数的参数,注意:fn 函数可以是一个 Generator 函数,也可以是一个返回 Promise 的普通函数,call 函数也是阻塞 effect

fork 函数和 call 函数很像,都是用来调用其他函数的,但是 fork 函数是非阻塞函数。也就是说,程序执行完 yield fork(fn, args) 这一行代码后,会立即接着执行下一行代码语句,而不会等待 fn 函数返回结果后,在执行下面的语句。

// sages.js

import { put, call, take,fork } from 'redux-saga/effects';

import { takeEvery, takeLatest } from 'redux-saga'

export const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

function* incrementAsync() {

// 延迟 1s 在执行 + 1操作

yield call(delay, 1000);

yield put({ type: 'INCREMENT' });

}

export default function* rootSaga() {

// while(true){

// yield take('INCREMENT_ASYNC');

// yield fork(incrementAsync);

// }

// 下面的写法与上面的写法上等效

yield takeEvery("INCREMENT_ASYNC", incrementAsync)

}

基本用法总结:

使用 createSagaMiddleware 方法创建 saga 的 Middleware

然后在创建的 redux 的 store 时,使用 applyMiddleware 函数将创建的 saga Middleware 实例绑定到 store 上

最后可以调用 saga Middleware 的 run 函数来执行某个或者某些 Middleware

在 saga 的 Middleware 中,可以使用 takeEvery 或者 takeLatest 等 API 来监听某个 action

当某个 action 触发后, saga 可以使用 call 发起异步操作

操作完成后使用 put 函数触发 action ,同步更新 state ,从而完成整个 State 的更新。

React Router Dom

react-router-dom 是应用程序中路由的库。

React 库中没有路由功能,需要单独安装 react-router-dom。

react-router-dom 提供两个路由器 BrowserRouter 和 HashRoauter。

前者是浏览器的路由方式,也就是使用 HTML5 提供的 history API,这种方式在 react 开发中是经常使用的路由方式。但是在打包后,打开会发现访问不了页面,所以需要通过配置 nginx 解决或者后台配置代理。

后者在路径前加入 #号成为一个哈希值。Hash 模式的好处是:再也不会因为我们刷新而找不到我们的对应路径,但是链接上面会有#/。

Route 用于路由匹配。

Link 组件用于在应用程序中创建链接。 它将在 HTML 中渲染为锚标记。

NavLink 是突出显示当前活动链接的特殊链接。

Switch 不是必需的,但在组合路由时很有用。

Redirect 用于强制路由重定向。

Fragments

在 React 中,需要有一个父元素,同时从组件返回 React 元素。有时在 DOM 中添加额外的节点会很烦人。

使用 Fragments,就可以不需要在 DOM 中添加额外的节点。

return (

)

ErrorBoundary

在 React 中,我们通常有一个组件树。如果任何一个组件发生错误,它将破坏整个组件树。没有办法捕捉这些错误,我们可以用错误边界优雅地处理这些错误。

错误边界有两个作用

如果发生错误,显示回退 UI记录错误

下面是 ErrorBoundary 类的一个例子。如果类实现了 getDerivedStateFromError 或 componentDidCatch 这两个生命周期方法的任何一个,那么这个类就会成为 ErrorBoundary。前者返回 {hasError: true} 来呈现回退 UI,后者用于记录错误。

import React from 'react'

export class ErrorBoundary extends React.Component {

constructor(props) {

super(props);

this.state = { hasError: false };

}

static getDerivedStateFromError(error) {

// Update state so the next render will show the fallback UI.

return { hasError: true };

}

componentDidCatch(error, info) {

// You can also log the error to an error reporting service

console.log('Error::::', error);

}

render() {

if (this.state.hasError) {

// You can render any custom fallback UI

return

OOPS!. WE ARE LOOKING INTO IT.

;

}

return this.props.children;

}

}

以下是如何在其中一个组件中使用 ErrorBoundary。使用 ErrorBoundary 类包裹 ToDoForm 和 ToDoList。 如果这些组件中发生任何错误,我们会记录错误并显示回退 UI。

import React from 'react';

import '../App.css';

import { ToDoForm } from './todoform';

import { ToDolist } from './todolist';

import { ErrorBoundary } from '../errorboundary';

export class Dashboard extends React.Component {

render() {

return (

);

}

}

Portals

默认情况下,所有子组件都在 UI 上呈现,具体取决于组件层次结构。

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。

我们可以将 children 组件移出 parent 组件并将其附加 id 为 someid 的 Dom 节点下。

首先,先获取 id 为 someid DOM 元素,接着在构造函数中创建一个元素 div,在 componentDidMount 方法中将 someRoot 放到 div 中 。 最后,通过 ReactDOM.createPortal(this.props.childen), domnode) 将 children 传递到对应的节点下。

render(){

// 使用 Portals 渲染到 body 上

return ReactDOM.createPortal(

{this.props.children}
,

document.body // DOM 节点

)

}

Context

一种组件间通信方式,常用于祖组件与后代组件间通信

Context提供了一种方式,能够让数据在组件树中传递而不必一级一级手动传递

在父组件创建Context容器对象:

const XxxContext = React.createContext()

在父组件中渲染子组件时,外面包裹XxxContext.Provider, 通过value属性给后代组件传递数据:

子组件

后代组件读取数据:

//第一种方式:仅适用于类组件

static contextType = XxxContext // 声明接收context

{this.context} // 读取context中的value数据

//第二种方式: 函数组件与类组件都可以

{

value => ( // value就是context中的value数据

{value.username}

)

}

LazyLoad

import React, { Component,lazy,Suspense } from 'react'

// 通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包

const Login = lazy(()=>import('@/pages/Login')) // 路由组件

// 通过指定在加载得到路由打包文件前显示一个自定义loading界面

// fallback里面可以放一个只显示加载中的一个通用组件

loading.....}>

Memo

多数情况下我们不需要对函数组件的渲染进行特殊优化,即使有些重复渲染也不会对体验造成太大影响,但有些情况下优化就显得很必要。在 class 组件中,我们可以通过 shouldComponentUpdate 阻止不必要的 rerender

shouldComponentUpdate(nextProps) {

return nextProps.demoUrl !== this.props.demoUrl;

}

但在函数组件中,没有 shouldComponentUpdate

为了解决函数组件中的优化问题,React 在 16.6 版本增加了 React.memo

React.memo 是一个高阶组件,类似于 React.PureComponent,只不过用于函数组件而非 class 组件。 如果函数组件在相同 props 下渲染出相同结果,可以把它包裹在 React.memo 中来通过缓存渲染结果来实现性能优化。这意味着 React 会跳过组件渲染,而使用上次渲染结果。

React.memo 默认只会浅比较 props,如果需要定制比较,可以给第二个参数传入自定义比较函数。

和 class 组件中的 shouldComponentUpdate 不同,如果 props 相同则应返回 true,否则返回 false。这点二者正好相反。

const DemoLoader = React.memo(props => {

const { demoUrl } = props;

return