学习目标
独立完成React项目;
掌握淘系React解决方案;
掌握常见的面试题;
预计完成天数46天,共计七周;
笔记代码地址2023年最新珠峰React全家桶
课程视频链接:2023年最新珠峰React全家桶【基础-进阶-项目-源码-淘系-面试题】
Create-react-app基础操作
当下以及未来的前端开发一定是组件化/模块化。
- 有助于团队协作开发
- 便于组件的复用:提高开发效率、方便后期维护、减少页面中的冗余代码
如何划分组件?
业务组件:针对项目需求封装的
- 普通业务组件:复用性低,只是单独拆选出来的一个模块;
- 通用业务组件:具备复用性
功能组件:适用于多个项目(列如:UI组件库中的组件)
- 通用功能组件
组件话开发必然带来工程化,基于webpack/vite/turbopack等工具实现组件的合并、压缩、打包等。
安装Create-react-app
React官方提供的脚手架,便于创建React项目。
全局安装create-react-app
脚手架:
1 | npm i create-react-app -g |
检查 create-react-app
版本。
1 | create-react-app --version |
使用Create-react-app创建项目基于脚手架床创建React工程化项目:
- 项目名称要遵循npm包命名规范:使用”数字、小写字母、_”命名;
1 | create-react-app 项目名称 |
一个React项目中,默认会安装:
- react:React框架的核心;
- react-dom:React视图渲染的核心【基于React构建WebApp(HTML页面)】
- react-scripts:一组脚本,帮忙运行或打包项目;
在项目根目录的package.json中定义了许多项目参数:
暴露webpack配置,一旦暴露就不能还原。在暴露之前建议提交到git。
1 | yarn eject |
脚手架的进阶应用

基于环境变量修改端口号

修改兼容列表

处理Proxy跨域
在src目录中,新建setupProxy.js,安装 http-proxy-middleware
:
1 | yarn add http-proxy-middleware |

MVC模式和MVVM模式
React是Web前端框架
- 目前市面上比较主流的前端框架
- React
- Angular
- Vue
主流的思想:不再直接操作DOM,而是改为“数据驱动思想”。
操作DOM思想:
- 操作DOM比较消耗性能,主要原因是:可能导致DOM重拍
- 操作起来也相对麻烦一些
数据驱动思想:
- 操作数据,框架会按照相关的数据,让页面重新渲染
- 框架底层构建虚拟DOM->真实DOM的渲染体系
- 有效避免DOM的重排/重绘
优点:
- 开发效率更高
- 性能更优
- React框架才用的是MVC体系;Vue采用的是MVVM体系
MVC:model数据层+view视图层+controler控制层

数据驱动视图的渲染,单向驱动;
MVVM:model数据层+view视图层+viewModel数据/视图监听层
JSX语法使用上的细节
JSX:JavaScript and xml(HTML) 把JS和HTML标签混合在一起
1 | import React from 'react'; // React语法核心 |
JSX的具体应用
{}
胡子语法中嵌入不同的值,所呈现出来的特点
number/string:值是啥,渲染出来啥
bool/null/undefined/Symbol/Bigint:渲染内容是空
不支持渲染:普通对象
数组对象:把每一项拿出来,分别渲染
函数对象:不支持在
{}
中渲染,但是可以作为函数组件,作为<componment/>
渲染
元素设置样式
行内样式,要以对象的形式设置
1 | root.render( |
设置样式类名:要把class替换为className
1 | <h2 className="box"></h2> |
需求一:基于数据的值,来判断元素的显示隐藏
1 | <div> |
需求二:从服务器获取了一组列表数据,循环动态绑定相关的内容
1 | import React from 'react'; |
JSX底层渲染机制[创建虚拟DOM]
- 关于JSX的底层处理机制
- 第一步:把JSX编译为虚拟DOM对象(virtualDOM)
虚拟DOM:框架内部构建大一套对象体系(对象的相关成员都是React内部规定的),基于这些属性描述出我们所构建视图中的DOM节点的相关特征。
@1 基于babel-prest-react-app把JSX编译为React.createElement(…)这种格式!!只要是元素节点,必然基于createElement进行处理:
1 | React.createElement(ele, props, ...children) |
@2 再把createElement方法执行,创建出virtualDOM虚拟DOM对象,【也有称之为:JSX元素、JSX对象、ReactChild对象】
1 | vitrualDOM = { |
- 第二步:把构建的virtualDOM渲染为真实DOM
真实 DOM:浏览器页面中,最后渲染出来,让用户看见的DOM元素。
基于ReactDOM中render方法处理
1 | // V16 |
补充说明:第一次渲染页面是直接从virtualDOM->真实DOM;但是后期视图更新的时候需要经过一个DOM-DIFF的对比,计算出补丁包PATCH(两次视图差异的部分),把PATCH补丁包进行渲染。
[Babel 中文网 · Babel - 下一代 JavaScript 语法的编译器 (babeljs.cn)](https://www.babeljs.cn/repl#?browsers=defaults%2C not ie 11%2C not ie_mob 11&build=&builtIns=false&corejs=3.21&spec=false&loose=false&code_lz=DwEwlgbgfAUABHYALAjFAEgUwDbYPZwDueATtiMAPSqxXjRA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Creact%2Cstage-2&prettier=false&targets=&version=7.20.15&externalPlugins=&assumptions={})
函数组件的底层渲染机制
React中的组件化开发:
- 函数组件
- 类组件
- Hooks组件:在函数组件中使用React Hooks函数
函数组件
创建一个函数返回JSX元素:
1 | const DemoOne = function DemoOne(){ |
在Src目录中创建一个xxx.jsx文件,创建一个函数,让函数返回JSX视图【或者JSX元素、virtualDOM虚拟DOM对象】,这就是创建了一个”函数组件”。
1 | <Componment/> //单闭合调用 |
组件的命名:一般采用PascalCase(大驼峰命名方法)
调用组件,可以给组件设置(传递)各种属性:
1 | <DemoOne title="我是标题" x={10} data={100, 200} style={{fontSize: '20px'}}/> |
如果设置的属性不是字符串,需要用{}
嵌套。
基于双闭合调用,还可以传递子节点:
1 | <Componment> |
props属性的细节知识
对象的设置规则:
- 冻结对象:
Object.freeze(obj)
,检测对象是否被冻结:Object.isFrozen(obj)=>true/false
;被冻结的对象不能被修改成员值、不能修改成员值、不能删除成员值; - 密封对象:
Object.seal(obj)
;检测对象是否密封Object.isSealed(obj)
;不能新增、删除;但是可以读取、修改; - 扩展对象:把对象设置为不可扩展:
Object.preventExtension(obj)
;检测是否可扩展:Object.isExtensible(obj)
;被设置不可扩展的对象:除了不能新增成员,其余的操作都可以处理;被冻结的对象是密封、也是不可扩展;
- 属性props的处理
- 调用组件:传递进来的属性是“只读”的【原理:props对象被冻结了】
- Object.isFrozen(props)=>true
- 获取:props.xxx
- 修改:props.xxx=xxx =>报错
1 | const DemoOne = function DemoOne(props){ |
作用:父组件(index.jsx)调用子组件(DemoOne.jsx)的时候,可以基于属性,把不同的信息传递给子组件,子组件接收响应的属性值,呈现不同的效果,让组件复用性更强;
虽然对传递进来的属性不能直接修改,但是可以做一些规则校验
设置默认值
1
2
3
4/*通过把函数当做对象,设置静态的私有属性方法,【把函数当做对象】,用来给其设置属性的规则*/
DemoOne.defaultProps = {
x: 0
}设置其他规则,列如:数据值格式、是否必传…【依赖于官方的一个插件:
prop-types
】1
2
3
4
5
6
7
8
9
10
11
12import PropTypes from 'prop-types';
函数组件.propTypes = {
// 类型是字符串,必传
title: PropTypes.string.isRequired,
// 类型是数字
x: PropTypes.number,
// 多种校验规则中的一个
y: PropTypes.oneOfType([
PropTypes.number,
PropsTypes.bool,
])
};传递进来的属性,首先会经历规则校验,不管校验成功还是失败,都会把属性给形参props,只不过如果不符合设定规则,控制台会抛出错误(不影响属性值的获取)
如果就想把传递的属性值进行修改:
把props中的某个属性赋值给其他内容【列如:变量、状态】
不直接操作props.xxx=xxx,但是可以修改变量/状态值
1
2let y = props.y;
y = 1000;
React中的插槽处理机制

1 | let {className, style, title,x, children } = props; |
初步尝试组件的封装
1 | import PropTypes from "prop-types"; |
静态组件和动态组件
函数组件是静态组件,第一次渲染组件把函数执行:
- 产生一个私有的上下文
- 把解析出来的props(含children)传递进来 但是被冻结了
- 对函数返回JSX元素 virtualDOM进行渲染
当我们点击按钮时,会把绑定的小函数执行:
- 修改上级上下文中的变量
- 私有变量发生了改变
- 但是视图不会更新
也就是说函数组件在第一次渲染完成后,组件中的内容,不会根据组件内的某些操作,再进行更新,所以称为静态组件。
除非在父组件中,重新调用这个函数组件 【可以传递不同的属性信息】
动态组件
类方法实现:
1 | import React from "react"; |
HOOKS实现;
ES6中class语法和继承的原理
1 | class Parent{ |
类组件第一次渲染的底层逻辑
render函数在渲染的时候,如果type是:
- 字符串:创建一个标签
- 普通函数:把函数执行,并且把props传递给函数
- 构造函数:把构造函数基于new执行,也就是创建类的一个实例,也会把解析出来的props传递过去,
每次调用一次类组件都会创建一个单独的实例。
把在类组件中编写的render函数执行,把返回的jsx virtualDOM当做组件视图进行渲染。
从调用类组件new Vote({…})开始,类组件内部发生的事情:
初始化属性 && 规则校验
先规则校验,校验完毕后,再处理属性其他操作
方案一:
1
2
3
4
5
6
7
8
9
10
11
12import PropTypes from "prop-types";
static defaultProps = {
// 属性校验规则
num: 0
}
static propTypes = {
title: PropTypes.string.isRequired,
}
constructor(props){
super(props); // 会把传递进来的属性挂载到this实例
console.log(this.props); // 获取到传递的属性
}方案二:
即使不在constructor中处理,【或者constructor都没写】,在constructor处理完毕后,React内部也会把传递的props挂载到实例上;所以在其他的函数中,只要保证this是实例,就可以基于this.props获取传递的属性。
- 同样this.props获取的属性对象也是被冻结的(只读的)Object.isFrozen(this.props)->true
- 设置规则校验
初始化状态
状态:后期修改状态,可以触发视图的更新
需要手动初始化,如果没有做相关的处理,则默认会往实例上挂载一个state,初始值是
null=>this.state=null
1
2
3
4state = {
supNum: 0,
oppNum: 3
}修改状态,控制视图更新
@1
this.setState({})
既可以修改状态,也可以让视图更新【推荐使用这种方法】@2
this.forceUpdate()
强制更新触发周期函数
componmentWillMount
周期函数(钩子函数)钩子函数:在程序运行到某个阶段,可以基于提供一个处理函数,让开发者在这个阶段做一些自定义的事情,一般也叫生命周期函数
此周期函数,目前是不安全的,虽然可以用,但是未来可能要被移除,不建议使用。
类组件更新的底层逻辑
当修改了相关状态,组件会更新;
第一种组件更新的逻辑是组件的内部状态做了修改,组件更新:
触发周期函数
shouldComponentUpdate
1
2
3
4
5
6
7
8
9shouldComponentUpdate(nextProps, nextState) {
// nextState:存储要修改的最新状态
// this.state:存储的还是修改前状态 [此时状态还没有改变]
console.log(this.state, nextState)
// 此周期函数需要返回一个true或false
// 返回true:允许更新,会继续执行下一个操作
// 返回false:不允许更新,接下来啥都不处理
return true;
}触发
componmentWillUpdate
周期函数:更新之前此周期函数也是不安全的
在这个阶段,状态还没有被修改
修改状态值/属性值 让this.state.xxx改为最新的值
触发render周期函数:组件更新
按照最新的状态/属性,把返回的JSX编译为virtualDOM
和上一次渲染的virtualDOM进行对比,
把差异的部分进行渲染为真实DOM
触发componmentDidUpdate周期函数:组件更新完毕
特殊说明:如果我们是基于this.forceUpdate()强制更新视图,会跳过shouldComponentUpdate周期函数的校验,直接从componmentWillUpdate开始更新(也就是说视图一定会更新);
第二种,组件的父组件更新,触发子组件更新
触发componementReceiveProps周期函数:接收最新属性之前
周期函数本事是不安全的
1
2
3
4
5UNSAFE_componmentReceiveProps(nextProps){
// this.props:存储之前的属性
// nextProps:传递进来的最新属性值
console.log('componmentWillReceiveProps:', this.props, nextgProps);
}触发周期函数
shouldComponentUpdate
….同上
父子组件嵌套,处理机制上遵循深度优先原则:父组件在操作中,遇到子组件,一定是把子组件处理完,父组件才能继续处理:
父组件第一次渲染
父willMount——>父render [子willMount ——>子 render ——>子didMount] ——>父didMount
父组件更新:
父shouldUpdate——>父willUpdate——>父render[子willReceiveProps ——>子 shouldUpdate ——>子willUpdate ——> 子render ——>子 didUpdate] ——>父didUpdate
父组件销毁:
父willUmount——>处理中[子willUnmount——>子销毁] ——>父销毁
周期函数示意图
PureComponment和Componment的区别
PureComponent
会给类组件默认加一个shouldComponment
周期函数。
- 在此周期函数中,会对新老的属性/状态进行浅比较
- 如果经过浅比较,发现属性和状态并没有改变,则返回false
有关REF操作的解读
受控组件:基于修改数据/状态,让视图更新,达到需要的效果 【推荐】
非受控组件:基于ref获取DOM元素,操作DOM元素,来实现需求和效果
基于ref获取DOM元素的方法
- 给需要获取的元素设置
ref='xxx'
,后期基于this.refs.xxx
去获取相应的DOM元素 【不推荐使用这种用法】
<h2 className="title" ref="titleBox">温馨提示</h2>
获取:this.refs.titleBox
把ref属性设置为一个函数
ref={x=>thhis.xxx=x}
x
是函数的形参:存储的就是当前的DOM元素- 然后获取的DOM元素”x”直接挂载在实例的某个属性上(列如box2)
原理:在render渲染的时候,会获取virtualDOM的属性
- 如果属性值是一个字符串,则会给this.refs增加这样一个成员,成员值就是当前的值
- 如果属性值是一个函数,则会把函数执行,把当前DOM传递给这个函数【x->DOM元素】,而在函数执行的内部,一般会把DOM元素直接挂到实例的某个属性上
基于
React.createRef()
方法创建一个REF对象->{current:null}
ref={REF对象}
获取:
this.xxx.current
1 | import React from "react"; |
给元素标签设置ref ,目前:获取对应的DOM元素
给类组件设置ref,目的:获取当前组件的实例,后续可以获取子组件中的相关信息
给函数组件设置ref直接报错,但是可以让其配合React.forwardRef实现ref的转发
目的:获取函数子组件内部的某个元素
关于setState的进阶处理
1 | import React from "react"; |
合成事件
合成事件是围绕浏览器原生事件,充当浏览器包装器的对象;它们将不同浏览器的行为合并为一个API,这样做是为了确保事件在不同浏览器中显示一致的属性。
合成事件的基本操作
在JSX元素上,直接基于onXxx={函数}
进行事件绑定。
浏览器标准事件,在React中大部分都支持。
基于React内部的处理,如果我们给合成事件绑定一个普通函数,当事件行为触发,绑定的函数执行,方法中的this会是undefined
1 | import React from 'react' |
使用bind绑定函数,可以解决这个问题:
1 | render() { |
这时候的this就是当前实例对象。
当然还有更方便的方法,那就是使用箭头函数定义事件触发函数:
1 | import React from 'react' |
合成事件对象
在事件函数中输出event事件变量:
1 | import React from 'react' |
React内部经过特殊处理,把各个浏览器的事件对象统一化后,构建的一个事件对象SyntheticBaseEvent
。合成事件对象包含了浏览器内置事件对象中的事件和方法
- clientX/clientY
- pageX/pageY
- target
- type
- preventDefault
- stopPropagation
- …
- nativeEvent:基于这个属性,可以获取浏览器内置的原生事件对象
给事件函数传递参数,使用bind函数处理,基于bind函数处理会把事件对象以最后一个实参传递给函数。
1 | handle2 = (x,y, ev)=> { |
事件及事件委托
阻止事件传播:
ev.stopPropagation();
1 | import React from 'react' |
React中合成事件处理原理
绝对不是给当前元素基于addEventListener单独做的事件绑定,React中的合成事件,都是基于“事件委托”处理的
在React17以后版本,都是委托给#root这个容器,捕获和冒泡都做了委托;
在React17版本以前,都是委托给doucment容器,而且只做了冒泡阶段的委托;
对于没有实现事件传播机制的委托,才是单独做的事件绑定;
循环事件绑定
1 | import React from "react"; |
Redux的基础操作和思想
除了Redux这5步操作之外,还需要一些其他的知识做配合。