文章目录
- 认识Vue
- Vue3带来的变化
- 如何使用Vue
- 声明式编程和命令式编程
- MVVM模型
- template属性
- data 属性
- methods属性
- methods方法绑定this
- Vue基础——模板语法
- Mustche双大括号语法
- 各种指令
- v-once指令
- v-text指令
- v-html
- v-pre
- v-clock
- v-bind的绑定属性
- 条件渲染
- 列表渲染
- **v-for基本使用**
- **数组更新检测**
- **替换数组的方法**
- v-for中的key是什么作用
- 认识VNode
- 虚拟DOM
- Vue3的Options-API
- 复杂data的处理方式
- 计算属性的setter和getter
- 认识侦听器watch
- 侦听器watch的配置选项
- 侦听watch的其他方式
- 知识的补充——浅拷贝的过程
- 对象的深拷贝(原生的方式)
- v-model的基本使用
- v-model修饰符-lazy
- v-model修饰符-number
- Vue3组件化开发(一)
- 注册组件的方式
- 注册局部组件
- Vue的开发模式
- 如何支持SFC
- Webpack使用前提
- Vue3组件化开发(二)(webpack部分各种插件和loader 有点复杂 回头详细看 )
- 认识webpack
- Webpack安装
- 创建局部的webpack
- Vue项目加载的文件有哪些?
- webpack5搭建Vue环境(这一部分有点复杂 回头二刷再发笔记。)
- Vite2搭建Vue环境
- Babel和devServer
- **Babel的底层原理**
- Vue源码的打包
- 运行时+编译器vs仅运行时
- VSCode对SFC文件的支持
- devServer和VueCLI
- 为什么要搭建本地服务器?
- Webpack watch
- webpack-dev-server
- 认识模块热替换(HMR)
- 框架的HMR
- HMR的原理
- hotOnly、host配置
- port、 open、compress
- Proxy
- historyApiFallback
- resolve模块解析
- extensions和alias配置
- 如何区分开发环境和生产环境
认识Vue
是一套用于构建用户界面的渐进式框架
什么是渐进式框架?
表示我们可以在项目中亿点点来引入和适用Vue而不一定需要全部适用Vue来开发整个项目。
Vue3带来的变化
源码通过monorepo的形式来管理源代码
- Mono:单个
- Repo:repository仓库
- 主要是将许多项目的代码存储在同一个repository中
- 这样做的目的是多个包本身相互独立,可以有自己的功能逻辑、单元测试等,同时又在同一个仓库下方便管理
-而且模块划分的更加清晰,可维护性、可拓展性更强。
源码使用TypeScript来进行重写
- 在Vue2.x的时候,Vue使用Flow来进行类型检测
- 在Vue3.x的时候,Vue的源码全部使用TypeScript来进行重构,并且vue本身对TypeScript支持也更好了。
使用Proxy进行数据劫持
- 在Vue2.x的时候,是 使Object.defineProperty来劫持数据的getter和setter方法
- 这种方式一致存在一个缺陷就是当给对象添加或者删除属性时,是无法劫持和监听的。
- 所以在Vue2.x的时候不得不提供一些特殊的API,比如 s e t 或者 set或者 set或者delete,事实上都是一些hack方法,也增加了开发者学习新的API的成本
- 而在Vue3.x开始,Vue使用Proxy来实现数据的劫持。
删除了一些不必要的API: - 移除了实例上的 o n , on, on,off,和$once
- 移除了一些特性:如filter、内敛模板等。
包括一些编译方面的优化:
-
生成Block Tree 、 Slot编译优化、diff算法优化
由Options API 到 Composition API: -
在Vue2.x的时候,我们会通过Options API来描述组件对象;
-
Options API包括data、props、methods、computed、生命周期等等这些选项;存在比较大的问题是多个逻辑可能是在不同的地方∶
√ 比如created中会使用某一个method来修改data的数据,代码的内聚性非常差; -
Composition API可以将相关联的代码放到同一处进行处理,而不需要在多个Options之间寻找;
Hooks函数增加代码的复用性∶
- 在Vue2.x的时候,我们通常通过mixins在多个组件之间共享逻辑;
- 但是有一个很大的缺陷就是mixins也是由一大堆的Options组成的,并且多个mixins会存在命名冲突的问题;
- 在Vue3.x中,我们可以通过Hook函数,来将一部分独立的逻辑抽取出去,并且它们还可以做到是响应式的;
如何使用Vue
Vue的本质,就是一个JavaScript的库:
- 刚开始我们不需要把它想象的非常复杂;
- 我们就把它理解成一个已经帮助我们封装好的库;
- 在项目中可以引入并且使用它即可。
那么安装和使用Vue这个JavaScript库有哪些方式呢?
- 方式一︰在页面中通过CDN的方式来引入;
- 什么是CDN呢?CDN称之为内容分发网络(Content Delivery Network或Content Distribution Network,缩写:CDN)
- 它是指通过相互连接的网络系统,利用最靠近每个用户的服务器;
- 更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户;
- 来提供高性能、可扩展性及低成本的网络内容传递给用户;
- 常用的CDN服务器大致可以分为两种
- 自己的CDN服务器︰需要购买自己的CDN服务器,目前阿里、腾讯、亚马逊、Google等都可以购买CDN服务器;
- 口开源的CDN服务器∶国际上使用比较多的是unpkg、JSDelivr、cdnjs ;
-
方式二∶下载Vue的JavaScript文件,并且自己手动引入;
-
方式三∶通过npm包管理工具安装使用它( webpack再讲);
-
方式四︰直接通过Vue CLI创建项目,并目使用它
声明式编程和命令式编程
原生开发和Vue开发的模式和特点,我们会发现是完全不同的,这里其实涉及到两种不同的编程范式∶
- 命令式编程和声明式编程﹔
- 命令式编程关注的是“how to do”,声明式编程关注的是“what to do”,由框架(机器)完成“how”的过程;
在原生的实现过程中,我们是如何操作的呢?
- 我们每完成一个操作,都需要通过JavaScript编写一条代码,来给浏览器一个指令;
- 这样的编写代码的过程,我们称之为命令式编程;
- 在早期的原生JavaScript和jQuery开发的过程中,我们都是通过这种命令式的方式在编写代码的;
在Vue的实现过程中,我们是如何操作的呢? - 我们会在createApp传入的对象中声明需要的内容,模板template、数据data、方法methods ;
- 这样的编写代码的过程,我们称之为是声明式编程;
- 目前Vue、React、Angular的编程模式,我们称之为声明式编程;
MVVM模型
MVC和MVVM都是一种软件的体系结构
- MVC是Model -Vue -Controller的简称,是在前期被使用非常框架的架构模式,比如iOS、前端;
- MVVM是Model-View-ViewModel的简称,是目前非常流行的架构模式;
通常情况下,我们也经常称Vue是一个MVVM的框架。
Vue官方其实有说明,Vue虽然并没有完全遵守MVVM的模型,但是整个设计是受到它的启发的。
template属性
- 在使用createApp的时候,我们传入了一个对象,接下来我们详细解析一下之前传入的属性分别代表什么含义。
- template属性:表示的是Vue需要帮助我们渲染的模板信息︰
- 目前我们看到它里面有很多的HTML标签,这些标签会替换掉我们挂载到的元素(比如id为app的div )的innerHTML;
- 模板中有一些奇怪的语法,比如{{}},比如@click,这些都是模板特有的语法
Vue提供了两种方式:
- 方式一︰使用script标签,并且标记它的类型为x-template ;
- 方式二∶使用任意标签(通常使用template标签,因为不会被浏览器渲染),设置id ;
template元素是一种用于保存客户端内容的机制,该内容再加载页面时不会被呈现,但随后可以在运行时使用JavaScript实例化;
data 属性
data属性是传入一个函数,并且该函数需要返回一个对象︰
- 在Vue2.x的时候,也可以传入一个对象(虽然官方推荐是一个函数);
- 在Vue3.x的时候,比如传入一个函数,否则就会直接在浏览器中报错;
data中返回的对象会被Vue的响应式系统劫持,之后对该对象的修改或者访问都会在劫持中被处理∶
- 所以我们在template中通过{{counter}}访问counter,可以从对象中获取到数据﹔
- 所以我们修改counter的值时,template中的 {{counter}}也会发生改变;
methods属性
methods属性是一个对象,通常我们会在这个对象中定义很多的方法∶
- 这些方法可以被绑定到template模板中;
- 在该方法中,我们可以使用this关键字来直接访问到data中返回的对象的属性;
methods方法绑定this
我们在methods中要使用data返回对象中的数据∶
那么这个this是必须有值的,并且应该可以通过this获取到data返回对象中的数据。
那么我们这个this能不能是window呢?
不可以是window,因为window中我们无法获取到data返回对象中的数据
﹔但是如果我们使用箭头函数,那么这个this就会是window了
.
为什么是window呢?
这里涉及到箭头函数使用this的查找规则
,它会在自己的上层作用于中来查找this
;
最终刚好找到的是script作用于中的this,所以就是window ;
Vue基础——模板语法
React的开发模式:
- React使用的jsx,所以对应的代码都是
编写的类似于js的一种语法
; - 之后通过Babel将jsx编译成 React.createElement函数调用;
Vue也支持jsx的开发模式(后续有时间也会讲到)∶ - 但是大多数情况下,使用
基于HTML的模板语法
; - 在模板中,允许开发者以声明式的方式将
DOM
和底层组件实例的数据
绑定在一起; - 在底层的实现中,Vue将模板编译成虚拟DOM渲染函数
所以,对于学习Vue来说,学习模板语法是非常重要的。
Mustche双大括号语法
如果我们希望把数据显示到模板(template )中,使用最多的语法是“Mustache”语法(双大括号)的文本插值。
- 并且我们前端提到过,·data返回的对象·是有添加到·Vue的响应式系统·中;
- 当·
data中的数据发生改变时
,对应的内容也会发生更新
。 - 当然,Mustache中不仅仅可以是data中的属性,也可以是一个
JavaScript的表达式
。
各种指令
v-once指令
v-once用于指定元素或者组件只渲染一次∶
当数据发生变化时,素或者组件以及其所有的子元素将视为静态内容
并且跳过;该指令可以用于性能优化
;
v-text指令
用于更新元素的text-Content.相当于插值语法
v-html
默认情况下,如果我们展示的内容本身是html 的,那么vue并不会对其进行特殊的解析。如果我们希望这个内容被Vue可以解析出来,那么可以使用v-html来展示;
v-pre
v-pre用于跳过元素和它的子元素的编译过程,显示原始的Mustache标签∶口跳过不需要编译的节点,加快编译的速度;
v-clock
■这个指令保持在元素上直到关联组件实例结束编译。
和CSS规则如[v-cloak] { display: none }一起用时,这个指令可以隐藏未编译的Mustache标签直到组件实例准备完毕。
v-bind的绑定属性
- 前端讲的一系列指令,主要是将值插入到
模板内容
中。 - 但是,除了内容需要动态来决定外,某些属性我们也希望动态来绑定。
- 比如动态绑定a元素的href属性;
- 比如动态绑定img元素的src属性;
- 绑定属性我们使用v-bind :
- 缩写∶
- 预期:any (with argument) | Object (without argument)
- 参数:attrOrProp (optional)
- 修饰符∶
√ .camel -将 kebab-case attribute名转换为camelCase。 - 用法︰动态地绑定一个或多个attribute,或一个组件prop 到表达式。
v-bind有一个对应的语法糖
,也就是简写方式
。在开发中,我们通常会使用语法糖的形式,因为这样更加简洁。
绑定class介绍
在开发中,有时候我们的元素class也是动态的,比如∶当数据为某个状态时,字体显示红色。当数据另一个状态时,字体显示黑色。
绑定class有两种方式:
对象语法
数组语法
绑定style介绍
我们可以利用v-bind:style来绑定一些CSS内联样式:
这次因为某些样式我们需要根据数据动态来决定;比如某段文字的颜色,大小等等;
css property名可以用驼峰式(camelCase)
或短横线分隔
(kebab-case,记得用引号括起来)来命名绑定class有两种方式:
对象语法
数组语法
动态绑定属性
在某些情况下,我们属性的名称可能也不是固定的:
- 前端我们无论绑定src、href、class、style,属性名称都是固定的;
- 如果属性名称不是固定的,我们可以使用:[属性名]=“值”的格式来定义;口这种绑定的方式,我们称之为动态绑定属性;
v-on绑定事件
前面我们绑定了元素的内容和属性
,在前端开发中另外一个非常重要的特性就是交互
。
在前端开发中,我们需要经常和用户进行各种各样的交互:
这个时候,我们就必须监听用户发生的事件,比如点击、拖拽、键盘事件
等等
在Vue中如何监听事件呢?使用v-on指令。
接下来我们来看一下v-on的用法∶
v-on参数传递
- 当通过methods中定义方法,以供@click调用时,需要
注意参数问题
: - 情况一︰如果该方法不需要额外参数,那么方法后的()可以不添加。
但是注意:如果方法本身中有一个参数,那么会默认将原生事件event参数传递进去
情况二︰如果需要同时传入某个参数,同时需要event时,可以通过$event传入事件。
v-on修饰符
条件渲染
在某些情况下,我们需要根据当前的条件决定某些元素或组件是否渲染,这个时候我们就需要进行条件判断了。
Vue提供了下面的指令来进行条件判断︰
- v-if
- v-else
- v-else-if
- v-show
v-if、v-else、v-else-if用于根据条件来渲染某一块的内容:这些内容只有在条件为true时,才会被渲染出来;
口这三个指令与JavaScript的条件语句if、else、else if类似;
- v-if的渲染原理:
- v-if是惰性的;
- 当条件为false时,其判断的内容完全不会被渲染或者会被销毁掉;
- 当条件为true时,才会真正渲染条件块中的内容;
template元素
因为v-if是一个指令,所以必须将其添加到一个元素上∶
- 但是如果我们希望切换的是多个元素呢?
- 此时我们渲染div,但是我们并不希望div这种元素被渲染;
- 这个时候,我们可以选择使用template ;
template元素可以当做不可见的包裹元素,并且在v-if上作用,但是最终template不会被渲染出来:
有点类似于小程序中的block
template元素可以当做不可见的包裹元素,并且在v-if上作用,但是最终template不会被渲染出来:口有点类似于小程序中的block
v-show
v-show和v-if的用法看起来是一致的,也是根据一个条件决定是否显示元素或者组件︰
v-show和v-if的区别
- 首先,在用法上的区别:
v-show是不支持template ;
v-show不可以和v-else一起使用; - 其次,本质的区别:
- v-show元素无论是否需要显示到浏览器上,它的DOM实际都是有渲染的,只是通过CSS的display属性来进行切换;
- v-if当条件为false时,其对应的元素压根不会被渲染到DOM中;
- 开发中如何进行选择呢?
- 如果我们的原生需要在显示和隐藏之间频繁的切换,那么使用v-show ;
- 如果不会频繁的发生切换,那么使用v-if ;
列表渲染
在真实开发中,我们往往会从服务器拿到一组数据,并且需要对其进行渲染。
- 这个时候我们可以使用
v-for
来完成; - v-for类似于JavaScript的for循环,可以用于遍历一组数据;
在真实开发中,我们往往会从服务器拿到一组数据,并且需要对其进行渲染。这个时候我们可以使用v-for来完成;
v-for类似于JavaScript的for循环,可以用于遍历一组数据;
v-for基本使用
v-for的基本格式是"item in数组"
:
- 数组通常是来自
data或者prop
,也可以是其他方式; - item是我们给每项元素起的一个
别名
,这个别名可以自定来定义;
我们知道,在遍历一个数组的时候会经常需要拿到数组的索引: - 如果我们需要索引,可以使用格式:“(item, indek)in数组”; - 注意上面的顺序∶数组元素项item是在前面的,索引项index是在后面的;
v-for也支持遍历对象,并且支持有一二三个参数:一个参数: - “value in object";
- 二个参数: “(value, key) in object”;
- 三个参数:“(value, key, index) in object”;v-for同时也支持数字的遍历︰
- 每一个item都是一个数字;
数组更新检测
Vue将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
替换数组的方法
上面的方法会直接修改原来的数组,但是某些方法不会替换原来的数组,而是会生成新的数组,比如filter()、concat(和slice).
v-for中的key是什么作用
- 在使用v-for进行列表渲染时,我们通常会给元素或者组件绑定一个
key属性
。这个key属性有什么作用呢?我们先来看一下官方的解释
︰- key属性主要用在Vue的
虚拟DOM算法
,在新旧nodes
对比时辨识VNodes
; - 如果
不使用key
,Vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素
的算法; - 而
使用key
时,它会基于key的变化重新排列元素顺序
,并且会移除/销毁key
不存在的元素;
- key属性主要用在Vue的
- 官方的解释对于初学者来说并不好理解,比如下面的问题:
什么是新旧nodes,什么是VNode ?
没有key的时候,如何尝试修改和复用的?
认识VNode
我们先来解释一下VNode的概念∶
- 因为目前我们还没有比较完整的学习组件的概念,所以目前我们先理解HTML元素创建出来的VNode ;
- VNode的全称是Virtual Node,也就是虚拟节点;
- 事实上,无论是组件还是元素,它们最终在Vue中表示出来的都是一个个VNode ;
- VNode的本质是一个JavaScript的对象;
虚拟DOM
如果我们不只是一个简单的div,而是有一大堆的元素,那么它们应该会形成一个VNode Tree :
Vue3的Options-API
复杂data的处理方式
- 我们知道,在模板中可以直接通过插值语法显示一些data中的数据。
- 但是在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示;
- 比如我们需要对
多个data数据进行运算
、三元运算符来决定结果
、数据进行某种转化
后显示; - 在模板中使用
表达式
,可以非常方便的实现,但是设计它们的初衷是用于简单的运算
; - 在模板中放入太多的逻辑会让
模板过重和难以维护
; - 并且如果多个地方都使用到,那么会有大量重复的代码;
- 比如我们需要对
我们有没有什么方法可以将逻辑抽离出去呢?
- 可以,其中一种方式就是将逻辑抽取到一个
method
中,放到methods的options中; - 但是,这种做法有一个直观的弊端,就是所有的data使用过程都会变成了一个
方法的调用
; - 另外一种方式就是使用
计算属性computed
;
认识计算属性computed
什么是计算属性呢? - 官方并没有给出直接的概念解释;
- 而是说︰对于任何包含响应式数据的复杂逻辑,你都应该使用
计算属性
; 计算属性
将被混入到组件实例中。所有getter和setter的 this 上下文自动地绑定为组件实例;
计算属性的用法︰
- 选项: computed
- 类型:{ [key: string]: Function \ { get: Function, set: Function } }
计算属性和methods的比较
- 在上面的实现思路中,我们会发现计算属性和methods的实现看起来是差别是不大的,而且我们多次提到
计算属性有缓存的
。 - 接下来我们来看一下同一个计算多次使用,计算属性和methods的差异:
原因
- 这是因为计算属性会基于它们的
依赖关系进行缓存
; - 在
数据不发生变化
时,计算属性是不需要重新计算
的; - 但是如果
依赖的数据发生变化
,在使用时,计算属性依然会重新进行计算
;
计算属性的setter和getter
- 计算属性在大多数情况下,只需要一个getter方法即可,所以我们会将计算属性直接写成一个函数。但是,如果我们确实想设置计算属性的值呢?
- 这个时候我们也可以给计算属性设置一个setter的方法;
事实上.Vue源码内部只是做了个逻辑
认识侦听器watch
什么是侦听器呢?
- 开发中我们在data返回的对象中定义了数据,这个数据通过
插值语法等方式绑定到template中
; - 当数据变化时,template会自动进行更新来显示最新的数据;
- 但是在某些情况下,我们希望在
代码逻辑
中监听某个数据的变化,这个时候就需要用侦听器watch
来完成了;
侦听器的用法如下︰
- 选项: watch
- 类型:{ [key: string]: string | Function | Object | Array}
侦听器watch的配置选项
-
默认情况下,watch只是在侦听info的引用变化,对于内部属性的变化是不会做出响应的:
-
这个时候我们可以使用一个
选项deep
进行更深层的侦听; -
注意前面我们说过watch里面侦听的属性对应的也可以是一个Object ;
-
还有另外一个属性,是希望一开始的就会立即执行一次∶
- 这个时候我们使用
immediate选项
; - 这个时候无论后面数据是否有变化,侦听的函数都会有限执行一次;
- 这个时候我们使用
侦听watch的其他方式
-
另外一个是Vue3文档中没有提到的,但是Vue2文档中有提到的是侦听对象的属性︰
-
还有另外一种方式就是使用$watch的API:
-
我们可以在created的生命周期(后续会讲到)中,使用his.$watchs来侦听;
- 第一个参数是要侦听的源;
- 第二个参数是侦听的回调函数callback ;
- 第三个参数是额外的其他选项,比如deep、immediate ;
知识的补充——浅拷贝的过程
对象是引用类型,直接copy的修改不会受到影响 但是对象的引用类型保存的是地址的时候,修改属性值是会受到影响的
方法二
引用lodash的库,const obj=_.clone();
对象的深拷贝(原生的方式)
用JSON.stringify()转化为字符串
,然后再用JSON.parse()的方法还原
,还原的时候会在内存中生成一个新的对象,跟原来的对象没有任何关系,把完全生成 的新的对象赋值给一个变量。
方法二:lodash
_.cloneDeep()
v-model的基本使用
- 表单提交是开发中非常常见的功能,也是和用户交互的重要手段:
- 比如用户在
登录、注册
时需要提交账号密码; - 比如用户在
检索、创建、更新
信息时,需要提交一些数据;
- 比如用户在
- 这些都要求我们可以在代码逻辑中获取到用户提交的数据,我们通常会使用v-model指令来完成
- :v-model指令可以在表单input、textarea以及select元素上创建
双向数据绑定
; - 它会根据
控件类型
自动选取正确的方法来更新元素; - 尽管有些神奇,但
v-model本质上不过是语法糖,它负责监听用户的输入事件来更新数据
,并在某种极端场景下进行一些特殊处理;
- :v-model指令可以在表单input、textarea以及select元素上创建
官方有说到,v-model的原理其实是背后有两个操作:
- v-bind绑定value属性的值;
- v-on绑定input事件监听到函数中,函数会获取最新的值赋值到绑定的属性中;
在真实开发中,我们的数据可能是来自服务器的,那么我们就可以先将值请求下来,绑定到data返回的对象中,再通过v-bind来进行值的绑定,这个过程就是值绑定。
v-model修饰符-lazy
lazy修饰符是什么作用呢?
- 默认情况下,v-model在进行双向绑定时,绑定的是
input事件
,那么会在每次内容输入后就将最新的值和绑定的属性 进行同步; - 如果我们在v-model后跟上lazy修饰符,那么会将绑定的事件切换为
change事件
,只有在提交时(比如回车)才会触发﹔
v-model修饰符-number
我们先来看一下v-model绑定后的值是什么类型的:
- message总是
string类型
,即使在我们设置type为number也是string类型
;
- 如果我们希望转化为
数字类型
,那么可以使用.number修饰符
: - 另外,在我们进行
逻辑判断
时,如果是一个string类型
,在可以转化的情况下会进行隐式转换
的:- 下面的score在进行判断的过程中会进行隐式转化的;
- 下面的score在进行判断的过程中会进行隐式转化的;
- 如果要自动过滤用户输入的首尾空白字符,可以给v-model添加 trim 修饰符:
Vue3组件化开发(一)
组件化思想:
- 如果我们将
一个页面中所有的处理逻辑全部放在一起
,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展
; - 但如果,我们
将一个页面拆分成一个个小的功能块
,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了; - 如果我们将一个个功能块拆分后,就可以
像搭建积木一下来搭建我们的项目
;
现在可以说整个的大前端开发都是组件化的天下,无论从三大框架(Vue、React、Angular)
,还是跨平台方案的Flutter
,甚至是移动端都在转向组件化开发,包括小程序的开发
也是采用组件化开发的思想。
我们需要通过组件化的思想来思考整个应用程序:
- 我们将一个完整的页面分成很多个组件;
- 每个组件都用于实现页面的一个功能块;
- 而每一个组件又可以进行细分;
- 而组件本身又可以在多个地方进行复用;
组件化是Vue、React、Angular的核心思想∶
- 前面我们的createApp函数传入了一个
对象App
,这个对象其实本质上就是一个组件
,也是我们应用程序的根组件
; - 组件化提供了一种抽象,让我们可以开发出
一个个独立可复用的小组件
来构造我们的应用; - 任何的应用都会被抽象成一颗
组件树
;
注册组件的方式
-
如果我们现在有一部分内容(模板、逻辑等),我们希望将这部分内容抽取到一个独立的组件中去维护,这个时候如何注册一个组件呢?
-
我们先从简单的开始谈起,比如下面的模板希望抽离到一个单独的组件∶
-
注册组件分成两种∶
全局组件
:在任何其他的组件中都可以使用的组件;局部组件
:只有在注册的组件中才能使用的组件;
我们先来学习一下全局组件的注册:
- 全局组件需要使用我们全局创建的
app来注册组件
; - 通过
component方法传入组件名称
、组件对象
即可注册一个全局组件了; - 之后,我们可以在
App组件的template中
直接使用这个全局组件
当然,我们组件本身也可以有自己的代码逻辑:
比如自己的data、computed、methods等等
在通过app.component注册一个组件的时候,第一个参数是组件的名称,定义组件名的方式有两种︰
- 方式一∶使用kebab-case (短横线分割符)
- 当使用kebab-case(短横线分隔命名)定义一个组件时,你也必须在引用这个自定义元素时使用kebab-case例如 ;
- 方式二∶使用PascalCase(驼峰标识符)
- 当使用PascalCase (首字母大写命名)定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说和都是可接受的;
注册局部组件
- 全局组件往往是在应用程序一开始就会全局组件完成,那么就意味着如果某些组件我们并没有用到,也会一起被注册︰
- 比如我们注册了三个全局组件:ComponentA、ComponentB、ComponentC ;
- 在开发中我们只使用了ComponentA、ComponentB,如果
ComponentC没有用到
但是我们依然在全局进行了注册,那么就意味着类似于webpack这种打包工具在打包我们的项目时
,我们依然会对其进行打包
- 这样最终打包出的JavaScript包就会
有关于ComponerdC的内容
,用户在下载对应的JavaScript时也会增加包的大小
;
所以在开发中我们通常使用组件的时候采用的都是局部注册:
局部注册
是在我们需要使用到的组件中,通过components属性选项
来进行注册;- 比如之前的App组件中,我们有data、computed、methods等选项了,事实上还可以有一个
components选项
; - 该components选项对应的是
一个对象
,对象中的键值对是组件的名称:组件对象
﹔
Vue的开发模式
-
目前我们使用vue的过程都是在html文件中,
通过template编写自己的模板、脚本逻辑、样式等
。 -
但是随着项目越来越复杂,我们会采用组件化的方式来进行开发∶
- 这就意味着每个组件都会有自己的
模板、脚本逻辑、样式
等; - 当然我们依然可以把它们
抽离到单独的js、css文件
中,但是它们还是会分离开来
; - 也包括我们的script是在
一个全局的作用域
下,很容易出现命名冲突
的问题; - 并且我们的代码为了适配一些浏览器,
必须使用ES5的语法
; - 在我们编写代码完成之后,依然需要
通过工具对代码进行构建、代码
;
- 这就意味着每个组件都会有自己的
-
所以在真实开发中,我们可以通过一个
后缀名为.vue
的single-file components(单文件组件)
来解决,并且可以使用webpack或者vite或者rollup等构建工具来对其进行处理。
单文件的特点
在这个组件中我们可以获得非常多的特性:
- 代码的高亮;
- ES6、CommonJS的模块化能力;
- 组件作用域的CSS ;
- 可以使用预处理器来构建更加丰富的组件,比如TypeScript、Babel、Less、Sass等;
如何支持SFC
如果我们想要使用这一的SFC的.vue文件,比较常见的是两种方式:
- 方式一∶
使用Vue CL
I来创建项目,项目会默认帮助我们配置好所有的配置选项,可以在其中直接使用.vue文件; - 方式二:
自己使用webpack或rollup或vite这类打包工具
,对其进行打包处理;
我们最终,无论是后期我们做项目,还是在公司进行开发~通常都会采用Vue CLI的方式来完成。
Webpack使用前提
- webpack的官方文档是https://webpack.js.org/
- webpack的中文官方文档https://webpack.docschina.org/
- DOCUMENTATION:文档详情,也是我们最关注的
- Webpack的运行是依赖Node环境的,所以我们电脑上必须有Node环境口
- 所以我们需要先安装Node.js,并且同时会安装npm ;
- 我当前电脑上的node版本是v14.15.5,npm版本是6.14.11(你也可以使用nvm或者n来管理Node版本) ;Node官方网站:https://nodejs.org/
Vue3组件化开发(二)(webpack部分各种插件和loader 有点复杂 回头详细看 )
认识webpack
- 事实上随着前端的快速发展,目前前端的开发已经变的越来越复杂了∶
- 比如开发过程中我们需要通过模块化的方式来开发;
- 比如也会使用一些高级的特性来加快我们的开发效率或者安全性,比如通过ES6+、TypeScript开发脚本逻辑,通过sass、less等方式来编写css样式代码;
- 比如开发过程中,我们还希望实时的监听文件的变化来并且反映到浏览器上,提高开发的效率;
- 比如开发完成后我们还需要将代码进行压缩、合并以及大他相关的优化;
等等…
- 但是对于很多的前端开发者来说,并不需要思考这些问题,日常的开发中根本就没有面临这些问题:
- 这是因为目前前端开发我们通常都会直接使用三大框架来开发:Vue、React、Angular ;
- 但是事实上,这三大框架的创建过程我们都是借助于脚手架(CL)的;
- 事实上Vue-CLI、create-react-app、Angular-CLI都是基于webpack来帮助我们支持模块化、less.TypeScript、打包优化等的;
webpack是一个静态的模块化打包工具,为现代的JavaScript应用程序;我们来对上面的解释进行拆解∶
打包bundler
: webpack可以将帮助我们进行打包,所以它是一个打包工具静态的static
·:这样表述的原因是我们最终可以将代码打包成最终的静态资源(部署到静态服务器);模块化module
: webpack默认支持各种模块化开发,ES Module、CommonJS、AMD等;现代的modern
:我们前端说过,正是因为现代前端开发面临各种各样的问题,才催生了webpack的出现和发展;
Webpack安装
- webpack的安装目前分为两个: webpack、webpack-cli
- 那么它们是什么关系呢?
- 执行webpack命令,会执行node_modules下的.bin目录下的webpack ;
- webpack在执行时是依赖webpack-cli的,如果没有安装就会报错;
- 而webpack-cli中代码执行时,才是真正利用webpack进行编译和打包的过程;
- 所以在安装webpack时,我们需要同时安装webpack-cli(第三方的脚手架事实上是没有使用webpack-cli的,而是类似于自己的vue-service-cli的东西)
Microsoft Windows [版本 10.0.19045.3086]
(c) Microsoft Corporation。保留所有权利。
C:\Users\33195>npm install webpack
added 74 packages in 6s
npm notice
npm notice New minor version of npm available! 9.6.2 -> 9.7.2
npm notice Changelog: https://github.com/npm/cli/releases/tag/v9.7.2
npm notice Run npm install -g npm@9.7.2 to update!
npm notice
C:\Users\33195>npm install -g npm@9.7.2
removed 17 packages, and changed 64 packages in 9s
28 packages are looking for funding
run `npm fund` for details
C:\Users\33195>npm --version
9.7.2
C:\Users\33195>npm install webpack-cli -g
added 117 packages in 7s
webpack的默认打包
-
我们可以通过webpack进行打包,之后运行打包之后的代码
- 在目录下直接执行webpack命令
-
生成一个dist文件夹,里面存放一个main.js的文件,就是我们打包之后的文件:
- 这个文件中的代码被压缩和丑化了;
- 另外我们发现代码中依然存在ES6的语法,比如箭头函数、const等,这是因为默认情况下webpack并不清楚我们打包后的文件是否需要转成ES5之前的语法,后续我们需要通过babel来进行转换和设置;
-
我们发现是可以正常进行打包的,但是有一个问题,webpack是如何确定我们的入口的呢?
- 事实上,当我们运行webpack时,webpack会查找当前目录下的src/index.js作为入口;
- 所以,如果当前项目中没有存在src/index.js文件,那么会报错;
-
当然,我们也可以通过配置来指定入口和出口
创建局部的webpack
- 前面我们直接执行webpack命令使用的是全局的webpack,如果希望使用局部的可以按照下面的步骤来操作。
- 第一步:创建package.json文件,用于管理项目的信息、库依赖等
npm init
- 第二步:安装局部的webpack
npm install webpack-cli -D
- 第三步:使用局部的webpack
npm webpack
- 第四步:在package.json中创建script脚本,执行脚本打包即可
npm run build
Vue项目加载的文件有哪些?
- JavaScript的打包:
- 将ES6转换成ES5的语法;
- TypeScript的处理,将其转换成JavaScript ;
- Css的处理:
- CSS文件模块的加载、提取;
- Less、Sass等预处理器的处理;
- 资源文件img.font :
- 图片img文件的加载;
- 字体font文件的加载;
- HTML资源的处理:
- 打包HTML资源文件;
- 处理vue项目的SFC文件.vue文件;
webpack5搭建Vue环境(这一部分有点复杂 回头二刷再发笔记。)
Vite2搭建Vue环境
Babel和devServer
为什么需要babel?
事实上,在开发中我们很少直接去接触babel,但是babel对于前端开发来说,目前是不可缺少的一部分:
开发中,我们想要使用ES6+的语法
,想要使用TypeScript
,开发React项目
,它们都是离不开Babel的;所以,学习Babel对于我们理解代码从编写到线上的转变过程至关重要;
那么Babel到底是什么呢?
- Babel是一个
工具链
,主要用于旧浏览器或者环境中将ECMAScript 2015+代码转换为向后兼容版本的JavaScript ; - 包括︰语法转换、源代码转换等;
Babel命令行使用 - babel本身可以作为一个独立的工具(和postcss一样),不和webpack等构建工具配置来单独使用。
- 如果我们希望在命令行尝试使用babel,需要安装如下库:
@babel/core
: babel的核心代码,必须安装;
@babel/cli
:可以让我们在命令行使用babel ;
- 使用babel来处理我们的源代码︰
- src:是源文件的目录;
- –out-dir :指定要输出的文件夹dist ;
插件的使用
比如我们需要转换箭头函数,那么我们就可以使用箭头函数转换相关的插件。
- 查看转换后的结果:我们会发现const 并没有转成var
- 这是因为plugin-transform-arrow-functions,并没有提供这样的功能;
- 我们需要使用plugin-transform-block-scoping 来完成这样的功能;
Babel的预设preset
- 但是如果要转换的内容过多,一个个设置是比较麻烦的,我们可以使用预设( preset ) :后面我们再具体来讲预设代表的含义;
- 安装@babel/preset-env预设︰
- 执行如下命令
Babel的底层原理
babel是如何做到将我们的**一段代码(ES6、TypeScript、React )**转成另外一段代码(ES5)的呢?
- 从一种
源代码(原生语言)
转换成另一种源代码(目标语言)
,这是什么的工作呢? - 就是
编译器
,事实上我们可以将babel看成就是一个编译器。 - Babel编译器的作用就是
将我们的源代码
,转换成浏览器可以直接识别的另外一段源代码
;
Babel也拥有编译器的工作流程∶ - 解析阶段( Parsing )
- 转换阶段(Transformation )
- 生成阶段( Code Generation )
Babel的执行阶段
当然这只是一个简化版的编译器工具流程,在每个阶段又会有自己具体的工作。
babel-loader
- 在实际开发中,我们通常会在构建工具中通过配置babel来对其进行使用的,比如在webpack中。
- 那么我们就需要去安装相关的依赖:
如果之前已经安装了@babel/core,那么这里不需要再次安装;
我们可以设置一个规则,在加载js文件时,使用我们的babel :
-
我们必须指定插件才会生效
-
如果我们一个个去安装使用插件,那么需要手动来管理大量的babel插件,我们可以直接给webpack提供一个preset , webpack会根据我们的预设来加载对应的插件列表,并且将其传递给babel。
-
比如常见的预设有三个:
- env
- react
- TypeScript
-
安装preset-env :
Babel的配置文件
像之前一样,我们可以将babel的配置信息放到一个独立的文件中,babel给我们提供了两种配置文件的编写︰ -
babel.config.json(或者js ,.cjs , .mjs )文件;
-
.babelrc.json(或者.babelrc , .js ,.cjs , .mjs )文件;
-
它们两个有什么区别呢﹖目前很多的项目都采用了多包管理的方式 ( babel本身、element-plus、umi等);.
-
babelrc.json :早期使用较多的配置方式,但是对于配置Monorepos项目是比较麻烦的;
-
babel.config.json ( babel7 ):可以直接作用于Monorepos项目的子包,更加推荐;
Vue源码的打包
如上所示界面上是没有效果的:并且我们查看控制台会发现如下的警告信息:
vue打包不同版本解析:
-
vue(.runtime).global(.prod).js :
- 通过浏览器中的
运行时+编译器vs仅运行时
在Vue的开发过程中我们有三种方式来编写DOM元素︰
- 方式一: template模板的方式(之前经常使用的方式);
- 方式二 :render函数的方式,使用h函数来编写渲染的内容;
- 方式三:通过.vue文件中的template来编写模板﹔
它们的模板分别是如何处理的?
- 方式二中的h函数可以直接返回一个虚拟节点,也就是vade节点;
- 方式一和方式三的template都需要有
特定的代码
来对其进行解析︰
- 方式三.vue文件中的template可以通过在vue-loader
对其进行编译和处理;’
- 方式一种的template我们必须要通过源码中一部分代码
来进行编译;
所以,Vue在让我们选择版本的时候分为运行时+编译器vs仅运行时
运行时+编译器
包含了对template模板的编译代码,更加完整,但是也更大一些;仅运行时
没有包含对template版本的编译代码,相对更小一些;
VSCode对SFC文件的支持
在前面我们提到过,真实开发中多数情况下我们都是使用SFC ( single-file components (单文件组件))
。
我们先说一下VSCode对SFC的支持︰
- 插件一:Vetur,从Vue2开发就一直在使用的VSCode支持Vue的插件;
- 插件二:Volar,官方推荐的插件(后续会基于Volar开友官方的VSCode插件);
devServer和VueCLI
为什么要搭建本地服务器?
- 目前我们开发的代码,为了运行需要有两个操作∶
操作一: npm run build
,编译相关的代码;- 操作二∶通过
live server
或者直接通过浏览器,打开index.html代码,查看效果;
- 这个过程经常操作会影响我们的开发效率,我们希望可以做到,当文件发生变化时,可以自动的完成编译和展示
- 为了完成自动编译,webpack提供了几种可选的方式:
- webpack watch mode ;
- webpack-dev-server (常用);
- webpack-dev-middleware ;
Webpack watch
-
webpack给我们提供了watch模式:
- 在该模式下,webpack依赖图中的所有文件,只要有一个
发生了更新
,那么代码将被重新编译
; - 我们
不需要手动
去运行npm run build指令了;
- 在该模式下,webpack依赖图中的所有文件,只要有一个
-
如何开启watch呢?两种方式:
- 方式一︰在导出的配置中,添加
watch: true
; - 方式二∶在启动webpack的命令中,添加
--watch的标识
;
- 方式一︰在导出的配置中,添加
-
这里我们选择方式二,在package.json的scripts 中添加一个watch 的脚本︰
webpack-dev-server
上面的方式可以监听到文件的变化,但是事实上它本身是没有自动刷新浏览器的功能的∶
- 当然,目前我们可以在VSCode中使用live-server来完成这样的功能;
- 但是,我们希望在
不使用live-server
的情况下,可以具备live reloading(实时重新加载)
的功能;
安装webpack-dev-server
修改配置文件,告知dev server,从什么位置查找文件:
webpack-dev-server在编译之后不会写入到任何输出文件,而是将bundle 文件保留在内存中:
- 事实上webpack-dev-server使用了一个库叫memfs ( memory-fs webpack自己写的)
认识模块热替换(HMR)
什么是HMR呢?
- HMR的全称是
Hot Module Replacement
,翻译为模块热替换
; - 模块热替换是指在
应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个页面
;
HMR通过如下几种方式,来提高开发的速度∶
不重新加载整个页面
,这样可以保留某些应用程序的状态不丢失
;- 只更新
需要变化的内容,节省开发的时间
; - 修改了
css、js源代码
,会立即在浏览器更新
,相当于直接在浏览器的devtools中直接修改样式;
如何使用HMR呢? - 默认情况下,
webpack-dev-server已经支持HMR
,我们只需要开启即可
﹔ - 在不开启HMR的情况下,当我们修改了源代码之后,整个页面会自动刷新,使用的是live reloading ;
开启HMR
- 修改webpack配置
- 浏览器可以看到如下效果:
- 但是你会发现,当我们修改了某一个模块的代码时,依然是刷新的整个页面
- 这是因为我们需要去
指定哪些模块发生更新
时,进行HMR;
- 这是因为我们需要去
框架的HMR
有一个问题∶在开发其他项目时,我们是否需要经常手动去写入module.hot.accpet相关的API呢?
- 比如
开发Vue、React项目
,我们修改了组件
,希望进行热更新
,这个时候应该如何去操作
呢? - 事实上社区已经针对这些有很成熟的解决方案了;
- 比如vue开发中,我们
使用vue-loader
,此loader支持vue组件的HMR,提供开箱即用的体验; - 比如react开发中,有
React Hot Loader
,实时调整react组件(目前React官方已经弃用了,改成使用react-refresh ) ;
HMR的原理
那么HMR的原理是什么呢?如何可以做到只更新一个模块中的内容呢?
- webpack-dev-server会创建两个服务︰
提供静态资源的服务( express)和Socket服务( net.Socket )
; - express server负责直接提供
静态资源的服务
(打包后的资源直接被浏览器请求和解析);
HMR Socket Server,是一个socket的长连接 - 长连接有一个最好的好处是
建立连接后双方可以通信
服务器可以直接发送文件到客户端); - 当服务器
监听到对应的模块发生变化
时,会生成两个文件.json ( manifest文件)和js文件( update chunk )
- 通过长连接,可以直接
将这两个文件主动发送给客户端(浏览器)
; - 浏览器
拿到两个新的文件
后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新
;
hotOnly、host配置
host设置主机地址:
- 默认值是localhost ;
- 如果希望其他地方也可以访问,可以设置为0.0.0.0;
localhost 和0.0.0.0的区别: - localhost:本质上是一个域名,通常情况下会被解析成127.0.0.1;
- 127.0.0.1:回环地址(Loop Back Address),表达的意思其实是我们主机自己发出去的包,直接被自己接收;
√正常的数据库包经常应用层–传输层–网络层–数据链路层–物理层;
√而回环地址,是在网络层直接就被获取到了,是不会经常数据链路层和物理层的;
√比如我们监听127.0.0.1时,在同一个网段下的主机中,通过ip地址是不能访问的;
- 0.0.0.0:监听IPV4上所有的地址,再根据端口找到不同的应用程序;
√比如我们监听0.0.0.0时,在同一个网段下的主机中,通过ip地址是可以访问的;
port、 open、compress
- port设置监听的端口,默认情况下是8080
- open是否打开浏览器∶
- 默认值是false,设置为true会打开浏览器;
- 也可以设置为类似于Google Chrome等值;
- compress是否为静态文件开启gzip compression
- 默认值是false,可以设置为true ;
- 默认值是false,可以设置为true ;
Proxy
proxy是我们开发中非常常用的一个配置选项,它的目的设置代理来解决跨域访问的问题:
比如我们的一个api请求是http://localhost:8888,但是本地启动服务器的域名是http://localhost:8000,这个时候发送网络请求就会出现跨域的问题;
那么我们可以将请求先发送到一个代理服务器,代理服务器和API服务器没有跨域的问题,就可以解决我们的跨域问题了
;
我们可以进行如下的设置
- target:表示的是代理到的目标地址,比如/api-hy/moment会被代理到 http://localhost8888/api-hy/moment ;
- pathRewrite :默认情况下,我们的/api-hy也会被写入到URL中,如果希望删除,可以使用pathRewrite ;
- secure:默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false ;
- changeOrigin :它表示是否更新代理后请求的headers中host地址;
changeOrigin的解析
这个changeOrigin官方说的非常模糊,通过查看源码我发现其实是要修改代理请求中的headers中的host属性:
- 因为我们真实的请求,其实是需要通过http://localhost:8888来请求的;
- 但是因为使用了代码,默认情况下它的值时http://localhost:8000 ;
- 如果我们需要修改,那么可以将changeOrigin设置为true即可;
historyApiFallback
- historyApiFallback是开发中一个非常常见的属性,它主要的作用是解决SPA页面在路由跳转之后,进行页面刷新时,返回404的错误。
- boolean值:默认是false
- 如果设置为true,那么在刷新时,返回404错误时,会自动返回index.html的内容;
- object类型的值,可以配置rewrites属性(了解)∶
- 可以配置from来匹配路径,决定要跳转到哪一个页面;
- 事实上devServer中实现historyApiFallback功能是通过connect-history-api-fallback库的:
- 可以查看
connect-history-api-fallback
文档
- 可以查看
resolve模块解析
resolve用于设置模块如何被解析∶
- 在开发中我们会有各种各样的模块依赖,这些模块可能来自于自己编写的代码,也可能来自第三方库;
- resolve可以帮助webpack从每个require/import语句中,找到需要引入到合适的模块代码;
- webpack使用enhanced-resolve 来解析文件路径;
webpack能解析三种文件的路径
- 绝对路径
由于已经获得文件的绝对路径,因此不需要再做进一步解析。 - 相对路径
在这种情况下,使用import或require 的资源文件所处的目录,被认为是上下文目录;
在import/require 中给定的相对路径,会拼接此上下文路径,来生成模块的绝对路径; - 模块路径
在resolve.modules中指定的所有目录检索模块;
√默认值是[‘node_modules’],所以默认会从node_modules中查找文件;
我们可以通过设置别名的方式来替换初识模块路径,具体后面讲解alias的配置;
确定文件还是文件夹
如果是一个文件:
如果文件具有扩展名,则直接打包文件;
否则,将使用resolve.extensions选项作为文件扩展名解析;
如果是一个文件夹:
会在文件夹中根据resolve.mainFiles配置选项中指定的文件顺序查找;
resolve.mainFiles的默认值是[‘index’];
再根据resolve.extensions来解析扩展名;
extensions和alias配置
extensions是解析到文件时自动添加扩展名︰
- 默认值是[‘.wasm’ , ‘.mjs’, ‘.js’, ‘.json’];
- 所以如果我们代码中想要添加加载.vue或者jsx或者ts等文件时,我们必须自己写上扩展名;
另一个非常好用的功能是配置别名alias : - 特别是当我们项目的目录结构比较深的时候,或者一个文件的路径可能需要…/…/…/这种路径片段;口我们可以给某些常见的路径起一个别名;
- 我们可以给某些常见的路径起一个别名
如何区分开发环境和生产环境
- 目前我们所有的webpack配置信息都是放到一个配置文件中的: webpack.config.js
- 当配置越来越多时,这个文件会变得越来越不容易维护;
- 并且某些配置是在开发环境需要使用的,某些配置是在生成环境需要使用的,当然某些配置是在开发和生成环境都会使用的;
- 所以,我们最好对配置进行划分,方便我们维护和管理
- 那么,在启动时如何可以区分不同的配置呢?
- 方案一:编写两个不同的配置文件,开发和生成时,分别加载不同的配置文件即可;
- 方式二∶使用相同的一个入口配置文件,通过设置参数来区分它们;
入口文件解析
- 我们之前编写入口文件的规则是这样的:./src/index,js,但是如果我们的配置文件所在的位置变成了config目录我们是否应该变成…/src/index.js呢?
如果我们这样编写,会发现是报错的,依然要写成./src/index.js ;
这是因为入口文件其实是和另一个属性时有关的context ;