From a90c6ae04930a51706c628f444bacac749678d78 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9B=B9=E9=B9=8F?= <653427831@qq.com>
Date: Sun, 31 Oct 2021 23:45:00 +0800
Subject: [PATCH] =?UTF-8?q?js=E4=B8=AD=E6=9B=B4=E6=96=B0=E4=BA=86=E9=94=99?=
=?UTF-8?q?=E8=AF=AF=E7=9A=84=E5=9C=B0=E6=96=B9=20=E6=96=B0=E5=A2=9E=20vue?=
=?UTF-8?q?=20=E5=8F=8A=20react=20=E5=86=85=E5=AE=B9=20vue=20=E4=B8=AD?=
=?UTF-8?q?=E6=96=B0=E5=A2=9E=20keep-alive=20=E5=8F=8A=E8=87=AA=E5=AE=9A?=
=?UTF-8?q?=E4=B9=89=E6=8C=87=E4=BB=A4=E5=86=85=E5=AE=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
03.HTML && CSS.md | 2 +-
04.JavaScript.md | 68 +++++++++--
07.Vue.md | 215 ++++++++++++++++++++++++++++++++
08.React.md | 360 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 635 insertions(+), 10 deletions(-)
create mode 100644 07.Vue.md
create mode 100644 08.React.md
diff --git a/03.HTML && CSS.md b/03.HTML && CSS.md
index 28acc16..8ba7831 100644
--- a/03.HTML && CSS.md
+++ b/03.HTML && CSS.md
@@ -212,7 +212,7 @@ CSS 盒模型本质上是一个盒子,它包括:边距,边框,填充和
(1)width和margin实现。`margin: 0 auto`;
- (2)绝对定位和margin-left: -width/2, 前提是父元素position: relative
+ (2)绝对定位和margin-left: margin-left: (父width - 子width)/2, 前提是父元素position: relative
- 对于宽度未知的块级元素
diff --git a/04.JavaScript.md b/04.JavaScript.md
index e36acb4..53f1fc3 100644
--- a/04.JavaScript.md
+++ b/04.JavaScript.md
@@ -24,6 +24,7 @@ BigInt:由于在 Number 与 BigInt 之间进行转换会损失精度,因而
console.log(typeof 1); // number
console.log(typeof true); // boolean
console.log(typeof 'mc'); // string
+console.log(typeof Symbol) // function
console.log(typeof function(){}); // function
console.log(typeof console.log()); // function
console.log(typeof []); // object
@@ -69,6 +70,18 @@ console.log(toString.call(null)); //[object Null]
缺点:写法繁琐不容易记,推荐进行封装后使用
+
+#### instanceof 的作用
+用于判断一个引用类型是否属于某构造函数;
+
+还可以在继承关系中用来判断一个实例是否属于它的父类型。
+
+#### instanceof 和 typeof 的区别:
+typeof在对值类型number、string、boolean 、null 、 undefined、 以及引用类型的function的反应是精准的;但是,对于对象{ } 、数组[ ] 、null 都会返回object
+
+为了弥补这一点,instanceof 从原型的角度,来判断某引用属于哪个构造函数,从而判定它的数据类型。
+
+
### var && let && const
ES6之前创建变量用的是var,之后创建变量用的是let/const
@@ -151,21 +164,58 @@ ES6之前创建变量用的是var,之后创建变量用的是let/const
- **闭包的优点**:延长局部变量的生命周期
- **闭包缺点**:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏
-### JS 中 this 的五种情况
+### JS 中 this 的情况
+
+1. 普通函数调用:通过函数名()直接调用:`this`指向`全局对象window`(注意let定义的变量不是window属性,只有window.xxx定义的才是。即let a =’aaa’; this.a是undefined)
+2. 构造函数调用:函数作为构造函数,用new关键字调用时:`this`指向`新new出的对象`
+3. 对象函数调用:通过对象.函数名()调用的:`this`指向`这个对象`
+4. 箭头函数调用:箭头函数里面没有 this ,所以`永远是上层作用域this`(上下文)
+5. apply和call调用:函数体内 this 的指向的是 call/apply 方法`第一个参数`,若为空默认是指向全局对象window。
+6. 函数作为数组的一个元素,通过数组下标调用的:this指向这个数组
+7. 函数作为window内置函数的回调函数调用:this指向window(如setInterval setTimeout 等)
+
+
+### call/apply/bind 的区别
+
+相同:
+
+1、都是用来改变函数的this对象的指向的。\
+2、第一个参数都是this要指向的对象。\
+3、都可以利用后续参数传参。
+
+不同:
+
+apply和call传入的参数列表形式不同。apply 接收 arguments,call接收一串参数列表
+```
+fn.call(obj, 1, 2);
+fn.apply(obj, [1, 2]);
+```
+bind:语法和call一模一样,区别在于立即执行还是等待执行,bind不兼容IE6~8
+bind 主要就是将函数绑定到某个对象,bind()会创建一个函数,返回对应函数便于稍后调用;而apply、call则是立即调用。
+
+总结:基于Function.prototype上的 ` apply 、 call 和 bind `调用模式,这三个方法都可以显示的指定调用函数的 this 指向。`apply`接收参数的是数组,`call`接受参数列表,`` bind`方法通过传入一个对象,返回一个` this ` 绑定了传入对象的新函数。这个函数的 `this`指向除了使用`new `时会被改变,其他情况下都不会改变。若为空默认是指向全局对象window。
+
+参考:☞ [call、apply、bind三者的用法和区别](https://blog.csdn.net/hexinyu_1022/article/details/82795517)
+
+### 箭头函数的特性
+
+1. `箭头函数没有自己的this`,会捕获其所在的上下文的this值,作为自己的this值
+1. `箭头函数没有constructor`,是匿名函数,不能作为构造函数,不能通过new 调用;
+1. `没有new.target 属性`。在通过new运算符被初始化的函数或构造方法中,new.target返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target 的值是undefined
+1. `箭头函数不绑定Arguments 对象`。取而代之用rest参数...解决。由于 箭头函数没有自己的this指针,通过 call() 或 apply() 方法调用一个函数时,只能传递参数(不能绑定this),他们的第一个参数会被忽略。(这种现象对于bind方法同样成立)
+1. 箭头函数通过 call() 或 apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。
+1. 箭头函数没有原型属性 Fn.prototype 值为 undefined
+1. 箭头函数不能当做Generator函数,不能使用yield关键字
-1. 作为普通函数执行时,`this`指向`window`。
-1. 当函数作为对象的方法被调用时,`this`就会指向`该对象`。
-1. 构造器调用,`this`指向`返回的这个对象`。
-1. 箭头函数 箭头函数的`this`绑定看的是`this所在函数定义在哪个对象下`,就绑定哪个对象。如果有嵌套的情况,则this绑定到最近的一层对象上。
-1. 基于Function.prototype上的 ` apply 、 call 和 bind `调用模式,这三个方法都可以显示的指定调用函数的 this 指向。`apply`接收参数的是数组,`call`接受参数列表,`` bind`方法通过传入一个对象,返回一个` this ` 绑定了传入对象的新函数。这个函数的 `this`指向除了使用`new `时会被改变,其他情况下都不会改变。若为空默认是指向全局对象window。
+参考:[箭头函数与普通函数的区别](https://www.cnblogs.com/biubiuxixiya/p/8610594.html)
### 原型 && 原型链
**原型关系:**
-- 每个 class都有显示原型 prototype
-- 每个实例都有隐式原型 _ proto_
-- 实例的_ proto_指向对应 class 的 prototype
+- 每个 class都有显示原型 prototype
+- 每个实例都有隐式原型 `__proto__`
+- 实例的 `__proto__` 指向对应 class 的 prototype
**原型:** 在 JS 中,每当定义一个对象(函数也是对象)时,对象中都会包含一些预定义的属性。其中每个`函数对象`都有一个`prototype` 属性,这个属性指向函数的`原型对象`。
diff --git a/07.Vue.md b/07.Vue.md
new file mode 100644
index 0000000..a67402c
--- /dev/null
+++ b/07.Vue.md
@@ -0,0 +1,215 @@
+
+## Vue 面试专题
+
+### 简述MVVM
+
+**什么是MVVM?**
+
+`视图模型双向绑定`,是`Model-View-ViewModel`的缩写,也就是把`MVC`中的`Controller`演变成`ViewModel。Model`层代表数据模型,`View`代表UI组件,`ViewModel`是`View`和`Model`层的桥梁,数据会绑定到`viewModel`层并自动将数据渲染到页面中,视图变化的时候会通知`viewModel`层更新数据。以前是操作DOM结构更新视图,现在是`数据驱动视图`。
+
+**MVVM的优点:**
+
+1.`低耦合`。视图(View)可以独立于Model变化和修改,一个Model可以绑定到不同的View上,当View变化的时候Model可以不变化,当Model变化的时候View也可以不变;\
+2.`可重用性`。你可以把一些视图逻辑放在一个Model里面,让很多View重用这段视图逻辑。\
+3.`独立开发`。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。\
+4.`可测试`。
+
+### Vue底层实现原理
+
+vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调\
+Vue是一个典型的MVVM框架,模型(Model)只是普通的javascript对象,修改它则试图(View)会自动更新。这种设计让状态管理变得非常简单而直观
+
+**Observer(数据监听器)** : Observer的核心是通过Object.defineProprtty()来监听数据的变动,这个函数内部可以定义setter和getter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是Watcher
+
+**Watcher(订阅者)** : Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:
+
+1. 在自身实例化时往属性订阅器(dep)里面添加自己
+1. 自身必须有一个update()方法
+1. 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调
+
+**Compile(指令解析器)** : Compile主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新试图
+
+### 谈谈对vue生命周期的理解?
+
+每个`Vue`实例在创建时都会经过一系列的初始化过程,`vue`的生命周期钩子,就是说在达到某一阶段或条件时去触发的函数,目的就是为了完成一些动作或者事件
+
+- `create阶段`:vue实例被创建\
+ `beforeCreate`: 最初调用触发,创建前,此时data和methods中的数据都还没有初始化,data和events都不能用\
+ `created`: 创建完毕,data中有值,未挂载,data和events已经初始化好,data已经具有响应式;在这里可以发送请求
+- `mount阶段`: vue实例被挂载到真实DOM节点\
+ `beforeMount`:在模版编译之后,渲染之前触发,可以发起服务端请求,去数据,ssr中不可用,基本用不上这个hook\
+ `mounted`: 在渲染之后触发,此时可以操作DOM,并能访问组件中的DOM以及$ref,SSR中不可用
+- `update阶段`:当vue实例里面的data数据变化时,触发组件的重新渲染\
+ `beforeUpdate` :更新前,在数据变化后,模版改变前触发,切勿使用它监听数据变化\
+ `updated`:更新后,在数据改变后,模版改变后触发,常用于重渲染案后的打点,性能检测或触发vue组件中非vue组件的更新
+- `destroy阶段`:vue实例被销毁\
+ `beforeDestroy`:实例被销毁前,组件卸载前触发,此时可以手动销毁一些方法,可以在此时清理事件、计时器或者取消订阅操作\
+ `destroyed`:卸载完毕后触发,销毁后,可以做最后的打点或事件触发操作
+
+#### 组件生命周期
+
+生命周期(父子组件) 父组件beforeCreate --> 父组件created --> 父组件beforeMount --> 子组件beforeCreate --> 子组件created --> 子组件beforeMount --> 子组件 mounted --> 父组件mounted -->父组件beforeUpdate -->子组件beforeDestroy--> 子组件destroyed --> 父组件updated
+
+**加载渲染过程** 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
+
+**挂载阶段** 父created->子created->子mounted->父mounted
+
+**父组件更新阶段** 父beforeUpdate->父updated
+
+**子组件更新阶段** 父beforeUpdate->子beforeUpdate->子updated->父updated
+
+**销毁阶段** 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
+
+### `computed与watch`
+
+通俗来讲,既能用 computed 实现又可以用 watch 监听来实现的功能,推荐用 computed, 重点在于 computed 的缓存功能 computed 计算属性是用来声明式的描述一个值依赖了其它的值,当所依赖的值或者变量 改变时,计算属性也会跟着改变; watch 监听的是已经在 data 中定义的变量,当该变量变化时,会触发 watch 中的方法。
+
+**watch 属性监听** 是一个对象,键是需要观察的属性,值是对应回调函数,主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作,监听属性的变化,需要在数据变化时执行异步或开销较大的操作时使用
+
+**computed 计算属性** 属性的结果会被`缓存`,当`computed`中的函数所依赖的属性没有发生改变的时候,那么调用当前函数的时候结果会从缓存中读取。除非依赖的响应式属性变化时才会重新计算,主要当做属性来使用 `computed`中的函数必须用`return`返回最终的结果 `computed`更高效,优先使用。`data 不改变,computed 不更新。`
+
+**使用场景** `computed`:当一个属性受多个属性影响的时候使用,例:购物车商品结算功能 `watch`:当一条数据影响多条数据的时候使用,例:搜索数据
+
+#### 组件中的data为什么是一个函数?
+
+1.一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。 2.如果data是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。
+
+#### 为什么v-for和v-if不建议用在一起
+
+1.当 v-for 和 v-if 处于同一个节点时,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。如果要遍历的数组很大,而真正要展示的数据很少时,这将造成很大的性能浪费
+2.这种场景建议使用 computed,先对数据进行过滤
+
+
+注意:3.x 版本中 `v-if` 总是优先于 `v-for` 生效。由于语法上存在歧义,建议避免在同一元素上同时使用两者。比起在模板层面管理相关逻辑,更好的办法是通过创建计算属性筛选出列表,并以此创建可见元素。
+
+解惑传送门 ☞ [# v-if 与 v-for 的优先级对比非兼容](https://v3.cn.vuejs.org/guide/migration/v-if-v-for.html#%E6%A6%82%E8%A7%88)
+
+
+### React/Vue 项目中 key 的作用
+
+- key的作用是为了在diff算法执行时更快的找到对应的节点,`提高diff速度,更高效的更新虚拟DOM`;
+
+ vue和react都是采用diff算法来对比新旧虚拟节点,从而更新节点。在vue的diff函数中,会根据新节点的key去对比旧节点数组中的key,从而找到相应旧节点。如果没找到就认为是一个新增节点。而如果没有key,那么就会采用遍历查找的方式去找到对应的旧节点。一种一个map映射,另一种是遍历查找。相比而言。map映射的速度更快。
+
+- 为了在数据变化时强制更新组件,以避免`“就地复用”`带来的副作用。
+
+ 当 Vue.js 用 `v-for` 更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。重复的key会造成渲染错误。
+
+### vue组件的通信方式
+
+- `props`/`$emit` 父子组件通信
+
+ 父->子`props`,子->父 `$on、$emit` 获取父子组件实例 `parent、children` ` Ref `获取实例的方式调用组件的属性或者方法 父->子孙 `Provide、inject` 官方不推荐使用,但是写组件库时很常用
+
+- `$emit`/`$on` 自定义事件 兄弟组件通信
+
+ `Event Bus` 实现跨组件通信 `Vue.prototype.$bus = new Vue()` 自定义事件
+
+- vuex 跨级组件通信
+
+ Vuex、`$attrs、$listeners` `Provide、inject`
+
+### nextTick的实现
+
+1. `nextTick`是`Vue`提供的一个全局`API`,是在下次`DOM`更新循环结束之后执行延迟回调,在修改数据之后使用`$nextTick`,则可以在回调中获取更新后的`DOM`;
+1. Vue在更新DOM时是异步执行的。只要侦听到数据变化,`Vue`将开启1个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个`watcher`被多次触发,只会被推入到队列中-次。这种在缓冲时去除重复数据对于避免不必要的计算和`DOM`操作是非常重要的。`nextTick`方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用;
+1. 比如,我在干什么的时候就会使用nextTick,传一个回调函数进去,在里面执行dom操作即可;
+1. 我也有简单了解`nextTick`实现,它会在`callbacks`里面加入我们传入的函数,然后用`timerFunc`异步方式调用它们,首选的异步方式会是`Promise`。这让我明白了为什么可以在`nextTick`中看到`dom`操作结果。
+
+### nextTick的实现原理是什么?
+
+在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后立即使用 nextTick 来获取更新后的 DOM。 nextTick主要使用了宏任务和微任务。 根据执行环境分别尝试采用Promise、MutationObserver、setImmediate,如果以上都不行则采用setTimeout定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列。
+
+### 使用过插槽么?用的是具名插槽还是匿名插槽或作用域插槽
+
+vue中的插槽是一个非常好用的东西slot说白了就是一个占位的 在vue当中插槽包含三种一种是默认插槽(匿名)一种是具名插槽还有一种就是作用域插槽 匿名插槽就是没有名字的只要默认的都填到这里具名插槽指的是具有名字的
+
+### keep-alive的实现
+
+keep-alive是Vue.js的一个内置组件。它能够不活动的组件实例保存在内存中,而不是直接将其销毁,它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中。
+
+作用:实现组件缓存,保持这些组件的状态,以避免反复渲染导致的性能问题。 需要缓存组件 频繁切换,不需要重复渲染
+
+场景:tabs标签页 后台导航,vue性能优化
+
+原理:`Vue.js`内部将`DOM`节点抽象成了一个个的`VNode`节点,`keep-alive`组件的缓存也是基于`VNode`节点的而不是直接存储`DOM`结构。它将满足条件`(pruneCache与pruneCache)`的组件在`cache`对象中缓存起来,在需要重新渲染的时候再将`vnode`节点从`cache`对象中取出并渲染。
+
+### keep-alive 的属性
+
+它提供了include与exclude两个属性,允许组件有条件地进行缓存。
+
+include定义缓存白名单,keep-alive会缓存命中的组件;exclude定义缓存黑名单,被命中的组件将不会被缓存;max定义缓存组件上限,超出上限使用LRU的策略置换缓存数据。
+
+在动态组件中的应用
+
+```js
+
{props.name}
+} + +// 父组件 Parent +const Parent = ()=>{ + returncount:{this.state.count}
+ + > + ) + } + + btnAction = ()=>{ + //不能直接修改state,需要通过setState进行修改 + //同步 + setTimeout(()=>{ + this.setState({ + count: this.state.count + 1 + }); + console.log(this.state.count); + }) + } +} + +export default Count; +``` + +```js +//自定义dom事件 +import React,{ Component } from "react"; +class Count extends Component{ + constructor(props){ + super(props); + this.state = { + count:0 + } + } + + render(){ + return ( + <> +count:{this.state.count}
+ + > + ) + } + + componentDidMount(){ + //自定义dom事件,也是同步修改 + document.querySelector('#btn').addEventListener('click',()=>{ + this.setState({ + count: this.state.count + 1 + }); + console.log(this.state.count); + }); + } +} + +export default Count; +``` + +### 生命周期 + +![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8bae01e6eb804d849e5bb889f787707d~tplv-k3u1fbpfcp-zoom-1.image) + +```js +安装 +当组件的实例被创建并插入到 DOM 中时,这些方法按以下顺序调用: + +constructor() +static getDerivedStateFromProps() +render() +componentDidMount() + +更新中 +更新可能由道具或状态的更改引起。当重新渲染组件时,这些方法按以下顺序调用: + +static getDerivedStateFromProps() +shouldComponentUpdate() +render() +getSnapshotBeforeUpdate() +componentDidUpdate() + +卸载 +当组件从 DOM 中移除时调用此方法: + +componentWillUnmount() +``` + +### 说一下 react-fiber + +#### 1)背景 + +react-fiber 产生的根本原因,是`大量的同步计算任务阻塞了浏览器的 UI 渲染`。默认情况下,JS 运算、页面布局和页面绘制都是运行在浏览器的主线程当中,他们之间是互斥的关系。如果 JS 运算持续占用主线程,页面就没法得到及时的更新。当我们调用`setState`更新页面的时候,React 会遍历应用的所有节点,计算出差异,然后再更新 UI。如果页面元素很多,整个过程占用的时机就可能超过 16 毫秒,就容易出现掉帧的现象。 + +#### 2)实现原理 + +- react内部运转分三层: + + - Virtual DOM 层,描述页面长什么样。 + - Reconciler 层,负责调用组件生命周期方法,进行 Diff 运算等。 + - Renderer 层,根据不同的平台,渲染出相应的页面,比较常见的是 ReactDOM 和 ReactNative。 + +`Fiber 其实指的是一种数据结构,它可以用一个纯 JS 对象来表示`: + +```js +const fiber = { + stateNode, // 节点实例 + child, // 子节点 + sibling, // 兄弟节点 + return, // 父节点 +} +``` + +- 为了实现不卡顿,就需要有一个调度器 (Scheduler) 来进行任务分配。优先级高的任务(如键盘输入)可以打断优先级低的任务(如Diff)的执行,从而更快的生效。任务的优先级有六种: + + - synchronous,与之前的Stack Reconciler操作一样,同步执行 + - task,在next tick之前执行 + - animation,下一帧之前执行 + - high,在不久的将来立即执行 + - low,稍微延迟执行也没关系 + - offscreen,下一次render时或scroll时才执行 + +- Fiber Reconciler(react )执行过程分为2个阶段: + + - 阶段一,生成 Fiber 树,得出需要更新的节点信息。这一步是一个渐进的过程,可以被打断。阶段一可被打断的特性,让优先级更高的任务先执行,从框架层面大大降低了页面掉帧的概率。 + - 阶段二,将需要更新的节点一次过批量更新,这个过程不能被打断。 + +- Fiber树:React 在 render 第一次渲染时,会通过 React.createElement 创建一颗 Element 树,可以称之为 Virtual DOM Tree,由于要记录上下文信息,加入了 Fiber,每一个 Element 会对应一个 Fiber Node,将 Fiber Node 链接起来的结构成为 Fiber Tree。Fiber Tree 一个重要的特点是链表结构,将递归遍历编程循环遍历,然后配合 requestIdleCallback API, 实现任务拆分、中断与恢复。 + +从Stack Reconciler到Fiber Reconciler,源码层面其实就是干了一件递归改循环的事情 + +传送门 ☞[# 深入了解 Fiber](https://juejin.cn/post/7002250258826657799) + +### Portals + +Portals 提供了一种一流的方式来将子组件渲染到存在于父组件的 DOM 层次结构之外的 DOM 节点中。结构不受外界的控制的情况下就可以使用portals进行创建 + +### 何时要使用异步组件?如和使用异步组件 + +- 加载大组件的时候 +- 路由异步加载的时候 + +react 中要配合 Suspense 使用 + +```js +// 异步懒加载 +const Box = lazy(()=>import('./components/Box')); +// 使用组件的时候要用suspense进行包裹 +