05.组件化
组件化开发
组件化开发的优势
- 利于团队协作开发
- 利于组件复用
- 利于SPA单页面应用开发
- ……
主流组件的对比
Vue中的组件化开发:
http://fivedodo.com/upload/html/vuejs3/guide/migration/functional-components.html
- 全局组件和局部组件
- 函数组件(functional)和类组件(Vue3不具备functional函数组件」)
React中的组件化开发:
没有明确全局和局部的概念(可以理解为都是局部组件,不过可以把组件注册到React上,这样每个组件中只要导入React即可使用)
- 函数组件
- 类组件
- Hooks组件:在函数组件中使用React Hooks函数
React函数组件
创建
两个情况都是创建了一个函数组件:
- 在SRC目录中,创建一个 xxx.jsx 的文件,就创建一个组件了;
- 我们在此文件中,创建一个函数,让函数返回JSX视图(或者JSX元素、virtualDOM虚拟DOM对象)
例子:
1 | // views/FunctionComponent.jsx |
调用
在文件中,可以基于ES6Module规范导入创建的组件(导入时可以忽略.jsx后缀名),然后像写标签一样调用。
1 | //两种调用方式 |
传递属性的规则:
调用组件时,可以给被调用的组件设置(传递)各种各样的属性,例如:
<DemoOne title="我是标题" x={10} data={[100, 200]} className="box" style={{ fontSize: '20px' }} />
若设置的属性值不是字符串格式,则需要基于
{}语法
进行嵌套。
命名
组件名,一般都采用
PascalCase
(大驼峰命名法)
渲染过程
由上两章可知组件的渲染过程是
- 基于
babel-preset-react-app
把调用的组件转换为createElement
格式
1 | React.createElement(DemoOne, { |
- 执行
createElement
方法创建一个virtualDOM
对象
1 | { |
- 基于
root.render
把virtualDOM
变为真实的DOM
type值不再是一个字符串,而是一个函数了,此时:
- 函数执行→
DemoOne()
- 把
virtualDOM
中的props
,作为实参传递给函数DemoOne()
→DemoOne(props)
- 接收函数执行的返回结果(也就是当前组件的virtualDOM对象)
- 最后由render函数把组件返回的虚拟DOM变为真实DOM,插入到id=root的容器中!!
Props的处理
- 调用组件,传递进来的属性(Props)是“只读”的,原理是:props对象被冻结了
Object.freeze()
(见下文解释)
1 | //测试 |
回顾js知识点:关于对象的规则设置(了解可跳过)
阮老师单推人😘
标准库 - 属性描述对象 - 《阮一峰 JavaScript 教程》 - 书栈网 · BookStack
控制对象状态的一节,讲述了以下内容:
扩展(弱):
Object.*preventExtensions*()
:使得一个对象无法再添加新的属性Object.isExtensible(obj)
:检测是否可扩展(返回bool值,下面都是)密封(较强)
Object.seal(obj)
:密封对象,使得一个对象既无法添加新属性,也无法删除旧属性。Object.isSealed(obj)
:检测是否被密封
- 被密封的对象:可以修改成员的值,但也不能删、不能新增、不能劫持
Object.defineProperty
冻结(强)
Object.freeze(obj)
:冻结对象,对象实际上变成了常量。Object.isFrozen(obj)
检测是否被冻结
- 被冻结的对象:不能修改成员值、不能新增成员、不能删除现有成员、不能给成员做劫持
Object.defineProperty
局限性:可以通过改变原型对象,来为对象增加属性。(详见阮一峰教程)
劫持:(也在”属性描述对象’’同一章中)
Object.defineProperty()
方法允许通过属性描述对象,定义或修改一个属性,然后返回修改后的对象()👏tips:
- 被设置不可扩展的对象:除了不能新增成员、其余的操作都可以处理!!
- 被密封的对象,也是不可扩展的!!
- 同理,被冻结的对象,即是不可扩展的,也是密封的!!
- 通过三个方法的互相调用可以部分解封,但如果要加入新的属性那就只能创建一个新对象了
💬Ps:
- 越底层越要看js基础,看来八股并不是没用。
- 是不是这部分我应该单独开一章🤦♂️🤦♀️
然后让我们举一个传Props的例子:
1 | // src\index.jsx |
1 | // src\views\DemoOne.jsx |
属性的使用逻辑
- 父组件
index.jsx
调用子组件DemoOne.jsx
的时候,可以基于属性,把不同的信息传递给子组件; - 子组件接收相应的属性值,呈现出不同的效果,让组件的复用性更强!!
对于传递进来的属性规则校验
- 原理:通过把函数当做对象,设置静态的私有属性方法,来给其设置属性的校验规则
- 可以设置默认值(见上面代码):
1 | /* 函数组件.defaultProps = { |
设置其它规则,例如:数据值格式、是否必传… (依赖于官方的一个插件:
prop-types
)https://github.com/facebook/prop-types
这里就是用这个插件来进行类型检验,让原本动态的js能像c++、java这样的静态语言可以不用担心类型转换出现的问题。
1
2
3
4
5
6
7
8
9import PropTypes from 'prop-types';
函数组件.propTypes = {
title: PropTypes.string.isRequired,// 类型是字符串、必传
x: PropTypes.number,// 类型是数字
y: PropTypes.oneOfType([
PropTypes.number,
PropTypes.bool,
])// 多种校验规则中的一个
};传递进来的属性,首先会经历规则的校验,不管校验成功还是失败,最后都会把属性给形参props,只不过如果不符合设定的规则,控制台会抛出警告错误{不影响属性值的获取}!!
🤔Tips:
使用 TypeScript(TS)的话,通常不需要使用 prop-types 库。
之前写python也有类似的库,看样子动态语言都得被静态束缚。
(那python是不是强类型呢😅,与前端无关,可以忽略)
修改props的规则
- 把props中的某个属性赋值给其他内容(例如:变量、状态…)
- 我们不直接操作props.xxx=xxx,但是我们可以修改变量/状态值!!
待规划文字(可以先不读,我复习时会把内容规整进入正文)
- 在JSX元素渲染的时候,如果type是函数,则把函数执行!
- 会把解析出来的props「含children」,传递给函数
单闭合和双闭合调用的区别「插槽」
React.Children
props是只读的「被冻结」
- props的规则设置
设置默认值
设置属性规则「基于prop-types插件」
- 属性和插槽都可以让组件具备更强的复用性
- 会把解析出来的props「含children」,传递给函数
1 | // index.jsx |
1 | // views/FunctionComponent.jsx |
04.JSX底层实操
巨量注释笔记😊因为自己写的处理jsx还是不够完善,后面的代码还是用会React官方的方法。所以可以先跳过本章
Part 1 jsx→viscalDOM
这里可以参看我的开源库
1 | /* |
对应的index.js,这里我们用03
的对象。
1 | // import React from "react"; |
如此完成createElement的覆盖
Part 2 viscalDOM →真实DOM
1 | /*part2.render函数:把虚拟DOM变为真实DOM(按v16),构建思路如下 |
定义一个对象迭代器:封装一个对象迭代的方法
- 基于传统的for/in循环,会存在一些弊端(性能较差(既可以迭代私有的,也可以迭代公有的);只能迭代“可枚举、非Symbol类型的”属性…)
一般而言,内置属性都是不可枚举的(枚举:可以被列举、例如for/in、Object.keys等列举出来的);自定义属性都是可枚举的,我们可以通过Object.defineProperty方法定义枚举属性
- 解决思路:获取对象所有的私有属性「私有的、不论是否可枚举、不论类型」
- Object.getOwnPropertyNames(arr) -> 获取对象非Symbol类型的私有属性「无关是否可枚举」
- Object.getOwnPropertySymbols(arr) -> 获取Symbol类型的私有属性
获取所有的私有属性:
let keys = Object.getOwnPropertyNames(arr).concat(Object.getOwnPropertySymbols(arr));
可以基于ES6中的Reflect.ownKeys代替上述操作「弊端:不兼容IE」
let keys = Reflect.ownKeys(arr);
1 | let function iterator(obj) { |
定义一个创建真实DOM的函数
这里先不对ref(获取真实DOM)和key(用于优化)进行处理
分析后函数任务就是:先根据type创建一个标签,然后把props中的属性设置给标签,最后把标签插入到指定的容器中。
于是接下来按步骤处理:
- type:标签名
- className
- style
- children
1 | export function render(virtualDOM, container) { |
完整代码
1 | //src\jsxHandle.js |
1 | //src\index.jsx |
踩坑
没想到运行时遇见这个,排错后发现是src\jsxHandle.js
文件中,each函数
的callback函数形参写反了
总结
- 底层真的用了很多js基础的内容
- 数据结构的运用很重要
因为自己写的处理jsx还是不够完善,后面的代码还是用会React官方的方法
03.jsx语法
1. JSX基础知识
大小驼峰:PasalCase、CamelCase、kabab-case、xxx_xxx
JSX(javascript and xml\html): 把JS与HTML标签混合在了一起(不是字符串拼接,❌element = “
🎈Tips: vscode如何支持JSX语法(格式化、快捷提示…)
- 后缀名设置为jsx
- webpack打包的规则中,也是会对.jsx这种文件,按照JS的方式进行处理的
1.1.语法特性
JSX语法具备过滤效果(过滤非法内容),有效防止XSS攻击(扩展思考:总结常见的XSS攻击和预防方案?)
1.1.1.一个根容器
最外层只能一个根容器(一个app有一个容器)
在ReactDOM.createRoot()的时候,不能直接把HTML/BODY做为根容器,需要指定一个额外的盒子「例如:#root」
1.1.2.一个根元素节点
容器可以有多个视图、每一个视图,只有一个“根节点”。
- 出现多个根节点则报错
Adjacent JSX elements must be wrapped in an enclosing tag.
- React.Fragment 空文档标记标签:
<></>
(既保证只有一个根节点,又不新增一个HTML层级结构!!)
1.1.3 动态绑定数据{}
把JS表达式
嵌入到HTML中,需要使用{}
(❗大括号中存放的是JS表达式)
JS表达式:执行有结果的表达式,如:数值、数学运算、三元运算符、循环(借助数组迭代的办法map,foreach没有返回值所以不行)
if(){}
不行,JSX中进行的判断一般都要基于三元运算符
来完成命令式循环
for
,for/in
,for/of
、while
不行
1.1.4 JS表达式要求
{}语法中嵌入不同的值,所呈现出来的特点
number/string:值是啥,就渲染出来啥
boolean/null/undefined/Symbol/BigInt:渲染的内容是空
除数组对象外,其余对象一般都不支持在{}中进行渲染,但是也有特殊情况:
- JSX虚拟DOM对象
- 给元素设置style行内样式,要求必须写成一个对象格式
数组对象:把数组的每一项都分别拿出来渲染「并不是变为字符串渲染,中间没有逗号」
JSX中遍历数组中的每一项,动态绑定多个JSX元素,一般都是基于数组中的map来实现的
和vue一样,循环绑定的元素要设置key值(作用:用于DOM-DIFF差异化对比)
函数对象:不支持在{}中渲染,但是可以作为函数组件,用
方式渲染。
1.1.5 给元素设置样式
- 行内样式:需要基于对象的格式处理,直接写样式字符串会报错;
1 | <h2 style={{ |
- 设置样式类名:需要把class替换为className;
1 | <h2 className="box"> |
1.2 核心语法初识
为了可以看懂代码于是这里先讲讲一些语法核心。
1 | import React from 'react'; //React语法核心 |
这样就能看懂练手了。😊
1.2 练手
1 | //src\index.jsx(index.js改过来的) |
2. JSX底层渲染机制
使用babel转换工具,可以直观看见。
第一步:JSX→virtualDOM
运行时会把我们编写的JSX语法编译为虚拟DOM对象(virtualDOM)。
虚拟DOM对象:框架自己内部构建的一套对象体系(对象的相关成员都是React内部规定的),基于这些属性描述出我们所构建视图中的DOM节点的相关特征。
具体流程
下面是我编写的例子
1 | //jsx |
让我们来解读一下:
- 基于
babel-preset-react-app
(一个专门对react的babel) 把JSX编译为React.createElement(...)
格式。元素节点都会基于createElement进行处理!
出现的函数的解释:
createElement – React 中文文档 (docschina.org)
React.createElement(ele,props,...children)
ele
:元素标签名\组件名
props
:元素的属性集合(对象)(无属性,则此值为null)
children
:当前元素的子节点,第三个及以后的参数都是返回虚拟DOM,可以看下文虚拟DOM结构
- 感觉Automatic模式就是表示的更清楚。
- 注意该方法开发时并不常用,有jsx就够了
再把 createElement 方法执行,创建出virtualDOM虚拟DOM对象(JSX元素、JSX对象、ReactChild对象…)!!
我们可以把
React.createElement
打印出来看看效果。
1 | // 大概结构 |
实操(非必要)
这里将简单编写一个creatElement函数
感觉太硬了,所以分个p,见
04.jsx底层实操
1 | //下面是官方笔记,不确定能不能跑通 |
第二步:virtualDOM→真实DOM
真实DOM:浏览器页面中,最后渲染出来,让用户看见的DOM元素(js基础内容)
这一步是基于ReactDOM中的render方法处理的
v16(16版本)写法
1 | ReactDOM.render( |
v18
1 | const root = ReactDOM.createRoot(document.getElementById('root')); |
实操(非必要)
render
推荐看
04
1 | const render = function render(virtualDOM, container) { |
更新:新老virtual对比,然后更新对应内容
第一次渲染页面是直接从virtualDOM->真实DOM;
后期视图更新时,会经过一个DOM-DIFF的对比,计算出补丁包PATCH(两次视图差异的部分),把PATCH补丁包进行渲染!!
02.MVC和MVVM
02.MVC和MVVM
虚拟DOM
主流的思想:不在直接去操作DOM,而是改为“数据驱动思想”
操作DOM思想:
- 操作DOM比较消耗性能「主要原因就是:可能会导致DOM重排(回流)/重绘」
- 操作起来也相对来讲麻烦一些
驱动思想:
- 我们不会在直接操作DOM
- 我们去操作数据「当我们修改了数据,框架会按照相关的数据,让页面重新渲染」
- 框架底层实现视图的渲染,也是基于操作DOM完成的
- 构建了一套 虚拟DOM->真实DOM 的渲染体系
- 有效避免了DOM的重排/重绘
- 开发效率更高、最后的性能也相对较好
状态管理
不再获取DOM元素,直接用state。也就是数据驱动
MVC和MVVM
React框架采用的是MVC体系;
Vue框架采用的是MVVM体系;
NG是MVVM,写法是MVC(不懂)
MVC
model数据层 + view视图层 + controller控制层
对应三层,我们的学习目标
视图层:用专业的语法去构建视图\页面(React中是基于jsx语法来构建视图的)。
数据层:视图中,需要“动态”(需要变化,不论是样式还是内容)处理的数据,都要有对应的数据模型。
控制层:在视图中,根据业务需求,进行某些操作时,会修改相关数据,然后React会按最新的数据,重新渲染视图。
数据驱动视图的渲染!!
视图中的表单内容改变,想要修改数据,需要开发者自己去写代码实现!!
“单向驱动”
MVVM
model数据层 + view视图层 + viewModel数据/视图监听层(数据和视图都被wacher监听)
数据驱动视图的渲染:监听数据的更新,让视图重新渲染
视图驱动数据的更改:监听页面中表单元素内容改变,自动去修改相关的数据
“双向驱动”

+
MVC VS MVVM
对比
- MVVM是双向驱动,MVC是单项驱动,表单更新MVVM会自动更新,而MVC需要开发者操作。
01.creact-react-app脚手架的使用
使用 create-react-app 构建React工程化项目
这里你需要了解一下creat-react-app脚手架的搭建过程。搜一下吧
这一步主要是下载cra
1.create-react-app概述
- 简介:create-react-app:基于webpack搭建一套脚手架
- 脚手架:默认把webpack打包规则处理好了,基础项目结构也创建好了
- 后面命令都是yarn,故可以了解一下yarn
- 下载
- 初始化
yarn init
1.1 react的版本
V16:用的多
V17:应该是底层升级,与16看不出来太多差别
V18:新版本、机制语法都升级了(默认安装)
1.2 初识create-react-app
1.2.1 安装create-react-app
全局安装$ npm i create-react-app -g 「mac需要加sudo」
基于脚手架创建项目「项目名称需要符合npm包规范」:使用“数字、小写、_”
1 | $ npx create-react-app xxx` # xxx是你自定义的项目名 |
1.2.2 安装后的文件目录
安装完毕,接下来就阅读一下文件目录
1 | |- node_modules 包含安装的模块 |
1.2.3 从package.json认识基本结构
create-react-app是一个集成了很多内容的脚手架,从package.json,我们可以了解它的基本信息
1 | //package.json |
2.暴露webpack配置项(非必须)
前置知识,学webpack
这里因为要深入学习故暴露配置项
2.1认识暴露后的文件目录
默认情况下,会把webpack配置项隐藏到node_modules中,如果想修改,则需要暴露配置项$ yarn eject
注意事项
1、一旦暴漏后,无法还原
2、 会提醒你先把代码提交到git区,避免暴露后覆盖原始代码(必须上传代码)
3、这里需要用一个空的git库,建议先创建一个新的库,然后
git add .
、git commit -m
(不需要上传到远端)
暴露后多了config文件夹(webpack配置)、scripts文件夹(对应下文scripts中的内容,执行相关命令的入口文件)
1 | /* 暴露后package.json中的变化 */ |
2.1.1 QUER一些疑惑:
cra为什么没有区分开发依赖,和生产依赖?
2.2真实项目中常用的一些修改操作
简单演示了如果配置less的流程,并不是目前开发必要的。sass和less按喜好来。当然文章是less。
2.2.1配置less
2.2.1.1 安装命令行:
1 | $ yarn add less less-loader@8 #安装8版本,为了兼容。(不确定你看的时候兼容了没) |
2.2.1.2 配置
vscode折叠代码块(方便看)
折叠所有 Ctrl+K+0(零)
展开所有 Ctrl+K+J
配置对应less的内容,其实就是把sass名改为less
1 | /*config\webpack.config.js*/ |
注意是改两次,一个lessRegex,一个lessModuleRegex
2.2.1.3写入一个less文件用于测试
1 | //src\index.less |
然后导入index.jsx
1 | //src\index.jsx |
待其他配置完之后测试
2.2.2 配置别名
配置一个别名,这样以后@就能直接访问src目录
1 | //config\webpack.config.js |
2.2.3 配置预览域名
可以修改端口号,有两种方式
1 | // scripts/start.js |
2.2.3.1 QUER一点疑惑
QUER:为什么localhost下使用cross-env设置的POST不生效
这是因为在webpackDevServer.config.js中,会对host进行判断,如果是localhost,就会使用默认的端口(生成的答案待解决)
2.2.4 配置跨域代理
我们可以看见paths.proxySetup
中指定了读取跨域文件的位置(在paths.proxySetup
中搜索paths.proxySetup
),也就是src/setupProxy.js
1 | /* |
测试
1 | fetch() |
也能配置到package.json中,但因为json的原因,只能配置一个
2.2.4.1 QUER一些疑惑:
别尝试这个了,配置后会有跨域问题,我还不会解决
TODO 学习node中间件
2.2.5配置浏览器兼容(可选)
解决老版本浏览器兼容
修改兼容后对postcss-loader、babel-loader生效,但对ES6内置API不兼容
可以用@babel/polyfill,但脚手架中不需要我们来自己安装,因为有react-app-poly(看package.json)
在index.jsx
中加入
1 | import 'react-app-polyfill/ie9'; |
1 | //package.json |
ps:每次配置都可以yarn start
测试一下效果
其余的一些优化配置、响应式布局的配置等,实战中再去处理!!
css动画
transform
transform:translate(负左正右,上下移动)
:可以放入单位、百分比(参照元素自身)transform:rotate(xxx)
:旋转,默认中心原点transform-origin: xxx xxx
:切换原点transform:scale(xxx)
:缩放大小,两参数(宽高),默认中心原点transform:skew(xxx xxx)
:一个参数水平移动(角度),两参数(水平和锤子)transform:matrix(a,b,c,d,e,f)
:ad:缩放,bc:倾斜(斜切)、ef:元素平移有设计软件,可以直接导出设置
transition
transition: <property(需要过渡的属性)> <duration(过渡的时长)> <timing-function(缓动函数)> <delay(开始延迟时间)>;
缓动函数:
贝塞尔函数
- ease:动画以低速开始,然后加快,在结束前变慢。
- ease-in:指数函数
- ease-out:log函数
- ease-in-out:慢开始加速后又慢
- cubic-bezier(n,n,n,n) 贝塞尔曲线(自定义数值)
线性函数
linear(
折线
- linear:固定时间
- linear(0,0.25,1):开始较慢,后面突然加快
阶跃
step(<>)
step(2,end):分两次跳跃
A
部署
一个项目部署的记录,主要记录知识点,不记录细节
前端
nginx、apache、tomcat
这里使用nginx
NGINX
安装
这里安装还是比较简单的(直接用包管理器安装)
ubantu
:使用
centos
:使用
配置
找到nginx配置文件nginx.config
配置前先备份一次sudo cp nginx.conf nginx.default.conf
配置解释
1 | ########### 每个指令必须有分号结束。################# |
操作:把server
块中的root(文件根目录)改为打包好的前端代码。
然后nginx -s reload
更改配置
踩坑
nginx -s reload
后没有生效?
解决办法:换了一个端口使用(这里换端口时也要设置用户名,用户要有对应的权限,一般为root)
后端
这里用到了apache
nginx与Apache的对比以及优缺点
fastapi部署pip install gunicorn
使用宝塔Linux
被降维打击了
总而言之,部署成功了,就是没钱得用没钱的办法,后面找朋友把另一个端口开了。
docker
安装
制作镜像Dockfile(一般情况下,使用同类项目的Dockfile)
前端还需要一个nginx.conf文件