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
 9- import 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文件