一脚手架介绍
1. 什么是: 已经拥有标准的文件夹结构和部分核心功能的半成品项目代码.
2. 为什么:
(1). 标准化
(2). 避免了大量重复的项目搭建工作。
3. 何时: 程序中所有的前端框架技术,都采用脚手架方式开发。
4. 如何安装可以反复生成脚手架代码的命令行工具:
(1). 什么是: 可以反复使用命令创建新的半成品项目的脚手架结构的工具
(2). 何时: 程序中,只要想用脚手架,都必须先安装这种命令行工具才行!
二脚手架的安装
(1)准备工作 安装最新稳定版的nodejs
卸载本地旧的nodejs,官网 https://nodejs.org/en/下载最新稳定版( 点左边绿色大方块的LTS(长期稳定版)下载)nodejs,并安装:
安装好后设置nodejs默认下载地址为淘宝镜像
在命令行窗口中输入: npm config set registry http://registry.npm.taobao.org,然后,按回车。这是设置npm库为国内淘宝镜像。
再运行 npm config get registry,看到出现淘宝镜像地址,说明上一步配置正确
(2)开始安装脚手架
命令行输入 npm i -g @vue/cli
i: install 安装
-g 在全局安装,不属于某一个项目,所有项目都可用。
(3)如果出错:
解决方法i
可以先运行npm cache clean -f,清空nodejs缓存,
可以通过另一种方式换镜像npm install -g cnpm --registry=https://registry.npm.taobao.org
这是在安装npm的替代工具cnpm,并设置默认库为国内淘宝镜像
输入cnpm -v ,看到版本号说明正常
然后运行cnpm i -g @vue/cli
解决方法(2):还可以检查系统环境变量:
右键单击我的电脑或此电脑 / 选最下边属性 / 点左侧高级系统设置 / 点右下角环境变量 / 在下方系统变量方块中双击path变量->如果弹出窗口列表中没有nodejs安装目录,就在这里点添加,输入nodejs安装目录(默认为: C:\\Program Files\\nodejs\\)->然后点确定,点确定,点确定
解决方法(3) 如果以上都不行,就重装nodejs,再从方法1开始尝试
补充:
如果报EUNSUPPORTEDPROTOCOL错误,是因为node就是版本太低,升级到最新LTS版本,再试!
如果报错: EUNSUPPORTEDPROTOCOL错误,可以把registry中的https换成http,试试。或者卸载nodejs,重新安装新的LTS版nodejs
如果说FEXIST错误,可进入出错提示中的路径,默认为:
C:\\Users\\登录操作系统的用户名\\AppData\\Roaming\\npm\\node_modules
删除@vue文件夹,再试
解决方法(4) (慎用!)如果以上都不行就重装系统,再重装nodejs,再试
补: 苹果本除尝试以上办法之外,还可尝试:
#cd到项目目录
#然后依次执行下面的命令
rm -rf node_modules
rm package-lock.json
npm cache clear --force
npm install
三 脚手架的设置
安装好脚手架后可以通过命令 vue -V 查看版本 显示出版本则为安装成功
每做一个新项目,都要用命令行工具生成一套新的半成品脚手架项目结构代码
(1). 在要创建项目的路径: 运行vue create 自定义项目名
(2). ? Please pick a preset: 请选择预设:
Default ([Vue 2] babel, eslint) 默认 vue2
Default (Vue 3 Preview) ([Vue 3] babel, eslint) 默认 vue 3 预览版本 (可能有bug 不稳定)
> Manually select features 选择 ----手动选择功能
(3). Check the features needed for your project: (Press <space> to select, <a> to 选择你需要的功能通过按空格键
toggle all, <i> to invert selection)
>( ) Choose Vue version 选择vue版本
(*) Babel //翻译工具,脚手架中采用了很多ES6甚至更新的技术,但是,大多数浏览器都有兼容性问题。所以,需要Babel工具将脚手架中时髦的技术,翻译回ES5标准的等效的js代码,来兼容更多浏览器!
( ) TypeScript
( ) Progressive Web App (PWA) Support 渐进式Web应用程序(PWA)支持
(*) Router //SPA应用的核心组件
(*) Vuex //可以结合服务器传来的参数 (或自定义参数) 在多个组件中使用 不用通过复杂的传参
( ) CSS Pre-processors 勾选后可以使用scss
( ) Linter / Formatter 代码质量检查工具
( ) Unit Testing
( ) E2E Testing
(4). ? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) N 选 n
a. vue-router路由器其实有两种客户端路由导航的方式: hash和history
b. hash方式(默认): 用#/相对路径作为客户端路由导航的方式
c. history方式: 用/相对路径作为客户端路由导航的方式
d. 原理:
1). 只要我们修改浏览器地址栏中的地址,按回车,浏览器默认都会向服务器端发送请求。
2). 但是,在客户端vue中修改地址栏中的相对路径,我们不想向服务器发送请求,而仅仅是在客户端本地切换不同的页面组件显示而已
3). 所以vue-router路由器,为了和服务器端地址区分,决定使用客户端的"#/相对路径"作为客户端导航的路径。就可与一般的服务器地址有效区分。
4). 但是,极个别的需求中,确实有客户端本地页面内锚点地址跳转的需求。所以,为了避免锚点地址跳转与vue默认的hash路由方式冲突,vue-router其实也支持不带#的history方式跳转。
5). 但是,因为history方式和浏览器默认向服务器端发送的地址有冲突,所以,history方式仅靠客户端是无法实现的。必须配合专业的服务器端,定义首页重定向功能!
6). 总结: 建议学习阶段,都不要开history模式,都用默认hash模式,带#。
(5). Where do you prefer placing config for Babel, ESLint, etc.? 你喜欢把Babel, ESLint等的配置放在哪里
In dedicated config files 在专用配置文件中
> In package.json 选择只放在一个文件中
(6). ? Save this as a preset for future projects? (y/N) N 保存当前的配置 用于其他项目 (当然不 每个项目需求不同)
(7). 等待...
(8). 看到Sccessfully created project xzvue.提示就算生成脚手架项目成功
(9). 结果: 在当前文件夹下,生成了一个名为xzvue的文件夹,其中包含标准的脚手架项目结构和部分核心代码。
(10). 删除.git文件夹(如果不上传代码的话)
三. 运行生成的脚手架项目:
(1). vscode->打开文件夹->创建的文件,选择文件夹打开
(2). 右键单击package.json文件,选择在"集成终端"中打开
(3). 在弹出的终端窗口中输入npm run serve 没有r结尾
a. Starting development server...
1). 启动了一个简易版的服务器
2). 将来我们在脚手架编写的所有网页都运行在这个简易版的服务器内
3). 这个服务器有一个本地地址和端口号: http://localhost:8080
4). 只要我们想调试和预览我们的网页时,都要访问http://localhost:8080
b. DONE Compiled successfully
1). npm run serve还负责编译vue脚手架中的源代码
2). 编译:
i. 发现原代码中的错误。如果源代码有错,就会编译错误,无法运行
ii. 将时髦的新技术代码,翻译为旧的ES5代码
iii. 压缩合并代码
(4). 按住ctrl点local: 对应的地址: http://localhost:8080,自动打开浏览器窗口,看到项目首页.
(5). npm run serve 是热编译:
a. 只要一修改原代码,按ctrl+s保存后,npm run serve立刻自动重新遍历,并自动刷新已经打开的页面!
b. 所以,npm run serve很可能只在当前第一次打开项目时运行一次即可!不用反复npm run serve!
c. 从此,再不会使用live server了!
四 脚手架的文件结构
1. 唯一完整的HTML页面:
(1). 被一分为三:
a. public/index.html:
<html>
<head>
... ...
</head>
<body>
<div id="app">
</div>
b. src/App.vue
<div id="app">
<router-view></router-view>
</div>
c. src/main.js
import App from "./App.vue"
new Vue({
router, //路由器
...,
render: h => h(App)
}).$mount('#app')
(2). 运行时,再自动拼接起来运行:
a. 程序先找到main.js,执行new Vue()
1). render: h=>h(App) //读取App.vue文件中的HTML内容
2). $mount("#app") //用读取到的App.vue代替index.html页面中空的<div id="app">的位置。
b. 在运行时,编译后的main.js也会重新自动引入到Index.html开头位置
c. 所以,虽然分开的三部分内容,最后依然是在一个index.html中共同运行的.
2. 创建页面组件:
(1). 所有页面组件文件都保存在src/views文件夹下
(2). 只不过每个页面组件都变成一个.vue文件了
(3). 每个.vue文件内标准的都有三部分组成:
a. <template>:
1). 问题: 原始的vue组件,HTML片段是保存在template属性中的模板字符串内,编写时没有任何提示和错误检查!极其困难!
2). 解决: 在新的.vue文件中,开辟出了专门<template>标签区域,专门编写组件的HTML内容代码。
3). 好处: 既有提示,又有错误检查!
4). 强调:
i. 要想有颜色提示,必须为vscode安装vetur插件
ii. 依然必须唯一父元素包裹
5). 程序中,只要一个组件的HTML片段,都要写在<template>标签内部.
b. <style>: 如果组件不包含样式,可省略
1). 问题: 原始的组件是定义在js文件中,没有地方写css代码!
2). 解决: 新的.vue文件中,开辟了专门<style>标签区域,专门编写仅当前组件可用的css代码。
3). 好处: 既有提示,又有错误检查!
4). 程序中,一个组件内所有所需的css样式,都写在这个组件的<style>标签范围内。
c. <script>: 如果组件不包含js功能,也可省略
1). <script>标签专门保存这个组件的js对象。
2). 组件js对象中的内容除了没有template之外,其余成员与之前将的组件没有任何差别!
3). 但是,这个组件对象不要起名,且必须用export default抛出
(模块化开发,接下来将会介绍)
3. 路由字典和路由器对象
(1). 路由字典和路由器对象保存在src/router/index.js
(2). 与上篇博客手写的SPA应用的路由器对象文件内容极其相似
4. 其它全局组件或子组件
(1). 其它全局组件或子组件都集中保存在src/components文件夹下
(2). 每个组件,无论是全局组件还是子组件,创建时,也都创建为.vue文件。也要包含三部分(<template>, <style>, <script>)——要求和views中页面组件要求完全一样!
(3). 全局组件: 和上篇博客写的SPA应用一样
a. 也要创建为普通的组件对象,但是必须不要起名,且必须用export default 抛出
b. 也要在main.js中, new Vue()之前,用Vue.component()转化为全局组件.
五ES6的模块化开发:
1. 问题: 手工编写的SPA应用中: ——极其混乱!不直观!
(1). 明明路由器才需要页面组件对象,但是却要跑到毫无关系的index.html中引入页面组件对象,再回到路由器文件中使用。
(2). 明明路由器才需要vue-router.js文件,但是也要跑到毫无关系的index.html中引入vue-router.js文件,再回到路由器文件中使用。
2. 解决: 谁,需要什么,就直接引用什么!——模块化开发
3. 什么是: 所有的文件和对象,都看做一个独立的模块,模块与模块之间可以自由的引用,并使用其他模块中的内容
4. 何时: 程序中,几乎所有的项目都采用模块化开发
5. 问题: 除了专门的脚手架结构(nodejs, vue, ...)之外,原生的浏览器暂时还没有支持ES6模块化开发的!
6. 如何: 不同的平台,采用的模块化开发的标准不一样:
(1). nodejs中:
a. 抛出一个模块对象: module.exports={ ... }
b. 引入一个模块对象: var 对象名=require("./模块文件的相对路径")
(2). vue脚手架中,采用的是ES6的模块化开发标准
a. 抛出一个模块对象: export default { ... }
//强调: 抛出模块时,不要定义对象名
b. 引入一个模块: import 自定义对象名 from "./模块文件的相对路径"
c. 在当前的文件中,就可使用自定义对象名来访问引入的模块中的成员。
结合上篇博客中的SPA的示意图
六避免组件间样式冲突
1. 问题:写在不同组件.vue中的css样式,如果不做任何特殊处理,默认组件之间的css样式一定会发生冲突!(可能由于标签名相同导致后者覆盖前者)
2. 原理: 其实,无论写在哪个.vue中的css样式,最后都会被打包编译进一个统一的大的css文件中集中保存!来自于不同.vue中的css样式,一旦放在一个css文件中,就极容易发生冲突!
3. 解决: 2种:
(1). 偷懒的不好的办法:
a. 只要在<style>元素上添加scoped属性即可!
<style scoped>
b. 原理:
(1). 凡是加了scoped的组件中的所有HTML元素上都会被自动添加一个随机属性: data-v-随机名。
a. 同一组件内的所有元素,属性名和随机名相同
b. 不同组件的元素,属性名和随机名各不相同!
(2). scoped还会给当前组件内<style>元素内的所有选择器自动添加属性选择器[data-v-随机名]。限制只有当前组件内的带有相同随机属性名的元素才能应用这个选择器样式.
(3). 因为不同组件内的HTML元素,随机属性名各不相同,所以,元素只能匹配和自己相同组件内的随机属性名相同的选择器。无法匹配到其它组件中随机属性名不同的选择器。
c. 问题: 如果用@import从外部引入的css文件,scoped无法为其中的选择器自动添加自定义随机属性,照样会发生样式冲突!(而且即使单加scoped依然有点影响效率,应为大面积添加自定义属性相当于大面积修改dom树)
(2). 好的解决方法:
a. 为每个组件中定义唯一的父级class,然后,一个组件内的所有选择器,都要以这个组件的父级class作为起点开始写!
b. 如何:
<template>
<div class="组件名">
<子元素>...
</div>
</template>
<style>
.组件名>子元素{ ... }
</style>
c. 结果: 因为不同组件的组件名和父级class名各不相同,所以,当前组件的style中的选择器与其他组件中的选择器肯定也是各不相同的!至少选择器开头的父元素选择器名称就不同。所以,即使多个组件的css选择器混在一个css中,也不会发生冲突!
d. 局限:
1). 如果从0开始开发一个新项目,都建议选择第二种自定义class名的方式!
2). 但是,如果旧的项目没有定义父级class,现在要维护!还是建议用scoped,否则修改极大!
补: 两种打包和编译的方式:
npm run serve: 临时打包和编译,适合给开发人员调试和预览之用。
npm run build: 真正的将项目源代码打包和编译为可上线的产品。当项目开发完之后,都要用npm run build打包和编译项目,再将打包和编译的结果交给用户上线。(会生成一个文件夹dist包含压缩后的主要项目文件)
补: 外部css引入到.vue文件中:
1. css文件保存在脚手架的src/assets/css文件夹下
2. 在.vue文件的<style>中:
@import url(css文件的相对路径)
七 下载axios组件
(1). 安装: 右键点击package.json文件,选择在集成终端中打开。在终端窗口中输入: npm i -save axios 回车
(2). 配置:
a. 在main.js中,new Vue()之前配置好axios:
import axios from "axios" //nodejs自带的核心模块以及用npm安装的第三方模块,不要加./
配置公共路径:
axios.defaults.baseURL="服务器端接口的公共基础路径部分"
b. 将配置好的axios对象,放入Vue类型的原型对象中:
1). Vue.prototype.axios=axios;
2). 原理: 其实, 无论是new Vue(),Vue.component()还是子组件,底层统一都是new Vue()对象。所以,放在Vue类型的原型对象中的axios属性,所有组件内,都可通过this.axios方式访问到!
八懒加载
1. 问题: 默认情况,所有通过import在组件开头引入的东西,都会被集中打包在一个大的文件中,在首屏加载时,一股脑都下载下来!文件太大,首屏加载速度太慢!
2. 解决: 懒加载
3. 什么是懒加载: 2种
(1). 异步延迟加载: 优先现在首页的内容,其它页面或组件的内容,在底层通过异步的方式,悄悄下载。等用户看完首页,想看其它页面时,其它页面已经通过异步方式下载完了,用户就可直接从本地加载其它页面。
(2). 彻底懒加载: 只下载首页,其余页面一点儿也不下载!只有用户改变了地址栏中的地址,想访问其它页面时,才临时从服务器端下载其他某一个组件的内容。
4. 异步延迟加载: (Vue脚手架默认)
(1). 优点: 提前通过异步方式将其余页面下载到客户端保存,当用户想看其他页面时,只要从客户端加载即可,不用请求服务器端——快
(2). 缺点: 不管用户看不看其他页面,都提前下载!偷跑流量。
(3). 如何: 2步:
a. 在router/index.js顶部只import引入首页组件,不要import引入其它页面的组件
b. 在router/index.js中, routes数组内,其余页面的路由字典项都要改为:
{
path:"/相对路径",
component: () => import(
//给将来独立的xxx.js文件起文件名
/* webpackChunkName: "自定义文件名" */
'../views/其它页面.vue'
)
}
(4). 结果:
a. 如果没有提前引入的其它页面组件,就会被单独编译为一个js文件
b. 其它页面组件的js文件,不会随首屏一起下载,而是在底层后台悄悄异步下载。
(5). 总结:项目中,除首页之外的其它页面组件,都要使用懒加载方式。
5. 彻底懒加载:
(1). 优点: 一点儿也不提前下载其他页面的内容,节约流量!
(2). 缺点: 当用户访问其它页面时,才临时下载其他页面的内容,速度慢。
(3). 如何:
a. 前两步和异步懒加载完全一样!
b. 在脚手架项目文件夹结构根目录,新建vue.config.js文件,其中:
module.exports={
chainWebpack:config=>{
config.plugins.delete("prefetch")
//删除index.html开头的带有prefetch属性的link,不要异步下载暂时不需要的页面组件文件
},
}
c. 强调: 因为vue.config.js文件是修改vue脚手架的配置,所以必须重启脚手架才行:
1). Ctrl+C,输入y,回车
2). npm run serve
(4). 结果:
a. 当访问首页时,没有下载其他页面的组件文件
b. 当切换到访问详情页时,才临时下载详情页组件:
九 proxy 解决跨域问题
1. 跨域共有几种方式: 3种:
(1). CORS: 纯服务器端跨域 (上篇博客有介绍过)
(2). JSONP: 客户端+服务器端一起跨域
(3). HTTP代理: 纯客户端跨域 (这里将会介绍到)
2. 项目中常采用的跨域方式: HTTP代理方式
3. 问题: 无论使用CORS方式还是JSONP方式跨域,都受制于服务器端!前端自己做不了主!
4. 解决: 不用ajax发送请求不就行了!——HTTP代理(究其根本还是ajax发送的请求导致会出现跨域问题)
5. 原理: 由脚手架内存中一个nodejs代理程序替axios发送请求,接收响应。因为发送的不是xhr ajax请求,所以不会受浏览器同源策略CORS的限制。
6. 如何:
(1). 在vue.config.js文件中,module.exports={中添加以下内容}
devServer: {
proxy: { //代理
//凡是以/开头的相对路径,都交给DevServer代为发送请求(统一前缀)
'/': {
target: `http://服务器端接口地址统一前缀部分`,
changeOrigin: true, //开启跨域
}
}
}
剩下的发送请求方式与之前介绍的相同
(2). 不再需要配置axios的完整路径,因为完整路径已经保存在http代理程序devServer中了。
所以可以注释掉main.js中axios.defaults.baseURL="http://xxx" 或改为"/"
十 插槽
1,插槽是组件中一块可以动态改变HTML片段的内容的区域 即动态改变<template></template>的内容区域(也有点相当于模板)
程序中,只要发现多个组件,拥有相同的结构框架,只是内部的HTML片段不同时,都可以用插槽
2,如何使用:
2.1先定义包含统一框架结构的组件——外壳组件
在components文件夹下,新建一个组件,将相同的结构框架代码复制到组件中
删除组件中将来可能发生变化的位置,并用<slot></slot>标签占位
组件中个别可能变化的文本,改为绑定语法,并在props成员中添加外来属性用于接收 可能用到的参数(代码如下)
1 //这是.vue后缀的文件
2 <template>
3 <div class="waike">
4 <div class="dialog">
5 <div class="title">
6 <span class="txt">{{title}}</span>
7 <!-- 插值语法引入需要使用数据 -->
8 <span class="close" @click="close">×</span>
9 </div>
10 <slot name="content"></slot>
11 <!-- 用slot标签暂时代替未来引入内容的区域 -->
12 <slot name="btn"></slot>
13 </div>
14 </div>
15 </template>
16 <script>
17 export default {
18 props:[ 'title',"close" ],
19
20 }
21 </script>
22 <style lang="scss">
23 //相同的样式
24 .waike{
25 background-color:rgba(0,0,0,.5);
26 position:fixed;
27 top:0; bottom:0; left:0; right:0;
28 .dialog{
29 background-color:#fff;
30 width:300px;
31 border:1px solid #aaa;
32 transform:translate(-50%,-50%);
33 position:absolute;
34 top:50%;
35 left:50%;
36 >.title{
37 height:36px;
38 background-color:#e4393c;
39 color:#fff;
40 line-height:36px;
41 >.txt{
42 margin-left:10px;
43 }
44 >.close{
45 float:right;
46 cursor:pointer;
47 margin-right:10px;
48 &:hover{
49 font-size:1.2em;
50 }
51 }
52 }
53 >.content{
54 >.row{
55 margin:5px 10px;
56 >.label{
57 display:inline-block;
58 width:80px;
59 text-align:right;
60 }
61 }
62 }
63 >.btns{
64 text-align:center;
65 border-top:1px solid #ccc;
66 height:36px;
67 line-height:36px;
68 >button{
69 margin-right:20px;
70 }
71 }
72 }
73 }
74
75 </style>
2.2再在每次创建其它组件时,动态改变组件中插槽区域的HTML片段内容
在template标签内只写入插槽中没有的结构 并通过组件间传参将外壳需要的数据传递给他
在此之前需要引入外壳文件通过:
import 外壳组件名 from "./外壳组件文件" //为了使用外壳,需要先引入
export default {
… …,
components:{ 外壳组件名 },//将其设置为本组件的子组件方便传参
… …}
实例代码如下
注意: 如果要绑定给子组件的属性值是写死的,不是来自于程序中的变量,就不应该加:冒号
1 //本文件为.vue文件
2 <template>
3 <waike title='登录' :close='close'>
4 <!-- 登录 和 close 都是父组件传递给外壳(子组件)的参数 -->
5 <template #content>
6 <div class="content">
7 <div class="row">
8 <span class="label">用户名</span>:
9 <input>
10 </div>
11 <div class="row">
12 <span class="label">密码</span>:
13 <input type="password">
14 </div>
15 </div>
16 </template>
17 <template #btn>
18 <div class="btns">
19 <button @click="login">登录</button>
20 <button @click="close">取消</button>
21 </div>
22 </template>
23 </waike>
24 </template>
25 <script>
26 // 引入外壳文件
27 import Waike from './Waike.vue'
28 export default {
29 props:[ "close" ],
30 components:{Waike},
31 // 设置外壳为本组件的子组件
32 methods:{
33 login(){
34 alert("登录成功");
35 }
36 }
37 }
38 </script>
具名插槽
如果一个带插槽的外壳组件,有多个部位将来可能会改变
可以为每个部位指定插槽名
创建外壳代码示例:
1 <div class="pop">
2 <div class="dialog">
3 <div class="title">
4 <span class="txt">{{title}}</span>
5 <span class="close" @click="close">×</span>
6 </div>
7 <slot name="插槽名1"></slot>
8 <slot name="插槽名2"></slot>
9 </div>
10 </div>
程序中使用带插槽的外壳组件时,可以将不同的HTML片段,插入不同的插槽中
如果一个带插槽的外壳组件包含多个具名插槽,则使用时,每个插槽对应的HTML片段,都要放在一个<template v-slot:插槽名>中
v-slot:插槽名,可简写为#插槽名
使用外壳代码示例:
1 <pop title="登录" :close="close">
2 <!--<template v-slot:content>-->
3 <template #插槽名1> //相当于 -><slot name=插槽名1
4 <div class="content">
5 … …
6 </div>
7 </template>
8 <!--<template v-slot:btns>-->
9 <template #插槽名2> //相当于 -><slot name=插槽名2
10 <div class="btns">
11 <button @click="login">登录</button>
12 <button @click="close">取消</button>
13 </div>
14 </template>
15 </pop>
组件间传递方法
十一 vuex
1. 问题: vue中组件的数据是专属的,不是共享的。所以,如果多个组件都需要使用一批数据时,就很麻烦!如果使用组件间传参实现,关系会极其错综复杂!极其不便于维护!
2. 解决: 如果我们可以有一个区域,能够专门保存所有组件共享的数据,所有组件都可随意访问!
3. 什么是Vuex: 替所有组件集中保存共有数据的一个块区域
4. 何时: 程序中只要多个组件都要用到一批共享的变量时,都可用Vuex实现
5. 如何:
(0). 安装脚手架时,要选择vuex
1). 在store/index.js中的state里,添加共用的属性
(2). 在页面上使用state中的变量:
a. 在要使用state中变量的组件中,先用import 引入vuex,再解构出mapState对象
import { mapState } from "vuex"
b. 在当前组件的计算属性computed中解构出mapState中想用的变量
computed:{
...mapState([ "uname" ])
//结果: 可用this.uname访问state里的uname了
},
因为state中的变量只能读取,不能直接修改!所以要写在computed中
c. 结果:
1). 在HTML中就可用uname变量用作绑定语法
2). 在js中就可用this.uname读取变量值
(3). 在其他组件中修改uname变量:
a. 要想修改变量,必须在mutations中添加专门的函数修改专门的变量
//mutations是专门保存修改变量的函数的区域
mutations: {
// 上边的 将来一个新的uname值
// state对象 ↓
setUname(state, new_uname){
//修改state中的uname为新的uname值
state.uname=new_uname;
},
setUid(state, new_uid){
state.uid=new_uid;
}
},
b. 在其他组件中使用mutations中的函数修改uname变量
1). 在要使用mutations中的方法的组件中,先用import 引入vuex,再解构出mapMutations对象
import { mapMutations } from "vuex"
//其实就是Store中的Mutation
2). 在当前组件的methods中解构出mapMutations中想用的方法
methods:{
...mapMutations ([ "setUname" ])
//结果: 可用this.setUname(新uname)修改state里的uname了
//其实就是Store中Mutations中的setUname
}
(4).结合服务器传来的参数在组件中实现业务逻辑:
a. mutations中虽然也是方法,但是不允许执行异步操作,比如ajax!
b. 如果想先发送ajax请求,再用响应结果修改变量值时,都要借助于actions。
c. actions是专门发送ajax请求的函数的区域
d. 如何:
actions: {
// 整个vuex 登录对话框传来的
// 环境 新用户对象
vlogin(context, user){
axios.post(
"/users/signin",
`uname=${user.uname}&upwd=${user.upwd}`
).then(result=>{
if(result.data.ok==1){
//调用Mutations中的setUname方法
//修改state里的uname
context.commit("setUname",result.data.uname);
//Mutations中的可修改变量的方法名
}else{
alert(result.data.msg);
}
})
}
}
e. 在其他组件中使用actions中的方法:
1). 在要使用actions中的方法的组件中,先用import 引入vuex,再解构出mapActions对象
import { mapActions } from "vuex"
//其实就是Store中的Actions
2). 在当前组件的methods中解构出mapActions中想用的方法
methods:{
...mapActions ([ "vlogin" ])
//结果: 可用this.vlogin(新用户对象)发送登录请求
//其实就是Store中Actions中的vlogin()方法
}
3). 在登录按钮上绑定vlogin方法。
store/index.js代码:
1 import Vue from 'vue'
2 import Vuex from 'vuex'
3 import axios from "axios"
4 //引入在main.js中已经配置好的axios模块对象
5
6 Vue.use(Vuex)
7
8 export default new Vuex.Store({
9 state: {
10 uid:-1,
11 uname:""
12 },
13 mutations: {
14 // 上边的 将来一个新的uname值
15 // state对象 ↓
16 setUname(state, new_uname){
17 //修改state中的uname为新的uname值
18 state.uname=new_uname;
19 },
20 setUid(state, new_uid){
21 state.uid=new_uid;
22 }
23 },
24 actions: {
25 // 整个vuex 登录对话框传来的
26 // 环境 新用户对象
27 vlogin(context, user){
28 axios.post(
29 "/users/signin",
30 `uname=${user.uname}&upwd=${user.upwd}`
31 ).then(result=>{
32 if(result.data.ok==1){
33 //调用Mutations中的setUname方法
34 //修改state里的uname
35 context.commit("setUname",result.data.uname);
36 alert("登录成功");
37 }else{
38 alert(result.data.msg);
39 }
40 })
41 }
42 },
43 modules: {
44 }
45 })
组件代码:
1 <template>
2 <!--因为外壳需要2个值,所以必须绑定两个值
3 在登录框中,标题"登录"不是变量,是写死的两个字,所以title不能加:-->
4 <waike title="登录" :close="close">
5 <template #内容>
6 <div class="content">
7 <div class="row">
8 <span class="label">用户名</span>:
9 <input v-model="uname">
10 </div>
11 <div class="row">
12 <span class="label">密码</span>:
13 <input v-model="upwd" type="password">
14 </div>
15 </div>
16 </template>
17 <template #按钮>
18 <div class="btns">
19 <button @click="login">登录</button>
20 <button @click="close">取消</button>
21 </div>
22 </template>
23 </waike>
24 </template>
25 <script>
26 import Waike from "./Waike"
27 //参数结构 vuex中的函数对象
28 import { mapActions } from "vuex"
29 export default {
30 props:[ "close" ],
31 components:{ Waike },
32 data(){
33 return {
34 uname:"",
35 upwd:""
36 }
37 },
38 methods:{
39 ...mapActions(["vlogin"]),
40 login(){
41 var user={
42 uname:this.uname,
43 upwd:this.upwd
44 };
45 this.vlogin(user);
46 this.uname="";
47 this.upwd="";
48 }
49 }
50 }
51 </script>
十二 vue3
在介绍vue3之前先介绍 vue3需要用到的只是 Typescript
TypeScript
一. TypeScript
1. js的问题: 弱类型和解释型
(1). 弱类型: 一个变量没有类型的限制,可以存储任意类型的数据。
(2). 解释型: 一边解释一边执行,解释到哪里,执行到哪里。
2. 解决: 用TypeScript代替JS。
3. 好处:
(1). 强类型: 一个变量在定义时,就要限制死变量的数据类型。在使用时,只能保存规定的数据类型,不能换用不同类型的数据.
(2). 编译型: 先把程序通读一遍,检查程序中的错误,并优化代码,产生编译后的中间代码。再用中间代码运行。
4. TypeScript和JS的关系: TypeScript在兼容ES6和ES5的基础上,为ES6又增加了个别新功能
5. 安装:
(1). 全局安装TypeScript语言的编译器:
npm i -g typescript
(2). 用vscode打开项目文件夹,右键选择在终端中打开,在终端中输入:
tsc -init
如果提示ps错误:
a. 点击vscode左下角齿轮图标,选设置:
b. 搜索"Shell windows"
c. 找到终端在 Windows 上使用的 shell 的路径
d. 点"在settings.json中编辑"
e. 将"...windows":后的值改为"C:\\WINDOWS\\System32\\cmd.exe"
f. 重启vscode
结果: 在当前项目文件夹中生成了tsconfig.json文件,其中保存的是将ts编译为js时所需的配置,比如:
target: "ES5" //在将ts文件编译为js文件时,编译为ES5的版本,兼容更多浏览器
module: "commenJS" //将来ts文件中模块化开发所采用的标准。
strict: true //将ts文件编译js文件时,自动启用严格模式
说明: tsc是ts语言的编译器, c是compile的意思,编译。
(3). 示例: 在项目文件夹新建1_first.ts文件:
1 let a:number=10;
2 console.log(a);
3 // a="hello"
4 // console.log(a);
(4). 先编译ts文件为js文件
tsc 文件名.ts
结果: tsc编译器将.ts文件的内容翻译为等效的js文件,保存在ts文件的旁边。
(5). 再运行js文件
node 文件名.js
(6). 问题: 为什么在ts文件中写的很多ts语法,在js中都去掉了!
因为ts语言的检查,是发生在编译阶段!不是发生在运行阶段。
所以,ts中写的专用的语法是在编译阶段使用的,编译后,将来运行时,不用再重新检查。所以不需要特殊语法。
(7). 自动编译:
a. 在vscode中选择"运行生成任务"->"xxx监视xxx"
b. 结果: 只要一修改ts文件,一保存,就自动编译,自动创建js文件
c. 不输入命令,运行js文件:
1). 先打开要运行的js文件
2). 点左边小虫+三角图标
3). 先运行和调试
4). 选择nodejs
5). 看到执行结果。
6. 变量声明: 强类型
(1). 标准语法:
var或const或let 变量名:数据类型=初始值
结果: 将来这个变量只能保存规定的数据类型
(2). 数据类型:
a. 基本类型: boolean, number, string
b. 数组: 2种写法,结果一样
1). let 数组名: 数据类型[]=[值1, 值2, ...]
2). let 数组名: Array<数据类型>=[值1, 值2,...]
c. any: 可以匹配任何数据类型
(3). 示例: 定义不同类型的变量,并输出变量值:
1 let age:number=25;
2 console.log(age);
3 let isAgree:boolean=true;
4 console.log(isAgree);
5 let str:string="Hello";
6 console.log(str);
7
8 let arr:string[]=["平平","安安","吉吉"];
9 console.log(arr[0]);
10 console.log(arr.length);
11 运行结果:
12 25
13 true
14 Hello
15 平平
16 3
7. 函数:
(1). 既没有参数,又没有返回值的函数,与普通js写法一样
(2). 如果函数有返回值:
function 函数名():返回值的类型{
...
return 返回值 //实际返回值的类型与声明函数时:后的返回值类型要一致。
}
(3). 如果函数有参数:
function 函数名(形参1:数据类型, 形参2:数据类型, ... ){
...
}
强调: ts中严格规定,一个函数定义了几个形参,就必须传几个实参!也数据类型也要对应!
(4). 如果既有形参,又有返回值?
function 函数名(形参1:数据类型, 形参2:数据类型, ... ):返回值数据类型{
return 返回值
}
(5). 可选参数和默认值:
a. 可选参数: ?表示参数可以没有。将来如果没有传入这个参数值,则参数值默认为undefined
function 函数名(形参1:数据类型, 形参2?:数据类型) {
... ...
}
b. 默认值
function 函数名(形参1:数据类型, 形参2:数据类型=默认值) {
... ...
}
坑: 虽然可选参数的实参值将来可能提供,也可能不提供,但是,因为有默认值保证,一定有值可用!所以,不能加?
(6). 实参值个数不确定时:
function 函数名(固定形参:数据类型, ...数组名:数据类型[]){
.........
}
(7). 重载:
a. 旧js中: 重载只能定义一个函数,在函数内根据传入的参数不同,执行不同的逻辑
function pay( ){
if(arguments.length==0){
手机支付
}else if(arguments.length==1){
现金支付
}else{
刷卡支付
}
}
pay();
pay(100);
pay("6553 1234", "123456")
b. TS中:
1). TS中要先定义多个同名函数的声明,只要形参列表不同即可!但是不要实现函数体。
function 函数名1():void;
function 函数名1(形参:数据类型):void;
function 函数名1(形参1:数据类型,形参2:数据类型):void;
强调: 这里只是不同重载版本的声明
说明: void代表这个函数没有返回值
2). 定义一个实际执行多种任务的函数来支持上方多种重载的情况:
function 函数名(形参?:数据类型){
if(形参===undefined){ //说明用户调用的是第一个重载版本
//没有传入任何实参
//就执行一项操作
}else{
//说明用户调用的是第二个重载版本,传了一个实参
//就执行另一项操作
}
}
(8). 示例: 定义自我介绍的函数,定义参数和返回值:
1 var intr=function(){
2 console.log(`I'm Li Lei, I'm 11`)
3 };
4 intr();
5 var intr2=function():string{
6 return `I'm Li Lei, I'm 11`
7 };
8 console.log(intr2());
9 var intr3=function(sname:string, sage:number){
10 console.log(`I'm ${sname}, I'm ${sage}`)
11 }
12 intr3("Li Lei",11);
13 js中运行结果:
14 3 I'm Li Lei, I'm 11
(9). 示例: 定义自我介绍的函数,定义求和的函数,个别参数不确定:
1 function add(a:number,b:number):number{
2 return a+b
3 }
4 console.log(add(3,5));
5 function intrSelf(
6 sname:string,
7 sage:number,
8 fav?:string
9 ){
10 console.log(`I'm ${sname}, I'm ${sage}, ${fav!==undefined?'I like'+fav:''}`)
11 }
12 intrSelf("Li Lei",11,"跑步");
13 intrSelf("Han Meimei",12);
14
15 function intrSelf2(
16 sname:string,
17 sage:number,
18 fav:string="nothing"
19 ){
20 console.log(`I'm ${sname}, I'm ${sage}, I like ${fav}`)
21 }
22 intrSelf2("Li Lei",11,"跑步");
23 intrSelf2("Han Meimei",12);
24 运行结果:
25 8
26 I'm Li Lei, I'm 11, I like跑步
27 I'm Han Meimei, I'm 12,
28 I'm Li Lei, I'm 11, I like 跑步
29 I'm Han Meimei, I'm 12, I like nothing
(10). 示例:定义求和的函数,计算任意多个数的和:
1 var addAll=(...arr:number[])=>{
2 var result=0;
3 for(var n of arr){
4 result+=n;
5 }
6 return result;
7 }
8 console.log(addAll(1,2,3))//6
9 console.log(addAll(1,2,3,4,5))//15
10 运行结果:
11 6
12 15
(11). 示例:使用ts重载实现三种支付方式:
1 function pay():void;
2 function pay(money:number):void;
3 function pay(cardId:string, pwd:string):void;
4
5 function pay(x?:any, y?:any){
6 //如果连第一个参数都没传,说明没有传任何参数
7 //则执行手机支付
8 if(x==undefined){
9 console.log("手机支付")
10 }else if(y==undefined){
11 //否则如果x传了值,但是y没有传值
12 //说明只传了一个参数,要执行现金支付
13 console.log(`现金支付,收您${x}元`)
14 }else{
15 //否则如果两个参数都传了,说明执行刷卡支付
16 console.log(`刷卡支付,从您卡号${x}中扣款成功...`)
17 }
18 }
19
20 pay();
21 pay(100);
22 pay("65531234","123456")
23 运行结果:
24 手机支付
25 现金支付,收您100元
26 刷卡支付,从您卡号65531234中扣款成功...
8. class:
(1). 定义class:
class 类型名{
属性名1:数据类型=初始值;
属性名2:数据类型=初始值;
constructor(形参1:数据类型, 形参2:数据类型){
this.属性名1=形参1;
this.属性名2=形参2;
}
方法名(){ ... }
}
(2). 强调: TS中class的属性,必须在构造函数外部上方提前定义,并指定数据类型,才能在构造函数中this.属性名=赋值
(3). 示例: 定义学生类型,描述所有学生统一结构:
1 class Student{
2 //class中要想包含一些属性
3 //必须现在构造函数外定义属性
4 sname:string="";
5 sage:number=0;
6 constructor(sname:string,sage:number){
7 this.sname=sname;
8 this.sage=sage;
9 }
10 intr():void{ //void表示没有return
11 console.log(`I'm ${this.sname}, I'm ${this.sage}`)
12 }
13 }
14
15 var lilei=new Student("Li Lei",11);
16 var hmm=new Student("Han Meimei",12);
17 lilei.intr();
18 hmm.intr();
19 运行结果:
20 I'm Li Lei, I'm 11
21 I'm Han Meimei, I'm 12
(4). 两种类型间继承:
class 父类型名{
属性名1:数据类型=初始值;
属性名2:数据类型=初始值;
constructor(形参1:数据类型, 形参2:数据类型){
this.属性名1=形参1;
this.属性名2=形参2;
}
方法名(){ ... }
}
class 子类型名 extends 父类型名{
子类型属性: 数据类型=初始值;
constructor(形参1:数据类型, 形参2:数据类型, 形参3:数据类型){
super(形参1, 形参2);
this.子类型属性=形参3;
}
方法名()
}
var 对象=new 子类型(实参值1, 实参值2, 实参值3)
(5). 结果: 对象中: 有三个属性(2个父class给的,1个子class给的)
和两个方法(1个父class给的,1个子class给的)
(6). 示例: 定义敌人类型和敌机类型,敌机类型继承敌人类型
1 class Enemy{
2 x:number=0;
3 y:number=0;
4 constructor(x:number,y:number){
5 this.x=x;
6 this.y=y;
7 }
8 fly(){
9 console.log(`飞到x=${this.x},y=${this.y}位置`)
10 }
11 }
12 class Plane extends Enemy{
13 score:number=0;
14 constructor(x:number,y:number,score:number){
15 super(x,y);
16 this.score=score;
17 }
18 getScore(){
19 console.log(`击落敌机得${this.score}分`)
20 }
21 }
22 var p1=new Plane(100,200,5);
23 p1.fly();
24 p1.getScore();
25 console.log(p1)
26 运行结果:
27 飞到x=100,y=200位置
28 击落敌机得5分
29 Plane {x: 100, y: 200, score: 5}
(7). 访问修饰符:
a. 问题: 默认,class中的所有成员无论在class内,还是在子类型内,还是在全局都用this.属性名或父对象.属性名所以访问,没有限制!但是有些数据,不想让所有人轻易都知道!
b. 解决: 访问控制修饰符: 专门修饰一个属性或一个方法的可用范围的关键字
c. 如何: 访问控制修饰符 属性名:数据类型=初始值
d. 包括: 3种:
1). public 公有 (默认), 表示子类型和外部都可访问到
2). protected 受保护,表示只有父子类型范围内才能使用,外部不能用。
3). private 私有,表示仅class内可用,子类型和外部都不能用!
e. 示例: 父类型中定义money属性,使用访问修饰符控制在不同范围内是否可访问
1 class Father{
2 public money:string="爸爸的500元";
3 pay(){
4 console.log(`爸爸自己花${this.money}`);
5 }
6 }
7 class Son extends Father{
8 pay(){
9 console.log(`孩子花${this.money}`)
10 }
11 }
12 var s1=new Son();
13 s1.pay();
14
15 var f1=new Father();
16 console.log(`外人也知道${f1.money}`);
17 运行结果:
18 孩子花爸爸的500元
19 外人也知道爸爸的500元
(8). 接口:
a. 什么是: 多个class遵循的一个统一结构标准。但是没有任何数据和实现。
b. 为什么: 在团队中,上级分配了开发任务给下级,如何保证下级一定按要求实现功能?!
c. 何时: 今后,只要希望别人一定要按照你的要求实现程序时,都用接口来规范!
d. 如何: 2步:
1). 定义接口:
interface I接口名{ //大写的i开头命名
规定的属性名: 类型;
规定的方法名(参数:数据类型):返回值;
}
2). 具体实现的人要"实现接口"
i. 如果要定义一个符合接口要求的class
class 类型名 implements I接口名{
必须包含接口中规定的属性和方法
}
e. 示例: 定义实现学生class的接口规范,并定义学生class遵守接口规范
1 //规定要定义学生类型必须包含姓名、年龄和自我介绍方法
2 interface IStudent{
3 sname:string;//只定义属性名,不要给属性值
4 sage:number;//只定义属性名,不要给属性值
5 intr:()=>void;//只定义方法名、参数和返回值,不要写{}方法实现!
6 }
7
8 //具体实现学生类型的人:
9 class Student implements IStudent{
10 sname:string="";
11 sage:number=0;
12 className:string="";
13 constructor(sname:string, sage:number,className:string){
14 this.sname=sname;
15 this.sage=sage;
16 this.className=className;
17 }
18 intr(){
19 console.log(`I'm ${this.sname}, I'm ${this.sage}`)
20 };
21 }
22
23 var lilei=new Student("Li Lei",11,"初一2班");
24 console.log(lilei);
25 lilei.intr();
26 运行结果:
27 Student {sname: 'Li Lei', sage: 11, className: '初一2班'}
28 I'm Li Lei, I'm 11
接下来开始介绍vue3
首先是安装过程的选项
1. 安装脚手架: vue create 项目名
(1). ? Please pick a preset:
Default ([Vue 2] babel, eslint)
Default (Vue 3 Preview) ([Vue 3] babel, eslint)
> Manually select features //手动选择特点
(2). ? Check the features needed for your project:
(*) Choose Vue version //版本选择
(*) Babel //编译
>(*) TypeScript //上面介绍到的
( ) Progressive Web App (PWA) Support
(*) Router //路由
(*) Vuex //vuex 传参神器
(*) CSS Pre-processors // 可用scss
( ) Linter / Formatter
( ) Unit Testing
( ) E2E Testing
(3). ? Choose a version of Vue.js that you want to start the project with
2.x
> 3.x (Preview) //选择3版本
(4). ? Use class-style component syntax? (y/N) n //vue3中大都使用的是函数 所以这里不选class-style
Vue 3逐渐不推荐使用class方式定义组件,而是更推崇函数方式。
(5). ? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? (Y/n) y //编译 当然
(6). ? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n)N //没有后端给我们定义接口暂时选N 用户hash模式(有#的url)
(7). ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys)
> Sass/SCSS (with dart-sass) //选择scss的解释方式
Sass/SCSS (with node-sass) //首选,但是,如果出错,可以改选第一个!
Less
Stylus
(8). ? Where do you prefer placing config for Babel, ESLint, etc.?
In dedicated config files
> In package.json //老规矩
(9). ? Save this as a preset for future projects? (y/N) n //当然不 项目不同要求不同
2. 绑定原理:
(1). Vue2的绑定原理: 访问器属性+虚拟DOM树
(2). 访问器属性的问题: 只能对初始就有的属性,添加监视(访问器属性)。如果今后动态添加进来的成员,就无法自动添加访问器属性,也就无法自动得到监视。结果,在程序中修改变量,页面不自动修改!
(3). Vue3的绑定原理: ES6的Proxy代理对象:
proxy在目标对象的外层搭建了一层拦截,外界对目标对象的某些操作,必须通过这层拦截。
(4). 对象内部所有现有属性自动被监视,而且更可贵的是,后添加的属性,一进入对象就被监视!
vue2中无法对改变的数据做到完全监视(如通过下标更改数组)
如例子无法监视到的情况的例子
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <meta http-equiv="X-UA-Compatible" content="IE=edge">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 </head>
9 <body>
10 <script>
11 var eric={
12 ename:"埃里克",
13 eage:25
14 };
15 Object.defineProperties(eric,{
16 _ename:{
17 value:eric.ename,
18 writable:true,
19 enumerable:false,
20 configurable:false
21 },
22 ename:{
23 get:function(){
24 console.log(`有人读取了ename属性值`)
25 return this._ename;
26 },
27 set:function(value){
28 console.log(`有人修改了ename属性值`)
29 this._ename=value;
30 },
31 enumerable:true,
32 configurable:false
33 },
34 _eage:{
35 value:eric.eage,
36 writable:true,
37 enumerable:false,
38 configurable:false
39 },
40 eage:{
41 get:function(){
42 console.log(`有人读取了eage属性值`)
43 return this._eage;
44 },
45 set:function(value){
46 console.log(`有人修改了eage属性值`)
47 this._eage=value;
48 },
49 enumerable:true,
50 configurable:false
51 }
52 });
53
54 console.log(eric.ename);
55 console.log(eric.eage);
56 eric.ename="杰克";
57 eric.eage=35;
58 console.log(eric);
59
60 eric.className="初一2班";
61 console.log(eric.className);
62 eric.className="初二2班";
63 </script>
64 </body>
65 </html>
vue3做到完全监视的相似例子
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <meta http-equiv="X-UA-Compatible" content="IE=edge">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 </head>
9 <body>
10 <script>
11 var eric = {
12 sname: "埃里克",
13 sage: 25
14 };
15 var logHandler = {
16 get: function(target, key) {
17 console.log(`${key} 被读取`);
18 return target[key];
19 },
20 set: function(target, key, value) {
21 console.log(`${key} 被设置为 ${value}`);
22 target[key] = value;
23 }
24 }
25 var ericProxy = new Proxy(eric, logHandler);
26
27 console.log(ericProxy.sname); // 控制台输出:sname 被读取
28 ericProxy.sname="杰克"; // 控制台输出:sname 被设置为 杰克
29 console.log(`埃里克的新名:${ericProxy.sname}`);
30
31 ericProxy.className="初一2班";
32 ericProxy.className="初二2班";
33 </script>
34 </body>
35 </html>
(5). 问题: 兼容性极差
3. 脚手架的使用上:
(1). <template>和<style>没有任何变化
(2). 变化一: main.ts中: 引入了很多其他的模块
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
其中 createApp(App).use(store).use(router).mount('#app')
代替了new Vue()
(3). 组件内的变化:
<script lang="ts">
import { defineComponent } from 'vue';
import HelloWorld from '@/components/HelloWorld.vue';
@是src文件夹的别名
以函数方式创建组件对象,并对组件中的各项内容添加监听
export default defineComponent({
name: 'Home',
components: {
HelloWorld,
},
});
</script>
(4). Vue3中也支持vue2的写法,但是会逐渐不支持。
(5). Vue3的新写法:
<div><!--template里和vue2写法完全一样-->
<button @click="minus">-</button>
<span>{{n}}</span>
<button @click="add">+</button>
</div>
//引入vue模块中提供的函数,用于创建组件对象和加工组件对象中的内容.
import { defineComponent, ref } from 'vue';
... ...
export default defineComponent({ //专门创建组件对象的函数
name: 'Home',
components: {
HelloWorld,
},
//代替了之前的beforeCreate和created
//作为整个组件的运行起点
setup(){ 这个函数由引入的defineComponent支持
//组件所需的所有变量和函数都放在setup()函数内
//错误: 不受监视
// let n=0; //变量!
//正确: 要想受监视的变量值,必须用ref()包裹
//ref必须在import { ... } from "vue"中解构出来才能用.
let n=ref(0);//new Proxy()对变量n添加监视!
//今后凡是用ref()包裹的变量值,要想操作必须用"变量.value"
//因为n已经不是对象的属性了,只是setup函数的一个变量而已,所有再使用n时,不用this.!
var add=()=>{
console.log("点了+")
n.value++;
}
var minus=()=>{
console.log("点了-")
if(n.value>0){
n.value--;
}
}
//所有要在页面上显示的变量或事件处理函数都要放在return中返回才行!
//因为有的成员需要抛出到界面,而有的成员不需要抛出到界面仅程序内部使用!
//return就是分水岭!
return {
n, add, minus
}
}
});