简介
我们继续上一节的内容,开始分析 React 官网:https://reactjs.org/docs/accessibility.html 的 “高级指引” 部分,这一部分会涉及到性能优化、Portals、Render Props 等概念的分析,跟上节奏,我们一起出发吧!
知识点
- 性能优化
- Portals
- Render Props
- 类型检查
准备
我们直接用上一节中的 react-demo-day5
项目来作为我们的 Demo
项目,还没有创建的小伙伴可以直接执行以下命令 clone
一份代码:
git clone -b dev https://gitee.com/vv_bug/react-demo-day5.git
接着进入到项目根目录 react-demo-day5
,并执行以下命令来安装依赖与启动项目:
npm install --registry https://registry.npm.taobao.org && npm start
等项目打包编译成功,浏览器会自动打开项目入口,看到上面截图的效果的时候,我们的准备工作就完成了。
性能优化
UI 更新需要昂贵的 DOM 操作,而 React 内部使用几种巧妙的技术以便最小化 DOM 操作次数。对于大部分应用而言,使用 React 时无需专门优化就已拥有高性能的用户界面。尽管如此,你仍然有办法来加速你的 React 应用。
shouldComponentUpdate 的作用
我们先贴一张官方的组件生命周期图:
可以看到,我们可以根据组件的 shouldComponentUpdate
方法去控制是否继续往下走 render
方法,以及后面的 节点 diff
对比,最后更新 DOM
节点等操作。
可以看到,我们在平时项目中,要尽量避免不必要的 render
操作,我们可以利用组件的 shouldComponentUpdate
方法去控制。
我们在 src/advanced-guides
目录下创建一个 optimizing
目录:
mkdir ./src/advanced-guides/optimizing
然后在 src/advanced-guides/optimizing
目录下创建一个 index.tsx
文件:
import React, {useState} from "react";
import HobbiesCom from "./hobbies.com";
function Optimizing() {
// 输入框 ref 引用
let inputRef = React.createRef<HTMLInputElement>();
// 额外的 state 数据
let [count, setCount] = useState<number>(0);
// hobbies 数据
let [hobbies] = useState<Array<string>>(["打游戏", "做美食"]);
/**
* 添加 hobby
*/
function handleAddHobby() {
if (inputRef.current) {
setCount(++count);
}
}
return (
<div>
<input placeholder="请输入你的爱好" ref={ inputRef }/>
<button onClick={ handleAddHobby }>添加</button>
{/* hobby 列表组件 */ }
<HobbiesCom hobbies={ hobbies }/>
{ count }
</div>
);
}
export default Optimizing;
可以看到,我们简单创建了一个输入框,然后通过 “添加” 按钮修改了 count
的值,而 count
是我们额外定义的一个 State
数据。
接着我们在 src/advanced-guides/optimizing
目录下创建一个 hobbies.com.tsx
组件:
import React from "react";
export type Prop = {
hobbies: Array<string>,
};
class HobbiesCom extends React.Component<Prop> {
state = {
choosed: -1
}
handleClick(index: number) {
this.setState({
choosed: index
});
}
render() {
return (
<ul>
{this.props.hobbies.map((hobby, index) => (
<li
style={this.state.choosed === index ? {backgroundColor: "red"} : undefined}
key={`hobby-${hobby}-index`}
onClick={this.handleClick.bind(this, index)}
>
{hobby}
</li>
))}
</ul>
);
}
}
export default HobbiesCom;
可以看到,我们在 HobbiesCom
组件中渲染了 hobbies
的数据。
我们重新运行项目看结果:
npm start
可以看到,即使我们没有修改 HobbiesCom
组件中的 habbies
属性值,但是 HobbiesCom
组件仍然走了 render
函数,我们希望的是当 habbies
数组的数据没有变化的时候, HobbiesCom
组件不需要重新进行渲染,那我们该怎么做呢?
我们修改一下 src/advanced-guides/optimizing/hobbies.com.tsx
组件:
import React from "react";
export type Prop = {
hobbies: Array<string>,
};
class HobbiesCom extends React.Component<Prop> {
state = {
choosed: -1
}
shouldComponentUpdate(nextProps: Prop, nextState: any) {
// 浅对比新老属性的 hoobies 变量跟新老 state 的 choosed 属性
return nextProps.hobbies !== this.props.hobbies || this.state.choosed !== nextState.choosed;
}
...
}
export default HobbiesCom;
[图片上传失败...(image-906254-1617350228836)]
可以看到,只有当 shouldComponentUpdate
方法返回是 true
的时候,组件才会走 render
方法,我们对属性跟 State
做了一个浅对比,所以避免了不必要 render
方法的执行。
其实在 React
中,提供了一个 PureComponent
组件,它的作用就是在 shouldComponentUpdate
方法中对属性跟 State
进行了一个浅对比。
我们可以用 PureComponent
组件改造一下 src/advanced-guides/optimizing/hobbies.com.tsx
组件:
import React from "react";
export type Prop = {
hobbies: Array<string>,
};
class HobbiesCom extends React.PureComponent<Prop> {
state = {
choosed: -1
}
handleClick(index: number) {
this.setState({
choosed: index
});
}
render() {
return (
<ul>
{ this.props.hobbies.map((hobby, index) => (
<li
style={ this.state.choosed === index ? {backgroundColor: "red"} : undefined }
key={ `hobby-${ hobby }-index` }
onClick={ this.handleClick.bind(this, index) }
>
{ hobby }
</li>
)) }
</ul>
);
}
}
export default HobbiesCom;
可以看到,我们只需要把继承的 React.Component
改成 React.PureComponent
即可,效果跟上面的一样,我就不演示了哈,小伙伴自己跑跑。
React.memo
我们上面说的都是 “类组件” 中的性能优化,在 “类组件” 中我们可以通过自定义 shouldComponentUpdate
方法或者 React.PureComponent
来减少不必要的 render
操作,但是在 “函数式组件” 中我们该怎么做呢?
在 React17+
,官方已经给给我们提供了一个高阶组件 React.memo
。
我们直接在 src/advanced-guides/optimizing
目录下创建一个 hobbies.func.tsx
函数式组件:
import React, {useState} from "react";
export type Prop = {
hobbies: Array<string>,
};
function HobbiesFunc(props: Prop) {
let [choosed, setChoosed] = useState(-1);
function handleClick(index: number) {
setChoosed(index);
}
return (
<ul>
{ props.hobbies.map((hobby, index) => (
<li
style={ choosed === index ? {backgroundColor: "red"} : undefined }
key={ `hobby-${ hobby }-index` }
onClick={ () => {
handleClick(index);
} }
>
{ hobby }
</li>
)) }
</ul>
);
}
function areEqual(prevProps: Prop, nextProps: Prop) {
/*
如果把 nextProps 传入 render 方法的返回结果与
prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
return prevProps.hobbies === nextProps.hobbies;
}
export default React.memo<Prop>(HobbiesFunc, areEqual);
可以看到,我们通过 React.memo
高阶组件中的第二个参数 areEqual
方法中做了判断,当新老 Props
中的 habbies
数据相等的时候,不执行组件的 render
操作。
React.memo
仅检查 props 变更。如果函数组件被 React.memo
包裹,且其实现中拥有 useState
,useReducer
或 useContext
的 Hook,当 context 发生变化时,它仍会重新渲染。
默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现,所以我们 Demo
中其实不需要定义第二个参数,默认就可以达到我们想要的效果了。
效果就不演示啦,跟前面的类组件一致,小伙伴自己跑跑看效果哦。
我们分别通过 Demo
演示了 “类组件” 跟 “函数式组件” 中的性能优化,小伙伴一定要自己打断点跑跑看效果哦。
Portals
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
ReactDOM.createPortal(child, container)
第一个参数(child
)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。第二个参数(container
)是一个 DOM 元素。
通常来讲,当你从组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点:
render() {
// React 挂载了一个新的 div,并且把子元素渲染其中
return (
<div> {this.props.children}
</div> );
}
然而,有时候将子元素插入到 DOM 节点中的不同位置也是有好处的:
render() {
// React 并*没有*创建一个新的 div。它只是把子元素渲染到 `domNode` 中。
// `domNode` 是一个可以在任何位置的有效 DOM 节点。
return ReactDOM.createPortal(
this.props.children,
domNode );
}
一个 portal 的典型用例是当父组件有 overflow: hidden
或 z-index
样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框。
我们还是通过 Demo
来演示一下。
首先在 src/advanced-guides
目录下创建一个 portals
目录:
mkdir ./src/advanced-guides
接着我们在 src/advanced-guides/portals
目录下创建一个组件 index.tsx
:
import React, {useState} from "react";
import Model from "./model";
function Portals() {
let [isShowModel, setShowModel] = useState(false);
function handleShow() {
if (!isShowModel) {
setTimeout(() => {
setShowModel(false);
}, 1000);
}
setShowModel((show) => {
return !show;
});
}
return (
<React.Fragment>
{isShowModel && (
<Model>
<div className="model">hello model</div>
</Model>
)}
<div onClick={handleShow}>点我 show model</div>
</React.Fragment>
);
}
export default Portals;
可以看到,我们用了一个 isShowModel
状态去控制 Model
组件的展示。
然后我们在 src/advanced-guides/portals
目录下创建一个 model.tsx
组件:
import ReactDOM from "react-dom";
function Model(props: any) {
return ReactDOM.createPortal(props.children, document.body);
}
export default Model;
很简单,我们直接用 ReactDOM.createPortal
定义并返回了一个 React
元素,并且把 Model
组件的子元素都绑定到了 document.body
节点上了。
接着我们去 src/main.scss
样式文件中添加一个 model
样式:
.root{
font-size: 16px;
}
.model{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 200px;
height: 200px;
margin: auto;
line-height: 200px;
text-align: center;
background-color: rgba(0,0,0,0.7);
}
我们重新运行项目看结果:
可以看到,当我们点击 “show model” 按钮的时候,页面中显示了我们 Model
组件的子元素,并且这些子元素最后都被挂载到了 document.body
节点上了。
下面我们换一种实现方式,我们来实现一个 Toast
组件,让它能像 Model
组件一样,挂载到 body
节点上,然后指定时间后自动消失。
我们在 src/advanced-guides/portals
目录底下创建一个 toast.tsx
组件:
import ReactDom from "react-dom";
import React from "react";
class Toast {
static divEle: HTMLDivElement | null;
static show(msg: string, timing: number = 1000) {
Toast.hide();
Toast.divEle = document.createElement("div");
ReactDom.render(
(<div className="model">{msg}</div>),
Toast.divEle
);
document.body.appendChild(Toast.divEle);
setTimeout(() => {
Toast.hide();
}, timing);
}
static hide() {
Toast?.divEle && document.body.removeChild(Toast.divEle);
Toast.divEle = null;
}
}
export default Toast;
可以看到,我们用 ReactDom.render
创建了一个 React
根元素,把 Toast
的内容挂载到了一个 div
元素中,接着又把这个 div
元素挂在到了 document.body
节点上了。
我们修改一下 src/advanced-guides/portals/index.tsx
组件,用一下我们的 Toast
组件:
import React, {useState} from "react";
import Model from "./model";
import Toast from "./toast";
function Portals() {
let [isShowModel, setShowModel] = useState(false);
function handleShow() {
if (!isShowModel) {
setTimeout(() => {
setShowModel(false);
}, 1000);
}
setShowModel((show) => {
return !show;
});
}
function apiShowModel() {
Toast.show("你好呀", 2000);
}
return (
<React.Fragment>
{isShowModel && (
<Model>
<div className="model">hello model</div>
</Model>
)}
<div onClick={handleShow}>点我 show model</div>
<div onClick={apiShowModel}>Api Show</div>
</React.Fragment>
);
}
export default Portals;
可以看到,我们在 apiShowModel
方法中调用了 Toast.show("你好呀", 2000)
方法创建了个一个 Toast
。
我们重新运行项目看结果:
可以看到,跟前面 Model
组件的效果一致。
下面我们用 ReactDom.createPortal
改造一下 Toast
组件:
import ReactDom from "react-dom";
import React from "react";
class Toast {
static divEle: React.RefObject<HTMLDivElement> | null;
static show(msg: string, timing: number = 1000) {
Toast.hide();
Toast.divEle = React.createRef<HTMLDivElement>();
ReactDom.render(
ReactDom.createPortal((<div ref={Toast.divEle} className="model">{msg}</div>), document.body),
document.createElement("div")
);
setTimeout(() => {
Toast.hide();
}, timing);
}
static hide() {
Toast?.divEle?.current && document.body.removeChild(Toast.divEle.current);
Toast.divEle = null;
}
}
export default Toast;
可以看到,我们把之前的 document.body.appendChild
改成了 ReactDom.createPortal
,效果跟前面的一致,我就不演示啦,很明显了吧,小伙伴有没有弄懂 ReactDom.createPortal
的原理呢?
Render Props
术语 “render prop” 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术,在 vue
跟 Angular
中叫 “插槽”。
具有 render prop 的组件接受一个返回 React 元素的函数,并在组件内部通过调用此函数来实现自己的渲染逻辑。
说起来抽象,我们还是通过 Demo
来分析一下。
比如我们现在有一个组件支持 “头部”、“内容”、“尾部” 的展示,其中 “头部”、“尾部” 支持自定义方式。
我们首先在 src/advanced-guides
目录下创建一个 render-props
目录:
mkdir ./src/advanced-guides/render-props
然后我们在 src/advanced-guides/render-props
目录下创建一个 index.tsx
组件:
import PropTypes from "prop-types";
type Prop = {
renderHeader: () => React.ElementType,
renderFooter: () => React.ElementType,
};
function RenderProp(props: Prop | undefined) {
return (
<div style={{backgroundColor: "darkcyan"}}>
{/* 渲染头部 */}
{props?.renderHeader()}
{/* 渲染内容 */}
<div>我是 RenderProps 组件内容模块</div>
{/* 渲染尾部 */}
{props?.renderFooter()}
</div>
);
}
RenderProp.propTypes = {
renderHeader: PropTypes.func,
renderFooter: PropTypes.func,
};
RenderProp.defaultProps = {
renderHeader: () => (<div>我是默认头部内容</div>),
renderFooter: () => (<div>我是默认尾部内容</div>),
};
export default RenderProp;
可以看到,我们创建了一个 RenderProp
组件,RenderProp
组件中渲染了三个部分 “头部”、“内容”、“尾部”,并且 “头部”、“尾部” 支持自定义。
我们修改一下 src/advanced-guides/index.tsx
组件,引入 RenderProp
组件:
/**
* 核心概念列表
*/
import CodeSplit from "./code-split";
import Context from "./context";
import ErrorBoundaries from "./error-boundaries";
import ErrorCom from "./error";
import ForwardRef from "./forward-ref";
import Optimizing from "./optimizing";
import Portals from "./portals";
import RenderProps from "./render-props";
function AdvancedGuides() {
return (
<ErrorBoundaries>
<div>
{/* 代码分割 */ }
<CodeSplit/>
{/* Context */ }
<Context/>
{/* 报错的组件 */ }
<ErrorCom/>
{/* Refs 转发 */ }
<ForwardRef/>
{/* 性能优化 */ }
<Optimizing/>
{/* Portals */ }
<Portals/>
{/* Render Props */ }
<RenderProps/>
</div>
</ErrorBoundaries>
);
};
export default AdvancedGuides;
然后我们重新运行项目看结果:
可以看到,RenderProp
组件渲染了默认的内容。
接下来我们修改一下 src/advanced-guides/index.tsx
组件,自定义 RenderProps
的 “头部” 跟 “尾部” 内容:
/**
* 核心概念列表
*/
import CodeSplit from "./code-split";
import Context from "./context";
import ErrorBoundaries from "./error-boundaries";
import ErrorCom from "./error";
import ForwardRef from "./forward-ref";
import Optimizing from "./optimizing";
import Portals from "./portals";
import RenderProps from "./render-props";
function AdvancedGuides() {
return (
<ErrorBoundaries>
<div>
{/* 代码分割 */}
<CodeSplit/>
{/* Context */}
<Context/>
{/* 报错的组件 */}
<ErrorCom/>
{/* Refs 转发 */}
<ForwardRef/>
{/* 性能优化 */}
<Optimizing/>
{/* Portals */}
<Portals/>
{/* Render Props */}
<RenderProps
renderHeader={() => (<div>我是自定义头部内容</div>)}
renderFooter={() => (<div>我是自定义尾部内容</div>)}
/>
</div>
</ErrorBoundaries>
);
};
export default AdvancedGuides;
然后重新运行项目看结果:
可以看到,我们成功的自定义了 RenderProp
组件的头部跟尾部内容。
有小伙伴要疑问了 “这样做的目的是什么呢?”我举个例子吧:
比如你的领导需要你开发一个 RenderProp
组件,告诉你需要支持 “头部”、“内容”、“尾部” 的渲染。ok,你能力很强,很快就完成了领导的需求,你开心的去休假去了,然后你领导把这个组件直接给到了另外一个开发手中,领导心血来潮了,说 “头部内容样式需要改改,赶紧把那个休假的人叫回来!!”,哈哈,这个时候你是不是就很无语了呢?那如果你的组件支持自定义功能,你就可以很牛逼的告诉领导:“ 不想用默认样式的话,支持自定义头部跟尾部的,爱怎么玩就怎么玩”。
使用 PropTypes 进行类型检查
随着你的应用程序不断增长,你可以通过类型检查捕获大量错误。对于某些应用程序来说,你可以使用 Flow 或 TypeScript 等 JavaScript 扩展来对整个应用程序做类型检查。但即使你不使用这些扩展,React 也内置了一些类型检查的功能。要在组件的 props 上进行类型检查,你只需配置特定的 propTypes
属性,比如我们上面的 RenderProp
组件:
import PropTypes from "prop-types";
type Prop = {
renderHeader: () => React.ElementType,
renderFooter: () => React.ElementType,
};
function RenderProp(props: Prop | undefined) {
return (
...
);
}
RenderProp.propTypes = {
renderHeader: PropTypes.func,
renderFooter: PropTypes.func,
};
RenderProp.defaultProps = {
renderHeader: () => (<div>我是默认头部内容</div>),
renderFooter: () => (<div>我是默认尾部内容</div>),
};
export default RenderProp;
我们使用了 ts
静态类型校验跟 prop-types
的动态校验,如果在使用组件的时候不按规定传递属性类型的话,开发模式中直接就会报错了。
我们修改一下 src/advanced-guides/index.tsx
文件:
{/* Render Props */}
<RenderProps
renderHeader={1}
renderFooter={() => (<div>我是自定义尾部内容</div>)}
/>
可以看到,首先是 IDE
报错了,说我们需要的是 function
类型,但是你传递的是 number
类型,Webpack
编译也直接提示报错了。
接着是页面中的提示 :
PropTypes
以下提供了使用不同验证器的例子:
import PropTypes from 'prop-types';
MyComponent.propTypes = {
// 你可以将属性声明为 JS 原生类型,默认情况下
// 这些属性都是可选的。
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// 任何可被渲染的元素(包括数字、字符串、元素或数组)
// (或 Fragment) 也包含这些类型。
optionalNode: PropTypes.node,
// 一个 React 元素。
optionalElement: PropTypes.element,
// 一个 React 元素类型(即,MyComponent)。
optionalElementType: PropTypes.elementType,
// 你也可以声明 prop 为类的实例,这里使用
// JS 的 instanceof 操作符。
optionalMessage: PropTypes.instanceOf(Message),
// 你可以让你的 prop 只能是特定的值,指定它为
// 枚举类型。
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// 一个对象可以是几种类型中的任意一个类型
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 可以指定一个数组由某一类型的元素组成
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// 可以指定一个对象由某一类型的值组成
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
// 可以指定一个对象由特定的类型值组成
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),
// An object with warnings on extra properties
optionalObjectWithStrictShape: PropTypes.exact({
name: PropTypes.string,
quantity: PropTypes.number
}),
// 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
// 这个 prop 没有被提供时,会打印警告信息。
requiredFunc: PropTypes.func.isRequired,
// 任意类型的必需数据
requiredAny: PropTypes.any.isRequired,
// 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
// 请不要使用 `console.warn` 或抛出异常,因为这在 `oneOfType` 中不会起作用。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},
// 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
// 它应该在验证失败时返回一个 Error 对象。
// 验证器将验证数组或对象中的每个值。验证器的前两个参数
// 第一个是数组或对象本身
// 第二个是他们当前的键。
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};
限制单个元素
你可以通过 PropTypes.element
来确保传递给组件的 children 中只包含一个元素。
import PropTypes from 'prop-types';
class MyComponent extends React.Component {
render() {
// 这必须只有一个元素,否则控制台会打印警告。
const children = this.props.children;
return (
<div>
{children}
</div>
);
}
}
MyComponent.propTypes = {
children: PropTypes.element.isRequired
};
默认 Prop 值
您可以通过配置特定的 defaultProps
属性来定义 props
的默认值:
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
// 指定 props 的默认值:
Greeting.defaultProps = {
name: 'Stranger'
};
// 渲染出 "Hello, Stranger":
ReactDOM.render(
<Greeting />,
document.getElementById('example')
);
如果你正在使用像 transform-class-properties 的 Babel 转换工具,你也可以在 React 组件类中声明 defaultProps
作为静态属性。此语法提案还没有最终确定,需要进行编译后才能在浏览器中运行。要了解更多信息,请查阅 class fields proposal。
class Greeting extends React.Component {
static defaultProps = {
name: 'stranger'
}
render() {
return (
<div>Hello, {this.props.name}</div>
)
}
}
defaultProps
用于确保 this.props.name
在父组件没有指定其值时,有一个默认值。propTypes
类型检查发生在 defaultProps
赋值后,所以类型检查也适用于 defaultProps
。
函数组件
如果你在常规开发中使用函数组件,那你可能需要做一些适当的改动,以保证 PropsTypes 应用正常。
假设你有如下组件:
export default function HelloWorldComponent({ name }) {
return (
<div>Hello, {name}</div>
)
}
如果要添加 PropTypes,你可能需要在导出之前以单独声明的一个函数的形式,声明该组件,具体代码如下:
function HelloWorldComponent({ name }) {
return (
<div>Hello, {name}</div>
)
}
export default HelloWorldComponent
接着,可以直接在 HelloWorldComponent
上添加 PropTypes:
import PropTypes from 'prop-types'
function HelloWorldComponent({ name }) {
return (
<div>Hello, {name}</div>
)
}
HelloWorldComponent.propTypes = {
name: PropTypes.string
}
export default HelloWorldComponent
上面的这些内容我们在前面的 Demo
中都有演示过,我们就不再演示了,小伙伴自己多敲敲哦!
总结
ok,React
的高级指引部分我们就算是分析完毕了,认认真真看到这里的小伙伴想必搞定面试跟简单的 React
项目应该是问题不大了,后面章节我们将会会介绍 React
中的 Hook
、“全家桶”、“源码分析” 等等。
这节到这就结束啦,下节见~
欢迎志同道合的小伙伴一起交流,一起学习。