![]() |
爱玩的跑步鞋 · 为iframe正名,你可能并不需要微前端 ...· 1 月前 · |
![]() |
卖萌的煎鸡蛋 · 线程工厂(Thread ...· 3 月前 · |
![]() |
光明磊落的玉米 · MySql :: stored ...· 1 年前 · |
![]() |
霸气的书签 · 内核与用户态程序共享内存的方法_内核态共享内 ...· 1 年前 · |
![]() |
无聊的板凳 · 教程:使用 Azure 专用终结点连接到 ...· 1 年前 · |
![]() |
面冷心慈的大象 · Python根据关键字逐行提取文本内容_py ...· 1 年前 · |
Linux安装Mysql学习一下如何在服务器上安装mysql学习文章:Linux CentOS7 安装MySQL8详细步骤讲的太详细了! 直接安装好! 力推!1.获取安装包前往官网下载对应Linux版本的压缩包并上传至服务器或者使用 wget 直接在服务器下载官网:MySQL :: Download MySQL Yum Repository清华镜像下载:Index of /mysql/yum/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror查看Linux版本命令:lsb_release -a在官网选择对应版本安装即可 (Centos选择 Linux-Generic即可)2.配置依赖使用yumremove mysql卸载mysql即其相关依赖 yum remove mysql检查是否有MariaDB并卸载rpm -qa | grep mariadb有的话就删除rpm -e --nodeps mariadb-libs-5.5.68-1.el7.x86_64# 安装mysql需要的依赖 yum install libaio3.解压安装包# 解压 xz 后缀 xz -d mysql-8.0.25-linux-glibc2.12-i686.tar.xz # 解压 tar后缀 tar -xvf mysql-8.0.25-linux-glibc2.12-i686.tar将解压的文件夹重命名为mysql一般将 mysql 放置在 /usr/local/mysql 目录下暂时赋予MySQL安装目录最大权限,方便安装chmod -R 777 /usr/local/mysql/4.创建mysql组合用户创建用户组: groupadd mysql创建用户并添加到组:useradd -r -g mysql -s /bin/false mysql将mysql目录所有权给创建的mysql用户:chown -R mysql:mysql ./5.修改Mysql配置文件mysql配置文件在 /etc/my.cnf 中: vi /etc/my.cnf将原内容全部删除改为以下内容# mysql服务端相关设置 [mysqld] # 设置端口 port=3306 # 设置mysql的安装目录 basedir=/usr/local/mysql # 设置mysql数据库的数据的存放目录 datadir=/usr/local/mysql/mysqldb # 允许最大连接数 max_connections=10000 # 允许连接失败的次数。这是为了防止有人从该主机试图攻击数据库系统 max_connect_errors=10 # 服务端使用的字符集默认为UTF8 character-set-server=utf8 # 创建新表时将使用的默认存储引擎 default-storage-engine=INNODB # 默认使用“mysql_native_password”插件认证 default_authentication_plugin=mysql_native_password # 设置连接客户端相关配置 [mysql] # 设置mysql客户端默认字符集 default-character-set=utf8 # [client]是mysql客户端执行的时候才会加载的选项组 [client] # 默认使用的端口 port=3306 # 字符集 default-character-set=utf86.进行安装安装MySQL进入mysql的bin目录下再执行./mysqld --initialize --console进行安装初始化# 最后一行这里会出现一个初始密码 9yru_L.=awom 类似这样的 2022-11-10T03:17:39.252918Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: 9yru_L.=awom启动MySQL先进入mysql的support-files目录再使用./mysql.server start命令进行启动如果启动失败了使用chmod -R 777 /usr/local/mysql重新给mysql安装目录赋予权限,然后执行即可7.将MySQL添加到系统进程并设置自启动添加到系统进程cp /usr/local/mysql/support-files/mysql.server /etc/init.d/mysqld设置自动启动chmod +x /etc/init.d/mysqldsystemctl enable mysqld8.修改root用户登录密码并设置远程访问使用 ln -s /usr/local/mysql/bin/mysql /usr/bin 命令将mysql命令全局化(环境变量)更改默认密码# 使用默认密码登录 mysql-uroot -p # 修改为自己的密码 alter user user() identified by "123456";设置允许远程访问# 切换到mysql数据库 use mysql; # 更新为允许使用root远程访问 update user set user.host = '%' where user.User = 'root'; # 刷新生效改动 flush privileges;如果为购买的云服务器,此处可能还需要去云服务控制台中,开启3306端口的访问。MySQL常用命令# 启动mysql systemctl start mysql # 查看mysql状态 systemctl status mysql # 停止mysql systemctl stop mysql # 重启mysql systemctl restart mysql这篇文章写的很详细 就是有一点细节没配置好 我进行了相关的补充希望可用帮助到大家
JavaScript深拷贝,看这一篇就够啦!大家好我是雪人⛄最近面试的时候被问到了深拷贝,我自信满满的写出了使用JSON的快捷方法,与递归深拷贝方法(只写了基础版的拷贝对象)。然后...面试官:如果传入的是Map呢?⛄:那可以判断一下,加一个 clone Map 的。面试官:如果传入的是Set呢?⛄:那可以判断一下,加一个 clone Set 的。面试官:如果传入的是Date RegExp Function呢?⛄:嗯?面试官:如果 Obj 中含有 Symbol 为 key 的呢?⛄:嗯?嗯?嗯?面试官:如果有循环引用怎么办? ⛄:☞我直接呆住,这确实没看过,痛定思痛,火速补习了一波,分享给大家。数据类型在实现深拷贝之前,我们需要先了解一下JS的数据类型,一共有两大类。基本数据类型:number,string,boolean,undefined,null,symbol,bigint引用数据类型:object,function,array,map,set等..(都是对象)那么他们有什么区别呢?基本数据类型在赋值的时候会直接创建一个新的栈地址去存放,改变值的时候也是直接改变这个栈中的值。let a = 10 let b = a b = 11 console.log(a) // 10 console.log(b) // 11但是引用数据类型是存放在堆中的,我们使用的变量是一个存放在栈中的“指针”,这个指针指向堆中的地址。let a = { value: 10 let b = a b.value = 11 console.log(a) // {value:11} console.log(b) // {value:11}我们发现如果是引用数据类型改变 b 的值 a 的值同样被改变,但是基本数据类型不会,这就是我们刚刚说到的堆和栈的问题。深拷贝了解了数据类型的知识后,我们有时候会需要在一个对象上做一些更改,跟原对象做对比展示,这个时候我们就需要用到深拷贝得到一个新的对象改变他才不会改变原对象啦。基本数据类型直接返回即可,无需特殊处理。引用数据类型我们也提到,有很多种:普通的 Object,正则对象RegExp,Array,Map,Set,Date,Array,Function。我们需要分类处理,不同的引用数据类型有不同的处理方式,接下来就给大家实现一下。是否为引用数据类型我们先封装一个方法用于判断是否为引用数据类型,便于之后使用。使用 typeof 判断的只有时候 object 或者 function 是引用数据类型,但是我们需要注意 null,typeof null == object,所以我们需要加一个 target !== nullfunction isObject(target) { return (typeof target === 'object' && target !== null ) || typeof target === 'function' }基本数据类型基本数据类型直接返回即可function deepClone(target) { if(!isObject(target)) return target }Date RegExp日期对象和正则对象可以将其传入对应的构造器重新构造function deepClone(target) { // ... if([Date, RegExp].includes(target.constructor)) return new target.constructor(target) }Function函数我们也需要用到函数的构造函数我们可以看一下使用构造函数构造出的函数是什么let fun = new Function('return 1') console.log(fun.toString()) function anonymous( return 1 */从上面的代码可以发现构造出的函数就是将我们传入的字符串套进一个匿名函数中,那么我么就可以利用这个构造函数去深拷贝一个含函数但是被 clone 的函数 toString 后也有一个 function 关键字,嵌套进匿名函数里面怎么能用呢?我们可以加一个 return 让匿名函数把我们要拷贝的函数返回出去,然后调用这个匿名函数就可以得到新的被克隆的函数啦function deepClone(target) { // ... if (target instanceof Function) { return new Function('return ' + target.toString())() }Map SetMap Set 我们直接使用迭代器遍历创建即可,遇到 value 为引用数据类型继续递归调用我们的深拷贝函数这里用到了 clone 函数,后面我们会讲解封装,大家先理解为递归调用深拷贝引用数据类型即可。function deepClone(target) { // ... // Map 对象使用迭代器迭代 if (target instanceof Map) { let newMap = new Map() for (const [key, val] of target) { if (isObject(val)) newMap.set(key, clone(val)) else newMap.set(key, val) return newMap // Set 对象使用迭代器迭代 if (target instanceof Set) { let newSet = new Set() for (const val of target) { if (isObject(val)) newSet.add(clone(val)) else newSet.add(val) return newSet }ArrayArray也是直接遍历即可,遇到引用数据类型直接递归调用深拷贝function deepClone(target) { // ... // Array 直接遍历即可 if (target instanceof Array) { const n = target.length let newArray = new Array(n) for (let i=0;i<n;i++) { const item = target[i] if (isObject(item)) newArray[i] = clone(item) else newArray[i] = item return newArray }Object普通 Object 我们要考虑以 string 和 symbol 为 key 值的两种情况,所以要使用 Reflect.ownKeys 获取 key 值,返回一个数组能得到所有 key 值。遇到 key 为引用数据类型继续递归调用 遇到function deepClone(target) { // ... // Object 对象 // 考虑 symbol 与 string 为 key const keys = Reflect.ownKeys(target) let newObj = {} for (const key of keys) { const val = target[key] if (isObject(val)) newObj[key] = clone(val) else newObj[key] = val return newObj }循环引用在 Object 对象中我们还需要判断一种特殊情况。我们看一看以下代码。let a = { b: null let b = { a.b = b上面这种情况 a 和 b 循环引用,我们深拷贝的时候就会不断递归 a 和 b 最后导致栈溢出然后报错。我们可以使用 WeakMap 来将 clone 过的对想保存 已经存在在 WeakMap 中的对象就无需再次 clone 直接返回结束递归即可。function deepClone(target) { // 防止循环引用导致栈溢出 weakMap可以防止内存泄漏 // 需要一个全局变量来保存 这里我们再封装一个 clone 函数进行深拷贝 创建闭包 const hashMap = new WeakMap() function clone(target) { // ... // 考虑循环引用问题 如果该对象已存在,则直接返回该对象 if (hashMap.has(target)) return hashMap.get(target) // ... // Object 保存在 weakMap 中 防止之后的循环引用 hashMap.set(target, newObj) return newObj // 最后再返回调用一下 return clone(target) }最终代码最终代码如下function deepClone(target) { // 防止循环引用导致栈溢出 weakMap可以防止内存泄漏 const hashMap = new WeakMap() // 判断是否为引用数据类型 function isObject(target) { return (typeof target == 'object' && target!==null || typeof target == 'function') // 主要 clone 函数 function clone(target) { // 非引用数据类型直接返回即可 if (!isObject(target)) return target // 考虑循环引用问题 如果该对象已存在,则直接返回该对象 if (hashMap.has(target)) return hashMap.get(target) // Date 与 RegExp 对象可直接使用构造器构建 if ([Date, RegExp].includes(target.constructor)){ return new target.constructor(target) // Function 可以使用构造器构造 if (target instanceof Function) { return new Function('return ' + target.toString())() // Map 对象使用迭代器迭代 if (target instanceof Map) { let newMap = new Map() for (const [key, val] of target) { if (isObject(val)) newMap.set(key, clone(val)) else newMap.set(key, val) return newMap // Set 对象使用迭代器迭代 if (target instanceof Set) { let newSet = new Set() for (const val of target) { if (isObject(val)) newSet.add(clone(val)) else newSet.add(val) return newSet // Array 数组使用迭代器迭代 if (target instanceof Array) { const n = target.length let newArray = new Array(n) for (let i=0;i<n;i++) { const item = target[i] if (isObject(item)) newArray[i] = clone(item) else newArray[i] = item return newArray // Object 对象 // 考虑 symbol 与 string 为 key const keys = Reflect.ownKeys(target) let newObj = {} for (const key of keys) { const val = target[key] if (isObject(val)) newObj[key] = clone(val) else newObj[key] = val // Object 保存在 weakMap 中 防止之后的循环引用 hashMap.set(target, newObj) return newObj return clone(target) }如果对你有帮助的话,请帮我点个赞吧
2023年IPoAC“鸟联网”仍然是最好的数据传输方式!时过境迁,互联网发展不断,如今已是2023年。TCP/IP 网络模型已经能很好的保证网络信息的传输,但是他们相比于 IPoAC 还是“落后”了很多,它可以说是点对点通讯技术的鼻祖,今天就大家来了解一下这个协议。勤奋的鸽子数千年以来,人类一直在利用信鸽传递信息。尤其是在战争时期,信鸽扮演了举足轻重的角色。据称凯撒、成吉思汗与惠灵顿公爵(在滑铁卢战役中)都曾使用禽类进行信息传递。而在第一次世界大战期间,美国陆军通信兵与海军就随军配备鸽舍。法国政府甚至在凡尔登战役期间为一只名为Cher Ami的美国信鸽授勋,用以奖励她作出的卓越贡献。而到第二次世界大战期间,英国共投入超过25万只信鸽,其中32只获得Dickin奖章这一专门为战争中的动物设立的荣誉嘉奖。对于打算建立鸽子网络的人们而言,我们只需要训练它们学会在两点之间飞行,拿出时间并准备好吃的作为奖励。通过在一个地方喂养鸽子,并将它们放在另一个位置,鸽子们很快就能掌握这条路线。事实上,经过训练,鸽子们甚至能够从完全陌生的位置返回家园。在最顶尖的赛事当中,信鸽甚至能够飞行长达1800公。RFC 1149由来RFC1149 的创作背景可以追溯到1990年代,当时互联网还没有像今天这样普及,各种新奇的想法和实验都在进行之中。在1990年代初期,互联网标准化机构 IETF 发布了 RFC1149。这份文档最初由两名 IETF 成员,即戴夫·卡罗尔和戴夫·布里德福德共同创作,他们为这份文档起了一个有趣的名字——"IP over Avian Carriers with Quality of Service"。这份文档是在1990年4月1日发布的,也就是愚人节,因此 RFC1149 更被视为一种愚人节的玩笑。它描述了一种名为 “IP over Avian Carriers” 的网络协议,也就是所谓的 “鸟联网” 。虽然这是一份幽默的文档,但它反映了互联网标准化过程中的自由和创意,也提醒我们,即使在技术最为严谨的环境中,仍有空间让我们发挥想象力和幽默感。具体实现RFC1149描述的协议流程如下:准备数据包:将要传输的数据打印在小型纸片上,或者写在卡片或者其他轻量级载体上。绑定载体:将纸片或者其他载体绑在鸟类身上。这通常需要一个小型的背包或者类似的装置。释放鸟类:释放鸟类并让它自由飞行到目的地。建议在鸟类身上附上发件人和收件人的地址和联系方式,以便鸟类到达目的地后能够被找到。收集数据包:在鸟类到达目的地后,收集数据包,并进行解码和处理。优势鸟类能够进行洲际飞行,因此提供了比电缆调制解调器或DSL更高的带宽。可以提供高吞吐量和低成本的大批数据传输能力,鸟类可以携带几十克重量的SD卡,单次能够携带300TB甚至以上的数据。劣势传输速度慢,鸽子会以平均每小时70公里的速度持续飞行。而在短距离之内,短程赛鸽的速度可达到每小时177公里,如果想实现几千公里距离的传输需要几个小时才能到达。传输可靠性差,鸟类容易受到天气和环境等因素的影响,如遇极端天气,丢包率可高达100%。一种常见的丢包方式:RFC 2549RFC2549 是一份正式的标准化文档,在1999年4月1日发布,它是对 RFC1149 的一次改进和完善,提出了一些新的概念和方法,使得 “鸟联网” 这一概念更加具体化和可行化。可以使用其他动物进行传输:候鸟,可以借助其实现双向传输,候鸟一般会有认巢的习惯,到达新的栖息地之后,还会寻找之前的巢穴。企鹅,不建议使用,因为不会飞。存在的风险:鹰,有时候信息载体可能会进入鹰的体内,导致数据被损坏。RFC 6214在2011年4月1日,互联网国际标准机构再发表了《RFC 1149在IPv6的应用》,并完善更多内容。但是仍有以下缺陷:缺乏可用的本地载体,在某些地方,例如新西兰,绝大多数的载体只能进行短程跳跃,并且仅会在背景光子辐射极低的情况下进行。(鹬鸵无法飞行以及只在夜间活动)载体有感染 H5N1 的风险。多播通讯受限与载体的归巢能力,可能会陷入“路由循环”。可行性实验超远距离传输2001年9月7日,一个名为格雷厄姆·特朗西(Graham Troup)的计算机工程师带领了一个由南非的技术学院学生组成的团队,他们试图验证一种基于鸟类的互联网协议(IP over Avian Carriers,简称IPoAC)的可行性。这个团队在南非的一个小镇的邮局将一只名为温斯顿(Winston)的信鸽放飞,它带着一个小存储设备和一份1.7KB的数据文件。这份数据需要传输到距离南非12000英里以外的英国。信鸽温斯顿一路飞行,途中经历了风暴和高山等艰险的路途,最终于两天后到达了目的地。整个过程的数据传输速度非常慢,但是信鸽在这个过程中成功地克服了所有的障碍,完成了数据传输任务。较高的丢包率在2001年4月28日,IPoAC曾被卑尔根的一个Linux用户组成功实验,当时该协议被命名为CPIP(Carrier Pigeon Internet Protocol, 鸽载互联网协议)。他们往一个距离约5公里远的目标发送了9个数据包,每个数据包中包含一条ping报文(ICMP Echo Request),各由一只鸽子承载,最后收到了4条响应。脚本开始于 2001年 4月 28日 星期六 11:24:09 $ /sbin/ifconfig tun0 tun0 Link encap:Point-to-Point Protocol inet addr:10.0.3.2 P-t-P:10.0.3.1 Mask:255.255.255.255 UP POINTOPOINT RUNNING NOARP MULTICAST MTU:150 Metric:1 RX packets:1 errors:0 dropped:0 overruns:0 frame:0 TX packets:2 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 RX bytes:88 (88.0 b) TX bytes:168 (168.0 b) $ ping -c 9 -i 900 10.0.3.1 PING 10.0.3.1 (10.0.3.1): 56 字节的数据。 64 字节,来自 10.0.3.1: icmp_seq=0 ttl=255 时间=6165731.1 毫秒 64 字节,来自 10.0.3.1: icmp_seq=4 ttl=255 时间=3211900.8 毫秒 64 字节,来自 10.0.3.1: icmp_seq=2 ttl=255 时间=5124922.8 毫秒 64 字节,来自 10.0.3.1: icmp_seq=1 ttl=255 时间=6388671.9 毫秒 --- 10.0.3.1 ping 统计 --- 已发送 9 个包, 已接收 4 个包, 55% packet loss round-trip min/avg/max = 3211900.8/5222806.6/6388671.9 ms 脚本结束于 2001年 4月 28日 星期六 14:14:28结语即使经过了数千年,这些看似不起眼的可爱小鸟仍然保有自己的崇高地位。参考文献:RFC 1149RFC 2549RFC 6214参考文章:什么是“鸟联网”以及如何对它进行改进以鸽子为载体的“鸽联网”仍是数据传输的最快方式神奇的鸽子网络:最早的点对点通信技术ChatGPT发布时间:RFC1149:1990.4.1RFC2549:1999.4.1RFC6214:2011.4.12023年IPoAC“鸟联网”仍然是最好的数据传输方式:2023.4.1⛄:愚人节快乐,。
【从零到一手撕脚手架 | 第四节】加速开发效率 使用plop生成开发模板 使用mock进行数据模拟Hello大家好我是⛄,之前我们已经配置了脚手架需要具备的基本功能:代码封装,团队协作规范等。但是可能我们有其他的需求,比如说我们想快速生成几个基础的组件模板我们可以使用Plop或者使用文件写入实现。比如我们不想等后端同学的接口,可以直接使用mock模拟数据生成。GitHub:LonelySnowman/sv3-template官方文档:SV3-Family | Vue3前置知识:Vue全家桶,了解Vite或WebPack等构建工具,Node.js您将收获到:从零到一构建一个规范的 Vue3+TS+Vite 脚手架配置 plop想一想平时我们是如何新建一个组件的,右键->新建文件->命名文件->编写组件代码,或者直接使用命令行生成文件,并且这些组件都会编写一些重复的内容,这些内容在新建时就具备,我们平常可能就直接ctrl+c、ctrl+v,还是有一点繁琐的。有没有一种方法能快速生成各种组件的模板呢?接下来就要用到我们的plop啦,它可以帮助我们快速生成开发模板,提高效率。plop是一种“微型生成器框架”。 这是因为它提供了一种快速生成代码或其他文本文件的便捷方式,同时又保持了很小的体积。我们总是在代码中创建不同的结构和模式,如路由、控制器、组件、工具类等。这些模式会随着时间的变化不断地被改变和优化,所以当你想要去创建一个新的已经存在的模版时,你很难在你的项目/代码中找到对于当前需要创建的模板的最佳实践,这时候就轮到 plop 大展身手了。使用 plop, 你能够随时在项目中更新某种特定代码模式的最佳实践,你只需要在命令行中键入 plop 就能快速地运行代码。这不仅能避免从整个代码库中寻找最佳模板然后复制的过程,而且又能正确高效地创建文件。官方文档:Learning to Plop : PLOP (plopjs.com)安装依赖pnpm install -D plop编写命令行交互接下来以新建一个vue组件的模板为例在项目根目录下创建一个用于存放开发模板的文件plop-templates再新建一个component文件夹代表这里面存放开发组件的模板在component中新建index.hbs编写文件模板,prompt.js编写交互逻辑|plop-templates |components |index.hbs |prompt.js接下来我们就可以在prompt.js中编辑交互逻辑啦我们需要在该模块导出一个配置项,主要需要配置三个内容description:该模板的名称prompts:该模板需要的参数actions:执行后需要进行的操作具体详情可以看下面的代码,均已加入详细注释const fs = require('fs'); const path = require('path'); // 引入便于我们验证问价夹中是否存在重名文件的工具函数,后面有详细代码 const verifyFileExist = require('../utils'); // 引入我们需要创建该模板到达的基础路径位置 const baseFile = path.join(__dirname, '../../src/views'); module.exports = { // 编写对模板件的描述 description: '创建组件', // 编写命令行交互逻辑,可在命名行中获取参数 prompts: [ name: 'componentName', // 该参数的名称 之后可以使用 componentName 调用 type: 'input', // 该参数的类型 input 代表输入 message: '请输入页面名称', // 该交互的提示信息 validate: (dirName) => { // 传入一函数对用户传入的参数进行校验 if (!dirName || dirName.trim === '') { // 验证用户是否输入 return '组件名称不能为空'; // 返回字符串报错 } else if (verifyFileExist(dirName, baseFile)) { // 验证文件是否存在 return '组件已经存在'; // 返回字符串报错 } else { return true; // 返回 ture 则继续执行 action // 编写接收完参数后需要执行的操作 actions: (data) => { const basePath = path.join(baseFile, `./${data.pageName}`); // 新建一个组件文件夹 fs.mkdirSync(basePath); // 新建一个组件文件夹下的通用组件文件夹 fs.mkdirSync(path.join(baseFile, `./${data.pageName}/components`)); // 最后我们需要返回一个 actions // type 用于配置执行的操作类型 add 代表新增文件 // path 代表新建文件的路径 // templateFile 代表我们使用的模板 下文会有 const actions = [ type: 'add', path: path.join(basePath, 'index.vue'), templateFile: path.join(__dirname, './index.hbs'), return actions; };因为检测一个文件是否存在需要经常使用,我们将其封装成一个函数抽离出来,在plop-templates/utils.js中编写:就是一个简单的验证函数,大家根据自己的习惯编写即可const fs = require('fs'); // 验证文件在文件夹中是否存在 function verifyFileExist(dirName, dirPath, tail = '') { const directories = fs.readdirSync(dirPath); return directories.includes(dirName + tail); module.exports = verifyFileExist;编写开发模板我们在plop-templates/components/index.hbs中编写组件的模板:每一个字符都会被写入actions中返回的path目标文件中,只有被双大括号包裹的内容会被解析成prompts传入的参数。下面的 {{ properCase pageName }} 就是表示写入 pageName 参数 并且小写。<template> <div> {{ properCase pageName }} </div> </template> <script lang="ts" setup> // typeScript </script> <style lang="scss" scoped> /* scss */ </style>调用指令要使用我们的开发模板,我们需要在项目根目录下新建一个plopfile.js进行配置,详细内容如下:module.exports = function (plop) { // 设置欢迎语 plop.setWelcomeMessage('请选择需要创建的模板'); // 设置选择的生成器模板 // 第一个参数为对应名称 // 第二个参数为 prompts 的地址 plop.setGenerator('components', require('./plop-templates/page/prompt')); };然后我们在package.json中配置一条指令便于我们调用{ "scripts": { "gen": "plop" }运行该指令就可以快速生成我们配置好的模板啦。以上内容为一个简单的小示例,plop 还有非常多强大的功能,大家可以前往官方文档探索:Learning to Plop : PLOP (plopjs.com)项目里已为大家配置好了各种配置 mock想想我们平时对接接口是怎么做的,首先前后端确定好接口,让后确定好接口文档。如果已经编写好前端代码,就差与后端对接了,只能自己傻傻等待么。当然不是,我们可以使用 mock 工具进行数据的模拟,提前编写好数据展示的内容,最后对接的时候关闭 mock 即可。mock 实现的方式有很多,我们使用的 vite 就有一个插件满足我们的需求:vite-plugin-mock安装依赖pnpm install -D mockjs vite-plugin-mock返回内容配置mock 的编写方式非常非常简单,只需要一个数组即可。我们在项目根目录下新建一个mock文件夹,并在其中新建一个user.ts用于用户登录的数据模拟。配置方式也是非常的简单url:配置接口URL地址method:配置接口的请求方式response:编写一个函数,编写处理逻辑,并返回响应的内容。import { MockMethod } from 'vite-plugin-mock'; export default [ // 前面的 /mock 为 mock 生效需要配置的根路径 后面会提到 url: '/mock/api/login', method: 'post', // 使用 body 可以获取请求体 response: ({ body }) => { // 简单编写一个逻辑 // 用户名不等于密码就是密码错误 if (body.username !== body.password) { // 返回JSON信息 return { code: 1, message: '密码错误', data: { username: '', roles: [], accessToken: '', // 其余的则显示登录成功 if (body.username === 'admin') { return { code: 0, message: '登录成功', data: { username: 'admin', roles: ['admin'], accessToken: 'admin', } else { return { code: 0, message: '登录成功', data: { username: 'common', roles: ['common'], accessToken: 'common', ] as MockMethod[]; 启用 mock启动 mock 需要我们配置一下 vite// 在 plugins 数组中加入如下配置 viteMockServe({ // 如果接口为 /mock/xxx 以 mock 开头就会被拦截响应配置的内容 mockPath: 'mock', // 数据模拟需要拦截的请求起始 URL localEnabled: true, // 本地开发是否启用 prodEnabled: false, // 生产模式是否启用 ]然后我们所有以 mock 开头的接口都会成为我们使用 mock 模拟的返回数据关闭 mock如果后端同学接口测试完毕,需要我们正式对接了,我们不想启用 mock 了该怎么办,可以在 vite 配置中关闭,但我喜欢使用环境变量配置mock的开关编写.env// 后端接口的根路径 VITE_APP_API_BASEURL = /api // 需要模拟的请求根路径 VITE_APP_MOCK_BASEURL = /mock/api编写.env.development// 选择 development 模式下是否开启 mock VITE_APP_USE_MOCK = true编写.env.production// 选择 production 模式下是否开启 mock VITE_APP_USE_MOCK = true配置 axios 全局请求路径之前我们已经将 axios 封装在 src/utils/http/index.ts 中,我们可以在此根据环境变量配置 axios 的请求路径const service: AxiosInstance = axios.create({ // 启用 mock 就请求 mock 路径 // 不启用 mock 就请求 正常后端路径 baseURL: Boolean(import.meta.env.VITE_APP_USE_MOCK) ? import.meta.env.VITE_APP_MOCK_BASEURL : import.meta.env.VITE_APP_API_BASEURL, timeout: 15000, });注意:这里我们并不需配置后端地址,统一请求 LocalHost。我们只需要开启本地代理在 vite proxy 中配置后端地址即可,这样方便一些。这样我们就可以轻松的选择开关 mock 啦结语每个人的使用习惯不同,这里提供了我常用的方法供大家参考,如有其他使用方法欢迎大家讨论。终于把第四节更新完啦,一个最基础的 Vue3+Vite+TS 前端开发脚手架就搭建完成了,日后技术栈更新会帮助大家提供更改好的方案,大家对该项目的意见也请及时指出,我会汲取大家的意见不断学习并更新。系列文章:【从零到一手撕脚手架 | 第一节】配置基础项目结构 Vite + TypeScrpit + Vue3 初始化项目【从零到一手撕脚手架 | 第二节】模块化封装 降低耦合度 封装 axios pinia router【从零到一手撕脚手架 | 第三节】项目集成CommitLInt+ESLint+Prettier+StyleLint+LintStaged【从零到一手撕脚手架 | 第四节】加速开发效率 使用plop生成开发模板 使用mock进行数据模拟参考学习项目:fast-vue3vue-pure-admin如果有任何不正确的地方请指正,我会及时更改。更文不易,如果对你有帮助的话,请给我点个赞吧关注我,后续文章不迷路⛄
【从零到一手撕脚手架 | 第三节】项目集成CommitLInt+ESLint+Prettier+StyleLint+LintStagedHello大家好我是⛄,前两节教大家如何初始化一个脚手架项目以及如何封装Vue技术栈常用的工具库。本小节教大家如何向我们的脚手架中配置ESLint、Prettier、StyleLint、LintStage。帮助大家规范项目代码,能够更好的进行团队协作开发。项目地址:GitHub:LonelySnowman/sv3-template官方文档:SV3-Family | Vue3前置知识:Vue全家桶,了解Vite或WebPack等构建工具,Node.js您将收获到:从零到一构建一个规范的 Vue3+TS+Vite 脚手架配置 ESLintESLint 是一个用来识别 ECMAScript 并且按照规则给出报告的代码检测工具,使用它可以避免低级错误和统一代码的风格。它拥有以下功能:查出 JavaScript 代码语法问题。根据配置的规则,标记不符合规范的代码。自动修复一些结构、风格问题。防止代码成为shi山,可以帮我们检查有没有死循环,有没有定义但未使用的变量等等。ESLint官方文档:ESLint - Pluggable JavaScript linter - ESLint中文安装依赖pnpm install -D eslint @eslint/create-config@eslint/create-config:eslint配置文件初始化工具生成配置文件# 生成 ESLint 配置文件模板 npx eslint --init# 出现如下选择 # 选择2 我们会使用 prettier 进行代码风格校验 How would you like to use ESLint? 1.只检查语法 2.检查语法并提示问题 3.检查语法、提示问题并且强制使用一些代码风格 # 你的项目用的哪一种模块化方式 选择1 What type of modules does your project use? 1.ES6 2.CommonJS 3.None # 使用的框架 选择2 Which framework does your project use? 1.React 2.Vue.js 3.None # 项目是否使用TS yes Does your project use TypeScript? # 项目在哪里跑的 选择1 Where does your code run? 1.browser 2.node # 项目用哪种配置文件 选择1 What format do you want your config file to be in? 1.JavaScript 2.YAML 3.JSON # 是否立即安装需要的依赖 Would you like to install them now? # 会帮我们安装如下插件 # pnpm install -D eslint-plugin-vue@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest熟悉之后我们就可以不使用配置生成工具直接新建配置文件进行配置即可ESLint配置文件名称可以为:.eslintrc.js、.eslint.config.js (根据个人习惯选择即可)这里是我常用的配置规则,更多配置规则请查阅ESLint中文。单独的语法配置需要在rules中编写,全部配置请参考:List of available rules - ESLint中文module.exports = { // 使 eslint 支持 node 与 ES6 env: { browser: true, es2021: true, node: true, // 引入推荐的语法校验规则 extends: [ 'plugin:vue/vue3-recommended', 'plugin:@typescript-eslint/recommended', overrides: [], 这里一定要配置对 先使用vue-eslint-parser 再使用@typescript-eslint/parser 先解析 <template> 标签中的内容 然后再解析 vue <script> 标签中的 TS 代码 // 选择使用的解析器 parser: 'vue-eslint-parser', // 解析器的详细配置 parserOptions: { // 使用最新版 ES 语法 ecmaVersion: 'latest', // 使用 ESLint TS 解析器 parser: '@typescript-eslint/parser', // 使用 ES 模块化规范 sourceType: 'module', // 使用的插件 plugins: ['vue', '@typescript-eslint'], // 自定义规则 rules: {}, };配置完规则后我们配置一条指令用于代码质量的修复// package.json // --cache 为仅检测改动过的代码 // --max-warnings 0 表示出现超过0个警告强制eslint以错误状态推出 "scripts": { "lint:eslint": "eslint --cache --max-warnings 0 {src,mock}/**/*.{vue,ts,tsx} --fix", },我们还可以配置忽略文件,让 ESLint 不对这些文件进行校验。新建 .eslintignore 进行配置。node_modules .vscode .idea /public /docs .husky .local Dockerfile自动修复大部分IDE支持在修改代码后进行自动修复WebStorme:直接在Setting中搜索ESLint即可进行配置VSCode:需要在项目目录下加入如下配置文件,还需要下载ESLint插件/.vscode/settings.json{ "editor.codeActionsOnSave": { "source.fixAll": true }配置 Prettier多人协作的项目开发中,保持统一的代码风格是一件很重要的事。Prettier就可以帮我们做到这个事情,但是ESLint不是也能规范代码风格么?可以是可以,但是相比Prettier差了很多,术业有专攻,Prettier可以让我们拥有超级整齐的代码。可以帮助我们配置是否使用分号,缩进的格式等等。官方文档:Prettier 中文网 · Prettier 是一个“有态度”的代码格式化工具安装依赖pnpm install -D prettier添加配置文件Prettier配置文件名称可以为:.ptettierrc.js、.ptettier.config.js (根据个人习惯选择即可)下面是我常用的一些配置,更多配置规则大家可以前官网查看:Options · Prettier 中文网module.exports = { printWidth: 80, //单行长度 tabWidth: 2, //缩进长度 useTabs: false, //使用空格代替tab缩进 semi: true, //句末使用分号 singleQuote: true, //使用单引号 }配置一个指令便于我们使用prettier进行修复代码风格{ "script": { "lint:prettier": "prettier --write **/*.{js,json,tsx,css,less,scss,vue,html,md}", }解决ESLint与Prettier冲突问题安装依赖pnpm install -D eslint-config-prettier eslint-plugin-prettiereslint-config-prettier 的作用是关闭eslint中与prettier相互冲突的规则。eslint-plugin-prettier 的作用是赋予eslint用prettier格式化代码的能力。 安装依赖并修改.eslintrc文件。配置ESLint// 此配置在eslint配置文件中新增 "extends": [ "eslint:recommended", "plugin:vue/vue3-recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended" // 在最后面新增extends ],配置完成之后我们对代码风格的配置只会使用prettier的配置,相当于将eslint中冲突的规则覆盖掉了。我们还可以配置忽略文件,让 Prettier 不对这些文件进行校验。新建 .prettierignore 进行配置。/dist/* /node_modules/**配置 StyleLinttylelint 是一个强大、先进的 CSS 代码检查器(linter),可以帮助你规避 CSS 代码中的错误并保持一致的编码风格。你可能会想:WTF,怎么有有一个配置编码风格的,不是已经有Prettier了么。StyleLint,是专用于规范样式代码的工具,可以做到一些Prettier做不到的功能,有了它能让我们的样式代码(CSS/Less/Scss)更加美观,比如说对CSS样式代码进行顺序规定。但是 StyleLint 与 Prettier 也有配置冲突,所以我们也要将 StyleLint 中与 Prettier 冲突的配置关闭。安装依赖pnpm install -D stylelint stylelint-config-standard pnpm install -D stylelint-config-prettier stylelint-config-html stylelint-order stylelint-scss postcss-html postcss-scss stylelint-config-standard-vuestylelint-config-standard:StyleLint 推荐配置stylelint-config-prettier:关闭与 prettier 冲突的配置stylelint-config-standard-vue:StyleLint Vue 项目推荐配置postcss-html postcss-scss:支持检查 scss 与 htmlstylelint-order:支持 css 样式排序添加配置文件Prettier配置文件名称可以为:.stylelintrc.js、.stylelint.config.js (根据个人习惯选择即可)更多详细配置规则请查看官方文档:List of rules | Stylelint 中文文档 (bootcss.com)module.exports = { // 继承推荐规范配置 extends: [ 'stylelint-config-standard', 'stylelint-config-prettier', 'stylelint-config-recommended-scss', 'stylelint-config-standard-vue', // 添加规则插件 plugins: ['stylelint-order'], // 不同格式的文件指定自定义语法 overrides: [ files: ['**/*.(scss|css|vue|html)'], customSyntax: 'postcss-scss', files: ['**/*.(html|vue)'], customSyntax: 'postcss-html', // 忽略检测文件 ignoreFiles: [ '**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts', '**/*.json', '**/*.md', '**/*.yaml', // 自定义配置规则 rules: { // 便于配置变量 关闭未知属性检测 'property-no-unknown': null, // 指定类选择器的模式 'selector-class-pattern': null, // 允许 Vue 的 global 'selector-pseudo-class-no-unknown': [ true, ignorePseudoClasses: ['global'], // 允许 Vue 的 v-deep 'selector-pseudo-element-no-unknown': [ true, ignorePseudoElements: ['v-deep'], // 允许对应内核前缀 'property-no-vendor-prefix': null, // 指定样式的排序 修复后会帮我们自动整理CSS样式的顺序 'order/properties-order': [ 'position', 'top', 'right', 'bottom', 'left', 'z-index', 'display', 'justify-content', 'align-items', 'float', 'clear', 'overflow', 'overflow-x', 'overflow-y', 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left', 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'width', 'min-width', 'max-width', 'height', 'min-height', 'max-height', 'font-size', 'font-family', 'text-align', 'text-justify', 'text-indent', 'text-overflow', 'text-decoration', 'white-space', 'color', 'background', 'background-position', 'background-repeat', 'background-size', 'background-color', 'background-clip', 'border', 'border-style', 'border-width', 'border-color', 'border-top-style', 'border-top-width', 'border-top-color', 'border-right-style', 'border-right-width', 'border-right-color', 'border-bottom-style', 'border-bottom-width', 'border-bottom-color', 'border-left-style', 'border-left-width', 'border-left-color', 'border-radius', 'opacity', 'filter', 'list-style', 'outline', 'visibility', 'box-shadow', 'text-shadow', 'resize', 'transition', };我们还可以配置忽略文件,让 StyleLint 不对这些文件进行校验。新建 .stylelintignore 进行配置。/dist/* /public/* public/* /mock/* /node_modules/* /plop-templates/* /types/*配置 Husky团队协作时,我们会遇到多种问题,最让人难受的就是每个人书写的代码风格不一致,使用的规范不一致,导致团队协作效率极低,代码可读性差。这时候就需要 Husky 来帮忙了,它可以帮助我们在代码提交前后进行一些自定义的操作,像是代码风格的校验,并且它支持所有的 Git 钩子(钩子是你可以放在钩子目录中触发的程序 在 Git 执行的某些点执行的操作)。Git全部钩子的详细介绍:Git - githooks Documentation (git-scm.com)本文用到的钩子:在 pre-commit 触发时进行代码格式验证,在 commit-msg 触发时对 commit 消息和提交用户进行验证。git hook执行时期备注pre-commitgit commit 执行前git commit --no verify 命令可以绕过该钩子commit-msggit commit 执行前git commit --no verify 命令可以绕过该钩子安装依赖pnpm install -D husky然后配置一个初始化 Hysky 的命令// package.json "script": { "prepare": "husky install" }运行这个命令# 运行后会初始化husky pnpm run prepare运行之后就会出现.husky文件夹,之后我们就可以配置在GItHook中执行的操作啦。配置 LintStaged有些同学感觉使用IDE的保存自动修复相当麻烦,我每次改完之后都需要等他修复一下,太不方便啦!接下来就要请出我们的好帮手:lint-staged,它可以帮助我们在 git 缓存中进行代码质量与风格的修复,在代码提交前进行统一校验,通过后才可以提交。安装依赖pnpm install -D lint-staged配置在 package.json 配置一个指令方便我们使用{ "script":{ "lint:lint-staged": "lint-staged", }使用 husky 配置一个 pre-commit 钩子# --no-install 代表强制使用本地模块 npx husky add .husky/pre-commit "npm run lint:lint-staged"LintStaged 的配置文件方式也有多种:在 package.json 中配置lint-staged.js 或 lint-staged.config.js.lintstagedrc.json 或 .lintstagedrc.yaml等等这里我们直接在 package.json 中配置// 我们直接配置在 package.json 中 配置的含义: <需要执行的文件>: <对应文件需要执行的命令数组> "lint-staged": { "*.{js,jsx,ts,tsx}": [ "eslint --fix", "prettier --write" "{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [ "prettier --write--parser json" "package.json": [ "prettier --write" "*.vue": [ "eslint --fix", "prettier --write", "stylelint --fix" "*.{scss,less,styl,html}": [ "stylelint --fix", "prettier --write" "*.md": [ "prettier --write" }这样就配置完毕啦,当我们使用 git commit -m "xxx" 时,lint-staged 会自动执行帮我们进行代码质量与风格的修复。配置 CommitLint每次看 Github 仓库的时候,总感觉有些不顺眼。原来是提交附带的信息乱糟糟的,都是"新增xx功能","修复xxBUG",非常的不工整,看着太不舒服啦。不要慌,我们可以使用 CommitLint 对提交的信息进行规范。官方文档:commitlint - Lint commit messages安装依赖pnpm install -D @commitlint/cli @commitlint/config-conventional@commitlint/config-conventional:commitlint自定义配置规则插件配置规则配置CommitLint配置文件名称可以为:.commitlintrc.js、.commitlint.config.js (根据个人习惯选择即可)配置文件内容如下官方文档的配置规则讲解:Rules (commitlint.js.org)规则由名称和配置数组组成:<配置名称>: [<警报级别>, <是否启用>, <规则对应的值>]警报级别0 无提示 disable1 警告 warning2 错误 error是否启用always 启用never 禁用规则对应的值:查看官方文档进行配置// 这里是通俗的解释 详情请前往官方文档查阅 module.exports = { ignores: [(commit) => commit.includes('init')], extends: ['@commitlint/config-conventional'], rules: { // 信息以空格开头 'body-leading-blank': [2, 'always'], 'footer-leading-blank': [2, 'always'], // 信息最大长度 'header-max-length': [2, 'always', 108], // 信息不能未空 'subject-empty': [2, 'never'], // 信息类型不能未空 'type-empty': [2, 'never'], // 提交信息的类型 下文有介绍 'type-enum': [ 'always', 'feat', 'fix', 'perf', 'style', 'docs', 'test', 'refactor', 'build', 'ci', 'chore', 'revert', 'wip', 'workflow', 'types', 'release', };提交信息的类型一般有如下规范(可根据团队习惯进行更改):feat : 增加一个新特性fix : 修复一个 bugperf : 更改代码以提高性能build : 更改构建系统和外部依赖项(如将 gulp 改为 webpack,更新某个 npm 包)ci : 对 CI 配置文件和脚本的更改docs : 仅仅修改文档说明refactor : 代码重构时使用style : 不影响代码含义的改动,例如去掉空格、改变缩进、增删分号test : 增加新的测试功能或更改原有的测试模块钩子配置# 配置 commit-msg 钩子 运行 commitlint # --no-install 代表强制使用本地模块 npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"配置完成后我们就可以在git commit时进行信息的规范我们需要按照以下格式进行提交:# git commit -m "类型: 信息" # 新增功能示例 git commit -m "feat: 新增用户登录功能" # 修复Bug示例 git commit -m "fix: 修复首页用户头像不显示问题"结语终于配置完啦,用到的依赖实在是太多啦,配置的头都大了,希望日后能有一个库能集成它们的所有功能,这样就会方便的不止一点,待我日后成长起来,如果没人去搞⛏,我可以尝试一下(胡言乱语)。系列文章:【从零到一手撕脚手架 | 第一节】配置基础项目结构 Vite + TypeScrpit + Vue3 初始化项目【从零到一手撕脚手架 | 第二节】模块化封装 降低耦合度 封装 axios pinia router【从零到一手撕脚手架 | 第三节】项目集成CommitLInt+ESLint+Prettier+StyleLint+LintStaged【从零到一手撕脚手架 | 第四节】加速开发效率 使用plop生成开发模板 使用mock进行数据模拟参考学习项目:fast-vue3vue-pure-admin如果有任何不正确的地方请指正,我会及时更改。更文不易,如果对你有帮助的话,请给我点个赞吧关注我,后续文章不迷路⛄
【从零到一手撕脚手架 | 第二节】模块化封装 降低耦合度 封装 axios pinia routerHello大家好我是⛄,前一节我们讲解了脚手架的基础项目搭建。接下来教大家将Vue技术栈常用的工具进行封装,让我们项目的代码更易维护。GitHub:LonelySnowman/sv3-template官方文档:SV3-Family | Vue3前置知识:Vue全家桶,了解Vite或WebPack等构建工具,Node.js您将收获到:从零到一构建一个规范的 Vue3+TS+Vite 脚手架封装axios状态码提示当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含 HTTP 状态码的信息头(server header)用以响应浏览器的请求。每个状态码都代表一种提示信息分类分类描述1**信息,服务器收到请求,需要请求者继续执行操作2**成功,操作被成功接收并处理3**重定向,需要进一步的操作以完成请求4**客户端错误,请求包含语法错误或无法完成请求5**服务器错误,服务器在处理请求的过程中发生了错误但是用户不一定了解每种状态码对应的提示信息,我们可以将状态码进行封装,将对应的中文含义返回给予用户提示。我们封装一个方法用于获取常见状态码对应的中文信息,将他放置在 /src/utils/http/status.ts:// 传入状态码获取对应提示信息 export const getMessage = (status: number | string): string => { let message = ''; switch (status) { case 400: message = '请求错误(400)'; break; case 401: message = '未授权,请重新登录(401)'; break; case 403: message = '拒绝访问(403)'; break; case 404: message = '请求出错(404)'; break; case 408: message = '请求超时(408)'; break; case 500: message = '服务器错误(500)'; break; case 501: message = '服务未实现(501)'; break; case 502: message = '网络错误(502)'; break; case 503: message = '服务不可用(503)'; break; case 504: message = '网络超时(504)'; break; case 505: message = 'HTTP版本不受支持(505)'; break; default: message = `连接出错(${status})!`; return `${message},请检查网络或联系管理员!`; };然后我们在响应拦截器对响应码进行判断提示,如果不是成功响应发出提示给用户,这里直接使用ElementPlus的Message组件,大家可以根据习惯选择其他方式。/src/utils/http/index.tsimport { getMessage } from './status'; import { ElMessage } from 'element-plus'; // ... // axios响应拦截 // 给予用户友好提示 service.interceptors.response.use( (response: AxiosResponse) => { if (response.status === 200) { return response; ElMessage({ message: getMessage(response.status), type: 'error', return response; // 请求失败 (error: any) => { const { response } = error; if (response) { // 请求已发出,但是不在2xx的范围 ElMessage({ message: getMessage(response.status), type: 'error', return Promise.reject(response.data); ElMessage({ message: '网络连接异常,请稍后再试!', type: 'error', );接口管理在我们成功将axios进项目之后,总是在组件中直接单独引用axios再进行配置请求是十分不方便的,对于一个接口我们可能会有多个组件会用到。接口一般会有一层最外围的规范,下面以一个最简单的为例:参数类型说明备注codeNumber结果码成功=1失败=-1未登录=401无权限=403messageString显示信息给予用户的提示信息dataObject数据JSON 格式所有接口均会按照这样的格式返回,那么我们可以使用TS设计一个类型,便于我们获得类型提示与校验。我们将他放置在 /src/utils/http/types.ts 下,并且可以传入一个泛型进行 data 数据格式的类型校验。export interface BaseResponse<T = any> { code: number | string; message: string; data: T; }这样我们就可以对第一层响应进行特殊处理,如果code不为1则说明发生错误,直接给予用户提示。/src/utils/http/index.ts// BaseResponse 为 res.data 的类型 // T 为 res.data.data 的类型 不同的接口会返回不同的 data 所以我们加一个泛型表示 // 此处相当于二次响应拦截 // 为响应数据进行定制化处理 const msgRequest = <T = any>(config: AxiosRequestConfig): Promise<T> => { const conf = config; return new Promise((resolve, reject) => { service .request<any, AxiosResponse<BaseResponse>>(conf) .then((res: AxiosResponse<BaseResponse>) => { const data = res.data; // 如果data.code为错误代码返回message信息 if (data.code != 1) { ElMessage({ message: data.message, type: 'error', reject(data.message); } else { ElMessage({ message: data.message, type: 'success', // 此处返回data信息 也就是 api 中配置好的 Response类型 resolve(data.data as T); };请求方式有多种,POST、GET、PUT、DELETE等等,为了简化axios配置项的使用,我们可以对不同的请求方式进行封装。为了获得TS的类型校验与提示,我们传入两个泛型,一个代表请求参数类型,一个代表返回的data类型。/src/utils/http/index.ts// 在最后使用封装过的axios导出不同的请求方式 export function get<T = any, U = any>( config: AxiosRequestConfig, url: string, parms?: U ): Promise<T> { return msgRequest({ ...config, url, method: 'GET', params: parms }); export function post<T = any, U = any>( config: AxiosRequestConfig, url: string, data: U ): Promise<T> { return msgRequest({ ...config, url, method: 'POST', data: data }); }接口分类最基础的接口封装完毕了,接下来我们要使用这些接口。就需要将这些接口分类管理,负责用户信息管理的接口放在一起,负责权限管理的接口放在一起等等。我们在/src/api下建立不同的文件夹代表不同类型的API,在index.ts中编写接口配置,在types.ts中编写接口所需的请求参数类型以及响应类型。/src/api/user/types.ts// 登录所需的参数 export type LoginRequest = { username: string; password: string; // 刷新登录信息需要的参数 export type reLoginRequest = { accessToken: string; // 登录后返回的响应信息 export type LoginResponse = { username: string; roles: Array<string>; accessToken: string; };然后我们就可以对此类型不同的接口进行封装,之后在组件中或者再次封装在Store中使用即可。/src/api/user/index.tsimport { post } from '@/utils/http'; // 导入类型 import { LoginRequest, LoginResponse, reLoginRequest } from '@/api/user/types'; // post 请求直接传入一个 data 即可 url 我们直接在此处封装好 // 需要更改时也只需在此处更改 export const userLogin = async (data?: LoginRequest) => { return post<LoginResponse>({}, '/login', data); export const refreshUserInfo = async (data?: reLoginRequest) => { return post<LoginResponse>({}, '/getUserInfo', data); };使用的时候我们可以直接在组件中引用,也可将其封装在store的action中,将相关的store与接口关联起来封装routerrouter在使用过程中如果我们直接在一个文件的一个数组中配置,最后路由越来越多会导致不易管理,我们可以将一个页面的路由配置在一个数组中最后统一导入,这样就会方便很多。我们将不同页面的路由放置在/src/router/modules/xxx.tsimport { RouteRecordRaw } from 'vue-router'; export default { path: '/login', name: 'LoginPage', component: () => import('@/views/login/index.vue'), meta: { role: ['common', 'admin'], children: [], } as RouteRecordRaw;然后我们在/src/router/index.ts导入这个路由import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'; import { ElMessage } from 'element-plus'; // import.meta.glob 为 vite 提供的特殊导入方式 // 它可以将模块中全部内容导入并返回一个Record对象 // 默认为懒加载模式 加入配置项 eager 取消懒加载 const modules: Record<string, any> = import.meta.glob(['./modules/*.ts'], { eager: true, const routes: Array<RouteRecordRaw> = []; // 将路由全部导入数组 Object.keys(modules).forEach((key) => { routes.push(modules[key].default); //导入生成的路由数据 const router = createRouter({ history: createWebHashHistory(), routes, router.beforeEach(async (_to, _from, next) => { next() router.afterEach((_to) => { NProgress.done(); export default router;这样我们就可以在module中直接创建路由,无需再次在index.ts中手动引入了。封装store同axios与touter一样,也拥有许多同类别的store数据,我们将他们放置在一个模块中便于调用,例如 user 模块专门用于保存与用户相关的信息与方法。/src/store/index.ts这里用于导出需要使用的pinia并使用持久化插件import { createPinia } from 'pinia'; import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; const pinia = createPinia(); pinia.use(piniaPluginPersistedstate); export default pinia;/src/store/xxx/types.ts以 user 为例这里用于定义stroe中state数据的类型export interface UserState { username: string; accessToken: string; refreshToken: string; roles: Array<string>; }/src/store/xxx/index.ts定义store模块的主要内容,state、getter、actionsstate用于报错与用户相关的数据getter保存需要二次处理的数据action封装一些与user模块相关的方法,我们刚刚封装过的api如果需要直接改变用户数据直接在action中调用即可import { defineStore } from 'pinia'; import { UserState } from './types'; import pinia from '@/store'; import { refreshUserInfo, userLogin } from '@/api/user'; import router from '@/router'; export const useUserStoreHook = defineStore( // 唯一ID 'User', state: () => ({ username: '游客', accessToken: '', roles: ['common'], getters: {}, actions: { // 用于更新store数据 // UserState为定义好的state类型 updateInfo(partial: Partial<UserState>) { this.$patch(partial); // 用户登录 storeUserLogin(data) { return userLogin(data).then((res) => { this.username = res.username; this.roles = res.roles; this.accessToken = res.accessToken; return res; // 刷新用户信息 refreshUserInfo() { if (this.username == '游客' && this.accessToken != '') { refreshUserInfo({ accessToken: this.accessToken, .then((res) => { this.username = res.username; this.roles = res.roles; this.accessToken = res.accessToken; .catch(() => { this.accessToken = ''; // 持久化保存 accessToken persist: { key: 'userInfo', storage: sessionStorage, paths: ['accessToken'], // 导出该Store export function useUserStore() { return useUserStoreHook(pinia); }使用的时候我们直接在需要使用store数据的组件中引用并使用即可<script lang='ts' setup> import { useUserStore } from '@/store/modules/user' const userStore = useUserStore() </script>结语vue3技术栈的常用的基础封装就完成了,每个人的封装习惯各不相同,只要团队用起来方便快捷就好。一个基础的 Vue3+TypeScrpit+Vite 的项目就此构造完毕!系列文章:【从零到一手撕脚手架 | 第一节】配置基础项目结构 Vite + TypeScrpit + Vue3 初始化项目【从零到一手撕脚手架 | 第二节】模块化封装 降低耦合度 封装 axios pinia router【从零到一手撕脚手架 | 第三节】项目集成CommitLInt+ESLint+Prettier+StyleLint+LintStaged【从零到一手撕脚手架 | 第四节】加速开发效率 使用plop生成开发模板 使用mock进行数据模拟参考学习项目:fast-vue3vue-pure-admin如果有任何不正确的地方请指正,我会及时更改。更文不易,如果对你有帮助的话,请给我点个赞吧关注我,后续文章不迷路⛄
【从零到一手撕脚手架 | 第一节】配置基础项目结构 Vite + TypeScrpit + Vue3 初始化项目hello大家好我是雪人⛄,不知不觉断更好久了,经过了长时间的学习,终于踏入了前端工程化的大门,大家再日常开发中总是会用到一个开发工具:脚手架,大家在使用其他人的脚手架或者一些官方脚手架的时候,可能只懂得使用并不懂得如何实现,看到一些代码只是知其然不知其所以然,今天为大家带来一套教程,教大家入门“脚手架”,相信你一定会有所收获。GitHub:LonelySnowman/sv3-template官方文档:SV3-Family | Vue3前置知识:Vue全家桶,了解Vite或WebPack等构建工具,Node.js您将收获到:从零到一构建一个规范的 Vue3+TS+Vite 脚手架项目使用的依赖:使用 Vite 进行项目构建使用 TypeScript使用 Sass 编写样式对 pinia,vue-router,axios 进行模块化封装使用 CommitLint,ESLint,StyleLint,Prettier,LintStage 进行团队项目规范使用 Mock.js 模拟数据,使用 plop 快速生成开发模板使用 ElementPlus 组件库确定项目目录结构首先列出目录结构,大家根据目录结构直接创建目录即可,后续会给大家详解这些目录。下面是我学习到的一些项目目录结构,大家可以参考,学习完毕后可以根据自己的习惯进行更改。sv3-template/ |- .husky/ # git钩子配置 |- build/ # 项目打包配置 |- mock/ # 数据请求模拟 |- plop-templates/ # 项目开发模板 |- public/ # 不经过打包的静态资源 |- type/ # ts类型定义 |- src/ # 项目资源 |- api/ # http请求管理 |- assets/ # 经过打包的静态资源 |- components/ # 通用组件 |- hooks/ # 通用组件状态逻辑函数 |- router/ # 项目路由管理 |- store/ # 组件状态管理 |- styles/ # 项目通用样式 |- utils/ # 工具函数 |- http/axios/ # axios封装 |- views/ # 页面组件接下来讲解一个基本的Vue3脚手架需要具备哪功能Vue3全家桶我们既然要开发Vue项目,Vue全家桶当然是最重要的,使用pinia进行状态管理,使用vue-router进行路由管理,axios进行http请求等等。下面会讲解全家桶的安装与基础配置,模块化的封装将会在下一节讲解。安装依赖这里我推荐大家使用pnpm进行依赖管理,pnpm的优点大家可在网上查阅,这里就不进行概述了。pnpm install axios pinia pinia-plugin-persistedstate vue vue-router nprogress # 本项目使用 element plus 大家可以根据个人习惯选择自己常用的组件库 pnpm install element-plus @element-plus/icons-vue pnpm install -D typescript sass下面简单介绍一下这些依赖的作用,大家根据个人习惯选择安装即可。vue:(⊙﹏⊙)这个应该不用多说axios:vue官方推荐http请求库pinia:vue官方推荐状态管理工具pinia-plugin-persistedstate:pinia数据持久化插件vue-router:路由管理工具typescript:使用TS语言sass:css预处理element-plus:亲民老牌组件库nprogres:简洁美观的进度加载条组件首先应该搭建一个基础的Vue项目结构新建以下目录sv3-template/ |- public/ # 不经过打包的静态资源 |- src/ # 项目资源 |- assets/ # 经过打包的静态资源 |- components/ # 通用组件 |- styles/ # 项目通用样式 |- utils/ # 工具函数 |- http/axios/ # axios封装 |- views/ # 页面组件 |- App.vue # 项目的主组件 |- main.ts # 入口ts文件 | - index.html # 入口html文件vueindex.html对页面进行基础配置<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" href="/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>sv3-template</title> </head> <body> <!-- 令 id="app" 便于vue进行挂载 --> <div id="app"></div> <!-- 引入main.ts文件 --> <script type="module" src="/src/main.ts"></script> </body> </html>/src/App.vue编写项目的主组件<template> <!-- 一般vue项目都会使用vue-router --> <!-- 所以我们这里直接写一个 router-view --> <router-view></router-view> </template> <script setup></script>/src/styles/_reset.scss_reset.scss是进行一个对基础HTML默认样式的重置这部分也是根据个人习惯配置即可这里引用一个开源项目:minireset.csshtml, body, blockquote, figure, fieldset, legend, textarea, iframe, margin: 0; padding: 0; font-size: 100%; font-weight: normal; list-style: none; button, input, select { margin: 0; html { box-sizing: border-box; *, *::before, *::after { box-sizing: inherit; video { height: auto; max-width: 100%; iframe { border: 0; table { border-collapse: collapse; border-spacing: 0; padding: 0; }/src/main.ts引入样式文件,挂载Vueimport { createApp } from 'vue'; import App from './App.vue'; import './styles/_reset.scss'; const app = createApp(App); app.mount('#app');/views/xxx.vue创建页面结构views/ |- home/ # 页面文件 |- components/ # 放置页面使用的组件 |- xxx.vue |- index.vue # 经过打包的静态资源我们这里可以随便写一个简单的组件<template> <div>Hello Vue</div> </template> <script lang="ts" setup></script> <style lang="scss" scoped></style>vue-router然后我们需要进行对路由的配置/src/router/index.ts这里路径中用到了 @ 是我们配置的别名,指向了src,在后面会讲解到如何配置import { createRouter, createWebHashHistory, RouteRecordRaw, RouteRecordRaw } from 'vue-router'; import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; // 配置路由 const routes: Array<RouteRecordRaw> = [{ path: '/', name: 'Home', component: () => import('@/viwes/home/index.vue'), meta: {}, children: [], const router = createRouter({ history: createWebHashHistory(), routes, router.beforeEach(async (_to, _from, next) => { NProgress.start(); next() router.afterEach((_to) => { NProgress.done(); export default router;/src/main.ts在main.ts中,令app使用router插件import { createApp } from 'vue'; import App from './App.vue'; import router from '@/router'; // ++ import './styles/_reset.scss'; const app = createApp(App); app.use(router); // ++ app.mount('#app');pinia/src/store/index.tsimport { createPinia } from 'pinia'; import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; const pinia = createPinia(); // 使用pinia数据持久化插件 pinia.use(piniaPluginPersistedstate); export default pinia;/src/main.ts在main.ts中,令app使用store插件import { createApp } from 'vue'; import App from './App.vue'; import router from '@/router'; import pinia from '@/store'; // ++ import './styles/_reset.scss'; const app = createApp(App); app.use(router); app.use(pinia); // ++ app.mount('#app');axios/src/utils/http/index.tsimport axios from 'axios'; import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError, InternalAxiosRequestConfig, } from 'axios'; const service: AxiosInstance = axios.create({ baseURL: '/' timeout: 15000, // axios实例拦截请求 service.interceptors.request.use( (config: InternalAxiosRequestConfig) => { return config; (error: AxiosError) => { return Promise.reject(error); // axios实例拦截响应 service.interceptors.response.use( (response: AxiosResponse) => { (error: any) => { export default service构建工具我们已经将vue3的基础项目结构搭建完毕,那么我们怎么才能让这个项目跑起来呢?因为浏览器是不能识别Vue,TS,Sass这些语言的,所以我们需要一个工具将它们转变成浏览器可以识别的语言:Html,CSS,JS。Vite就可以做到这些事情,接下来教大家配置Vite帮助我们构建项目。安装依赖pnpm install -D vite @vitejs/plugin-vue @vitejs/plugin-vue-jsx pnpm install -D @types/node @types/nprogress vue-tscvite:项目构建工具@vitejs/plugin-vue:使vite能够编译vue组件@vitejs/plugin-vue-jsx:使vite能够编译jsx组件@types/node:node类型包,使ts支持node@types/nprogress:nprogress的类型支持vue-tsc:vue文件的类型检查工具vite环境变量Vite官方文档对环境变量的介绍:环境变量和模式 | Vite 官方中文文档 (vitejs.dev)Vite 在一个特殊的 import.meta.env 对象上暴露环境变量。我们为了配置方便,可以将一些配置项写在环境变量中。我们在项目根目录下新建三个文件:.env,.env.production,.env.development.env:所有情况下都会加载.env.[mode]:只在指定模式下加载npm run dev 会加载 .env 和 .env.development 内的配置npm run build 会加载 .env 和 .env.production 内的配置mode 可以通过命令行 --mode 选项来重写。.env注意:环境变量名称必须与VITE作为前缀,前缀可以在Vite配置中修改# axios请求的 baseURL VITE_APP_API_BASEURL = /api剩下的.env.[mode]之后会介绍到,这里我们就先配置这一项即可/src/utils/http/index.tsconst service: AxiosInstance = axios.create({ // 这样我们就可以在环境变量中改变 axios 的 baseURL baseURL: import.meta.env.VITE_APP_API_BASEURL timeout: 15000, });环境变量类型支持我们在开发过程中,环境变量可能会越来越多,我们可能想要获得智能的TypeScript语法提示来让我们知道有哪些环境变量。在项目根目录下新建types文件夹/types/env.d.ts/// <reference types="vite/client" /> interface ImportMetaEnv { // 我们每次添加完新的环境变量就在此添加一次ts类型 // 这样我们就能在使用 import.meta.env 时获得ts语法提示 readonly VITE_APP_API_BASEURL: string; interface ImportMeta { readonly env: ImportMetaEnv; vite配置文件vite.config.ts在项目根目录下创建 vite.config.ts 文件下面的配置项的解释均已注释官网有更加详细的配置介绍:配置 Vite | Vite 官方中文文档 (vitejs.dev)import { defineConfig, loadEnv } from 'vite'; import type { UserConfig, ConfigEnv } from 'vite'; import { fileURLToPath } from 'url'; import vue from '@vitejs/plugin-vue'; import vueJsx from '@vitejs/plugin-vue-jsx'; export default defineConfig(({ mode }: ConfigEnv): UserConfig => { // 获取当前工作目录 const root = process.cwd(); // 获取环境变量 const env = loadEnv(mode, root); return { // 项目根目录 root, // 项目部署的基础路径 base: '/', publicDir: fileURLToPath(new URL('./public', import.meta.url)), // 无需处理的静态资源位置 assetsInclude: fileURLToPath(new URL('./src/assets', import.meta.url)), // 需要处理的静态资源位置 plugins: [ // Vue模板文件编译插件 vue(), // jsx文件编译插件 vueJsx(), // 运行后本地预览的服务器 server: { // 是否开启https https: false, // 指定服务器应该监听哪个 IP 地址。 如果将此设置为 0.0.0.0 或者 true 将监听所有地址,包括局域网和公网地址。 host: true, // 开发环境预览服务器端口 port: 3000, // 启动后是否自动打开浏览器 open: false, // 是否开启CORS跨域 cors: true, // 代理服务器 // 帮助我们开发时解决跨域问题 proxy: { // 这里的意思是 以/api开头发送的请求都会被转发到 http://xxx:3000 '/api': { target: 'http://xxx:3000', // 改变 Host Header changeOrigin: true, // 发起请求时将 '/api' 替换为 '' rewrite: (path) => path.replace(/^\/api/, ''), // 打包配置 build: { // 关闭 sorcemap 报错不会映射到源码 sourcemap: false, // 打包大小超出 4000kb 提示警告 chunkSizeWarningLimit: 4000, rollupOptions: { // 打包入口文件 根目录下的 index.html // 也就是项目从哪个文件开始打包 input: { index: fileURLToPath(new URL('./index.html', import.meta.url)), // 静态资源分类打包 output: { format: 'esm', chunkFileNames: 'static/js/[name]-[hash].js', entryFileNames: 'static/js/[name]-[hash].js', assetFileNames: 'static/[ext]/[name]-[hash].[ext]', // 配置别名 resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), '#': fileURLToPath(new URL('./types', import.meta.url)), });ts配置文件项目根目录下新建tsconfig.json/tsconfig.json{ "compilerOptions": { // 编译出JS的目标ES版本 "target": "esnext", // 使用的ES版本 "module": "esnext", // 用于选择模块解析策略,有'node'和'classic'两种类型 "moduleResolution": "node", // 开启严格模式 "strict": true, // 强制代码中使用的模块文件名必须和文件系统中的文件名保持大小写一致 "forceConsistentCasingInFileNames": true, // 允许使用 xxx 代替 * as xxx 导入 "allowSyntheticDefaultImports": true, // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react' "jsx": "preserve", // 用来指定编译时是否生成.map文件 "sourceMap": true, // 通过为导入内容创建命名空间,实现CommonJS和ES模块之间的互操作性 "esModuleInterop": true, // 是否可以导入 json module "resolveJsonModule": true, // 是否检测定义了但是没使用的变量 "noUnusedLocals": true, // 是否检查是否有在函数体中没有使用的参数 "noUnusedParameters": true, // 是否启用实验性的装饰器特性 "experimentalDecorators": true, // ts中可以使用的库 这里配置为 dom 与 es模块 "lib": ["dom", "esnext"], // 不允许变量或函数参数具有隐式any类型 "noImplicitAny": false, // 启用阻止对下载库的类型检查 "skipLibCheck": true, // types用来指定需要包含的模块 "types": ["vite/client", "element-plus/global"], // 编译的时候删除注释 "removeComments": true, // 使用绝对路径时使用baseUrl去解析导入路径 "baseUrl": ".", // 为导入路径配置别名 "paths": { "@/*": ["src/*"], "#/*": ["types/*"] // 配置虚拟目录 "rootDirs": [] // 指定需要编译文件 "include": [ "src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "types/**/*.d.ts", "types/**/*.ts", "build/**/*.ts", "build/**/*.d.ts", "mock/**/*.ts", "vite.config.ts" // 指定不需要编译的文件 "exclude": ["node_modules", "dist", "**/*.js"] }指令配置最后我们将构建指令加入 package.json中/package.json"scripts": { "dev": "vite --mode development", // 先进行语法检查 再进行项目打包 "build": "vue-tsc --noEmit --skipLibCheck && vite build", },结语接下来我们就可以运行项目吧项目跑起来啦!pnpm run dev一个基础的 Vue3+TypeScrpit+Vite 的项目就此构造完毕!系列文章:【从零到一手撕脚手架 | 第一节】配置基础项目结构 Vite + TypeScrpit + Vue3 初始化项目【从零到一手撕脚手架 | 第二节】模块化封装 降低耦合度 封装 axios pinia router【从零到一手撕脚手架 | 第三节】项目集成CommitLInt+ESLint+Prettier+StyleLint+LintStaged【从零到一手撕脚手架 | 第四节】加速开发效率 使用plop生成开发模板 使用mock进行数据模拟参考学习项目:fast-vue3vue-pure-admin如果有任何不正确的地方请指正,我会及时更改。更文不易,如果对你有帮助的话,请给我点个赞吧关注我,后续文章不迷路⛄
【学习笔记】黑马程序员Node.js全套入门教程 | 基础篇⛄最近要写一些npm命令发现文件读写和路径API忘记了,索性直接复习了一遍NodeJS,边学边忘真的痛苦。⛄本文包含以下内容:对NodeJS的基础介绍,NodeJS的内置包的简单介绍,CommonJS模块化介绍,npm包下载与发布介绍。⭐注:本文是对黑马程序员Node.js全套入门教程的学习笔记记录,加入了一些自己的练习改动与思考。⭐推荐大家去看原视频:黑马程序员Node.js全套入门教程初识NodeJs思考与认识JS为什么可以在浏览器中被执行====浏览器==== 待执行的JS代码 JavaScript解析引擎 ====浏览器====浏览器中含有JavaScript解析引擎负责解析JS代码不同的浏览器使用不同的JavaScript解析引擎:Chrome => V8Firefox => OdinMonkey(奥丁猴)Safri => JSCoreIE浏览器 => Chakra(查克拉)等...Chrome浏览器的V8解析引擎性能最好为什么JavaScript可以操作DOM和BOM====浏览器==== DOMAPI BOMAPI AjaxAPI 待执行的JS代码(调用WebAPI) JavaScript解析引擎每个浏览器都内置了DOM、BOM这样的API函数,因此,浏览器中的JavaScript才可以调用它们。浏览器中的JavaScript运行环境运行环境是指代码正常运行所需的必要环境。====Chrome浏览器运行环境==== V8引擎 内置API ↑ ↓ 待执行的JavaScript代码 ====Chrome浏览器运行环境====V8引擎负责解析和执行JavaScript 代码。内置API是由运行环境提供的特殊接口,只能在所属的运行环境中被调用。JavaScript能否做后端开发JS可以在浏览器中运行,我们需要通过Node.js让JS代码在服务端运行Node.js 简介什么是Node.jsNode.js 是一个基于Chrome V8引擎的JavaScript运行环境。官网地址:Node.js (nodejs.org)Node.js中的JavaScript运行环境====Node.js运行环境==== V8引擎 内置API(fs path http JS内置对象等) ↑ ↓ 待执行的JavaScript代码 ====Chrome浏览器运行环境====浏览器是JavaScript 的前端运行环境。Node.js 是JavaScript的后端运行环境。Node.js 中无法调用DOM和BOM等浏览器内置API。Node.js 可以做什么Node,js作为一个JavaScript 的运行环境,仅仅提供了基础的功能和API。然而,基于Node.,js提供的这些基础能,很多强大的工具和框架如雨后春笋,层出不穷,所以学会了Node.js,可以让前端程序员胜任更多的工作和岗位。基于 Express 框架 (opens new window),可以快速构建 Web 应用基于 Electron 框架 (opens new window),可以构建跨平台的桌面应用基于 restify 框架 (opens new window),可以快速构建 API 接口项目读写和操作数据库、创建实用的命令行工具辅助前端开发等...安装Node.js如果希望通过Node.,js 来运行Javascript 代码,则必须在计算机上安装Node.js环境才行。前往官网下载安装:节点.js (nodejs.org)区分LTS版本和Current版本的不同LTS为长期稳定版,对于追求稳定性的企业级项目来说,推荐安装LTS版本的Node.js。Current 为新特性尝鲜版,对热衷于尝试新特性的用户来说,推荐安装Current 版本的Node.js。但是,Current 版本中可能存在隐藏的Bug 或安全性漏洞,因此不推荐在企业级项目中使用Current版本的 Node.js查看已安装的Node.js版本号打开终端输入node -v,即可查看node.js版本号什么是终端终端(英文: Terminal)是专门为开发人员设计的,用于实现人机交互的一种方式。使用node <js文件名>运行js文件常用模块fs文件系统模块fs 模块是Node.js 官方提供的、用来操作文件的模块。它提供了一系列的方法和属性,用来满足用户对文件的操作需求。导入模块// Nodejs内置模块 无需额外安装 const fs = require('fs')读取文件内容使用fs.readFile()方法,可以读取指定文件中的内容fs.readFile(path[, options], callback)参数解释:path:必选参数,字符串,表示文件的路径。options:可选参数,表示以什么编码格式来读取文件。callback:必选参数,文件读取完成后,通过回调函数拿到读取的结果,该函数会传入两个参数。err:文件读取错误时发生的报错dataStr:文件内容示例代码const fs = require('fs') fs.readFile('./test.text', 'utf8', function (err, dataStr) { // 如果读取成功,则err为null // 如果读取失败,err的值为错误对象 if(err) { console.log(err) console.log('+++++++++') // 打印成功的结果 console.log(dataStr) })写入文件内容使用fs.writeFile0方法,可以向指定的文件中写入内容,语法格式如下该方法只能创建文件,不能创建目录fs.writeFile(file, data[, options], callback)参数解释:file:必选参数,需要指定一个文件路径的字符串,表示文件的存放路径。data:必选参数,表示要写入的内容。options:可选参数,表示以什么格式写入文件内容,默认值是utf8。callback:必选参数,文件写入完成后的回调函数,该函数会传入一个参数。err:文件写入错误时发生的报错示例代码const fs = require('fs') fs.writeFile('./write.text', 'Hello FS Module!', 'utf8', function (err) { // 如果写入成功,则err为null // 如果写入失败,err的值为错误对象 if (err) { console.log(err) })处理路径问题在使用fs 模块操作文件时,如果提供的操作路径是以./或../开头的相对路径时,很容易出现路径动态拼接错误的问题。原因:代码在运行的时候,会队执行node命令时所处的目录,动态拼接出被操作文件的完整路径。(相对于用户所在目录,而不是相对于文件目录)__dirname__dirname表示当前文件所处的目录,更改后可解决路径问题const fs = require('fs') fs.readFile(__dirname + '/test.text', 'utf8', function (err, dataStr) { // ... fs.writeFile(__dirname + '/write.text', 'Hello FS Module!', 'utf8', function (err) { // ... })path路径模块path模块是Node.js官方提供的、用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理需求。导入模块// Nodejs内置模块 无需额外安装 const fs = require('fs')路径拼接使用path.join(方法,可以把多个路径片段拼接为完整的路径字符串,语法格式如下path.join([...path])参数解释:path:路径片段的序列()const path = require('path') const pathStr = path.join('/a', '/b/c', '../', './d', 'e') console.log(pathStr) // \a\b\d\e const pathStr2 = path.join(__dirname, './files/1.text') console.log(pathStr2) // 将相对路径转化为绝对路径与 __dirname 使用字符串加法的不同如果使用字符串加法__dirname + './a'会在路径中多出一个点(.)使用path.join可以解决此问题获取路径中的文件名使用path.basename()方法,可以获取路径中的最后一部分,经常通过这个方法获取路径中的文件名,语法格式如下path.basename(path[, ext])参数解释:path:必选参数,表示一个路径的字符串ext:可选参数,表示文件扩展名使用示例const path = require('path') const fpath = '/a/b/c/index.html' let fullName = path.basename(fpath) console.log(fullName) // index.html let nameWithoutExt = path.basename(fpath, '.html') console.log(nameWithoutExt) // index获取路径中的文件扩展名使用path.extname)方法,可以获取路径中的扩展名部分path.extname(path)参数解释:path:必选参数,表示一个路径的字符串使用示例const path = require('path') const fpath = '/a/b/c/index.html' let fext = path.extname(fpath) console.log(fext) // .htmlhttp模块http模块是Node.js 官方提供的、用来创建 web服务器的模块。通过 http模块提供的 http.createServer()方法,就能方便的把一台普通的电脑,变成一台Web服务器,从而对外提供Web资源服务。服务器和普通电脑的区别在于,服务器上安装了web服务器软件,例如:IIS、Apache等。通过安装这些服务器软件,就能把一台普通的电脑变成一台web服务器。在Node.js 中,我们不需要使用IIS、Apache等这些第三方web服务器软件。因为我们可以基于Node,js提供的http模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外提供web服务。基本四步导入http模块创建web服务器实例为服务器实例绑定request事件,监听客户端请求启动服务器导入模块const http = require('http')创建web服务器实例const server = http.createServer()为服务器绑定request事件server.on('request', (req, res) => { console.log('Someone visit our web server.') const url = req.url // 请求地址 const method = req.method // 请求url // 根据路径判断返回不同内容 let content = '<h1>404 Not found!</h1>' if(url === '/' || url === '/index.html') { content = '<h1>首页</h1>' res.setHeader('Content-Type', 'text/html; charset=utf-8') // 设置响应头 res.end(content) // 向客户端响应内容 })启动服务器server.listen(80, () => { console.log('http server running at http://127.0.0.1') })模块化模块分类Node.js 中根据模块来源的不同,将模块分为了3大类,分别是内置模块(内置模块是由Node.js官方提供的,例如fs、path、http等)自定义模块(用户创建的每个.js文件,都是自定义模块)第三方模块(由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载)加载模块// 加载内置fs模块 const fs = require('fs') // 加载用户自定义模块 const custom - require('./custom.js') // 加载第三方模块 const moment - require('moment')注意:使用require方法加载其它模块时,会执行被加载模块中的代码。模块作用域和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。该作用域防止了全局变量污染的问题module对象在每个.js自定义模块中都有一个module对象,它里面存储了和当前模块有关的信息id,path,exports,parent,filename等...module.exports在自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,供外界使用。外界用require()方法导入自定义模块时,得到的就是 module.exports所指向的对象。代码示例// moduleA.js // 导出变量 module.exports.username = 'zs' module.exports.sayHello = function() { console.log('Hello!') }// main.js // 导入并使用变量 const moduleA = require('./moduleA.js') moduleA.sayHello()exports对象由于module.exports单词写起来比较复杂,为了简化向外共享成员的代码,Node提供了exports对象。默认情况下,exports和module.exports 指向同一个对象。最终共享的结果,还是以module.exports指向的对象为准。时刻谨记,require)模块时,得到的永远是 module.exports指向的对象console.log(module.exports) // {} console.log(exports) // {} console.log(module.exports === exports) // true CommonJS规范Node.js遵循了CommonJS模块化规范,CommonJS规定了模块的特性和各模块之间如何相互依赖。CommonJS规定:每个模块内部,module变量代表当前模块。module变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的 module.exports属性。require)方法用于加载模块。模块加载机制模块第一次加载后会被缓存,即多次调用 require() 不会导致模块的代码被执行多次,提高模块加载效率。内置模块加载内置模块加载优先级最高。自定义模块加载加载自定义模块时,路径要以 ./ 或 ../ 开头,否则会作为内置模块或第三方模块加载。导入自定义模块时,若省略文件扩展名,则 Node.js 会按顺序尝试加载文件:按确切的文件名加载补全 .js 扩展名加载补全 .json 扩展名加载补全 .node 扩展名加载报错第三方模块加载若导入第三方模块, Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块。如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。例如,假设在 C:\Users\bruce\project\foo.js 文件里调用了 require('tools'),则 Node.js 会按以下顺序查找:C:\Users\bruce\project\node_modules\toolsC:\Users\bruce\node_modules\toolsC:\Users\node_modules\toolsC:\node_modules\tools目录作为模块加载当把目录作为模块标识符进行加载的时候,有三种加载方式:在被加载的目录下查找 package.json 的文件,并寻找 main 属性,作为 require() 加载的入口如果没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的 index.js 文件。若失败则报错npm与包包概念Node.js 中的第三方模块又叫做包。就像电脑和计算机指的是相同的东西,第三方模块和包指的是同一个概念,只不过叫法不同。来源不同于Node.js 中的内置模块与自定义模块,包是由第三方个人或团队开发出来的,免费供所有人使用。Node.js 中的包都是免费且开源的,不需要付费即可免费下载使用。为什么需要包由于Node.js 的内置模块仅提供了一些底层的API,导致在基于内置模块进行项目开发的时,效率很低。包是基于内置模块封装出来的,提供了更高级、更方便的API,极大的提高了开发效率。包和内置模块之间的关系,类似于jQuery和浏览器内置API之间的关系。从哪里下载包国外有一家IT公司,叫做npm, Inc.这家公司旗下有一个非常著名的网站: https://www.npmjs.com/,它是全球最大的包共享平台,你可以从这个网站上搜索到任何你需要的包,只要你有足够的耐心!npm, Inc.公司提供了一个地址为https://registry.npmjs.org/的服务器,来对外共享所有的包,我们可以从这个服务器上下载自己所需要的包。如何下载包npm, Inc.公司提供了一个包管理工具,我们可以使用这个包管理工具,从https://registry.npmjs.org/服务器把需要的包下载到本地使用。这个包管理工具的名字叫做Node Package Manager (简称npm包管理工具),这个包管理工具随着Node.js的安装包一起被安装到了用户的电脑上。npmnpm是Nodejs官方的包管理工具。初次装包完成后,在项目文件夹下多一个叫做node_ modules的文件夹和package-lockjson的配置文件。node_modules 文件夹用来存放所有已安装到项目中的包。require()导入第三方包时,就是从这个目录中查找并加载包。package-lockjson 配置文件用来记录node modules目录下的每一个包的下载信息,例如包的名字、版本号、下载地址等。# 初始化npm配置文件 # -y表示使用配置默认选项 # 执行后出现 package.json 文件 npm init -y # 下载包 npm install <包名称> # 删除包 npm uninstall <包名称> # 切换镜像源加速下载 npm config set registry=https://registry.npm.taobao.org/ # 在执行npm install命令时,如果提供了-g参数,则会把包安装为全局包。 # 全局包会被安装到C:\Users\用户目录VAppData\RoamingInpm\node_modules目录下。 npm install <包名称> -g上述命令只能在英文的目录下成功运行!所以,项目文件夹的名称一定要使用英文命名,不要使用中文,不能出现空格。运行npm install命令安装包的时候,npm包管理工具会自动把包的名称和版本号,记录到package.json 中。// packge.json "name": "nodeNpm", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" "keywords": [], "author": "", "license": "ISC" }发布包新建itheima-tools文件夹,作为包的根目录在itheima-tools文件夹中,新建如下三个文件:package.json(包管理配置文件)index.js(包的入口文件)README.md(包的说明文档)// packge.json "name": "myTool", // 包名称 "version": "1.0.0", // 包版本 "description": "", // 包的描述 "main": "index.js", // 包的入口文件 "scripts": { // 包的可执行指令 "test": "echo \"Error: no test specified\" && exit 1" "keywords": [], // 搜索关键字,使用那些关键字可以搜索到该作者 "author": "", // 包的作者 "license": "ISC" // 包遵循的开源协议 }// main.js module.export = { add(a, b){ return a+b }包根目录中的README.md文件,是包的使用说明文档。通过它,我们可以事先把包的使用说明,以 markdown的格式写出来,方便用户参考。README文件中具体写什么内容,没有强制性的要求;只要能够清晰地把包的作用、用法、注意事项等描述清楚即可。我们所创建的这个包的 README.md文档中,会包含以下6项内容:安装方式、导入方式、格式化时间、转义HTML中的特殊字符、还原HTML中的特殊字符、开源协议npm发布注册npm账号访问https://www.npmjs.com/网站,点击 sign up按钮,进入注册用户界面填写账号相关的信息:Full Name、Public Email、Username、Password点击Create an Account按钮,注册账号登录邮箱,点击验证链接,进行账号的验证登录npm账号npm账号注册完成后,可以在终端中执行npm login命令,依次输入用户名、密码、邮箱后,即可登录成功。注意:在运行npm login命令之前,必须先把下包的服务器地址切换为npm的官方服务器。否则会导致发布包失败!# 输入账号密码后即可成功登录 npm login发布# 将包发布在npm上 npm publish # 删除发布的包 npm unpublish <包名> --forcenpm unpublish命令只能删除72小时以内发布的包。npm unpublish 删除的包,在24小时内不允许重复发布发布包的时候要慎重。尽量不要往npm上发布没有意义的包!⛄以上便是基础篇的全部内容了,学习后能让你对NodeJS有一个大致的了解。
WebPack5高级篇⛄继续学习WebPack5的高级篇。⛄这一篇主要讲的是WebPack5打包优化的思路,全是配置项,无技术含量,主要是思路的学习。⭐注:本文是对尚硅谷 Web 前端之 Webpack5 教程的学习笔记记录,加入了一些自己的练习改动与思考。⭐推荐大家去看原视频:尚硅谷Webpack5入门到原理(面试开发一条龙)_哔哩哔哩_bilibili我的总结下面都是一些配置项,在这里大概描述一下优化的思路开发者体验优化SourceMap:打包后报错可映射源码报错位置打包加速HotModuleReplacement:热模块替换OneOf:正则匹配优化Include/Exclude:匹配需要打包的文件,对不需要的进行过滤Cache:缓存优化,对Eslint检查与Babel编译结果进行缓存Thread:多进程打包压缩代码体积TreeShaking:只打包所需库的被引用内容,而不是打包整个库Babel:有优化Babel体积的插件ImageMinimizer:打包图片压缩兼容性Core-js:babel的补丁,可以将asyc函数,promise对象等ES6语法及其他内容进行向下兼容浏览器渲染优化CodeSplit:将JS文件进行分割,按需加载,需要哪个库就导入哪一个。而不是直接全部导入。Preload/Prefetch:可以在浏览器空闲时间进行加载资源NetworkCache:对请求的资源进行缓存PWA:离线时仍可以访问项目⭐都是要配置非常多的内容,我认为这个章节不需要看如何配置,只需要过一遍概念与优化思路。⭐在大家有需要的时候,按照这个思路去官网查找最新配置即可。介绍本章节主要介绍 Webpack 高级配置。所谓高级配置其实就是进行 Webpack 优化,让我们代码在编译/运行时性能更好~我们会从以下角度来进行优化:提升开发体验提升打包构建速度减少代码体积优化代码运行性能提升开发体验SourceMap为什么开发时我们运行的代码是经过 webpack 编译后的,例如下面这个样子:/* * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). * This devtool is neither made for production nor for readable output files. * It uses "eval()" calls to create a separate source file in the browser devtools. * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) * or disable the default devtool with "devtool: false". * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). /******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ({ /***/ "./node_modules/css-loader/dist/cjs.js!./node_modules/less-loader/dist/cjs.js!./src/less/index.less": /*!**********************************************************************************************************!*\ !*** ./node_modules/css-loader/dist/cjs.js!./node_modules/less-loader/dist/cjs.js!./src/less/index.less ***! \**********************************************************************************************************/ /***/ ((module, __webpack_exports__, __webpack_require__) => { eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);\n// Imports\n\n\nvar ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \".box2 {\\n width: 100px;\\n height: 100px;\\n background-color: deeppink;\\n}\\n\", \"\"]);\n// Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);\n\n\n//# sourceURL=webpack://webpack5/./src/less/index.less?./node_modules/css-loader/dist/cjs.js!./node_modules/less-loader/dist/cjs.js"); /***/ }), // 其他省略所有 css 和 js 合并成了一个文件,并且多了其他代码。此时如果代码运行出错那么提示代码错误位置我们是看不懂的。一旦将来开发代码文件很多,那么很难去发现错误出现在哪里。所以我们需要更加准确的错误提示,来帮助我们更好的开发代码。是什么SourceMap(源代码映射)是一个用来生成源代码与构建后代码一一映射的文件的方案。它会生成一个 xxx.map 文件,里面包含源代码和构建后代码每一行、每一列的映射关系。当构建后代码出错了,会通过 xxx.map 文件,从构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源。怎么用通过查看Webpack DevTool 文档open in new window可知,SourceMap 的值有很多种情况.但实际开发时我们只需要关注两种情况即可:开发模式:cheap-module-source-map优点:打包编译速度快,只包含行映射缺点:没有列映射module.exports = { // 其他省略 mode: "development", devtool: "cheap-module-source-map", };生产模式:source-map优点:包含行/列映射缺点:打包编译速度更慢module.exports = { // 其他省略 mode: "production", devtool: "source-map", };简单来说,推荐在开发模式使用cheap-module-source-map,在生产模式使用source-map。提升打包构建速度HotModuleReplacement为什么开发时我们修改了其中一个模块代码,Webpack 默认会将所有模块全部重新打包编译,速度很慢。所以我们需要做到修改某个模块代码,就只有这个模块代码需要重新打包编译,其他模块不变,这样打包速度就能很快。是什么HotModuleReplacement(HMR/热模块替换):在程序运行中,替换、添加或删除模块,而无需重新加载整个页面。怎么用基本配置此配置在WebPack5是默认开启的module.exports = { // 其他省略 devServer: { host: "localhost", // 启动服务器域名 port: "3000", // 启动服务器端口号 open: true, // 是否自动打开浏览器 hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了) };JS配置JS默认不能热模块替换,上面的配置修改后我们发现,修改JS仍然会刷新整个页面。需要使用 module.hot.accept 使js开启热加载,但是需要先判断是否支持 module.hot// main.js // 判断是否支持HMR功能 if (module.hot) { module.hot.accept("./js/count.js", function (count) { const result1 = count(2, 1); console.log(result1); module.hot.accept("./js/sum.js", function (sum) { const result2 = sum(1, 2, 3, 4); console.log(result2); }上面这样写会很麻烦,所以实际开发我们会使用其他 loader 来解决。比如:vue-loader,react-hot-loader。加入以上loader自动配置js热加载。OneOf为什么打包时每个文件都会经过所有 loader 处理,虽然因为 test 正则原因实际没有处理上,但是都要过一遍。比较慢。是什么顾名思义就是只能匹配上一个 loader, 剩下的就不匹配了。怎么用将rules中配置oneof数组,数组中存放匹配值。原本rules会全部遍历去匹配,但是实际上我们书写的正则其实是一一对应的,加入进OneOf之后,匹配到一个其他就不会去匹配了。const path = require("path"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { entry: "./src/main.js", output: { path: undefined, // 开发模式没有输出,不需要指定输出目录 filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 // clean: true, // 开发模式没有输出,不需要清空输出结果 module: { rules: [ oneOf: [ // 用来匹配 .css 结尾的文件 test: /\.css$/, // use 数组里面 Loader 执行顺序是从右到左 use: ["style-loader", "css-loader"], test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"], test: /\.s[ac]ss$/, use: ["style-loader", "css-loader", "sass-loader"], test: /\.styl$/, use: ["style-loader", "css-loader", "stylus-loader"], test: /\.(png|jpe?g|gif|webp)$/, type: "asset", parser: { dataUrlCondition: { maxSize: 10 * 1024, // 小于10kb的图片会被base64处理 generator: { // 将图片文件输出到 static/imgs 目录中 // 将图片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件扩展名 // [query]: 添加之前的query参数 filename: "static/imgs/[hash:8][ext][query]", test: /\.(ttf|woff2?)$/, type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", test: /\.js$/, exclude: /node_modules/, // 排除node_modules代码不编译 loader: "babel-loader", plugins: [ new ESLintWebpackPlugin({ // 指定检查文件的根目录 context: path.resolve(__dirname, "../src"), new HtmlWebpackPlugin({ // 以 public/index.html 为模板创建文件 // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源 template: path.resolve(__dirname, "../public/index.html"), // 开发服务器 devServer: { host: "localhost", // 启动服务器域名 port: "3000", // 启动服务器端口号 open: true, // 是否自动打开浏览器 hot: true, // 开启HMR功能 mode: "development", devtool: "cheap-module-source-map", };Include/Exclude为什么开发时我们需要使用第三方的库或插件,所有文件都下载到 node_modules 中了。而这些文件是不需要编译可以直接使用的。所以我们在对 js 文件处理时,要排除 node_modules 下面的文件。是什么include包含,只处理 xxx 文件exclude排除,除了 xxx 文件以外其他文件都处理怎么用const path = require("path"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { entry: "./src/main.js", output: { path: undefined, // 开发模式没有输出,不需要指定输出目录 filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 // clean: true, // 开发模式没有输出,不需要清空输出结果 module: { rules: [ oneOf: [ // 用来匹配 .css 结尾的文件 test: /\.css$/, // use 数组里面 Loader 执行顺序是从右到左 use: ["style-loader", "css-loader"], test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"], test: /\.s[ac]ss$/, use: ["style-loader", "css-loader", "sass-loader"], test: /\.styl$/, use: ["style-loader", "css-loader", "stylus-loader"], test: /\.(png|jpe?g|gif|webp)$/, type: "asset", parser: { dataUrlCondition: { maxSize: 10 * 1024, // 小于10kb的图片会被base64处理 generator: { // 将图片文件输出到 static/imgs 目录中 // 将图片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件扩展名 // [query]: 添加之前的query参数 filename: "static/imgs/[hash:8][ext][query]", test: /\.(ttf|woff2?)$/, type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", test: /\.js$/, // exclude: /node_modules/, // 排除node_modules代码不编译 include: path.resolve(__dirname, "../src"), // 也可以用包含 loader: "babel-loader", // .... };生产模式也是如此配置。Cache为什么每次打包时 js 文件都要经过 Eslint 检查 和 Babel 编译,速度比较慢。我们可以缓存之前的 Eslint 检查 和 Babel 编译结果,这样第二次打包时速度就会更快了。是什么对Eslint检查和Babel编译结果进行缓存怎么用const path = require("path"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { entry: "./src/main.js", output: { path: undefined, // 开发模式没有输出,不需要指定输出目录 filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 // clean: true, // 开发模式没有输出,不需要清空输出结果 module: { rules: [ oneOf: [ // ... test: /\.js$/, // exclude: /node_modules/, // 排除node_modules代码不编译 include: path.resolve(__dirname, "../src"), // 也可以用包含 loader: "babel-loader", options: { cacheDirectory: true, // 开启babel编译缓存 cacheCompression: false, // 缓存文件不要压缩 plugins: [ new ESLintWebpackPlugin({ // 指定检查文件的根目录 context: path.resolve(__dirname, "../src"), exclude: "node_modules", // 默认值 cache: true, // 开启缓存 // 缓存目录 cacheLocation: path.resolve( __dirname, "../node_modules/.cache/.eslintcache" // ... // ... };Thead为什么当项目越来越庞大时,打包速度越来越慢,甚至于需要一个下午才能打包出来代码。这个速度是比较慢的。我们想要继续提升打包速度,其实就是要提升 js 的打包速度,因为其他文件都比较少。而对 js 文件处理主要就是 eslint 、babel、Terser 三个工具,所以我们要提升它们的运行速度。我们可以开启多进程同时处理 js 文件,这样速度就比之前的单进程打包更快了。是什么多进程打包:开启电脑的多个进程同时干一件事,速度更快。需要注意:请仅在特别耗时的操作中使用,因为每个进程启动就有大约为 600ms 左右开销。怎么用我们启动进程的数量就是我们 CPU 的核数。如何获取 CPU 的核数,因为每个电脑都不一样。// nodejs核心模块,直接使用 const os = require("os"); // cpu核数 const threads = os.cpus().length;下载包npm i thread-loader -D使用const os = require("os"); // 添加此代码 const path = require("path"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); // cpu核数 const threads = os.cpus().length; // 添加此代码 // 获取处理样式的Loaders const getStyleLoaders = (preProcessor) => { return [ MiniCssExtractPlugin.loader, "css-loader", loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解决大多数样式兼容性问题 preProcessor, ].filter(Boolean); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "../dist"), // 生产模式需要输出 filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 clean: true, module: { rules: [ oneOf: [ // 用来匹配 .css 结尾的文件 test: /\.css$/, // use 数组里面 Loader 执行顺序是从右到左 use: getStyleLoaders(), test: /\.less$/, use: getStyleLoaders("less-loader"), test: /\.s[ac]ss$/, use: getStyleLoaders("sass-loader"), test: /\.styl$/, use: getStyleLoaders("stylus-loader"), test: /\.(png|jpe?g|gif|webp)$/, type: "asset", parser: { dataUrlCondition: { maxSize: 10 * 1024, // 小于10kb的图片会被base64处理 generator: { // 将图片文件输出到 static/imgs 目录中 // 将图片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件扩展名 // [query]: 添加之前的query参数 filename: "static/imgs/[hash:8][ext][query]", test: /\.(ttf|woff2?)$/, type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", test: /\.js$/, // exclude: /node_modules/, // 排除node_modules代码不编译 include: path.resolve(__dirname, "../src"), // 也可以用包含 // 加入以下代码 use: [ loader: "thread-loader", // 开启多进程 options: { workers: threads, // 数量 loader: "babel-loader", options: { cacheDirectory: true, // 开启babel编译缓存 plugins: [ new ESLintWebpackPlugin({ // 指定检查文件的根目录 context: path.resolve(__dirname, "../src"), exclude: "node_modules", // 默认值 cache: true, // 开启缓存 // 缓存目录 cacheLocation: path.resolve( __dirname, "../node_modules/.cache/.eslintcache" // 添加此代码 ESlint threads, // 开启多进程 new HtmlWebpackPlugin({ // 以 public/index.html 为模板创建文件 // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源 template: path.resolve(__dirname, "../public/index.html"), // 提取css成单独文件 new MiniCssExtractPlugin({ // 定义输出文件名和目录 filename: "static/css/main.css", // css压缩 // new CssMinimizerPlugin(), // CSS压缩也可以开启多进程 optimization: { minimize: true, minimizer: [ // css压缩也可以写到optimization.minimizer里面,效果一样的 new CssMinimizerPlugin(), // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了 new TerserPlugin({ parallel: threads // 开启多进程 // ... };减少代码体积TreeShaking为什么开发时我们定义了一些工具函数库,或者引用第三方工具函数库或组件库。如果没有特殊处理的话我们打包时会引入整个库,但是实际上可能我们可能只用上极小部分的功能。这样将整个库都打包进来,体积就太大了。是什么Tree Shaking 是一个术语,通常用于描述移除 JavaScript 中的没有使用上的代码。注意:它依赖 ES Module。怎么用Webpack 已经默认开启了这个功能,无需其他配置。babel为什么Babel 为编译的每个文件都插入了辅助代码,使代码体积过大!Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend。默认情况下会被添加到每一个需要它的文件中。你可以将这些辅助代码作为一个独立模块,来避免重复引入。是什么@babel/plugin-transform-runtime: 禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtime 并且使所有辅助代码从这里引用。怎么用下载包npm i @babel/plugin-transform-runtime -D配置{ test: /\.js$/, // exclude: /node_modules/, // 排除node_modules代码不编译 include: path.resolve(__dirname, "../src"), // 也可以用包含 use: [ loader: "thread-loader", // 开启多进程 options: { workers: threads, // 数量 loader: "babel-loader", options: { cacheDirectory: true, // 开启babel编译缓存 cacheCompression: false, // 缓存文件不要压缩 plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积 }ImageMinimizer为什么开发如果项目中引用了较多图片,那么图片体积会比较大,将来请求速度比较慢。我们可以对图片进行压缩,减少图片体积。注意:如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。是什么image-minimizer-webpack-plugin: 用来压缩图片的插件怎么用下载包npm i image-minimizer-webpack-plugin imagemin -D还有剩下包需要下载,有两种模式:无损压缩npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D有损压缩npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D有损/无损压缩的区别配置我们以无损压缩配置为例:const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); optimization: { minimizer: [ // 压缩图片 new ImageMinimizerPlugin({ minimizer: { implementation: ImageMinimizerPlugin.imageminGenerate, options: { plugins: [ ["gifsicle", { interlaced: true }], ["jpegtran", { progressive: true }], ["optipng", { optimizationLevel: 5 }], "svgo", plugins: [ "preset-default", "prefixIds", name: "sortAttrs", params: { xmlnsOrder: "alphabetical", },下面是一些版本问题,轮子还没有完善,忽略,需要的时候大家直接去官网查阅最新版本即可。Error: Error with 'src\images\1.jpeg': '"C:\Users\86176\Desktop\webpack\webpack_code\node_modules\jpegtran-bin\vendor\jpegtran.exe"' Error with 'src\images\3.gif': spawn C:\Users\86176\Desktop\webpack\webpack_code\node_modules\optipng-bin\vendor\optipng.exe ENOENT我们需要安装两个文件到 node_modules 中才能解决, 文件可以从课件中找到:jpegtran.exe需要复制到 node_modules\jpegtran-bin\vendor 下面jpegtran 官网地址open in new windowoptipng.exe需要复制到 node_modules\optipng-bin\vendor 下面OptiPNG 官网地址优化代码性能CodeSplit为什么打包代码时会将所有 js 文件打包到一个文件中,体积太大了。我们如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。所以我们需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。是什么代码分割(Code Split)主要做了两件事:分割文件:将打包生成的文件进行分割,生成多个 js 文件。按需加载:需要哪个文件就加载哪个文件。怎么用代码分割实现方式有不同的方式,为了更加方便体现它们之间的差异,我们会分别创建新的文件来演示多入口文件目录├── public ├── src | ├── app.js | └── main.js ├── package.json └── webpack.config.js下载包npm i webpack webpack-cli html-webpack-plugin -D新建文件app.jsconsole.log("hello app");main.jsconsole.log("hello main");配置// webpack.config.js const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { // 单入口 // entry: './src/main.js', // 多入口 entry: { main: "./src/main.js", app: "./src/app.js", output: { path: path.resolve(__dirname, "./dist"), // [name]是webpack命名规则,使用chunk的name作为输出的文件名。 // 什么是chunk?打包的资源就是chunk,输出出去叫bundle。 // chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名无关。 // 为什么需要这样命名呢?如果还是之前写法main.js,那么打包生成两个js文件都会叫做main.js会发生覆盖。(实际上会直接报错的) filename: "js/[name].js", clear: true, plugins: [ new HtmlWebpackPlugin({ template: "./public/index.html", mode: "production", };运行指令npx webpack此时在 dist 目录我们能看到输出了两个 js 文件。总结:配置了几个入口,至少输出几个 js 文件。提取重复代码如果多入口文件中都引用了同一份代码,我们不希望这份代码被打包到两个文件中,导致代码重复,体积更大。我们需要提取多入口的重复代码,只打包生成一个 js 文件,其他文件引用它就好。修改文件app.jsimport { sum } from "./math"; // 两个文件均引用sum console.log("hello app"); console.log(sum(1, 2, 3, 4));main.jsimport { sum } from "./math"; // 两个文件均引用sum console.log("hello main"); console.log(sum(1, 2, 3, 4, 5));math.jsexport const sum = (...args) => { return args.reduce((p, c) => p + c, 0); };代码分割配置// webpack.config.js const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { // 单入口 // entry: './src/main.js', // 多入口 entry: { main: "./src/main.js", app: "./src/app.js", output: { path: path.resolve(__dirname, "./dist"), // [name]是webpack命名规则,使用chunk的name作为输出的文件名。 // 什么是chunk?打包的资源就是chunk,输出出去叫bundle。 // chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名无关。 // 为什么需要这样命名呢?如果还是之前写法main.js,那么打包生成两个js文件都会叫做main.js会发生覆盖。(实际上会直接报错的) filename: "js/[name].js", clean: true, plugins: [ new HtmlWebpackPlugin({ template: "./public/index.html", mode: "production", // ==========以下为新增代码============= optimization: { // 代码分割配置 splitChunks: { chunks: "all", // 对所有模块都进行分割 // 以下是默认值 // minSize: 20000, // 分割代码最小的大小 // minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0 // minChunks: 1, // 至少被引用的次数,满足条件才会代码分割 // maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量 // maxInitialRequests: 30, // 入口js文件最大并行请求数量 // enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests) // cacheGroups: { // 组,哪些模块要打包到一个组 // defaultVendors: { // 组名 // test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块 // priority: -10, // 权重(越大越高) // reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块 // }, // default: { // 其他没有写的配置会使用上面的默认值 // minChunks: 2, // 这里的minChunks权重更大 // priority: -20, // reuseExistingChunk: true, // }, // }, // 修改配置 cacheGroups: { // 组,哪些模块要打包到一个组 // defaultVendors: { // 组名 // test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块 // priority: -10, // 权重(越大越高) // reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块 // }, default: { // 其他没有写的配置会使用上面的默认值 minSize: 0, // 我们定义的文件体积太小了,所以要改打包的最小文件体积 minChunks: 2, priority: -20, reuseExistingChunk: true, // ==========以下为新增代码============= };此时我们会发现生成 3 个 js 文件,其中有一个就是提取的公共模块。按需加载,动态导入main.jsconsole.log("hello main"); document.getElementById("btn").onclick = function () { // 动态导入 --> 实现按需加载 // 即使只被引用了一次,也会代码分割 import("./math.js").then(({ sum }) => { alert(sum(1, 2, 3, 4, 5)); };app.jsconsole.log("hello app");public/index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Code Split</title> </head> <body> <h1>hello webpack</h1> <button id="btn">计算</button> </body> </html>我们可以发现,一旦通过 import 动态导入语法导入模块,模块就被代码分割,同时也能按需加载了。单入口const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "./dist"), filename: "js/[name].js", clean: true, plugins: [ new HtmlWebpackPlugin({ template: "./public/index.html", mode: "production", optimization: { // 代码分割配置 splitChunks: { chunks: "all", // 对所有模块都进行分割 };最终配置最终我们会使用单入口+代码分割+动态导入方式来进行配置。// webpack.prod.js const os = require("os"); const path = require("path"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); // cpu核数 const threads = os.cpus().length; // 获取处理样式的Loaders const getStyleLoaders = (preProcessor) => { return [ MiniCssExtractPlugin.loader, "css-loader", loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解决大多数样式兼容性问题 preProcessor, ].filter(Boolean); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "../dist"), // 生产模式需要输出 filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 clean: true, module: { rules: [ oneOf: [ // 用来匹配 .css 结尾的文件 test: /\.css$/, // use 数组里面 Loader 执行顺序是从右到左 use: getStyleLoaders(), test: /\.less$/, use: getStyleLoaders("less-loader"), test: /\.s[ac]ss$/, use: getStyleLoaders("sass-loader"), test: /\.styl$/, use: getStyleLoaders("stylus-loader"), test: /\.(png|jpe?g|gif|svg)$/, type: "asset", parser: { dataUrlCondition: { maxSize: 10 * 1024, // 小于10kb的图片会被base64处理 generator: { // 将图片文件输出到 static/imgs 目录中 // 将图片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件扩展名 // [query]: 添加之前的query参数 filename: "static/imgs/[hash:8][ext][query]", test: /\.(ttf|woff2?)$/, type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", test: /\.js$/, // exclude: /node_modules/, // 排除node_modules代码不编译 include: path.resolve(__dirname, "../src"), // 也可以用包含 use: [ loader: "thread-loader", // 开启多进程 options: { workers: threads, // 数量 loader: "babel-loader", options: { cacheDirectory: true, // 开启babel编译缓存 cacheCompression: false, // 缓存文件不要压缩 plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积 plugins: [ new ESLintWebpackPlugin({ // 指定检查文件的根目录 context: path.resolve(__dirname, "../src"), exclude: "node_modules", // 默认值 cache: true, // 开启缓存 // 缓存目录 cacheLocation: path.resolve( __dirname, "../node_modules/.cache/.eslintcache" threads, // 开启多进程 new HtmlWebpackPlugin({ // 以 public/index.html 为模板创建文件 // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源 template: path.resolve(__dirname, "../public/index.html"), // 提取css成单独文件 new MiniCssExtractPlugin({ // 定义输出文件名和目录 filename: "static/css/main.css", // css压缩 // new CssMinimizerPlugin(), optimization: { minimizer: [ // css压缩也可以写到optimization.minimizer里面,效果一样的 new CssMinimizerPlugin(), // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了 new TerserPlugin({ parallel: threads, // 开启多进程 // 压缩图片 new ImageMinimizerPlugin({ minimizer: { implementation: ImageMinimizerPlugin.imageminGenerate, options: { plugins: [ ["gifsicle", { interlaced: true }], ["jpegtran", { progressive: true }], ["optipng", { optimizationLevel: 5 }], "svgo", plugins: [ "preset-default", "prefixIds", name: "sortAttrs", params: { xmlnsOrder: "alphabetical", // 代码分割配置 splitChunks: { chunks: "all", // 对所有模块都进行分割 // 其他内容用默认配置即可 // devServer: { // host: "localhost", // 启动服务器域名 // port: "3000", // 启动服务器端口号 // open: true, // 是否自动打开浏览器 // }, mode: "production", devtool: "source-map", };eslint动态导入npm i eslint-plugin-import -D// .eslintrc.js module.exports = { // 继承 Eslint 规则 extends: ["eslint:recommended"], env: { node: true, // 启用node中全局变量 browser: true, // 启用浏览器中全局变量 plugins: ["import"], // 解决动态导入import语法报错问题 --> 实际使用eslint-plugin-import的规则解决的 parserOptions: { ecmaVersion: 6, sourceType: "module", rules: { "no-var": 2, // 不能使用 var 定义变量 };统一命名配置 output: { path: path.resolve(__dirname, "../dist"), // 生产模式需要输出 filename: "static/js/[name].js", // 入口文件打包输出资源命名方式 chunkFilename: "static/js/[name].chunk.js", // 动态导入输出资源命名方式 assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash) clean: true, plugins: [ // 提取css成单独文件 new MiniCssExtractPlugin({ // 定义输出文件名和目录 filename: "static/css/[name].css", chunkFilename: "static/css/[name].chunk.css", }Preload / Prefetch为什么我们前面已经做了代码分割,同时会使用 import 动态导入语法来进行代码按需加载(我们也叫懒加载,比如路由懒加载就是这样实现的)。但是加载速度还不够好,比如:是用户点击按钮时才加载这个资源的,如果资源体积很大,那么用户会感觉到明显卡顿效果。我们想在浏览器空闲时间,加载后续需要使用的资源。我们就需要用上 Preload 或 Prefetch 技术。是什么Preload:告诉浏览器立即加载资源。Prefetch:告诉浏览器在空闲时才开始加载资源。它们共同点:都只会加载资源,并不执行。都有缓存。它们区别:Preload加载优先级高,Prefetch加载优先级低。Preload只能加载当前页面需要使用的资源,Prefetch可以加载当前页面资源,也可以加载下一个页面需要使用的资源。总结:当前页面优先级高的资源用 Preload 加载。下一个页面需要使用的资源用 Prefetch 加载。它们的问题:兼容性较差。我们可以去 Can I Useopen 网站查询 API 的兼容性问题。Preload 相对于 Prefetch 兼容性好一点。怎么用npm i @vue/preload-webpack-plugin -Dconst PreloadWebpackPlugin = require("@vue/preload-webpack-plugin"); plugins: [ new PreloadWebpackPlugin({ rel: "preload", // preload兼容性更好 as: "script", // rel: 'prefetch' // prefetch兼容性更差 }Network Cache为什么将来开发时我们对静态资源会使用缓存来优化,这样浏览器第二次请求资源就能读取缓存了,速度很快。但是这样的话就会有一个问题, 因为前后输出的文件名是一样的,都叫 main.js,一旦将来发布新版本,因为文件名没有变化导致浏览器会直接读取缓存,不会加载新资源,项目也就没法更新了。所以我们从文件名入手,确保更新前后文件名不一样,这样就可以做缓存了。是什么它们都会生成一个唯一的 hash 值。fullhash(webpack4 是 hash)每次修改任何一个文件,所有文件名的 hash 至都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效。chunkhash根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。我们 js 和 css 是同一个引入,会共享一个 hash 值。contenthash根据文件内容生成 hash 值,只有文件内容变化了,hash 值才会变化。所有文件 hash 值是独享且不同的。怎么用{ output: { path: path.resolve(__dirname, "../dist"), // 生产模式需要输出 // [contenthash:8]使用contenthash,取8位长度 filename: "static/js/[name].[contenthash:8].js", // 入口文件打包输出资源命名方式 chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // 动态导入输出资源命名方式 assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash) clean: true, plugins: [ new MiniCssExtractPlugin({ // 定义输出文件名和目录 filename: "static/css/[name].[contenthash:8].css", chunkFilename: "static/css/[name].[contenthash:8].chunk.css", }Core-js为什么过去我们使用 babel 对 js 代码进行了兼容性处理,其中使用@babel/preset-env 智能预设来处理兼容性问题。它能将 ES6 的一些语法进行编译转换,比如箭头函数、点点点运算符等。但是如果是 async 函数、promise 对象、数组的一些方法(includes)等,它没办法处理。所以此时我们 js 代码仍然存在兼容性问题,一旦遇到低版本浏览器会直接报错。所以我们想要将 js 兼容性问题彻底解决是什么core-js 是专门用来做 ES6 以及以上 API 的 polyfill。polyfill翻译过来叫做垫片/补丁。就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上,使用该新特性。怎么用Eslint兼容修改main.js// 添加promise代码 const promise = Promise.resolve(); promise.then(() => { console.log("hello promise"); });此时 Eslint 会对 Promise 报错。下载包npm i @babel/eslint-parser -D.eslintrc.jsmodule.exports = { // 继承 Eslint 规则 extends: ["eslint:recommended"], parser: "@babel/eslint-parser", // 支持最新的最终 ECMAScript 标准 env: { node: true, // 启用node中全局变量 browser: true, // 启用浏览器中全局变量 plugins: ["import"], // 解决动态导入import语法报错问题 --> 实际使用eslint-plugin-import的规则解决的 parserOptions: { ecmaVersion: 6, // es6 sourceType: "module", // es module rules: { "no-var": 2, // 不能使用 var 定义变量 };此时观察打包输出的 js 文件,我们发现 Promise 语法并没有编译转换,所以我们需要使用 core-js 来进行 polyfill。js兼容npm i core-js// 全部引入 import "core-js"; // ... // 添加promise代码 const promise = Promise.resolve(); promise.then(() => { console.log("hello promise"); });这样引入会将所有兼容性代码全部引入,体积太大了。我们只想引入 promise 的 polyfill。手动按需引入import "core-js/es/promise"; // 添加promise代码 const promise = Promise.resolve(); promise.then(() => { console.log("hello promise"); });自动按需引入babel.config.jsmodule.exports = { // 智能预设:能够编译ES6语法 presets: [ "@babel/preset-env", // 按需加载core-js的polyfill { useBuiltIns: "usage", corejs: { version: "3", proposals: true } }, };此时就会自动根据我们代码中使用的语法,来按需加载相应的 polyfill 了。PWA为什么开发 Web App 项目,项目一旦处于网络离线情况,就没法访问了。我们希望给项目提供离线体验。是什么渐进式网络应用程序(progressive web application - PWA):是一种可以提供类似于 native app(原生应用程序) 体验的 Web App 的技术。其中最重要的是,在 离线(offline) 时应用程序能够继续运行功能。内部通过 Service Workers 技术实现的。怎么用npm i workbox-webpack-plugin -D npm i serve -gconst WorkboxPlugin = require("workbox-webpack-plugin"); plugins: [ new WorkboxPlugin.GenerateSW({ // 这些选项帮助快速启用 ServiceWorkers // 不允许遗留任何“旧的” ServiceWorkers clientsClaim: true, skipWaiting: true, }main.jsif ("serviceWorker" in navigator) { window.addEventListener("load", () => { navigator.serviceWorker .register("/service-worker.js") .then((registration) => { console.log("SW registered: ", registration); .catch((registrationError) => { console.log("SW registration failed: ", registrationError); }总结我们从 4 个角度对 webpack 和代码进行了优化:1.提升开发体验使用 Source Map 让开发或上线时代码报错能有更加准确的错误提示。2.提升 webpack 提升打包构建速度使用 HotModuleReplacement 让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。使用 OneOf 让资源文件一旦被某个 loader 处理了,就不会继续遍历了,打包速度更快。使用 Include/Exclude 排除或只检测某些文件,处理的文件更少,速度更快。使用 Cache 对 eslint 和 babel 处理的结果进行缓存,让第二次打包速度更快。使用 Thead 多进程处理 eslint 和 babel 任务,速度更快。(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效果)3.减少代码体积使用 Tree Shaking 剔除了没有使用的多余代码,让代码体积更小。使用 @babel/plugin-transform-runtime 插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小。使用 Image Minimizer 对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。)4.优化代码运行性能使用 Code Split 对代码进行分割成多个 js 文件,从而使单个文件体积更小,并行加载 js 速度更快。并通过 import 动态导入语法进行按需加载,从而达到需要使用时才加载该资源,不用时不加载资源。使用 Preload / Prefetch 对代码进行提前加载,等未来需要使用时就能直接使用,从而用户体验更好。使用 Network Cache 能对输出资源文件进行更好的命名,将来好做缓存,从而用户体验更好。使用 Core-js 对 js 进行兼容性处理,让我们代码能运行在低版本浏览器。使用 PWA 能让代码离线也能访问,从而提升用户体验。
WebPack5入门到原理⛄最近报名了字节跳动的前端青训营,大作业是要做一个组件库项目。⛄当我自信的打开IDE准备大展身手的时候发现一点思路都没有,网上搜罗了很多教程后发现自己对工程化的知识了解尚浅。⛄于是就发现了谷谷的这套教程,真的讲的很好,学完Webpack就可以去搭建一个组件库脚手架了。⭐注:本文是对尚硅谷 Web 前端之 Webpack5 教程的学习笔记记录,加入了一些自己的练习改动与思考。⭐推荐大家去看原视频:尚硅谷Webpack5入门到原理(面试开发一条龙)_哔哩哔哩_bilibili前言为什么需要打包工具?开发时,我们会使用框架(React、Vue),ES6模块化语法,Less/Sass等css 预处理器等语法进行开发。这样的代码要想在浏览器运行必须经过编译成浏览器能识别的JS、Css 等语法,才能运行。所以我们需要打包工具帮我们做完这些事。除此之外,打包工具还能压缩代码、做兼容性处理、提升代码性能等。有哪些打包工具?GruntGulpParcelWebpackRollupvite...基本使用webpack是一个静态资源打包工具。它会以一个或多个文件作为打包的入口,将我们整个项目所有文件编译组合成一个或多个文件输出出去。输出的文件就是编译好的文件,就可以在浏览器段运行了。我们将lebpack输出的文件叫做bundle 。功能介绍Webpack本身功能是有限的:开发模式:仅能编译JS中的 ES Module语法,箭头函数等ES6语法不会编译生产模式:能编译JS中的ES Module语法,还能压缩JS代码开始使用资源目录webpack_code # 项目根目录(所有指令必须在这个目录运行) └── src # 项目源码目录 ├── js # js文件目录 │ ├── count.js │ └── sum.js └── main.js # 项目主文件创建文件count.jsexport default function count(x, y) { return x - y; }sum.jsexport default function sum(...args) { return args.reduce((p, c) => p + c, 0); }main.jsimport count from "./js/count"; import sum from "./js/sum"; console.log(count(2, 1)); console.log(sum(1, 2, 3, 4));下载依赖打开终端,来到项目根目录。运行以下指令:初始化package.jsonnpm init -y此时会生成一个基础的 package.json 文件。需要注意的是 package.json 中 name 字段不能叫做 webpack, 否则下一步会报错下载依赖npm i webpack webpack-cli -D启用Webpack输出文件会打包在项目目录下 dist 文件夹下开发模式# npx会临时改变环境变量,去调用node_moudle中 .bash 文件中的命令 npx webpack ./src/main.js --mode=development生产模式# 此状态下打包会将ES6全部转为ES5 并且压缩代码 npx webpack ./src/main.js --mode=productionnpx webpack: 是用来运行本地安装 Webpack 包的。./src/main.js: 指定 Webpack 从 main.js 文件开始打包,不但会打包 main.js,还会将其依赖也一起打包进来。--mode=xxx:指定模式(环境)。Webpack 本身功能比较少,只能处理 js 资源,一旦遇到 css 等其他资源就会报错。(我们需要其他的Webpack工具来帮忙处理)基本配置五大核心概念entry(入口)指示 Webpack 从哪个文件开始打包output(输出)指示 Webpack 打包完的文件输出到哪里去,如何命名等loader(加载器)webpack 本身只能处理 js、json 等资源,其他资源需要借助 loader,Webpack 才能解析plugins(插件)扩展 Webpack 的功能mode(模式)开发模式:development生产模式:production准备Webpack配置文件在项目根目录下新建文件:webpack.config.jsmodule.exports = { // 入口 entry: "", // 输出 output: {}, // 加载器 module: { rules: [], // 插件 plugins: [], // 模式 mode: "", };Webpack 是基于 Node.js 运行的,所以采用 Common.js 模块化规范修改配置文件配置文件// Node.js的核心模块,专门用来处理文件路径 const path = require("path"); module.exports = { // 入口 // 相对路径和绝对路径都行 entry: "./src/main.js", // 输出 output: { // path: 文件输出目录,必须是绝对路径 // path.resolve()方法返回一个绝对路径 // __dirname 当前文件的文件夹绝对路径 path: path.resolve(__dirname, "dist"), // filename: 输出文件名 filename: "main.js", // 加载器 module: { rules: [], // 插件 plugins: [], // 模式 mode: "development", // 开发模式 运行指令# 此时webpack会根据配置文件进行打包 npx webpack开发模式介绍开发模式顾名思义就是我们开发代码时使用的模式。这个模式下我们主要做两件事:编译代码,使浏览器能识别运行开发时我们有样式资源、字体图标、图片资源、html 资源等,webpack 默认都不能处理这些资源,所以我们要加载配置来编译这些资源代码质量检查,树立代码规范提前检查代码的一些隐患,让代码运行时能更加健壮。提前检查代码规范和格式,统一团队编码风格,让代码更优雅美观。处理样式资源学习使用 Webpack 如何处理 Css、Less、Sass、Scss、Styl 样式资源介绍Webpack 本身是不能识别样式资源的,所以我们需要借助 Loader 来帮助 Webpack 解析样式资源我们找 Loader 都应该去官方文档中找到对应的 Loader,然后使用官方文档找不到的话,可以从社区 Github 中搜索查询Webpack 官方 Loader 文档open in new window处理CSS资源下载包# 需要下载 css-loader 与 style-loader 两个loader npm i css-loader style-loader -D功能介绍css-loader:负责将 Css 文件编译成 Webpack 能识别的模块style-loader:会动态创建一个 Style 标签,里面放置 Webpack 中 Css 模块内容此时样式就会以 Style 标签的形式在页面上生效配置const path = require("path"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "dist"), filename: "main.js", module: { rules: [ // 用来匹配 .css 结尾的文件 test: /\.css$/, // use 数组里面 Loader 执行顺序是从右到左 use: [ "style-loader", // 将js中css通过创建 style 标签添加 html 文件中生效 "css-loader", // 将css资源编译成 commonjs 的模块到js中 plugins: [], mode: "development", };添加Css资源src/css/index.css.red { color: red; }src/main.jsimport count from "./js/count"; import sum from "./js/sum"; // 引入 Css 资源,Webpack才会对其打包 import "./css/index.css"; console.log(count(2, 1)); console.log(sum(1, 2, 3, 4));public/index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h1 class="red">Hello Webpack</h1> <script src="../dist/main.js"></script> </body> </html>运行指令npx webpack之后会神奇的发现CSS样式在页面中生效啦!处理Less资源下载包npm i less-loader less -D功能介绍less-loader:负责将 Less 文件编译成 Css 文件配置const path = require("path"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "dist"), filename: "main.js", module: { rules: [ // 用来匹配 .css 结尾的文件 test: /\.css$/, // use 数组里面 Loader 执行顺序是从右到左 use: ["style-loader", "css-loader"], test: /\.less$/, // loader: 'xxx', 只能使用一个loader // use可以使用多个 loader use: ["style-loader", "css-loader", "less-loader"], plugins: [], mode: "development", };添加Less资源src/less/index.less.pinkLess { color: pink; }src/main.jsimport count from "./js/count"; import sum from "./js/sum"; // 引入资源,Webpack才会对其打包 import "./css/index.css"; import "./less/index.less"; console.log(count(2, 1)); console.log(sum(1, 2, 3, 4));public/index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h1 class="red">Hello Webpack</h1> <h2 class="pinkLess">Hello Webpack Less</h2> <script src="../dist/main.js"></script> </body> </html>运行指令npx webpacl处理Styl资源下载包npm i stylus-loader -D功能介绍stylus-loader:负责将 Styl 文件编译成 Css 文件配置const path = require("path"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "dist"), filename: "main.js", module: { rules: [ // 用来匹配 .css 结尾的文件 test: /\.css$/, // use 数组里面 Loader 执行顺序是从右到左 use: ["style-loader", "css-loader"], test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"], test: /\.s[ac]ss$/, use: ["style-loader", "css-loader", "sass-loader"], test: /\.styl$/, use: ["style-loader", "css-loader", "stylus-loader"], plugins: [], mode: "development", };添加Styl资源src/styl/index.styl.box width 100px height 100px background-color pinksrc/main.jsimport { add } from "./math"; import count from "./js/count"; import sum from "./js/sum"; // 引入资源,Webpack才会对其打包 import "./css/index.css"; import "./less/index.less"; import "./sass/index.sass"; import "./sass/index.scss"; import "./styl/index.styl"; console.log(count(2, 1)); console.log(sum(1, 2, 3, 4));运行指令npx webpack处理Sass和Scss资源下载包npm i sass-loader sass -D功能介绍sass-loader:负责将 Sass 文件编译成 css 文件sass:sass-loader 依赖 sass 进行编译配置const path = require("path"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "dist"), filename: "main.js", module: { rules: [ // 用来匹配 .css 结尾的文件 test: /\.css$/, // use 数组里面 Loader 执行顺序是从右到左 use: ["style-loader", "css-loader"], test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"], // scss资源 test: /\.s[ac]ss$/, use: ["style-loader", "css-loader", "sass-loader"], plugins: [], mode: "development", };添加Sass资源src/sass/index.scss.blueScss { color: blue; }src/sass/index.sass.greenSass color: greensrc/main.jsimport count from "./js/count"; import sum from "./js/sum"; import "./css/index.css"; import "./less/index.less"; import "./sass/index.sass"; import "./sass/index.scss"; console.log(count(2, 1)); console.log(sum(1, 2, 3, 4));运行指令npx webpack处理图片资源过去在 Webpack4 时,我们处理图片资源通过 file-loader 和 url-loader 进行处理现在 Webpack5 已经将两个 Loader 功能内置到 Webpack 里了,我们只需要简单配置即可处理图片资源配置const path = require("path"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "dist"), filename: "main.js", module: { rules: [ // 图片配置 test: /\.(png|jpe?g|gif|webp)$/, type: "asset", plugins: [], mode: "development", 添加图片资源src/img/jpegImg.jpegsrc/img/pngImg.pngsrc/img/gifImg.gif使用图片资源src/css/index.css.jpegImg { height: 100px; width: 100px; background: url("../img/jpegImg.jpeg"); background-size: cover; .pngImg { height: 100px; width: 100px; background: url("../img/pngImg.png"); background-size: cover; .gifImg { height: 100px; width: 100px; background: url("../img/gifImg.gif"); background-size: cover; }运行指令npx webpack输出资源情况此时如果查看 dist 目录的话,会发现多了三张图片资源因为 Webpack 会将所有打包好的资源输出到 dist 目录下为什么样式资源没有呢?因为经过 style-loader 的处理,样式资源打包到 main.js 里面去了,所以没有额外输出出来对图片资源进行优化将小于某个大小的图片转化成 data URI 形式(Base64 格式)const path = require("path"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "dist"), filename: "main.js", module: { rules: [ // ... test: /\.(png|jpe?g|gif|webp)$/, type: "asset", parser: { dataUrlCondition: { maxSize: 10 * 1024 // 小于10kb的图片会被base64处理 plugins: [], mode: "development", };优点:减少请求数量缺点:体积变得更大此时输出的图片文件就只有两张,有一张图片以 data URI 形式内置到 js 中了 (注意:需要将上次打包生成的文件清空,再重新打包才有效果)修改输出资源的名称和路径配置const path = require("path"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "dist"), filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 module: { rules: [ // ... test: /\.(png|jpe?g|gif|webp)$/, type: "asset", parser: { dataUrlCondition: { maxSize: 10 * 1024, // 小于10kb的图片会被base64处理 generator: { // 将图片文件输出到 static/imgs 目录中 // 将图片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件扩展名 // [query]: 添加之前的query参数 filename: "static/imgs/[hash:8][ext][query]", plugins: [], mode: "development", };修改index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <!-- 修改引用js的路径 --> <script src="../dist/static/js/main.js"></script> </head> <body> <h1 class="red">Hello Webpack</h1> <h2 class="pinkLess">Hello Webpack Less</h2> <h2 class="greenSass">Hello Webpack Sass</h2> <h2 class="blueScss">Hello Webpack Scss</h2> <div class="jpegImg"></div> <div class="pngImg"></div> <div class="gifImg"></div> </body> </html>运行指令npx webpack此时输出文件目录:(注意:需要将上次打包生成的文件清空,再重新打包才有效果)├── dist └── static ├── imgs │ └── 7003350e.png └── js └── main.js自动清空上次打包资源配置const path = require("path"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "dist"), filename: "static/js/main.js", clean: true, // 自动将上次打包目录资源清空 module: { rules: [ // ... plugins: [], mode: "development", };运行指令npx webpack观察 dist 目录资源情况处理字体图标资源这里以阿里巴巴矢量图标库为例图标库地址:https://www.iconfont.cn/添加字体图标资源src/fonts/iconfont.ttfsrc/fonts/iconfont.woffsrc/fonts/iconfont.woff2src/css/iconfont.css引入资源src/main.jsimport "./css/iconfont.css";public/index.html <!-- 使用字体图标 --> <i class="iconfont icon-arrow-down"></i> <i class="iconfont icon-ashbin"></i> <i class="iconfont icon-browse"></i>配置const path = require("path"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "dist"), filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 clean: true, // 自动将上次打包目录资源清空 module: { rules: [ // 添加此处配置 test: /\.(ttf|woff2?)$/, type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", plugins: [], mode: "development", };type: "asset/resource"和type: "asset"的区别:type: "asset/resource" 相当于file-loader, 将文件转化成 Webpack 能识别的资源,其他不做处理type: "asset" 相当于url-loader, 将文件转化成 Webpack 能识别的资源,同时小于某个大小的资源会处理成 data URI 形式运行指令npx webpack处理音频资源配置const path = require("path"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "dist"), filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 clean: true, // 自动将上次打包目录资源清空 module: { rules: [ // 添加此处配置 test: /\.(ttf|woff2?|map4|map3|avi)$/, type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", plugins: [], mode: "development", };JS兼容性与代码格式规范Webpack 对 js 处理是有限的,只能编译 js 中 ES 模块化语法,不能编译其他语法,导致 js 不能在 IE 等浏览器运行,所以我们希望做一些兼容性处理。其次开发中,团队对代码格式是有严格要求的,我们不能由肉眼去检测代码格式,需要使用专业的工具来检测。针对 js 兼容性处理,我们使用 Babel 来完成针对代码格式,我们使用 Eslint 来完成我们先完成 Eslint,检测代码格式无误后,在由 Babel 做代码兼容性处理ESLint可组装的 JavaScript 和 JSX 检查工具这句话意思就是:它是用来检测 js 和 jsx 语法的工具,可以配置各项功能我们使用 Eslint,关键是写 Eslint 配置文件,里面写上各种 rules 规则,将来运行 Eslint 时就会以写的规则对代码进行检查配置文件配置文件由很多种写法:.eslintrc.*:新建文件,位于项目根目录.eslintrc.eslintrc.js.eslintrc.json区别在于配置格式不一样package.json 中 eslintConfig:不需要创建文件,在原有文件基础上写ESLint 会查找和自动读取它们,所以以上配置文件只需要存在一个即可具体配置我们以 .eslintrc.js 配置文件为例:module.exports = { // 解析选项 parserOptions: {}, // 具体检查规则 rules: {}, // 继承其他规则 extends: [], // ... // 其他规则详见:https://eslint.bootcss.com/docs/user-guide/configuring };parserOptions 解析选项parserOptions: { ecmaVersion: 6, // ES 语法版本 sourceType: "module", // ES 模块化 ecmaFeatures: { // ES 其他特性 jsx: true // 如果是 React 项目,就需要开启 jsx 语法 }rules 具体规则"off" 或 0 - 关闭规则"warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)"error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)rules: { semi: "error", // 禁止使用分号 'array-callback-return': 'warn', // 强制数组方法的回调函数中有 return 语句,否则警告 'default-case': [ 'warn', // 要求 switch 语句中有 default 分支,否则警告 { commentPattern: '^no default$' } // 允许在最后注释 no default, 就不会有警告了 eqeqeq: [ 'warn', // 强制使用 === 和 !==,否则警告 'smart' // https://eslint.bootcss.com/docs/rules/eqeqeq#smart 除了少数情况下不会有警告 }更多规则详见:规则文档extends 继承开发中一点点写 rules 规则太费劲了,所以有更好的办法,继承现有的规则。现有以下较为有名的规则:Eslint 官方的规则open in new window:eslint:recommendedVue Cli 官方的规则open in new window:plugin:vue/essentialReact Cli 官方的规则open in new window:react-app// 例如在React项目中,我们可以这样写配置 module.exports = { extends: ["react-app"], rules: { // 我们的规则会覆盖掉react-app的规则 // 所以想要修改规则直接改就是了 eqeqeq: ["warn", "smart"], };在Webpack中使用下载包npm i eslint-webpack-plugin eslint -D定义Eslint配置文件.eslintrc.jsmodule.exports = { // 继承 Eslint 规则 extends: ["eslint:recommended"], env: { node: true, // 启用node中全局变量 browser: true, // 启用浏览器中全局变量 parserOptions: { ecmaVersion: 6, sourceType: "module", rules: { "no-var": 2, // 不能使用 var 定义变量 };main.js// 配置好之后会发现报错 var result1 = count(2, 1); console.log(result1); var result2 = sum(1, 2, 3, 4); console.log(result2);webpack.config.jsconst path = require("path"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "dist"), filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 clean: true, // 自动将上次打包目录资源清空 module: { rules: [ // 加入插件 Webpack 在编译时会对Eslint进行报错 plugins: [ new ESLintWebpackPlugin({ // 指定检查文件的根目录 context: path.resolve(__dirname, "src"), mode: "development", };VSCode Webstorm 插件不同的IDE都有对应的Eslint插件,可以对Eslint规则进行报错,还可以配置保存后一键修改,非常舒服。BableJavaScript 编译器。主要用于将 ES6 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中配置文件配置文件由很多种写法:babel.config.* :新建文件,位于项目根目录babel.config.jsbabel.config.json.babelrc.* :新建文件,位于项目根目录.babelrc.babelrc.js.babelrc.jsonpackage.json 中 babel:不需要创建文件,在原有文件基础上写Babel 会查找和自动读取它们,所以以上配置文件只需要存在一个即可具体配置babel.config.jsmodule.exports = { // 预设 presets: [], };presets 预设简单理解:就是一组 Babel 插件, 扩展 Babel 功能@babel/preset-env: 一个智能预设,允许您使用最新的 JavaScript。@babel/preset-react:一个用来编译 React jsx 语法的预设@babel/preset-typescript:一个用来编译 TypeScript 语法的预设webpack.config.jsconst path = require("path"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "dist"), filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 clean: true, // 自动将上次打包目录资源清空 module: { rules: [ // 对js进行编译 test: /\.js$/, exclude: /node_modules/, // 排除node_modules代码不编译 loader: "babel-loader", plugins: [], mode: "development", };打开打包后的 dist/static/js/main.js 文件查看,会发现箭头函数等 ES6 语法已经转换了处理HTML资源下载包npm i html-webpack-plugin -D配置webpack.config.jsconst path = require("path"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "dist"), filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 clean: true, // 自动将上次打包目录资源清空 module: { rules: [], plugins: [ new ESLintWebpackPlugin({ // 指定检查文件的根目录 context: path.resolve(__dirname, "src"), new HtmlWebpackPlugin({ // 以 public/index.html 为模板创建文件 // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源 template: path.resolve(__dirname, "public/index.html"), mode: "development", };public/index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>webpack5</title> <!-- 去除对js的引入 HtmlWebpackPlugin 会自动引入 --> </head> <body> <!-- ... --> </body> </html>运行指令npx webpack开发服务器&自动化每次写完代码都需要手动输入指令才能编译代码,太麻烦了,我们希望一切自动化下载包npm i webpack-dev-server -D配置webpack.config.jsconst path = require("path"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "dist"), filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 clean: true, // 自动将上次打包目录资源清空 module: { rules: [ // ... plugins: [ // ... // 开发服务器 devServer: { host: "localhost", // 启动服务器域名 port: "3000", // 启动服务器端口号 open: true, // 是否自动打开浏览器 mode: "development", };运行指令npx webpack serve注意运行指令发生了变化并且当你使用开发服务器时,所有代码都会在内存中编译打包,并不会输出到 dist 目录下。开发时我们只关心代码能运行,有效果即可,至于代码被编译成什么样子,我们并不需要知道。生产模式介绍生产模式是开发完成代码后,我们需要得到代码将来部署上线。这个模式下我们主要对代码进行优化,让其运行性能更好。优化主要从两个角度出发:优化代码运行性能优化代码打包速度文件目录├── webpack-test (项目根目录) ├── config (Webpack配置文件目录) │ ├── webpack.dev.js(开发模式配置文件) │ └── webpack.prod.js(生产模式配置文件) ├── node_modules (下载包存放目录) ├── src (项目源码目录,除了html其他都在src里面) │ └── 略 ├── public (项目html文件) │ └── index.html ├── .eslintrc.js(Eslint配置文件) ├── babel.config.js(Babel配置文件) └── package.json (包的依赖管理配置文件)修改 webpack.dev.jsconst path = require("path"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { entry: "./src/main.js", output: { path: undefined, // 开发模式没有输出,不需要指定输出目录 filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 // clean: true, // 开发模式没有输出,不需要清空输出结果 module: { rules: [ // 用来匹配 .css 结尾的文件 test: /\.css$/, // use 数组里面 Loader 执行顺序是从右到左 use: ["style-loader", "css-loader"], test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"], test: /\.s[ac]ss$/, use: ["style-loader", "css-loader", "sass-loader"], test: /\.styl$/, use: ["style-loader", "css-loader", "stylus-loader"], test: /\.(png|jpe?g|gif|webp)$/, type: "asset", parser: { dataUrlCondition: { maxSize: 10 * 1024, // 小于10kb的图片会被base64处理 generator: { // 将图片文件输出到 static/imgs 目录中 // 将图片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件扩展名 // [query]: 添加之前的query参数 filename: "static/imgs/[hash:8][ext][query]", test: /\.(ttf|woff2?)$/, type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", test: /\.js$/, exclude: /node_modules/, // 排除node_modules代码不编译 loader: "babel-loader", plugins: [ new ESLintWebpackPlugin({ // 指定检查文件的根目录 context: path.resolve(__dirname, "../src"), new HtmlWebpackPlugin({ // 以 public/index.html 为模板创建文件 // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源 template: path.resolve(__dirname, "../public/index.html"), // 其他省略 devServer: { host: "localhost", // 启动服务器域名 port: "3000", // 启动服务器端口号 open: true, // 是否自动打开浏览器 mode: "development", };运行开发模式的指令:npx webpack serve --config ./config/webpack.dev.js修改 webpack.prod.jsconst path = require("path"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "../dist"), // 生产模式需要输出 filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 clean: true, module: { rules: [ // 用来匹配 .css 结尾的文件 test: /\.css$/, // use 数组里面 Loader 执行顺序是从右到左 use: ["style-loader", "css-loader"], test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"], test: /\.s[ac]ss$/, use: ["style-loader", "css-loader", "sass-loader"], test: /\.styl$/, use: ["style-loader", "css-loader", "stylus-loader"], test: /\.(png|jpe?g|gif|webp)$/, type: "asset", parser: { dataUrlCondition: { maxSize: 10 * 1024, // 小于10kb的图片会被base64处理 generator: { // 将图片文件输出到 static/imgs 目录中 // 将图片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件扩展名 // [query]: 添加之前的query参数 filename: "static/imgs/[hash:8][ext][query]", test: /\.(ttf|woff2?)$/, type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", test: /\.js$/, exclude: /node_modules/, // 排除node_modules代码不编译 loader: "babel-loader", plugins: [ new ESLintWebpackPlugin({ // 指定检查文件的根目录 context: path.resolve(__dirname, "../src"), new HtmlWebpackPlugin({ // 以 public/index.html 为模板创建文件 // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源 template: path.resolve(__dirname, "../public/index.html"), // devServer: { // host: "localhost", // 启动服务器域名 // port: "3000", // 启动服务器端口号 // open: true, // 是否自动打开浏览器 // }, mode: "production", };运行生产模式的指令:npx webpack --config ./config/webpack.prod.js配置运行命令为了方便运行不同模式的指令,我们将指令定义在 package.json 中 scripts 里面// package.json // 其他省略 "scripts": { "start": "npm run dev", "dev": "npx webpack serve --config ./config/webpack.dev.js", "build": "npx webpack --config ./config/webpack.prod.js" }以后启动指令:开发模式:npm start 或 npm run dev生产模式:npm run buildCSS高级处理生产CSS文件通过linke引入Css 文件目前被打包到 js 文件中,当 js 文件加载时,会创建一个 style 标签来生成样式这样对于网站来说,会出现闪屏现象,用户体验不好我们应该是单独的 Css 文件,通过 link 标签加载性能才好下载包npm i mini-css-extract-plugin -D配置webpack.prod.js这里加入了一个插件,在use里面加入了一个loaderconst path = require("path"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "../dist"), // 生产模式需要输出 filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 clean: true, module: { rules: [ // 用来匹配 .css 结尾的文件 test: /\.css$/, // use 数组里面 Loader 执行顺序是从右到左 use: [MiniCssExtractPlugin.loader, "css-loader"], test: /\.less$/, use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"], test: /\.s[ac]ss$/, use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"], test: /\.styl$/, use: [MiniCssExtractPlugin.loader, "css-loader", "stylus-loader"], test: /\.(png|jpe?g|gif|webp)$/, type: "asset", parser: { dataUrlCondition: { maxSize: 10 * 1024, // 小于10kb的图片会被base64处理 generator: { // 将图片文件输出到 static/imgs 目录中 // 将图片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件扩展名 // [query]: 添加之前的query参数 filename: "static/imgs/[hash:8][ext][query]", test: /\.(ttf|woff2?)$/, type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", test: /\.js$/, exclude: /node_modules/, // 排除node_modules代码不编译 loader: "babel-loader", plugins: [ new ESLintWebpackPlugin({ // 指定检查文件的根目录 context: path.resolve(__dirname, "../src"), new HtmlWebpackPlugin({ // 以 public/index.html 为模板创建文件 // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源 template: path.resolve(__dirname, "../public/index.html"), // 提取css成单独文件 new MiniCssExtractPlugin({ // 定义输出文件名和目录 filename: "static/css/main.css", // devServer: { // host: "localhost", // 启动服务器域名 // port: "3000", // 启动服务器端口号 // open: true, // 是否自动打开浏览器 // }, mode: "production", };CSS兼容性处理下载包npm i postcss-loader postcss postcss-preset-env -D配置webpack.prod.jsconst path = require("path"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "../dist"), // 生产模式需要输出 filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 clean: true, module: { rules: [ // 用来匹配 .css 结尾的文件 test: /\.css$/, // use 数组里面 Loader 执行顺序是从右到左 use: [ MiniCssExtractPlugin.loader, "css-loader", loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解决大多数样式兼容性问题 test: /\.less$/, use: [ MiniCssExtractPlugin.loader, "css-loader", loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解决大多数样式兼容性问题 "less-loader", test: /\.s[ac]ss$/, use: [ MiniCssExtractPlugin.loader, "css-loader", loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解决大多数样式兼容性问题 "sass-loader", test: /\.styl$/, use: [ MiniCssExtractPlugin.loader, "css-loader", loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解决大多数样式兼容性问题 "stylus-loader", test: /\.(png|jpe?g|gif|webp)$/, type: "asset", parser: { dataUrlCondition: { maxSize: 10 * 1024, // 小于10kb的图片会被base64处理 generator: { // 将图片文件输出到 static/imgs 目录中 // 将图片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件扩展名 // [query]: 添加之前的query参数 filename: "static/imgs/[hash:8][ext][query]", test: /\.(ttf|woff2?)$/, type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", test: /\.js$/, exclude: /node_modules/, // 排除node_modules代码不编译 loader: "babel-loader", plugins: [ new ESLintWebpackPlugin({ // 指定检查文件的根目录 context: path.resolve(__dirname, "../src"), new HtmlWebpackPlugin({ // 以 public/index.html 为模板创建文件 // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源 template: path.resolve(__dirname, "../public/index.html"), // 提取css成单独文件 new MiniCssExtractPlugin({ // 定义输出文件名和目录 filename: "static/css/main.css", // devServer: { // host: "localhost", // 启动服务器域名 // port: "3000", // 启动服务器端口号 // open: true, // 是否自动打开浏览器 // }, mode: "production", };兼容性控制我们可以在 package.json 文件中添加 browserslist 来控制样式的兼容性做到什么程度。{ // 其他省略 "browserslist": ["ie >= 8"] }想要知道更多的 browserslist 配置,查看browserslist 文档open in new window以上为了测试兼容性所以设置兼容浏览器 ie8 以上。实际开发中我们一般不考虑旧版本浏览器了,所以我们可以这样设置:{ // 其他省略 "browserslist": ["last 2 version", "> 1%", "not dead"] }合并配置webpack.prod.jsconst path = require("path"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 获取处理样式的Loaders // 谷谷太贴心了 上面代码耦合度比较高 这里直接写了一个函数处理 很好的做法 const getStyleLoaders = (preProcessor) => { return [ MiniCssExtractPlugin.loader, "css-loader", loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解决大多数样式兼容性问题 preProcessor, ].filter(Boolean); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "../dist"), // 生产模式需要输出 filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 clean: true, module: { rules: [ // 用来匹配 .css 结尾的文件 test: /\.css$/, // use 数组里面 Loader 执行顺序是从右到左 use: getStyleLoaders(), test: /\.less$/, use: getStyleLoaders("less-loader"), test: /\.s[ac]ss$/, use: getStyleLoaders("sass-loader"), test: /\.styl$/, use: getStyleLoaders("stylus-loader"), test: /\.(png|jpe?g|gif|webp)$/, type: "asset", parser: { dataUrlCondition: { maxSize: 10 * 1024, // 小于10kb的图片会被base64处理 generator: { // 将图片文件输出到 static/imgs 目录中 // 将图片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件扩展名 // [query]: 添加之前的query参数 filename: "static/imgs/[hash:8][ext][query]", test: /\.(ttf|woff2?)$/, type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", test: /\.js$/, exclude: /node_modules/, // 排除node_modules代码不编译 loader: "babel-loader", plugins: [ new ESLintWebpackPlugin({ // 指定检查文件的根目录 context: path.resolve(__dirname, "../src"), new HtmlWebpackPlugin({ // 以 public/index.html 为模板创建文件 // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源 template: path.resolve(__dirname, "../public/index.html"), // 提取css成单独文件 new MiniCssExtractPlugin({ // 定义输出文件名和目录 filename: "static/css/main.css", // devServer: { // host: "localhost", // 启动服务器域名 // port: "3000", // 启动服务器端口号 // open: true, // 是否自动打开浏览器 // }, mode: "production", };CSS压缩下载包npm i css-minimizer-webpack-plugin -D配置webpack.prod.jsconst path = require("path"); const ESLintWebpackPlugin = require("eslint-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // 获取处理样式的Loaders const getStyleLoaders = (preProcessor) => { return [ MiniCssExtractPlugin.loader, "css-loader", loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", // 能解决大多数样式兼容性问题 preProcessor, ].filter(Boolean); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "../dist"), // 生产模式需要输出 filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 clean: true, module: { rules: [ // 用来匹配 .css 结尾的文件 test: /\.css$/, // use 数组里面 Loader 执行顺序是从右到左 use: getStyleLoaders(), test: /\.less$/, use: getStyleLoaders("less-loader"), test: /\.s[ac]ss$/, use: getStyleLoaders("sass-loader"), test: /\.styl$/, use: getStyleLoaders("stylus-loader"), test: /\.(png|jpe?g|gif|webp)$/, type: "asset", parser: { dataUrlCondition: { maxSize: 10 * 1024, // 小于10kb的图片会被base64处理 generator: { // 将图片文件输出到 static/imgs 目录中 // 将图片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件扩展名 // [query]: 添加之前的query参数 filename: "static/imgs/[hash:8][ext][query]", test: /\.(ttf|woff2?)$/, type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", test: /\.js$/, exclude: /node_modules/, // 排除node_modules代码不编译 loader: "babel-loader", plugins: [ new ESLintWebpackPlugin({ // 指定检查文件的根目录 context: path.resolve(__dirname, "../src"), new HtmlWebpackPlugin({ // 以 public/index.html 为模板创建文件 // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源 template: path.resolve(__dirname, "../public/index.html"), // 提取css成单独文件 new MiniCssExtractPlugin({ // 定义输出文件名和目录 filename: "static/css/main.css", // css压缩 new CssMinimizerPlugin(), // devServer: { // host: "localhost", // 启动服务器域名 // port: "3000", // 启动服务器端口号 // open: true, // 是否自动打开浏览器 // }, mode: "production", };Html压缩JS压缩默认生产模式已经开启了:html 压缩和 js 压缩不需要额外进行配置总结本章节我们学会了 Webpack 基本使用,掌握了以下功能:两种开发模式开发模式:代码能编译自动化运行生产模式:代码编译优化输出Webpack 基本功能开发模式:可以编译 ES Module 语法生产模式:可以编译 ES Module 语法,压缩 js 代码Webpack 配置文件5 个核心概念entryoutputloaderpluginsmodedevServer 配置Webpack 脚本指令用法webpack 直接打包输出webpack serve 启动开发服务器,内存编译打包没有输出留言⛄太感谢谷谷啦,这套课程真的很棒!!!⭐尚硅谷,让天下有学不完的知识!!!
一篇文章彻底解决跨域设置cookie问题!大家好我是雪人~~⛄之前做项目的时候发现后端传过来的 SetCookie 不能正常在浏览器中使用。是因为谷歌浏览器新版本Chrome 80将Cookie的SameSite属性默认值由None变为Lax。接下来带大家解决该问题。原理讲解我们可以看到Cookie有以下属性Cookie属性名称:Cookie的name。值:Cookie的value。Domain: Cookie的域。如果设成xxx.com(一级域名),那么子域名x.xxx.com(二级域名),都可以使用xxx.com的Cookie。Path:Cookie的路径。如果设为/,则同域名全部路径均可使用该Cookie。如果设为/xxx/,则只有路径为/xxx/可以使用该Cookie。Expires / Max-Age:Cookie的超时时间。如果值为时间,则在到达指定时间后Cookie失效。如果值为Session(会话),Cookie会同Session一起失效,当整个浏览器关闭的时候Cookie失效。Size:Cookie的大小。HttpOnly:值为true时,Cookie只会在Http请求头中存在,不能通过doucment.cookie(JavaScript)访问Cookie。Secure:值为true时,只能通过https来传输Cookie。SameSite:值为Strict,完全禁止第三方Cookie,跨站时无法使用Cookie。值为Lax,允许在跨站时使用Get请求携带Cookie,下面有一个表格介绍Lax的Cookie使用情况。值为None,允许跨站跨域使用Cookie,前提是将Secure属性设置为true。Priority :Cookie的优先级。值为Low/Medium/High,当Cookie数量超出时,低优先级的Cookie会被优先清除。还需要了解两个概念:跨站:两个域名不属于同站(域名-主机名/IP相同,协议相同)。跨域:两个域名不属于同源(域名-主机名/IP相同,端口号相同,协议相同)。并且谷歌浏览器新版本Chrome 80将Cookie的SameSite属性默认值由None变为Lax。这下就很清楚明了了,有两种解决方案:将Cookie的SameSite值设为None,Secure值改为true,并且升级为https,我们就可以跨域使用Cookie。将Cookie的SameSite值设为Lax/Strict,并且将前后端部署在同一台服务器下,我们就可以在同一站点使用Cookie。注意:如果是本地测试想要前后端对接我们就只能使用方案一了两种方案需要先解决浏览器同源策略也就是跨域问题前端设置这里以vue的axios为例import axios from 'axios' // 只需要将axios中的全局默认属性withCredentials修改为true即可 // 在axios发送请求时便会携带Cookie axios.defaults.withCredentials = true后端设置这里以Django为例Django跨域问题请参考另一篇文章:【Django跨域】一篇文章彻底解决Django跨域问题!-阿里云开发者社区 (aliyun.com)# 我们需要修改 seeting.py 修改项目设置 # 记得先设置允许访问的IP ALLOWED_HOSTS = ['*'] # 就像我们上面所说的一样有两种解决方案 # 方案一 # 将session属性设置为 secure SESSION_COOKIE_SECURE = True # 设置cookie的samesite属性为None SESSION_COOKIE_SAMESITE = 'None' # 且将协议升级为https # 方案二 # 前后端部署在同一台服务器即可 # 记得先解决ajax的跨域问题 # 加入以下代码即可 CORS_ALLOW_CREDENTIALS = True CORS_ALLOW_ALL_ORIGINS = True CORS_ALLOW_HEADERS = ('*')是不是非常简单呢,不同的前后端框架按照原理解决即可。如果对你有帮助的话请给我点个赞吧。
Butterfly主题使用阿里巴巴iconfont⛄yooo~ 今天教大家美化Butterfly的字体图标⛄Butterfly主题支持 font-awesome v6 但是还是不够用怎么办呢?⛄可以使用阿里巴巴iconfont来拓展啦Butterfly版本:4.5.1选择图标iconfont图标库地址:iconfont-阿里巴巴矢量图标库找到自己需要的图标之后点击 添加入库将图标添加至项目中生成代码 并 复制代码Butterfly配置新建css文件在Butterfly主题文件夹下的 /source/css 文价夹中新建 font.css 吧复制的代码粘贴进去/* font.css */ @font-face { font-family: "iconfont"; src: url('//at.alicdn.com/t/c/font_3829267_g9vxomx6ua4.woff2?t=1671175363970') format('woff2'), url('//at.alicdn.com/t/c/font_3829267_g9vxomx6ua4.woff?t=1671175363970') format('woff'), url('//at.alicdn.com/t/c/font_3829267_g9vxomx6ua4.ttf?t=1671175363970') format('truetype'); .iconfont { font-family: "iconfont" !important; font-size: 16px; /* 字体大小自行修改 */ font-style: normal; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; .icon-github:before { content: "\ea0b"; color: red; /* 图标颜色需要自行修改 */ 此处省略项目中其他字体代码 */引入css文件在Butterfly主题文件夹下的 _config.yml 修改配置# 省略其他代码 inject: head: - <link rel="stylesheet" href="/css/font.css"> # 引入刚刚新建的css文件 bottom: # - <script src="xxxx"></script> # ...使用iconfont在Butterfly主题文件夹下的 _config.yml 修改配置使用格式 iconfont <icon名>icon名可在iconfont项目中查看# 可配置导航栏 menu: 首页: / || iconfont icon-home # ... # ... # 可配置社交图标 social: iconfont icon-csdn: https://blog.csdn.net/xxx || CSDN iconfont icon-juejin: https://juejin.cn/user/xxx || 掘金 fab fa-github: https://github.com/xxx || Github⛄以上就是全部配置内容啦⛄如果对你有帮助请给我点赞吧
Element UI Upload⭐今天教大家使用ElementUI的自定义上传⭐请求一次上传多张图片最近写项目的时候需要一次上传多张图片,使用ElementUI Upload的时候发现如果是默认方案,上传多张图片并不是真正的一次上传多张,而是发送多次请求,一次请求携带一张图片接下来分享一下我的解决思路ElementUI版本:2.15.9Vue版本:2.7.10Html部分<!-- 需要携带以下参数 --> <!-- ref 用于获取组件触发API --> <!-- auto-upload 关闭自动上传 --> <!-- http-request 设置自定义上传的函数 --> <!-- on-change 图片列表改变时触发的函数 --> <!-- multiple 允许上传多个文件 --> <el-upload ref="upload" :auto-upload="false" :http-request="uploadFile" :on-change="changeFileLength" multiple> <i class="el-icon-upload"></i> <div class="el-upload__text">点击上传文件</div> </el-upload> <!-- 上传时点击的按钮 --> <el-button @click="upload" type="success">上传文件</el-button>JS部分export default { name: "uploadCT", data(){ return{ // 上传文件的列表 uploadFiles: [], // 上传文件的个数 filesLength: 0, // 上传需要附带的信息 info:{ id:"", name:"", methods:{ // 修改当前文件列表长度 changeFileLength(file, fileList){ this.filesLength = fileList.length // 用户点击上传调用 async upload(){ // 触发上传 调用配置 :http-request="uploadFile" // 即触发 uploadFile函数 await this.$refs.upload.submit(); // 上传完成后执行的操作 ... // 该函数还是会被调用多次 // 每次param参数传入一个文件 uploadFile(param){ // 将文件加入需要上传的文件列表 this.uploadFiles.push(param.file) // 当uploadFiles长度等于用户需要上传的文件数时进行上传 if (this.uploadFiles.length == this.filesLength){ // 创建FormData上传 let fd = new FormData() // 将全部文件添加至FormData中 this.uploadFiles.forEach(file => { fd.append('file', file) // 将附加信息添加至FormData fd.append("id", this.info.id) fd.append("name", this.info.name) // 配置请求头 const config = { headers: { "Content-Type": "multipart/form-data", // 上传文件 this.$axios.post("/upload/upload_CT/", fd, config).then(res => { /*上传成功处理*/ }).catch(err => {/*报错处理*/}); }Vue组件完整代码请根据如下步骤配置配置upload组件与上传文件按钮配置changeFileLength函数配置upload函数配置uploadFile函数<template> <!-- 需要携带以下参数 --> <!-- ref 用于获取组件触发API --> <!-- auto-upload 关闭自动上传 --> <!-- http-request 设置自定义上传的函数 --> <!-- on-change 图片列表改变时触发的函数 --> <!-- multiple 允许上传多个文件 --> <el-upload ref="upload" :auto-upload="false" :http-request="uploadFile" :on-change="changeFileLength" multiple> <i class="el-icon-upload"></i> <div class="el-upload__text">点击上传文件</div> </el-upload> <!-- 上传时点击的按钮 --> <el-button @click="upload" type="success">上传文件</el-button> </template> <script> export default { name: "uploadCT", data(){ return{ // 上传文件的列表 uploadFiles: [], // 上传文件的个数 filesLength: 0, // 上传需要附带的信息 info:{ id:"", name:"", methods:{ // 修改当前文件列表长度 changeFileLength(file, fileList){ this.filesLength = fileList.length // 用户点击上传调用 async upload(){ // 触发上传 调用配置 :http-request="uploadFile" // 即触发 uploadFile函数 await this.$refs.upload.submit(); // 上传完成后执行的操作 ... // 该函数还是会被调用多次 // 每次param参数传入一个文件 uploadFile(param){ // 将文件加入需要上传的文件列表 this.uploadFiles.push(param.file) // 当uploadFiles长度等于用户需要上传的文件数时进行上传 if (this.uploadFiles.length == this.filesLength){ // 创建FormData上传 let fd = new FormData() // 将全部文件添加至FormData中 this.uploadFiles.forEach(file => { fd.append('file', file) // 将附加信息添加至FormData fd.append("id", this.info.id) fd.append("name", this.info.name) // 配置请求头 const config = { headers: { "Content-Type": "multipart/form-data", // 上传文件 this.$axios.post("/upload/upload_CT/", fd, config).then(res => { /*上传成功处理*/ }).catch(err => {/*报错处理*/}); </script>上述组件就是全部配置内容啦⛄如果对你有帮助请给我点个赞如果有任何问题请留言给我
Hexo⭐零基础搭建Hexo个人博客!⭐本文主题以Butterfly为例!⭐Hexo官网:HexoHexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。安装安装Node.jsHexo基于Node.js运行npm安装Hexo全局安装 hexo-clinpm install -g hexo-cli使用快速使用# 进入项目目录 cd <项目名> # 安装项目依赖 npm install --registry=https://registry.npm.taobao.org # 预览项目 hexo s访问 http://localhost:4000 出现以下页面这是Hexo的默认主题下面是常见的一些基础命令要速成的可直接跳过生成项目初始化hexo项目hexo init <项目名>安装依赖# 进入项目目录 cd <项目名> # 安装依赖包 npm install # 下载速度慢可使用淘宝镜像加速 npm install --registry=https://registry.npm.taobao.org项目目录. ├── _config.yml ├── package.json ├── scaffolds ├── source | ├── _drafts | └── _posts └── themes_config.yml:Hexo配置文件夹package.json:依赖信息scaffolds:模板文件夹当您新建文章时,Hexo 会根据 scaffold 来建立文件。Hexo的模板是指在新建的文章文件中默认填充的内容。例如,如果您修改scaffold/post.md中的Front-matter内容,那么每次新建一篇文章时都会包含这个修改。source:资源文件夹是存放用户资源的地方。除 _posts 文件夹之外,开头命名为 _ (下划线)的文件 / 文件夹和隐藏的文件将会被忽略。Markdown 和 HTML 文件会被解析并放到 public 文件夹,而其他文件会被拷贝过去。themes:主题文件夹基本配置可配置参数很多推荐大家去官网配置页查看官网配置:配置 | Hexo下面讲解一些基础的配置# Site title: 雪人的小屋 # 标题 subtitle: '无 限 进 步 !' # 子标题 description: '希望我的分享能帮助到大家' # 描述 keywords: author: LonelySnowman # 作者 language: zh-CN # 语言配置 # 简体中文:zh-CN timezone: 'Asia/Shanghai' # 时区配置 # 纽约:America/New_York 日本:Japan 上海:Asia/Shanghai常见命令hexo init # 初始化项目 hexo g # 生成静态文件 hexo clean # 清楚静态文件 hexo d # 推送静态文件至 git远程仓库 hexo s # 本地预览项目主题官网主题页面:Themes | Hexo在主题页面任意挑选一款主题根据其官方配置一步步搭建即可本文以Butterfly主题为例Butterfly主题官网:Butterfly - A Simple and Card UI Design theme for Hexo安装主题Butterfly版本:4.5.1# -- 在Hexo项目根目录下 # -- 选择任意一种方式安装即可 # github 安装 git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly # gitee 安装 git clone -b master https://gitee.com/immyw/hexo-theme-butterfly.git themes/butterfly # npm 安装 npm i hexo-theme-butterfly在themes文件夹下出现butterfly文件夹其中 _config.yml 为主题配置文件夹注意:配置中的路径均以 主题问价夹中的source为根目录修改Hexo配置# Hexo的 _config.yml 修改主题 theme: butterfly安装依赖# butterfly使用需要安装 pug 以及 stylus 的渲染器 npm install hexo-renderer-pug hexo-renderer-stylus --save简单美化以下仅讲解一些基础配置 全面的配置官方文档写的很详细明了首页面基础配置# Hexo根目录下的 _config.yml 配置文件 # Site title: 雪人的小屋 # 博客标题 subtitle: '无 限 进 步 !' # 子标题 展示到title下方 打字动画显示 description: '希望我的分享能帮助到大家' # 侧边栏作者下方显示的描述 keywords: author: LonelySnowman # 作者名 language: zh-CN # 语言配置 timezone: 'Asia/Shanghai' # 时区配置 展示subtitlesubtitle: # 开启subtitle enable: true # 打字动画展示 effect: true # Effect Speed Options (打字效果速度參數) startDelay: 300 # 动画开始延迟 ms typeSpeed: 150 # 打字速度 ms backSpeed: 50 # 回到初始状态的速度 ms loop: true # 循环打字 source: false sub:展示封面# 配置中的路径均以 主题问价夹中的source为根目录 index_img: /img/404.jpg顶部子页面选项menu: 首页: / || fas fa-home 归档: /archives/ || fas fa-archive 标签: /tags/ || fas fa-tags 分类: /categories/ || fas fa-folder-open 清单||fas fa-list: 音乐: /music/ || fas fa-music 电影: /movies/ || fas fa-video 友链: /link/ || fas fa-link 关于: /about/ || fas fa-heart更多美化配置请前往对应主题官方文档查看预览页面# 生成静态文件 hexo g # 预览页面 hexo s部署推荐大家使用 GitHub Pages部署当然也可以使用其他多种方法 Vercel,Gitee,Nginx...安装依赖npm install hexo-deployer-git --save更改配置# 更改 deploy: type: git repository: git@github.com:用户名/用户名.github.io.git # 注意这里的分支 # 看看自己Github使用的分支模板 是 master 还是 main branch: main 新建仓库在GitHub上新建仓库仓库名:用户名.github.io# 上传静态文件 hexo d访问访问 https://用户名.github.io就可以看到自己的博客啦!近日将博客从Django+Vue更改为了Hexo,自己写的主题水平还是差了很多,暂时也没有精力去维护。欢迎大家一起交流:雪人的小屋 - 无 限 进 步 ! (snowhouse.space)(我的博客)
DRF快速入门⭐都快2023年了还有人自己写增删改查代码?!?我不允许还不会有人用DRF!⭐今天教大家使用 Django Rest FrameWork 自动生成Restful风格的增删改查代码和接口文档!⭐参考文章:首页 - Django REST 框架 (django-rest-framework.org)(官方文档)注意:Django Rest FrameWork 是在Django框架下使用以下内容需要先创建一个Django项目什么是Restful风格:RESTful API 一种流行的 API 设计风格安装依赖安装Rest FrameWork 需要满足以下条件:Python (3.6, 3.7, 3.8, 3.9, 3.10)Django (2.2, 3.0, 3.1, 3.2, 4.0, 4.1)# 安装Django pip install django # 安装DRF pip install djangorestframework # 安装自动生成接口文档需要的依赖 pip install coreapi设置以下内容在项目中 seeting.py 配置INSTALLED_APPS = [ 'rest_framework', # 注册app 'myapp', # 记得注册自己的app # 数据库记得自行配置 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': '***', 'USER': '***', 'PASSWORD': '***', 'HOST': '127.0.0.1', 'PORT': '3306', }快速使用配置models以下内容在 app 中的 models.py 配置from django.db import models class Student(models.Model): # 模型字段 name = models.CharField(max_length=100, verbose_name="姓名") sex = models.BooleanField(default=1, verbose_name="性别") age = models.IntegerField(verbose_name="年龄") class_null = models.CharField(max_length=5, verbose_name="班级编号") description = models.TextField(max_length=1000, verbose_name="个性签名") class Meta: # 表名称 db_table = "tb_student" # 表详细名称 verbose_name_plural = "学生"配置好models记得迁移数据库python manage.py makemigrations python manage.py migrate配置serializers以下内容在app中新建 serializers.py 目录from rest_framework import serializers from app.models import Student # 创建序列化器类,回头会在试图中被调用 class StudentModelSerializer(serializers.ModelSerializer): class Meta: # 导入对应models model = Student # 选择生成对应models全部的字段 fields = "__all__" # 可选择操作的字段 # fields = ['name', 'sex']配置view以下内容在app中 views.py 配置from rest_framework.viewsets import ModelViewSet from .models import Student from .serializers import StudentModelSerializer # 生成代码 配置一个类即可 class StudentViewSet(ModelViewSet): queryset = Student.objects.all() serializer_class = StudentModelSerializer配置url以下内容在app目录下 urls.py 配置from django.urls import path from . import views from rest_framework.routers import DefaultRouter from rest_framework.documentation import include_docs_urls # 路由列表 urlpatterns = [ # 配置api文档路由 title配置API文档的标题 path('docs/', include_docs_urls(title='API document')), router = DefaultRouter() # 可以处理视图的路由器 router.register('students', views.StudentViewSet) # 向路由器中注册视图集 urlpatterns += router.urls # 将路由器中的所以路由信息追到到django的路由列表中以下内容在项目目录下 urls.py 配置from django.contrib import admin from django.urls import path, include from app import urls urlpatterns = [ path('admin/', admin.site.urls), path('stu/', include(urls)), ]配置seetings以下内容在项目目录下 seetings.py 配置# 配置默认API文档 REST_FRAMEWORK = { # coreapi接口文档 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema', }启动项目# 直接启动项目就可用访问啦 python manage.py runserverhttp://127.0.0.1:8000/stu/ 访问apiroot界面http://127.0.0.1:8000/stu/students/ 访问对数据库学生的操作界面http://127.0.0.1:8000/stu/docs/ 访问接口文档页面预览:接口文档api管理页面⭐以上就是一些基础的配置内容啦!基础的CURD代码已经生成完毕!⭐当然DRF还可以配置的东西非常多 JWT,分页... (之后的文章会介绍到)⭐以上内容只是快速的配置 全面API请查阅官方文档:首页 - Django REST 框架 (django-rest-framework.org)⭐如果对你有帮助请给我点个赞吧
Django解决跨域⭐还有人不会用Django配置CORS?⭐耗时3600秒整理的资料直接拿走!一篇文章彻底解决Django跨域问题!⭐本文包含以下内容:Django解决跨域问题,Django解决跨域携带Cookie问题等⭐官方文档:Django-cors-headers ·皮皮 (pypi.org) 最全面的配置推荐大家前往官网学习CORS,Cross-Origin Resource Sharing,是一个新的 W3C 标准,它新增的一组HTTP首部字段,允许服务端其声明哪些源站有权限访问哪些资源。换言之,它允许浏览器向声明了 CORS 的跨域服务器,发出 XMLHttpReuest 请求,从而克服 Ajax 只能同源使用的限制。在我们的django框架中就是利用CORS来解决跨域请求的问题。CORS详细介绍:跨源资源共享(CORS) - HTTP | MDN (mozilla.org)基本使用1.安装依赖项目主页:adamchainz/django-cors-headers:Django 应用程序,用于处理跨域资源共享 (CORS) 所需的服务器标头 (github.com)pip install django-cors-headers2.修改设置修改Django项目文件夹下的 setting.py 文件# 记得修改允许访问的IP ALLOWED_HOSTS = ['*'] # 允许全部IP访问项目# setting.py 修改以下内容 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'corsheaders', # 注册app corsheaders 'app01',# 你的app MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', # 加入中间键 位置必须在这里 不能在其他位置 'django.middleware.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', 如果你的项目没有考虑到 csrf 网络攻击,可注释掉,否则会报错没有传递 csrf cookie 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]# 在 setting.py 末尾添加以下设置 CORS_ALLOW_CREDENTIALS = True CORS_ALLOW_ALL_ORIGINS = True CORS_ALLOW_HEADERS = ('*')配置完以上内容后Django就可用跨域访问啦!基本需求就已经解决啦!配置内容详解请向下查看学习!详细配置以下内容均在 setting.py 中配置下面是一些常用的 全面的需要大家去官方文档查阅配置允许访问的域名白名单# 允许所有 域名/IP 跨域 CORS_ALLOW_ALL_ORIGINS = True # 配置可跨域访问的 域名/IP CORS_ALLOWED_ORIGINS = [ '127.0.0.1:8000', 'localhost:8080', 'myname.com', # 使用正则表达式匹配允许访问的 域名/IP CORS_ALLOWED_ORIGIN_REGEXES = [ r"^https://\w+\.example\.com$", ]配置允许的跨域请求方式# 配置允许的请求方式 CORS_ALLOW_METHODS = [ '*', # * 表示允许全部请求头 'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS' ]配置允许的请求头CORS_ALLOW_HEADERS = [ "accept", "accept-encoding", "authorization", "content-type", "dnt", "origin", "user-agent", "x-csrftoken", "x-requested-with", ]允许跨域访问Cookie# 改为True即为可跨域设置Cookie CORS_ALLOW_CREDENTIALS = True # 这里有一个需要注意的点 # chrome升级到80版本之后,cookie的SameSite属性默认值由None变为Lax # 也就是说允许同站点跨域 不同站点需要修改配置为 None(需要将Secure设置为True) # 需要前端与后端部署在统一服务器下才可进行跨域cookie设置 # 总结:需要设置 samesite = none、secure = True(代表安全环境 需要 localhost 或 HTTPS)才可跨站点设置cookieCookie属性key:键value:值max_age:多久后过期,时间为秒,默认为None,临时cookie设置即关闭浏览器就消失expires:过期时间,具体时间path:生效路径,默认‘/'domain:生效的域名,你绑定的域名secure:HTTPS传输时应设置为true,默认为falsehttponly:值应用于http传输,这时JavaScript无法获取SameSite属性详解LaxCookies 允许与顶级导航一起发送,并将与第三方网站发起的 GET 请求一起发送。这是浏览器中的默认值。StrictCookies 只会在第一方上下文中发送,不会与第三方网站发起的请求一起发送。NoneCookie 将在所有上下文中发送,即允许跨站发送。配置的介绍Django版本高于2.1:直接设置即可如果DJango版本低于2.1:需要下载 django-cookie-samesite 再设置其他详细Cookie配置内容请参考官方文档:配置 | Django 文档 | Django (djangoproject.com)# 以下内容均在 setting.py 配置 # 将session属性设置为 secure SESSION_COOKIE_SECURE = True # 设置set_cookie的samesite属性 SESSION_COOKIE_SAMESITE = 'None' SESSION_COOKIE_SAMESITE = 'Lax' SESSION_COOKIE_SAMESITE = 'Strict'配置使用CORS的URL# 配置Django项目中哪些URL使用CORS进行跨域 # 默认为 r'^.*$',即匹配所有 URL # 以下案例为 /api/*** 均可进行跨域访问 CORS_URLS_REGEX = r"^/api/.*$"如果对你有帮助请给我点个赞吧❤关注我查看更多技术分享文章 Django+Vue 全栈
Vue3+Ts+Vite今天学习一下如何初始化一个 Vue3 + Ts + Vite 的项目学习地址:开始 {#getting-started} | Vite中文网 (vitejs.cn)与时俱进 开始用全新的技术本文包含以下内容:基础框架的搭建,别名配置,vue-router配置,pinia配置,axios配置,ESLint配置。安装# Vite 需要 Node.js 版本 >= 12.0.0 npm init vite@latest # 根据相关问题进行回答 # 需要选择 框架以及使用语言 配置项目名 # 使用附加命令创建指定项目 无需再选择 npm init vite@latest vue-ts-viet-prj --template vue ts # npm 7+, 需要额外的双横线: npm init vite@latest my-vue-app -- --template vue ts # 进入项目目录 cd vite-project # 安装依赖 npm install # 运行项目 npm run dev配置别名习惯Vue2脚手架中用 @符号指向Src的习惯了 在Vite中配置一下需要修改 vite.config.ts tsconfig.json// viet.config.ts import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { resolve } from 'path' export default defineConfig({ plugins: [vue()], resolve: { alias: { // 配置别名指向src目录 "@": resolve(__dirname, 'src'), // 使用别名的文件后缀 extensions: ['.js', '.json', '.ts'] })// tsconfig.json "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "moduleResolution": "Node", "strict": true, "jsx": "preserve", "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "lib": ["ESNext", "DOM"], "skipLibCheck": true, "noEmit": true, // 加入以下配置项 "baseUrl": ".", // 用于设置解析非相对模块名称的基本目录,相对模块不会受到baseUrl的影响 "paths": { // 用于设置模块名到基于baseUrl的路径映射 "@/*": ["src/*"] "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], "references": [{ "path": "./tsconfig.node.json" }], }Vue-Router# Vue-Router 4+ 版本支持 Vue3 npm install vue-router@4新建 目录/文件夹 src/router/index.ts// index.ts import { createRouter,createWebHashHistory,RouteRecordRaw } from 'vue-router'; // 添加类型校验 const routes: RouteRecordRaw[] = [ path: "/", name: "home", component: ()=>import('@/components/HelloWorld.vue') path: "/logIn", name: "logIn", component: ()=>import('@/view/LogIn.vue') // 创建router const router = createRouter({ // 配置为Hash模式 history: createWebHashHistory(), // 配置toures routes, // 路由跳转时返回顶部 scrollBehavior () { return {top: 0} // 设置前置路由守卫 router.beforeEach((to, from, next) => { next() // 设置后置路由守卫 router.afterEach((to, from, failure) => { export { router }// main.ts import { createApp } from 'vue' import './style.css' import App from './App.vue' // 引入router import { router } from './router' const app = createApp(App); // 挂载到 Vue 实例 app.use(router) app.mount("#app");<!-- App.vue --> <!-- 记得在App.vue中添加 router-view --> <router-view></router-view>Pinianpm install pinia新建 目录/文件 src/store/index.ts// index.ts * 1. 定义容器并导出 * 2. 使用容器中的state * 3. 修改容器中的state * 4. 使用容器中的action import { defineStore } from "pinia"; * 1. 定义容器并导出 * 参数一: 容器ID, 唯一, 将来 Pinia 会把所有的容器挂载到根容器 * 参数二: 选项对象 * 返回值: 函数, 调用的时候要空参调用, 返回容器实例 export const mainStore = defineStore('main', { * 类似组件的 data, 用于存储全局的的状态 * 注意: * 1.必须是函数, 为了在服务端渲染的时候避免交叉请求导致的数据交叉污染 * 2.必须是箭头函数, 为了更好的 TS 类型推导 state: () => { return { state: { token: true * 类似组件的 computed, 用来封装计算属性, 具有缓存特性 getters: { * 类似组件的 methods, 封装业务逻辑, 修改state * 注意: 里面的函数不能定义成箭头函数(函数体中会用到this) actions: { })// main.ts import { createApp } from 'vue' import App from './App.vue' import {createPinia} from 'pinia' // 创建 Pinia 实例 const pinia = createPinia() // 创建 Vue 实例 const app = createApp(App) // 挂载到 Vue 根实例 app.use(pinia) app.mount('#app')Axiosnpm insall axios新建 目录/文件 src/utils/request.ts src/api/xxx.ts// request.ts import axios from 'axios' // 导入pinia import { mainStore } from '@/store' const store = mainStore() // 创建axios const $http = axios.create({ //设置默认请求地址 baseURL: 'http://localhost:8080', //设置请求超时时间 timeout:5000, //设置请求头 headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' // 请求拦截器 $http.interceptors.request.use(config => { // 验证 token const token = store.state.token; if (config.headers!= undefined) config.headers.Authorization = token return config; },error => { return Promise.reject(error); //响应拦截 $http.interceptors.response.use(res => { // 状态码为200正常返回 if (res.status === 200) { return Promise.resolve(res); } else { return Promise.reject(res); }, error => { return Promise.reject(error); // 导出封装的axios export default $http// api/user.ts import request from "@/utils/request" export function login(data: object) { return request({ url: '/user/login', method: 'post', export function getInfo(token: object) { return request({ url: '/user/info', method: 'get', params: { token } export function logout() { return request({ url: '/user/logout', method: 'post' }ESLint参考文章:在 Vue3 + Vite + TS 项目中配置 ESLint,让 VSCode 编辑器自动修复错误 - 知乎 (zhihu.com)使用ESLint进行代码规范 保存就更改 看起来就舒服多了# 安装 # 指定一下版本号 不然会有很多不兼容以及奇奇怪怪的问题 npm install eslint@7.2.0 eslint-plugin-vue@7.20.0 vue-eslint-parser @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-airbnb-base@14.2.1 eslint-plugin-import -D添加 .eslintrc.js 配置文件 手动添加即可写入以下代码module.exports = { root: true, globals: { defineEmits: 'readonly', defineProps: 'readonly', extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:vue/vue3-recommended', 'airbnb-base', parserOptions: { parser: '@typescript-eslint/parser', ecmaVersion: 2020, rules: { 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 禁用 debugger 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 禁用 console 'no-bitwise': 'off', // 禁用按位运算符 'no-tabs': 'off', // 禁用 tab 'array-element-newline': ['error', 'consistent'], // 强制数组元素间出现换行 indent: [ 'error', { MemberExpression: 0, SwitchCase: 1, ignoredNodes: ['TemplateLiteral'] }, ], // 强制使用一致的缩进 quotes: ['error', 'single'], // 强制使用一致的反勾号、双引号或单引号 'comma-dangle': ['error', 'always-multiline'], // 要求或禁止末尾逗号 'object-curly-spacing': ['error', 'always'], // 强制在大括号中使用一致的空格 'max-len': ['error', 120], // 强制一行的最大长度 'no-new': 'off', // 禁止使用 new 以避免产生副作用 'linebreak-style': 'off', // 强制使用一致的换行风格 'import/extensions': 'off', // 确保在导入路径中统一使用文件扩展名 'eol-last': 'off', // 要求或禁止文件末尾存在空行 'no-shadow': 'off', // 禁止变量声明与外层作用域的变量同名 'no-unused-vars': 'warn', // 禁止出现未使用过的变量 'import/no-cycle': 'off', // 禁止一个模块导入一个有依赖路径的模块回到自己身上 'arrow-parens': 'off', // 要求箭头函数的参数使用圆括号 semi: ['error', 'never'], // 要求或禁止使用分号代替 ASI eqeqeq: 'off', // 要求使用 === 和 !== 'no-param-reassign': 'off', // 禁止对 function 的参数进行重新赋值 'import/prefer-default-export': 'off', // 如果模块只输入一个名字,则倾向于默认输出 'no-use-before-define': 'off', // 禁止在变量定义之前使用它们,则倾向于默认输出 'no-continue': 'off', // 禁用 continue 语句 'prefer-destructuring': 'off', // 优先使用数组和对象解构 'no-plusplus': 'off', // 禁用一元操作符 ++ 和 -- 'prefer-const': 'warn', // 要求使用 const 声明那些声明后不再被修改的变量 'global-require': 'off', // 要求 require() 出现在顶层模块作用域中 'no-prototype-builtins': 'off', // 禁止直接调用 Object.prototypes 的内置属性 'consistent-return': 'off', // 要求 return 语句要么总是指定返回的值,要么不指定 'one-var-declaration-per-line': 'off', // 要求或禁止在变量声明周围换行 'one-var': 'off', // 强制函数中的变量要么一起声明要么分开声明 'import/named': 'off', // 确保命名导入与远程文件中的命名导出相对应 'object-curly-newline': 'off', // 强制大括号内换行符的一致性 'default-case': 'off', // 要求 switch 语句中有 default 分支 'no-trailing-spaces': 'off', // 禁用行尾空格 'func-names': 'off', // 要求或禁止使用命名的 function 表达式 radix: 'off', // 强制在 parseInt() 使用基数参数 'no-unused-expressions': 'off', // 禁止出现未使用过的表达式 'no-underscore-dangle': 'off', // 禁止标识符中有悬空下划线 'no-nested-ternary': 'off', // 禁用嵌套的三元表达式 'no-restricted-syntax': 'off', // 禁用特定的语法 'no-await-in-loop': 'off', // 禁止在循环中出现 await 'import/no-extraneous-dependencies': 'off', // 禁止使用外部包 'import/no-unresolved': 'off', // 确保导入指向一个可以解析的文件/模块 'template-curly-spacing': ['error', 'always'], // 要求或禁止模板字符串中的嵌入表达式周围空格的使用 '@typescript-eslint/no-var-requires': 'off', // 除import语句外,禁止使用require语句 '@typescript-eslint/no-empty-function': 'off', // 不允许空函数 '@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型 'guard-for-in': 'off', // 要求 for-in 循环中有一个 if 语句 'class-methods-use-this': 'off', // 强制类方法使用 this 'vue/html-indent': ['error', 2], // 在<template>中强制一致缩进 'vue/html-self-closing': 'off', // 执行自闭合的风格 'vue/max-attributes-per-line': [ // 强制每行属性的最大数量 'warn', singleline: { max: 3, allowFirstLine: true, multiline: { max: 1, allowFirstLine: false, 'vue/singleline-html-element-content-newline': 'off', // 要求单行元素的内容前后有一个换行符 }不同IDE设置ESLint方法不同大家自行查找即可WebStorm:WebStorm 2021.1 使用 ESLint自动格式化代码_程序员鱼丸的博客-CSDN博客_webstorm代码格式化插件VsCode:VsCode 如何配置Eslint - 掘金 (juejin.cn)配置完真的舒服多了懒人必备不想自己配置的话 直接 clone 写好的代码开箱即用# 克隆项目 git clone https://gitee.com/lonelysnowman/vue3-vite-ts-template.git # 进入项目目录 cd vue3-vite-ts-template # 安装依赖 npm install # 淘宝镜像加速下载 npm install --registry=https://registry.npm.taobao.org # 启动服务 npm run dev关注我查看更多前端技术文章
Nginx部署Vue项目今天教大家使用Nginx快速的将Vue打包好的Dist文件部署在服务器上供他人访问⭐关注我查看更多笔记:Linux中安装Nginx获取dist文件在 Vue2 / Vue3 项目文件夹下输入以下命令npm run build在vue项目文件夹下会出现 dist文件夹将该文件夹上传至服务器 使用 scp 命令 与 XFTP 等均可Nginx配置检查nginx首先检查自己是否安装nginx未安装请查看我的另一篇安装 nginx 的博客# 以下两个命令无效说明 未安装nginx 或 安装在了其他地方 # 出现版本号说明已安装 nginx -V # 没有配置环境变量使用以下命令查看 /usr/local/nginx/sbin/nginx -Vnginx一般安装在 /usr/local/nginx 下可在 /usr/local/nginx/sbin/nginx 使用修改配置文件一般配置文件在目录 /usr/local/nginx/conf 下不在此处请检查nginx安装地址使用vim修改 或 在本地修改后上传至服务器均可vim /usr/local/nginx/conf/nginx.confworker_processes 4; user root; events { worker_connections 1024; http { keepalive_timeout 65; # 配置响应的文件类型 include mime.types; # 出现 css/js 无效的问题可使用绝对路径 # include /usr/local/nginx/conf/mime.types; server { # 配置访问的端口号 # http默认为 80 端口 listen 80; # 设置为服务器的外网地址或域名 server_name 112.124.239.485; # 配置报错文件 error_page 404 /404.html; error_page 500 502 503 504 /50x.html; location / { # dist文件夹的绝对路径 root /root/VuePrj/dist; # html文件名 index index.html; autoindex on; # 配置访问日志地址 access_log /root/VuePrj/dist/access.log; error_log /root/VuePrj/dist/error.log; }重启nginxnginx -s reload # 未配置环境变量使用绝对路径运行 /usr/local/nginx/sbin/nginx -s reload # 未启动nginx 先启动 nginx访问部署地址根据配置文件访问相应地址即可http://112.124.239.485部署完成!如果对你有帮助 请帮我点赞关注我查看更多技术文章
Vue3入门学习笔记Vue3入门必备!⭐关注我查看更多配套笔记学习视频:https://www.bilibili.com/video/BV1Zy4y1K7SH/【尚硅谷Vue全家桶】本博客是对该视频内容的整理以及加入自己的理解 想全面学习的推荐大家去看原视频1.Vue3快速上手1.Vue3简介2020年9月18日,Vue.js发布3.0版本,代号:One Piece(海贼王)耗时2年多、2600+次提交、30+个RFC、600+次PR、99位贡献者github上的tags地址:https://github.com/vuejs/vue-next/releases/tag/v3.0.02.Vue3带来了什么1.性能的提升打包大小减少41%初次渲染快55%, 更新渲染快133%内存减少54%2.源码的升级使用Proxy代替defineProperty实现响应式重写虚拟DOM的实现和Tree-Shaking…3.拥抱TypeScriptVue3可以更好的支持TypeScript4.新的特性Composition API(组合API)setup配置ref与reactivewatch与watchEffectprovide与inject…新的内置组件FragmentTeleportSuspense其他改变新的生命周期钩子data 选项应始终被声明为一个函数移除keyCode支持作为 v-on 的修饰符…2.创建工程1.使用 vue-cli官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上 vue --version ## 安装或者升级你的@vue/cli npm install -g @vue/cli ## 创建 vue create vue_test ## 启动 cd vue_test npm run serve2.使用 vite 创建之前的构建工具:grunt gulp webpackvite 由 尤雨溪 团队开发官方文档:https://v3.cn.vuejs.org/guide/installation.html#vitevite官网:https://vitejs.cn什么是vite?—— 新一代前端构建工具。优势如下:开发环境中,无需打包操作,可快速的冷启动。轻量快速的热重载(HMR)。真正的按需编译,不再等待整个应用编译完成。传统构建 与 vite构建对比图## 创建工程 npm init vite-app <project-name> ## 进入工程目录 cd <project-name> ## 安装依赖 npm install ## 运行 npm run dev3.内容浅析1.main.js// 引入的不再是 Vue 构造函数了 // 引入的是名为 createApp 的工厂函数 import { createApp } from 'vue' import App from './App.vue' // 创建应用实例对象->app 类似Vue2中的 vm // 但是 app 比 vm 更 “轻” // app上挂载的方法 比 vm 少很多 createApp(App).mount('#app') // app上的方法 // mount 挂载 组件到页面中 // unmount 卸载 组件在页面中 Vue 2 对比import Vue from 'vue' import App from './App.vue' new Vue({ render: h => h(App), }).$mount('#app')2.App.vue<template> <!-- Vue3 允许可以不包含根标签 --> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App"/> </template>4.常用 Composition API官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html1.setup理解:Vue3.0中一个新的配置项,值为一个函数。setup是所有Composition API(组合API)“ 表演的舞台 ”。组件中所用到的:数据、方法等等,均要配置在setup中。setup函数的两种返回值:若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)若返回一个渲染函数:则可以自定义渲染内容。(了解)注意点:尽量不要与Vue2.x配置混用Vue2.x配置(data、methos、computed…)中可以访问到setup中的属性、方法。但在setup中不能访问到Vue2.x配置(data、methos、computed…)。如果有重名, setup优先。setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)<template> <h1>我是App组件</h1> <h1>姓名:{{name}}</h1> <h1>年龄:{{age}}</h1> <button @click="sayHello">说话</button> </template> <script> // 虽然Vue3 支持 Vue2 的配置方法 data methods... 但是不要使用 会有优先级的问题 import {h} from 'vue' export default { name: 'App', // Vue2 中的 data methods mounted 均需要写在 setup 中 setup(){ // 数据定义 // let 定义的数据不是响应式数据 let name = '张三' let age = 18 // 函数定义 fuction sayHello(){ alert(`我叫${name},我${age}岁了,你好!`) // 1.全部返回 返回后可在template中使用 return { name, sayHello // 2.返回渲染函数 (需要import) 直接在 模板中渲染 <h1>snowman</h1> return ()=>{return h('h1','snowman')} </script>2.ref函数作用: 定义一个响应式的数据语法: const xxx = ref(initValue)创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。JS中操作数据: xxx.value模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>备注:接收的数据可以是:基本类型、也可以是对象类型。基本类型的数据:响应式依然是靠Object.defineProperty()的get与set完成的。对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。<template> <!-- 在模板中无需 .value vue检测到为RefImpl对象 自动 .value --> <h1>我是App组件</h1> <h1>姓名:{{name}}</h1> <h1>年龄:{{age}}</h1> <button @click="changeData">说话</button> </template> <script> // 引入ref import {ref} from 'vue' export default { name: 'App', setup(){ let name = ref('张三') let age = ref(18) let job = ref({ type:"前端工程师", salary:"30K", // 函数定义 fuction changeData(){ // 修改人的信息(错误改法) name = '李四' age = 48 // (正确写法) name.value = '李四' age.value = 48 // 改写对象 job.value.type = "UI设计师" return { name, changeData, </script>3.reactive函数作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)reactive定义的响应式数据是“深层次的”。内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。<template> <!-- 在模板中无需 .value vue检测到为RefImpl对象 自动 .value --> <h1>我是App组件</h1> <h1>姓名:{{name}}</h1> <h1>年龄:{{age}}</h1> <button @click="changeData">说话</button> </template> <script> // 引入ref import {ref,reactive} from 'vue' export default { name: 'App', setup(){ // 直接使用 reactive 函数 // 与ref不同的是 job 直接为 Proxy 无需 .value 取值 let job = reactive({ type:"前端工程师", salary:"30K", // 你不管写多深都能检测到 c:666 // Proxy 支持 以数组索引改变响应式!!! let hobby = reactive(['抽烟', '喝酒', '烫头']) // const 声明的对象 内部成员可变 不可赋值成新对象 const person = reactive({ hobby: ['抽烟', '喝酒', '烫头'], c:666 // 函数定义 fuction changeData(){ // 改写对象 job.type = "UI设计师" job.a.b.c = 999 // Vue3 我认为厉害的地方 hobby[0] = '弹琴' // 可以检测到 无需使用数组方法改变! return { changeData, </script>4.Vue3.0中的响应式原理vue2.x的响应式实现原理:对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。Object.defineProperty(data, 'count', { get () {}, set () {} })模拟Vue2 实现响应式let person = { name:"李四", age:18, let p ={} Object.defineProperty(p,"name",{ // 加入此配置 delete p.name 可以调用但是无法更新页面 configurable:true, get(){ return person.name set(value){ console.log('有人修改了name属性,我要去更新页面') person.name = value })存在问题:新增属性、删除属性, 界面不会更新。(Vue2 使用 Vue.set Vue.delete || this.$set this.$delete 解决) 原理为 defineProperty 对 get 与 set 进行加工直接通过下标修改数组, 界面不会自动更新。(Vue2 使用 push pop...解决)Vue3.0的响应式实现原理:通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。通过Reflect(反射): 对源对象的属性进行操作。MDN文档中描述的Proxy与Reflect:Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/ProxyReflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect模拟Vue3 实现响应式let person = { name:"李四", age:18, // 构造器必须有两个参数 // 参数一:要代理的对象 // 参数二:Proxy检测对象方法的配置 // 神奇的是 Proxy代理对象后 get set 什么都没写 就能检测到对象的增删改查 const p = new Proxy(person,{}) // 以下为简化原理 // 但是Vue3没有这么写 太low了 const p = new Proxy(person,{ // target 为源对象 propName 为被get的成员变量 get(target,propName){ console.log(`有人读取了p身上的${propName}属性`) return target[propName]set },set // value 为更改后的值ser // 修改或追加属性是 都会调用 set 理解: 从 undefined -> value 也是一种set set(target,propName,value){ // 响应式操作可在此进行 console.log(`有人修改了p身上的${propName}属性`) target[propName] = value deleteProperty(target,propName){ console.log(`有人删除了p身上的${propName}属性`) return delete target[propName] /* 插播一个小知识点 .不能将数字作为对象的属性名,[1]可以 .运算符可以将关键字作为属性添加,[]运算符不可以 .运算符不能调用字符串变量,["on"+type]可以 // 隆重介绍 Reflect let obj = {a:1,b:2} // 以下两种方法均可从obj 读取 数据 obj.a Reflect.get(obj,'a') // 以下两种方法均可从obj 修改 数据 obj.a = 666 Reflect.set(obj,'a',666) // 以下两种方法均可从obj 删除 数据 delete obj.a Reflect.deleteProperty(obj,'a') // ECMA 语法规范组织 正在将 Object上的API移植到Reflect // defineProperty() 有返回值 true false // 不能重复定义属性 重复定义抛出异常 // 直接抛出异常对封装框架不友好 需要 try catch Object.defineProperty() // 可重复定义属性 重复定义返回 false Reflect.defineProperty()Vue3 实现原理雏形let person = { name:"李四", age:18, // 使用反射操作 const p = new Proxy(person,{ get(target,propName){ console.log(`有人读取了p身上的${propName}属性`) return Reflect.get(target,propName) },set set(target,propName,value){ console.log(`有人修改了p身上的${propName}属性`) Reflect.set(target,propName,value) deleteProperty(target,propName){ console.log(`有人删除了p身上的${propName}属性`) return Reflect.deleteProperty(target,propName) })5.reactive对比ref从定义数据角度对比:ref用来定义:基本类型数据。reactive用来定义:对象(或数组)类型数据。备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象。从原理角度对比:ref通过Object.defineProperty()的get与set来实现响应式(数据劫持)。reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。从使用角度对比:ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value。reactive定义的数据:操作数据与读取数据:均不需要.value。6.setup的两个注意点setup执行的时机在beforeCreate之前执行一次,this是undefined。比Vue第一个生命周期还要早 在set up中调用This是没有用处的setup的参数props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。context:上下文对象attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs。slots: 收到的插槽内容, 相当于 this.$slots。emit: 分发自定义事件的函数, 相当于 this.$emit。<template> <!-- 假设为父组件 --> <button :msg="msg" :school:"school">说话</button> <Demo @hello="methods..."> <!-- Vue3 更支持 v-solt:name 而不是 solt="name" --> <template v-solt:name> <span>此处为插槽内容</span> </template> </Demo> </template> <script> // 此处为 子组件 export default { name: 'App', // 不声明 props 接收 会在 vc中的 $attrs中接收 // 声明后才可在setup中接收 props:['msg','school'] // Vue3 新增配置项 给子组件定义自定义事件需要 emits声明一下 类似 props emits:['hello'] // 插槽属性可在 this.$solts中找到 获取真实/虚拟 DOM节点 // setup 可传入两个参数 // props 父组件给子组件传入的参数 // context 上下文 setup(props,context){ </script>7.计算属性与监视1.computed函数与Vue2.x中computed配置功能一致写法import {computed} from 'vue' setup(){ let person = reactive({ firstName:"张", lastName:"三", // 计算属性——简写 只读的情况 let fullName = computed(()=>{ return person.firstName + '-' + person.lastName // 计算属性——完整 计算属性会被 修改的情况 // 由于 person 为 proxy 对象 可直接新增属性进行响应式 person.fullName = computed({ get(){ return person.firstName + '-' + person.lastName set(value){ const nameArr = value.split('-') person.firstName = nameArr[0] person.lastName = nameArr[1] }2.watch函数与Vue2.x中watch配置功能一致两个小“坑”:监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。监视reactive定义的响应式数据中某个属性时:deep配置有效。Vue2<script> // Vue2的写法 export default { name: 'App', wacth:{ // 简写方法 当 sum改变时执行 sum(newValue,oldValue){ console.log(newValue) // 完整配置项方法 sum:{ // 开启深度监视 deep:true, // handler为sum改变时执行的函数 handler(newValue,oldValue){ // immediate: true 选项,表示组件创建时立即执行 immediate: true </script>Vue3<script> // Vue3的写法 import {watch,ref} from 'vue' export default { name: 'App', setup(){ let sum = ref(0) watch(sum,(newValue,oldValue)=>{ console.log('sum 改变了') </script>不同情况 watch调用方法不一样//情况一:监视ref定义的响应式数据 watch(sum,(newValue,oldValue)=>{ console.log('sum变化了',newValue,oldValue) },{immediate:true}) //情况二:监视多个ref定义的响应式数据 watch([sum,msg],(newValue,oldValue)=>{ console.log('sum或msg变化了',newValue,oldValue) /* 情况三:监视reactive定义的响应式数据 // oldValue = newValue 这个是 Vue3的Bug 若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!! 若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 watch(person,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) },{immediate:true,deep:false}) //此处的deep配置不再奏效 强制开启 deep //情况四:监视reactive定义的响应式数据中的某个属性 // 不能 person.job 直接当作第一个参数 需要写成 函数形式 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //情况五:监视reactive定义的响应式数据中的某些属性 watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //特殊情况 // reactive中的对象 需要开启 Deep 不是 reactive 不默认开启 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效ref .value 的问题let person = ref({ name:"张三", age:19, job:{ salary:{ money:1000 // 不加value 为 ref 需要手动开启 deep watch(person,(newValue,oldValue)=>{},{deep:true}) // 加value 为 reactive 强制开启 deep watch(person.value,(newValue,oldValue)=>{})3.watchEffect函数watch的套路是:既要指明监视的属性,也要指明监视的回调。watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。watchEffect有点像computed:但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。// watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。 watchEffect(()=>{ // 用到 sum 与 person 则会监视 sum 与 person // 智能监视 用谁判断谁 多层对象也可判断 (非常强大) const x1 = sum.value const x2 = person.age console.log('watchEffect配置的回调执行了') })8.生命周期![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bEiO4o2e-1614733632386)(https://resource.guanchao.site/ueditor/php/upload/image/20210219/1613699827731669.png#pic_center)]](https://ucc.alicdn.com/images/user-upload-01/img_convert/07a6f5c06eb68aa37860b12ec7744e60.png#pic_center)Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:beforeDestroy改名为 beforeUnmountdestroyed改名为 unmountedVue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:beforeCreate===>setup()created===>setup()beforeMount == = > onBeforeMountmounted => onMountedbeforeUpdate= == >onBeforeUpdateupdated = == == >onUpdatedbeforeUnmount == >onBeforeUnmountunmounted ===>onUnmounted<script> export default = { // 直接写在配置项中 仍可使用 beforeCreate(){ created(){ beforeMount(){ mounted(){ beforeUpdate(){ updated(){ // destory 改名为 unmount beforeUnmount(){ unmounted(){ </script><script> import {onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue' export default = { setup(){ // 直接在 setup中设置也可 onBeforeMount(()=>{ // 函数体 onMounted(()=>{ // 函数体 onBeforeUpdate(()=>{ // 函数体 onUpdated(()=>{ // 函数体 onBeforeUnmount(()=>{ // 函数体 onUnmounted(()=>{ // 函数体 </script>9.自定义hook函数类似 Vue2 minxin什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。类似于vue2.x中的mixin。自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。let point = reactive({ function savePoint(event){ point.x = event.pageX point.y = event.pageY console.log(event.pageX,event.pageY) onMounted(()=>{ window.addEventListener('click',savePoint) onBeforeUnmount(()=>{ window.removeEventListener('click',savePoint) })在 src 文件夹下 新建 hook文件夹新建 usePoint.js// usePoint.js // 需要混入的功能放入一个函数中 export default = function(){ let point = reactive({ function savePoint(event){ point.x = event.pageX point.y = event.pageY console.log(event.pageX,event.pageY) onMounted(()=>{ window.addEventListener('click',savePoint) onBeforeUnmount(()=>{ window.removeEventListener('click',savePoint) // 需要用到的数据返回 return point }// 在其他 .vue 文件中的使用 import usePoint from '../hooks/usePoint' const point = usePoint() return {point}10.toRef作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。语法:const name = toRef(person,'name')应用: 要将响应式对象中的某个属性单独提供给外部使用时。扩展:toRefs与toRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)setup(){ let person = reactive({ a:"a", b:"b", c:"c", name:"王二", return { // 改动后为引用数据类型 a:toRef(person,'a'), b:toRef(person,'b'), // 同样为开辟新的 ref 原person未改变 name:ref(name) // 此处为开拓新的内存赋值 c:person.c, // toRefs 特别好用 简化语法 // ES6 合并对象 toRefs返回值为对象 将每一个key都封装为 ref对象 ...toRefs(person) }5.其它 Composition API1.shallowReactive 与 shallowRefshallowReactive:只处理对象最外层属性的响应式(浅响应式)。shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。什么时候使用?如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。const person = shallowReactive({ hobby: ['抽烟', '喝酒', '烫头'], b:{ // b 改变 无响应式 c:666 // c改变无响应式 const person = ref({ // 内部调用 reactive 响应式存在 hobby: ['抽烟', '喝酒', '烫头'], b:{ // b 改变 无响应式 c:666 // c改变无响应式 // 第一层次为响应式 const person = shallowRef({ // person.value 变为 Object 响应式消失 hobby: ['抽烟', '喝酒', '烫头'], b:{ // b 改变 无响应式 c:666 // c改变无响应式 })2.readonly 与 shallowReadonlyreadonly: 让一个响应式数据变为只读的(深只读)。shallowReadonly:让一个响应式数据变为只读的(浅只读)。应用场景: 不希望数据被修改时。// 应用场景: 数据为其他组件传输 不希望使用者更改 在使用前使用readOnly覆盖 const person = reactive({ hobby: ['抽烟', '喝酒', '烫头'], b:{ // b 改变 无响应式 c:666 // c改变无响应式 // 将person覆盖为 只读的形式 // 内部的全部数据均不可更改 person = readOnly(person) // shallow 表示 只有浅层次不可更改 // hobby不可更改 但是 c 可以更改 person = shallowReadonly(person)3.toRaw 与 markRawtoRaw:作用:将一个由reactive生成的响应式对象转为普通对象。使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。markRaw:作用:标记一个对象,使其永远不会再成为响应式对象。应用场景:有些值不应被设置为响应式的,例如复杂的第三方类库等。当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。// 应用场景: 还原被 ref 与 reactive 操作的数据 const person = reactive({ hobby: ['抽烟', '喝酒', '烫头'], b:{ // b 改变 无响应式 c:666 // c改变无响应式 console.log(toRow(person)) // 输出的为原始 Person 数据 let person = { hobby: ['抽烟', '喝酒', '烫头'], c:666 function addCar(){ let car = {name:'奔驰', price:40} // 被markRaw修饰后 car 永远不会成为响应式数据了 person.car = markRaw(car) return { // 此方法有误区就是当想给 person 添加新的属性时无法添加 // toRefs是将person拆开返回一个下级每一层的对象返回 ...toRefs(person), // 直接返回person 虽然需要 person.*** 但是由于Proxy可以给添加的信息增添响应式 person }4.customRef作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。实现防抖效果:<template> <input type="text" v-model="keyword"> <h3>{{keyword}}</h3> </template> <script> import {ref,customRef} from 'vue' export default { name:'Demo', setup(){ // let keyword = ref('hello') //使用Vue准备好的内置ref //自定义一个myRef function myRef(value,delay){ let timer //通过customRef去实现自定义 // track 通知get追踪数据 // trigger 通知Vue重新渲染DOM 查看数据改变 return customRef((track,trigger)=>{ return{ get(){ track() //告诉Vue这个value值是需要被“追踪”的 // 修改 value时会进更改 return value set(newValue){ // 防抖效果 clearTimeout(timer) timer = setTimeout(()=>{ value = newValue trigger() //告诉Vue去更新界面 },delay) let keyword = myRef('hello',500) //使用程序员自定义的ref return { keyword </script>5.provide 与 inject作用:实现祖与后代组件间通信套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据具体写法:祖组件中:setup(){ ...... let car = reactive({name:'奔驰',price:'40万'}) provide('car',car) // 给自己的后代组件传递数据 ...... }后代组件中:setup(props,context){ ...... const car = inject('car') // 接收祖先组件传递的数据 return {car} ...... }6.响应式数据的判断isRef: 检查一个值是否为一个 ref 对象isReactive: 检查一个对象是否是由 reactive 创建的响应式代理isReadonly: 检查一个对象是否是由 readonly 创建的只读代理isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理// readonly 处理的数据 仍然是代理 Proxy 对象6.Composition API 的优势1.Options API 存在的问题使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。2.Composition API 的优势我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。可以使用 Hook 函数进行分装7.新的组件1.Fragment在Vue2中: 组件必须有一个根标签在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中好处: 减少标签层级, 减小内存占用// Fragment 类似于wx小程序 的 block组件<template> <!--Vue3支持无根标签实际上是 自动添加一层根Fragment在虚拟DOM中使用 在渲染真实DOM是会删除 提高渲染速度--> <Fragment> <h3>hello</h3> <h3>hello</h3> <h3>hello</h3> <h3>hello</h3> </Fragment> </template>2.Teleport什么是Teleport?—— Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。<!-- teleport包裹的Html不会在当前组件中渲染 会传送到 to 参数指定的位置 --> <!-- to 支持的写法 #app body css选择器 html标签 --> <teleport to="移动位置"> <div v-if="isShow" class="mask"> <div class="dialog"> <h3>我是一个弹窗</h3> <button @click="isShow = false">关闭弹窗</button> </div> </div> </teleport>3.Suspense等待异步组件时渲染一些额外内容,让应用有更好的用户体验使用步骤:异步引入组件import {defineAsyncComponent} from 'vue' // 异步/动态 引入组件 const Child = defineAsyncComponent(()=>import('./components/Child.vue'))使用Suspense包裹组件,并配置好default与 fallback<template> <div class="app"> <h3>我是App组件</h3> <!-- Suspense 内部存在两个插槽 --> <!-- default 当其中的数据准备完毕后进行加载 --> <!-- fallback 当default中的数据未准备完毕时展示 --> <Suspense> <template v-slot:default> <Child/> </template> <template v-slot:fallback> <h3>加载中.....</h3> </template> </Suspense> </div> </template>/* 静态引入: 当最内层组件(最后渲染的组件)全部准备完毕后 再进行渲染 动态引入: 无需等待最后加载完毕再渲染 准备好哪个渲染哪个 使用动态引入的组件时 组件内部可以返回promise实例对象 出现动态加载的原因 1.网速慢 2.使用异步Promise返回数据 */8.其他1.全局API转移Vue 2.x 有许多全局 API 和配置。例如:注册全局组件、注册全局指令等。//注册全局组件 Vue.component('MyButton', { data: () => ({ count: 0 template: '<button @click="count++">Clicked {{ count }} times.</button>' //注册全局指令 Vue.directive('focus', { inserted: el => el.focus() }Vue3.0中对这些API做出了调整:将全局的API,即:Vue.xxx调整到应用实例(app)上2.x 全局 API(Vue)3.x 实例 API (app)Vue.config.xxxxapp.config.xxxxVue.config.productionTip移除Vue.componentapp.componentVue.directiveapp.directiveVue.mixinapp.mixinVue.useapp.useVue.prototypeapp.config.globalProperties// vue3 可以识别 是执行 build 还是 serve 所以移除了 config.production2.其他改变data选项应始终被声明为一个函数。过度类名的更改:Vue2.x写法.v-enter, .v-leave-to { opacity: 0; .v-leave, .v-enter-to { opacity: 1; }Vue3.x写法.v-enter-from, .v-leave-to { opacity: 0; .v-leave-from, .v-enter-to { opacity: 1; }移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes移除v-on.native修饰符父组件中绑定事件<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />子组件中声明自定义事件<script> export default { // 声明的事件为自定义事件 其余为原生事件 emits: ['close'] </script>移除过滤器(filter)过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。...
Vue2入门学习笔记Vue2入门必备!⭐关注我查看更多配套笔记学习视频:https://www.bilibili.com/video/BV1Zy4y1K7SH/【尚硅谷Vue全家桶】本博客是对该视频内容的整理以及加入自己的理解 想全面学习的推荐大家去看原视频1.Vue动画自己使用 Css 也能实现<!-- 使用标签transition包裹 --> // apper属性可以让动画加载时出现 简写 apper 属性直接添加 <transition name="hello" :apper="true" apper> <div> <button @click="isShow = !isShow"> 显示/隐藏 </button> <h1 v-show="isShow"> 我是动画元素 </h1> </div> </transition> <!-- 必须使用 vue 规定的名称识别 --> <style> /* .之后的名称与name一致 不加name默认为v */ .hello-enter-active { animation: 动画名 持续时间; .hello-leave-active { animation: 动画名 持续时间 everse; .v-enter-active { animation: 动画名 持续时间; .v-leave-active { animation: 动画名 持续时间 everse; @keyfarm 动画名{ </style>2.Vue过渡<!-- 使用标签transition包裹 --> // apper属性可以让动画加载时出现 简写 apper 属性直接添加 <transition name="hello" :apper="true" apper> <div> <button @click="isShow = !isShow"> 显示/隐藏 </button> <h1 v-show="isShow"> 我是动画元素 </h1> </div> </transition> <!-- 必须使用 vue 规定的名称识别 --> <style> /* .之后的名称与name一致 不加name默认为v */ /* 给所需动画添加过度属性 */ transition: all 0.5s linear; /* 初始效果 离开的终点 */ .v-enter,.v-leave-to { animation: 动画名 持续时间; /* 过渡效果中 */ .v-enter-active,.v-leave-active { transition: all 0.5s linear; /* 结束效果 结束的起点 */ .v-leave-enter-to,.v-leave { animation: 动画名 持续时间 everse; @keyfarm 动画名{ </style>3.多个元素过渡<!-- 使用标签transition包裹 --> // apper属性可以让动画加载时出现 简写 apper 属性直接添加 <button @click="isShow = !isShow"> 显示/隐藏 </button> <!-- 多个动画添加需要使用transition-group --> <transition-group group name="hello" apper> <div> <h1 v-show="isShow"> 我是动画元素 </h1> <h1 v-show="isShow"> 我是动画元素 </h1> </div> </transition-group> <!-- 必须使用 vue 规定的名称识别 --> <style> /* .之后的名称与name一致 不加name默认为v */ /* 给所需动画添加过度属性 */ transition: all 0.5s linear; /* 初始效果 离开的终点 */ .v-enter,.v-leave-to { animation: 动画名 持续时间; /* 过渡效果中 */ .v-enter-active,.v-leave-active { transition: all 0.5s linear; /* 结束效果 结束的起点 */ .v-leave-enter-to,.v-leave { animation: 动画名 持续时间 everse; @keyfarm 动画名{ </style>4.超级好用的动画库animate.css<transition enter-active-class="animate__fadeIn" leave-active-class="animate__fadeOut" <h1 v-show="visible" class="animate__animated">Animate.css</h1> </transition>// 使用方法 // name="animate__animated animate__bounce" 在 transition中添加该属性 // 在官网找到喜欢的属性配置 复制添加到 // enter-active-class="" leave-active-class="" // npm安装 // npm install animate.css // 引用 animate.css 动画库 import animate from 'animate.css' Vue.use(animate)官网:Animate.css | A cross-browser library of CSS animations.5.Http请求 代理// 对 xhr 进行封装 // xhr new XMLHttpRequest() xhr.open() xhr.send() // jQuery $.get $.post // axios // 与 xhr平级 // fetchVue 项目中 推荐使用 axios解决跨域问题1.CORS2.Jsonp script src 只能使用 get3.代理服务器 代理服务器默认端口号 80801.-- nginx2.-- vue-cli5.1简易代理const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, // 在配置文件中添加以下配置 开启代理服务器 代理服务器开启在 8080 devServer: { // 需要转发给谁 proxy:'http://后端服务器地址', 8080端口服务器 的根目录为 public 其中有的东西直接回返回 不会转发请求 // 在axios中请求 axios.get('http://localhost:8080/后端路由地址').then( response =>{ errror =>{ 5.2完美代理const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, // 在配置文件中添加以下配置 开启代理服务器 代理服务器开启在 8080 devServer: { proxy:{ // 配置 转发服务器的前缀 '/api':{ // 转发地址 target:'http://后端服务器地址', //加入此配置项 转发地址为 /api/student 替换为 /student 不然会请求原路径 pathRewrite:{ '^/api':'' ws:true, // 用于支持 websocket changeOrigin:true, // 说谎(伪装请求地址) 默认为 true // 可配置多个 '/cas':{ target:'http://后端服务器地址', pathRewrite:{ '^/cas':'' ws:true, changeOrigin:true, axios.get('http://localhost:8080/配置的前缀/后端路由地址')6.静态组件import '....' // vue 会进行严格的语法检查 放在assat进行引入 // 推荐放入 public 文件夹 在 index.html 中使用 link 引入 bootstrap.. 等 ES6 合并对象语法 Obj = {...对象1名称,...对象2名称}7.vue-resource vue的插件库对 xhr 进行封装Vue.use(vueResource) 在main.js中进行全局配置在 vm 中会出现 $http使用 this.$http 代替 axios api完全一致 vue1.0广泛使用8.slot 插槽8.1默认插槽<!-- 正常自闭和组件 / 正常组件 --> <Student/> <Student></Student> <!-- 相同组件需要展示不同的元素时使用 --> <!-- 默认插槽写法 --> <!-- 父组件 --> <Student> <!-- 需要插入的内容 --> <img src=""> </Student> <!-- 子组件 --> <div> <!-- 父组件传入插槽的内容 --> <slot>使用者没有传入插槽 会将我展示</slot> </div> <!-- style 可以在 父/子 组件书写 -->8.2(具名插槽)多个插槽<!-- 多个插槽写法 --> <!-- 父组件 --> <Student> <!-- 需要插入的内容 --> <img src="" slot="slot名称1"> <vido src="" slot="slot名称2"> <!-- template写法 template标签 不影响结构--> <!-- slot:slot名称1 为 vue2.7 更新的写法 只能在 template标签中使用 --> <template slot="slot名称1" slot:slot名称1> <h1>我是h1</h1> <div>我是div</viv> </template> </Student> <!-- 子组件 --> <div> <!-- 父组件传入插槽的内容 --> <slot name="slot名称1">使用者没有传入插槽 会将我展示</slot> <slot name="slot名称2">使用者没有传入插槽 会将我展示</slot> </div> <!-- style 可以在 父/子 组件书写 -->8.3作用域插槽<!-- 作用域插槽写法 --> <!-- 父组件 --> <Student> <!-- 作用域必须使用 template 标签 传参 作用域 --> <template scop="Obj"> <!-- games msg 在Obj中 --> <h1>{{Obj.games}}</h1> <h1>{{Obj.msg}}</h1> </template> </Student> <!-- 子组件 --> <div> <!-- 子组件插槽中给父组件传参 --> <slot :games="games" msg="这里是msg信息">使用者没有传入插槽 会将我展示</slot> </div> <!-- style 可以在 父/子 组件书写 -->9.Vuex多组件共享数据 状态 = 数据多个组件依赖于同一个状态来自不同组件的行为需要修改同一状态1.npm i vuex 2.import Vuex from 'vuex' // 在 index.js 中 引入 // vue2 -> vuex3 // vue3 -> vuex4 3.Vue.use(Vuex) Vue({ store:"hello", // 在 vm 中 出现 $store 保存传入的数据在components同级文件夹下添加 store 文件夹添加 index.js// 该文件用于创建 Vuex 中最为核心的store // 引入 import Vue from 'vue' import Vuex from 'vuex' // 应用Vuex插件 Vue.use(Vuex) // 准备 actions const actions = { // 需要dispatch的函数 "函数名"(context,value){ console.log('actions 中的函数被调用了',context,value); // 分配给 其他 dispatch 分段处理 便于维护代码 context.dispatch("函数2",value); context.commit("函数名(使用大写与actions进行区分)",value); // 准备 mutations 操作 data // 开发者工具 检测 mutations const mutations = { "大写函数名"(state,value){ state.value += value; // 准备 state 用于存储数据 全局 data const state = { // 准备数据 "value":0, // 改变后会替代 vc 里的 data // 实现全局的 计算属性 computed const getters = { // 对 state 进行加工 bigSum(state){ return state.sum*10; // 创建并暴露 store export default new Vuex.Store({ actions, mutations, state, getters, })main.jsimport store from './store/index.js' // 配置项中添加store new Vue({ store })js 执行 import 全部 import 会被提升到最高层vue 规定必须 先 ues(vuex) 再 创建 store 实例使用2.import Vuex from 'vuex'// vue2 -> vuex3// vue3 -> vuex43.Vue.use(Vuex)methds:{ this.$store.dispatch("事件名",传入的数据); // 直接跨过action this.$store.commit("事件名",传入的数据); // 获得计算属性 this.$store.getters.bigSum; }可直接夸过 Actions 进行 Commit 调用 mutation 的函数 vuex 开发者工具 历史记录符号回到该次数据 历史记录符号可以删除历史记录 禁用符号合并记录 下载符号底部分 可以 导入导出 / 粘贴复制ES6 语法let Obj1 = {x:1,y:2} let Obj2 ={ ...Obj1, // 直接将Obj1的键值对插入 }9.1 mapState mapGettersimport {mapState} from 'vue' // 映射 State 中的数据 computed:{ // 借助mapState生成计算属性 从state中读取数据 ...mapState({sum:'sum',adress:'adress'}) // 可直接在计算属性中添加 // 不可简写 对象简写方式为 {sum:sum} === {sum} !== {sum:"sum"} // Vue 可识别 简写方法 与上述方法用途一致 ...mapState(['sum','subject']) // 全局计算属性获取 ...mapGeters(['bigSum']) }9.2 mapAction mapMutationsimport {mapState} from 'vue' // 映射 State 中的数据 methods:{ // 数组 / 对象 形式默认生成方法 需要在调用时传参 /*add(value){ this.$store.commit('add',value) ...mapActions(['add']), ...mapMutations(['add']) }9.3Vuex模块化index.jsimport Vuex from 'vuex' // 计数相关属性 const countOptions = { // 配置该项可在 组件 中使用更简洁的写法 namespaced:true, // 与正常 vuex 配置完全一致 action:{}, mutation:{}, state:{}, getter:{}, // 人员相关属性 const personOptions = { action:{}, mutation:{}, state:{}, getter:{}, export default new Vuex.Store({ modules:{ countAbout:countOptions, personAbout:personOptions })// 组件中简写使用 computed:{ // 普通简写 // 调用 state时 需要使用 countAbout.data ...mapState(['countAbout','personAbout']), // 需要在 vuex 中 开启 namespaced // 调用时 直接使用 data 就可以 ...mapState('countAbout',['data']), ...mapState('personAbout',['data']), // 其他 map 同理使用 // getter 模块化之后 值为 countAbout/bigSum countAbout不再为对象 }10.路由10.1 路由基础单页面应用 路由改变 页面局部刷新 (没有全部刷新)使用 vue 的插件库 vue-routernpm install -g vue-router/* vue-router 3 版本对应 vue 2 vue-router 4 版本对应 vue 3 // npm install vue-router@3在 components 同级文件夹下创建 router 文件夹index.js// 引入 vue-router import VueRouter from 'vue-router' // 引入组件 import TheList from '../components/TheList.vue'; import AboutSnowman from '../components/AboutSnowman.vue'; // 创建 并 暴露 一个路由器 export default new VueRouter({ routes: [ path:"/TodoList", component:TheList, path:"/about", component:AboutSnowman, // path 为 * 表示匹配全部 可以匹配全部路径 路径匹配为从上至下 path:"*", component:AboutSnowman, })main.js// 引入 路由器 import router from './router' new Vue({ render: h => h(App), // 设置 router router:router, }).$mount('#app')<!-- 最终 在 html 页面中转化为 a 标签 --> <!-- to 为跳转到的路由 --> <!-- active-class 为在该路由时应用的 样式 --> <router-link to="/TodoList" active-class="active">每日清单</router-link> <router-link to="/about" active-class="active">关于我的主页</router-link> <!-- 视图展示 对应路由的位置 --> <router-view></router-view>在 components 同级文件夹下创建 pages 文件夹便于分类 pages存放 路由组件 components文件夹存放 一般组件原理 : {不断销毁与挂在组件}每个组件都有自己的$route属性,里面存储着自己的路由信息整个应用只有一个router, 可以通过组件的$router属性获取到10.2 多级路由 (嵌套路由)index.js// 引入 vue-router import VueRouter from 'vue-router' // 使用插件 import Vue from 'vue' Vue.use(VueRouter) // 引入组件 import TheList from '../components/TheList.vue'; import AboutSnowman from '../components/AboutSnowman.vue'; // 创建 并 暴露 一个路由器 export default new VueRouter({ routes: [ // 一级路由 path:"/TodoList", component:TheList, // 路由嵌套从这里开始 // 二级路由 children:[{ // 第二级不需要加 / 会出问题 path:"new", component:TheList, // 一级路由 path:"/about", component:AboutSnowman, })<!-- 最终 在 html 页面中转化为 a 标签 --> <!-- to 为跳转到的路由 --> <!-- active-class 为在该路由时应用的 样式 --> <!-- 二级路由必须携带父亲级路由的路径 --> <router-link to="/TodoList/new" active-class="active">每日清单</router-link> <router-link to="/about/new" active-class="active">关于我的主页</router-link> <!-- 视图展示 对应路由的位置 --> <router-view></router-view>10.3 路由传参query参数 get传参<router-link to="/TodoList/new?name=雪人&age=18" active-class="active">每日清单</router-link> <!-- 加 v-bind + ES6 `` 动态传参 --> <router-link :to="`/TodoList/new?name=${name}&age=${age}`" active-class="active">每日清单</router-link> <!-- 对象式 传参 --> <router-link :to="`{ path="/TodoList/new", query:{ name:name, age:age, `" active-class="active">每日清单</router-link> <router-link to="/about/new" active-class="active">关于我的主页</router-link>// 组件中获取 this.$route.query.name this.$route.query.age10.4 命名路由export default new VueRouter({ routes: [ // 配置姓名 简化传参 name:'ListName', path:"/TodoList", component:TheList, children:[{ // 配置姓名 简化传参 name:'new', path:"new", component:TheList, })<!-- 对象式 传参 --> <!-- 使用 name 必须使用对象式 --> <router-link :to="`{ // 此处直接传入 路由器 定义的名字 name:"ListName", query:{ name:name, age:age, `" active-class="active">每日清单</router-link>10.5 params参数<!-- params参数 传参 需要配置占位符 --> <router-link :to="`/TodoList/new/11/王二`" active-class="active">每日清单</router-link> <!-- params参数 对象式 --> <router-link :to="`{ // params 只能使用name path会报错!!! name:"ListName", params:{ id:11, name:"王二", `" active-class="active">每日清单</router-link>export default new VueRouter({ routes: [ // 添加占位符设置参数 name:'ListName', path:"/TodoList/:id/:name", component:TheList, children:[{ // 添加占位符设置参数 name:'new/:id/:name', path:"new", component:TheList, })// 组件中获取 this.$route.params.name this.$route.params.age10.6 props的配置export default new VueRouter({ routes: [ name:'ListName', path:"/TodoList/:id/:name", component:TheList, children:[{ name:'new/:id/:name', path:"new", component:TheList, // props 写法一 // 该对象中的所有 key-value 都会以props的形式传给 组件 TheList中 props:{id:1,name:'hello'}, // props 写法二 // props值为布尔值,若布尔值为真,就会把该参数的 params值当做 props 值传递给组件 props:true, // props 写法三 // 值为函数 返回值 以props 传递给组件 // $route 被回调传入 真好 props($route){ return {id:$route.query.id,name:'hello'} // 结构赋值写法 props({query}){ return {id:query.id,name:query.name} })10.7 replace 记录默认为push记录 栈的不断添加replace 替换当前记录 不能返回<!-- 开启replace :replace="true" --> <router-link to="/TodoList/new" active-class="active" :replace="true">每日清单</router-link> <!-- 开启replace 简写方式 --> <router-link to="/about/new" active-class="active" replace>关于我的主页</router-link>10.8 编程式路由导航在 $router 中 直接操作 不借助 router-link<template> <button @click="showPush"> push查看 </button> <button @click="showReplace"> replace查看 </button> <button @click="back"> </button> <button @click="forward"> </button> <button @click="go"> </button> </template> <script> export default { name:'Message', methods:{ showPush(){ // 调用 $router 上的API this.$router.push({ name:"xiangqing", query:{ id:'11', name:'王二', showReplace(){ // 调用 $router 上的API this.$router.replace({ name:"xiangqing", query:{ id:'11', name:'王二', back(){ this.$router.back forward(){ this.$router.forward go(){ // 正数 前进 n 次 // 复数 后退 n 次 this.$router.go(n) </script>10.8 缓存路由组件组件切走时会被销毁 内容消失 如何保存?需要保存数据的去缓存<!-- inlude="" 配置需要缓存的组件 否则缓存全部组件 --> <!-- 防止组件被销毁 数据丢失 --> <keep-alive inlude="组件名"> <router-view></router-view> </keep-alive> <!-- 缓存多个组件 --> <keep-alive :inlude="['组件名1','组件名2','组件名3']"> <router-view></router-view> </keep-alive>10.9激活与失活声明周期用于路由组件的 开启定时器 比 mounted更高效methods:{ activated(){ // 开启定时器 deactivated(){ // 定时器自动关闭 }10.10路由守卫1.前 / 后 置路由守卫保护路由的安全 验证数据库再展示全局前置路由守卫export default new VueRouter({ routes: [ path:"/TodoList", component:TheList, // 用于存放用户需要的特殊数据 // 可以在判断中进行索引 不指定默认为 undefined 为 false meta:{isAuth:true,title:"每日清单"}, // 全局前置路由守卫 // to 路由去向 路由对象 // from 路由由来 路由对象 // next 回调函数 使用 next()才能切换路由 router.beforeEach((to,from,next)=>{ if(to.path === '/home'){ // 利用meta判断 if(from.meta.isAuth){ next() next() next() export default router// 全局后置路由守卫 // 无 next 路由跳转已经完成后触发 router.afterEach((to,from)=>{ // 可在跳转完成后更改路由 doucment.title = to.meta.title || '网页标题' // 短路判断 })2.独享路由守卫export default new VueRouter({ routes: [ path:"/TodoList", component:TheList, // 用于存放用户需要的特殊数据 // 可以在判断中进行索引 不指定默认为 undefined 为 false meta:{isAuth:true,title:"每日清单"}, // 独享路由守卫 仅有前置 无后置 beforeEnter:(to,from,next)=>{ if(...){ next() })3.组件内路由守卫<script> mounted(){ // 通过路由器规则 进该组件时被调用 // 在beforeEnter 之前 // to 一定是该组件路由 beforeRouteEnter(to,from,next){ // 通过路由器规则 离开该组件时被调用 // from 一定是该组件路由 beforeRouteLeave(to,from,next){ </script>11.浏览器路由模式main.js// hash 带 # 兼容性好 不美观 // history 不带 # 兼容性差(相对) 美观 会寻找服务器路径资源 容易出错(后端解决) new VueRouter({ // 设置模式 mode:history, })12.打包 上传服务器>>> npm run build // 生成 dist 文件夹 // 为打包后的 css js html 文件 // 直接点击 index.html 无法运行必须在服务器部署如何部署服务器node.js express 框架->npm init->text_serve 包名->npm i express中间件解决 history 模式 404问题->npm i connect-history-api-fallback// 在 server.js 同级目录下 新建 jsconst express = require('express') const history = require('connect-history-api-fallback') const app = express() // 解决history 404 问题 app.use(history) // 默认选择 index.html app.use(express.static(__dirname+"./static")) app.get('/person',(req,res)=>{ res.send({ name:'tom', age:18 app.listen(5005,(err)=>{ if(!err) console.log('服务器启动成功了!') })13.Vue UI 组件库7.1 移动端常用组件库Vant https://youzan.github.io/vanteCube UI https://didi.github.io/cube-uiMint Ul http://mint-ui.github.io7.2PC端常用UI组件库Element Ul https://element.eleme.cnlView Ul https://www.iviewui.comant design Ant Design - 一套企业级 UI 设计语言和 React 组件库直接用就行此处介绍Element UI->npm i element-uiimport Vue from 'vue'; import ElementUI from 'element-ui'; // 引入全部样式 import 'element-ui/lib/theme-chalk/index.css'; import App from './App.vue'; // 此处use 后 会注册全部组件为全局组件 // 引入的 js 非常大 7.2M(别用) Vue.use(ElementUI); new Vue({ el: '#app', render: h => h(App) });按需引入-> npm install babel-plugin-component -D 按需引入的库 -D为开发依赖(开发环境)->npm install babel-preset-es2015 --save->npm install --save-dev @babel/cli需要修改 babel.config.js 配置module.exports = { presets: [ '@vue/cli-plugin-babel/preset', ['es2015', { modules: false }], plugins: [ 'component', libraryName: 'element-ui', styleLibraryName: 'theme-chalk', }main.js// 按需引入 import {Button,Row,...} from 'element-ui' // 配置之后会引入需要的 css 而不是全部css import 'element-ui/lib/theme-chalk/index.css'; // Button 的组件名 可以自定义 // 注册全局组件 VUe.component(Button.name,Button)
Vue2入门学习笔记Vue2入门必备!⭐关注我查看更多配套笔记学习视频:https://www.bilibili.com/video/BV1Zy4y1K7SH/【尚硅谷Vue全家桶】本博客是对该视频内容的整理以及加入自己的理解 想全面学习的推荐大家去看原视频1.Vue脚手架基础官方提供的 Vue 官方 标准开发工具官方文档:https://cli.vuejs.org/zh/Vue CLI(command line interface)脚手架0.配置淘宝镜像 不然会很慢 甚至安装失败npm config set registry https://registry.npm.taobao.org1.全局安装@vue/clinpm install -g @vue/cli2.切换到对应目录 创建脚手架vue create vue_projectone输入后会选择 使用Vue的版本 2/3/自定义bable : ES6 语法转换为 ES5eslint : 语法检查工具 检查代码是否合理选择好之后输入回车进行如下操作cd 项目名npm run serve之后后开始编译代码停止终端运行 ctrl + c 连续按两次 y/n 输入什么都会关闭脚手架结构讲解srcmain.js 整个项目的入口文件App.vue.gitignore git 忽略上传git文件的 配置babel.config.js babel的配置文件package.json npm包配置文件serve 代码执行后直接运行 man.jsbuild 构建 css js htmllint 进行语法检查package-lock.json 包版本控制文件README.md 教程app.vueapp.vue内容<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App"/> </div> </template> <script> // 导入vue import HelloWorld from './components/HelloWorld.vue' export default { name: 'App', components: { HelloWorld </script> <style> /*写样式*/ #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; </style>public => index.html<!DOCTYPE html> <html lang=""> <head> <meta charset="utf-8"> <!-- 针对IE浏览器的一个特殊配置,含义是让IE浏览器以最高的渲染级别渲染页面 --> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- 开启移动端的理想视口 --> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <!-- 引入 favicon.ico <%= BASE_URL %> 代表“./”底层有处理--> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <!--会将 package.json 中的内容当作标题 webpack相关内容 --> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> <!--当浏览器不支持js时 noscript会渲染noscript的标签--> <noscript> <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <!--容器--> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>分析render函数引入的是残缺的Vue 不含模板解析器 需要自己使用render #省体积 省空间或者自己引入完整版vueimport Vue from 'vue' import App from './App.vue' Vue.config.productionTip = false new Vue({ // h 官方说法是 createElement 是一个函数 // createElement("h1","你好啊") 生成<h1>你好啊</h1> // 传入 组件 为渲染组件 render: h => h(App), }).$mount('#app')/* 关于不同版本的Vue: 1.vue.js 与vue.runtime.xxx.js的区别: (1).vue.js是完整版的Vue,包含:核心功能+模板解析器。 (2).vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。 2.因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement的数去指定具体内容。 */2.脚手架配置Vue脚手架隐藏了所有webpack相关的配置,若想查看具体的webpakc配置,请执vue inspect > output.js// 仅仅是输出让你看的文件 在这里更改是无效的如何自行配置?配置参考 | Vue CLI (vuejs.org)在 package.json 同级目录下创建 vue.config.js// Commonjs 的暴露 // node.js 使用的 commonjs的暴露 不能用ES6 module.exports = { pages: { index: { // page 的入口 entry: 'src/index/main.js', // 模板来源 template: 'public/index.html', // 在 dist/index.html 的输出 filename: 'index.html', // 当使用 title 选项时, // template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title> title: 'Index Page', // 在这个页面中包含的块,默认情况下会包含 // 提取出来的通用 chunk 和 vendor chunk。 chunks: ['chunk-vendors', 'chunk-common', 'index'] // 当使用只有入口的字符串格式时, // 模板会被推导为 `public/subpage.html` // 并且如果找不到的话,就回退到 `public/index.html`。 // 输出文件名会被推导为 `subpage.html`。 subpage: 'src/subpage/main.js' }关闭语法检查lintOnSave:false //与page平级3.ref属性<h1 ref="title">H1标签</h1> <School ref="sc">H1标签</School> 组件标签使用 <script> // vuecomponent 对象属性 console.log(this.$refs.title) // 获取真实dom // 可在此处操作DOM console.log(this.$refs.sc) // 获得vc对象 </script> <!-- 1.被用来给元素或子组件注册引用信息(id的替代者) 2.应用在htm1标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc) 3.使用方式: 打标识:<h1 ref="xxx ">.....</h1>或<School ref="xxx"></School> 获取: this.$refs.xxx -->4.props配置<template> <div> <!-- 传入data参数 --> <Student name="李四" sex="女" age="18"/> </div> </template> <template> <div> <!-- age不绑定为字符串 无法使用数字计算 可以使用绑定 --> <Student name="李四" sex="女" :age="18"/> </div> </template> export default { // 加入此项配置项 接收数据 props:['name','age','sex'], // 第二种写法 限制类型 props:{ name:String, age:Number, sex:String, // 第三种写法 props:{ name:{ type:String, // 指定类型 required:true, // 是否为必须的参数 default:"王二", // 不传参默认为该值 // props 中接收的数据无法更改 会报错 // 强行更改方法 new Vue({ props:['name','age','sex'], data(){ return { myAge = this.age; // 修改 myAge 代替 age // props 优先级更高 会优先展示 // props 不能传入 vue 已经使用的属性 // ref key ...## 配置项props 功能:让组件接收外部传过来的数据 (1).传递数据: <Demo name="xxx"/> (2).接收数据: 第一种方式(只接收)∶ props:["name"] 第二种方式(限制类型): props:{ name: Number 第三种方式(限制类型、限制必要性、指定默认值): props:{ name:{ type:String, // 指定类型 required:true, // 是否为必须的参数 default:"王二", // 不传参默认为该值 备注: props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告, 若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。5.mixin(混入)mixin.js//两个组件共同使用相同的配置 export const mixin = { methods:{ alert(this.name); export const mixin = { data(){ return { x:200, y:200, }vue中的script// 引入一个混合组件 import {mixin} from "../mixin"; export default { name:"School", // 复用配置 mixins:[minxin,mixin2], // 数据重复 // 不破坏原有数据 将现有数据与原有数据整合 如果重复现有数据优先级更高 data(){ return{ x:300, y:200, // 生命周期 mounted 等挂载函数 混入会执行相同两段代码 // 来者不拒 均会执行 mounted(){ console.log("多次重复执行"); // 全局混合 // main.js 中 import {mixin} from "../mixin"; // vm 与 vc 均会执行该函数 Vue.mixin(mixin); 6.插件聚合代码 书写全局配置在 src 添加 plugs.jsexport default { // 该参数 为 Vue的构造函数 install(Vue,x,y,z){ // 全局过滤器 Vue.filter("mySlice",fuction(value){}); // 全局指令 Vue.directive("fbind",{}); // 定义混入 Vue.mixin({}); // 给Vue原型添加方法 vm 与 vc 均能使用 Vue.prototype.hello = ()=>{alert("你好啊")}; }main.js// 引入插件 import plugs from "../plugs.js" // 在声明 vm 之前开启插件 // 能接收多个 参数 但是第一个必须是插件名 install的第一个参数必须为 Vue Vue.use(plugs,1,2,3) // 全局声明之后 可在 全部 vm vc 中使用 7.scoped样式 / less 编译<style scoped> /* 加入scoped之后 各个css之间相互独立 不互相影响 */ /* 一般app组件中 不会使用scoped */ /* 使css样式局部生效 */ </style> <style lang="less"> /* 支持使用less */ /* 需要安装包 less-loader */ /* 需要考虑兼容性问题 */ /* 安装对应版本的 less-loader 不然会报错 */ </style> <style lang="css"> </style>8.给兄弟组件传参1.传给父元素 (props传给父元素一个方法)2.由父元素传给子元素的兄弟元素(9. 中含其他方法 使用ref与自定义事件 与mounted)<template> <School :receive="receive"></School> </template> <script> export default{ name:"Compents", methods:{ receive(x){ console.log('我是app组件我收到了数据',x) </script>School.vue<script> export default{ name:"School", props:["receive"], method:{ add(x){ receive(x); // 可在子元素调用父元素的方法 </script>9.组件的自定义事件1.js内置事件 click keyup 等2.自定义事件app.vue<template> <Student v-on:atguigu="getStudentName"></Student> <Student ref="studnet"></Student> </template> <script> // 挂载mounted 子传父 mounted(){ // $on 当atguigu事件触发的时候 this.$refs.student.$on('atguigu',this.getStudentName) methods:{ getStudentName(name){ console.log("获得name数据") </script>Student.vue<template> <h1 @click="ad"></h1> </template> <script> // props:['getStudentName'], // 触发事件 子传父 methods:{ ad(){ // 触发事件 使用方法 $emit this.$emit('atguigu',this.name) </script>10.解除绑定自定义事件Student.vue<template> <h1 @click="unBind"></h1> </template> <script> methods:{ unBind(){ // 触发事件 使用方法 $emit this.$emit('atguigu',this.name) // 解绑单个自定义事件 this.$off('atguigu') // 解绑多个自定义事件 // 传入数组 this.$off(['atguigu','...','...']) // 解绑全部自定义事件 this.$off() // 摧毁 vc实例 自定义事件解绑(全部失效) this.$destroy() </script>11.全局事件总线设计一个不在任何组件内的组件 作为全部事件的中转站A中触发自定义事件 中转X 触发C的操作// 条件 //在所有组件都能访问的地方添加 数据对象 // 可以使用 $on $off $emit1.windows 2.VueCompents(需要改原型)使用 Vue 在main.js中操作VueCompent对象的____proto____指向Vue的原型对象// 在main.js 中添加 'X' 傀儡 作为 vm / vc 可以使用 $on $emit $off // 则傀儡必须为 Vue 实例 vm import Vue from 'vue' import App from './App.vue' Vue.config.productionTip = false new Vue({ el:"app", render: h => h(App), beforeCreate(){ Vue.prototype.$bus = this; //全局事件总线 }).$mount('#app') // 在其他 vc 中使用全局事件总线 methods:{ unBind(){ // 触发事件 使用方法 $emit this.$bus.$emit('atguigu',this.name) // 解绑全部自定义事件 this.$bus.$off() // 摧毁 全局事件总线 vm 实例 自定义事件解绑(全部失效) this.$bus.$destroy() beforDestroy(){ // 在在被摧毁之前解绑该组件 在总线中的事件 // 解绑单个自定义事件 this.$bus.$off('atguigu') // 解绑多个自定义事件 // 传入数组 this.$bus.$off(['atguigu','...','...']) 12.消息订阅与发布/* 消息订阅与发布 1.订阅消息︰消息名 2.发布消息︰消息内容 // 需要使用 消息订阅与发布的库 // 在各种框架里都能使用 不局限于 vue // 有多种库存在 消息订阅与发布是一种理念 // 这里推荐 : pubsub-js // 类似于 $bus // 订阅消息 import pubsub from 'pubsub-js' // 在mounted中订阅 // 需要设置id // 非vue管理必须使用箭头函数!! const this.pubid = pubsub.subscribe("消息名",fuction(msgName,data){ // msgName 为消息名 // data 为之后的传递的参数 // 或者传入 在 methods 中配置的方法 this 不会出问题 const this.pubid = pubsub.subscribe("消息名",this.method); // 在销毁前取消丁订阅 beforDistory pubsub.unsubscribe(this.pubid); // 发布消息 pubsub.publish('hello',666);13.$nextTick在DOM节点 下一次更新之后 执行回调函数this.$nextTick(()=>{ this.$refs.editInput.forEach((element)=>{ element.focus();
Vue2入门笔记Vue入门必备本!⭐关注我查看全套配套笔记学习视频:https://www.bilibili.com/video/BV1Zy4y1K7SH/【尚硅谷Vue全家桶】本博客是对该视频内容的整理以及加入自己的理解 想全面学习的推荐大家去看原视频1.基础1.特点1.采用组件化模式,提高代码复用率,让代码更好维护2.声明式编码,让编码人员无需直接操作DOM,提高开发效率3.使用虚拟DOM + 优秀的Diff算法,尽量复用DOM节点2.官网内容官方网址 :cn.vuejs.org3.下载及安装1.下载在官方网址 : 安装 — Vue.js (vuejs.org)需要配置以下内容:2.安装开发者工具一、Chrome浏览器安装方式:①:点击右上角三个点②:点击更多工具③:点击扩展程序④:点击右上角的开发者模式,将他启用⑤:将下载的Vue.crx文件直接拖动到浏览器窗口即可二:Edge浏览器安装方式①:点击浏览器右上角的三个点②:点击扩展③:点击左下角的开发人员模式,将他启用④:将Vue.crx文件拖动到浏览器即可https://pan.baidu.com/s/1MtYvMPew4lb14piIrs9x6w 提取码:66663.关闭开发者提示Vue.config.productionTip = false;2.基础使用// 非Vue控制的函数尽量写成箭头函数1.基础Vue全局配置(传参)/使用对象方法<div id="root"> <h1>你好,{{name}}</h1> <!-- 使用模板语法去渲染 --> </div> <div class="root"> <h1>你好,{{name}}</h1> <!-- {{ }} 中必须写JS表达式! --> <h2>现在时间是{{Date.now()}}</h2> </div> <!-- 表达式: 一个表达式会生成一个值,可以放在任何一个需要值的地方 --> <script> x = new Vue({ el:'#root', //el用于指定当前Vue实例为哪个容器服务 通常为css选择器字符串 data:{ //储存数据,只能在指定的容器中去使用,值写成一个对象 name:'尚硅谷', // 只会选择第一个 root // 容器与Vue是一一对应的!!! const y = new Vue({ el:'.root', data:{ name:'尚硅谷' // data第二种写法 函数式写法 vue自动调用该函数 // 不能使用箭头函数 this 会指向windows data:fuction(){ console.log('QQQ', this) return{ name:'尚硅谷' // 使用方法修改参数 y.$mount('#root') // s修改实例构造器中的 element </script>2.模板语法<!-- 功能:用于解析标签体内容 写法:{[xXXx}]. xxx是js表达式,且可以直接读取到data中的所有属性 --> <h1>你好,{{name}}</h1> <!-- 指令语法 设置元素的属性 功能:用于解析标签(包括:标签属性、标签体内容、绑定事件.....) 举例:v-bind:href=""xxx”或简写为:href="xxx"",xxx同样要js表达式,且可以直接读取到data中的所有属性 备注:Vue中有很多的指令,且形式都是: v-????,此处我们只是拿v-bind举个例子。 --> <a v-bind:href="url">点我</a> <!-- 简写 --> <a :href="url">点我</a>3.数据绑定<input v-bind:value="url">我是单项绑定 <input v-model:value="url">我是双向绑定 <!-- v-model 只能用在表单类元素上 输入类元素 含有value值 --> <!-- 简写 --> // 绑定后 为js表达式 不绑定为字符串 <input :value="url">我是单项绑定 <input v-model="url">我是双向绑定4.MVVM模型1.M:模型(Model):对应data中的数据2.V:视图(View):模板3.VM:视图模型(ViewModel) : Vue 实例对象心因此 vm 也经常作为 Vue的实例对象const vm = new Vue({})5.vm<!-- vm中实例含有的内容 可以直接在 Vue 模板中直接使用 --> <h1> {{ $mount('#root') }} </h1> {{name}} 存在是因为会自动添加到 vm 对象中6.进阶技术 Object方法// 使用 object.defineProperty 添加对象成员 Object.defineProperty(对象名,添加的键,添加的值) Object.defineProperty(obj,keys,{ value:18, // 设置 enumerable:true, // 控制属性是否可以被枚举(被遍历) 默认为false writable:true, // 控制属性是否可以被修改 默认为false configurable:true, // 控制属性石佛可以被删除 默认为false // 当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值 get:function(){ return number; // 简写 get(){ return number; // getter 与 setter set(value){ number = value; })7.数据代理<!--数据代理:通过一个对象代理对另一个对象中属性的操作(读/写) --> <script> obj1 = { x:100, obj2 = { y:200, Object.defineProperty(obj2,x,{ value:obj1.x, // 此处与 obj1 的属性绑定 </script> 数据代理在 Vue 中的应用<!-- vm 实例中的数据 与构造器中 data中的数据绑定 -->_data实际上里面为数据劫持8.事件的基本使用<div id="root"> <!-- 事件触发回调函数 --> // 需要传参可以加括号 但是 event会消失 加入 $event Vue帮助传参 <button v-on:click="showInfo($event,66)">点我提示信息</button> <!-- 事件修饰符 + 简写 @ --> <a href="#" @click.prevent="methods">点我跳转</a> // 阻止跳转 <!-- 1.prevent: 阻止默认事件(常用); 2.stop:阻止事件冒泡(常用); 3.once:事件只触发一次(常用); 4.capture:使用事件的捕获模式; 5.self:只有event.target是当前操作的元素是才触发事件; 6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕; // 默认的时候 先执行回调函数 等待回调函数执行完毕再运行默认事件(鼠标下滑等) --> </div> <script> x = new Vue({ el:'#root', data:{ // 数据劫持 + 数据代理 name:'尚硅谷', methods:{ // methods中的函数可以加到data中 但会拖累Vue showInfo(event,number){ // event 为事件对象 alert("同学你好" + number); aClick(e){ e.preventDefault(); alert("同学你好") </script>9.键盘事件<!--准备好一个容器--> <div id="root"> <h2>欢迎来到{{name}学习</h2> <!-- 这里的.enter代表按下键盘enter键 --> <input type="text" placeholder="按下回车提示输入"@keyup.enter="showInfo"> </div> <script type="text/javascript"> Vue.config.productionTip = false //阻止 vue在启动时生成生产提示 new Vue({ el : "#root", data:{ name:"尚硅谷" methods:showInfo(e){ console.log(e.target.value) </script> 1.Vue中常用的按赞别名: 回车 =>enter 删除=>delete(捕获“删除”和“退格”键) 退出=>esc 空格=>space 换行=>tab 上=>up 下=>down 左=>left 右=> right 2.Vue未提供别名的按健,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名) caps-lock 大小写键别名 3.系统修饰健(用法特殊): ctrl、alt、shift、meta(win) (1).配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键。事件才被触发。 (2).配合keydown使用:正常触发事件。 4.也可以使用keyCode去指定具休的按键(不推荐) 5.Vue.config.keyCodes.自定义健名=健码,可以去定制按健别名/*修饰符是可以连续使用的(链式调用)@click.prevent.top@keydown.alt.y / y 与 alt 一起按下 /*/10.计算属性<div id="root"> <h2>欢迎来到{{name}}学习</h2> // 模板中可以写一些简单的语句 但是不能写 Vm中没有的! <h2>欢迎来到{{name = !name}}学习</h2> <input type="text" placeholder="按下回车提示输入"@keyup.enter="showInfo"> </div> <script type="text/javascript"> Vue.config.productionTip = false //阻止 vue在启动时生成生产提示 new Vue({ el : "#root", data:{ // firstname 叫属性 // "尚硅谷" 叫属性值 firstname:"尚硅谷", lastname:"snowman", methods:{ get(){ // method中的方式不会缓存 会被多次调用 // computed的属性叫做计算属性 computed:{ // 输入 {{fullname}} 自动调用fullname get函数 fullname:{ // fullname 被读取调用get get(){ // 此处的 this 被 Vue 配置为 vm // get 什么时候调用? // 1.初次读取fullname时 2.所依赖的数据改变时 return this.firstname + this.lastname; // fullname 被修改时 调用set set(value){ const arr = value.split('-'); this.firstname = arr[0]; this.lastname = arr[1]; * 1.定义:要用的属性不有在,要通过已有属性计算得来。 * 2.原理:底层什助了objcet.defineproperty方法捉供的getter和setter. * 3.get函数什么时候执行? (1).初次读取时会执行一次. (2).当依赖的数据发生改变时会被再次调用。 * 4.优势:与methods实现和比,内部有级存机制(复用)。效率更高。调试方便 * 5,备注: 1.计算届性最终会出现在vm上,直接读取使用即可.. 2.如果计算属性要被修返,那必须写set函数夫响应修改,fset中要引起计算时依赖的数据发牛 // 简写方式 确定只读不改 fullname(){ return "小猪佩奇"; </script>11.监视属性Vue ({ // 监视在 vm 中的值是否发生变化 变化时 作何处理 watch:{ isHot:{ // 初始化时调用一次handler immediate:true, // isHot发生变化 传参 newValue为新的值 oldValue 为旧的值 handler(newValue,oldValue){ console.log(newValue,oldValue); // 第二种写法 添加到vm实例中 vm.$watch('isHot',{ immediate:true, handler(newValue,oldValue){ console.log(newValue,oldValue); })12.深度监视// Vue可以检测多层级的监视 watch不检测 * (1).vue中的watch默认不监测对象内部值的改变(一层)。 * (2).配置deep:true可以监测对象内部值改变(多层)。 * 备注: * (1).vue自身可以监测对象内部值的改变,但vue提供的watch默认不可以! * (2).使用watch时根据数据的具体结构,决定是否采用深度监视。 Vue ({ data:{ number:{ a:500, b:600, name:'逯少', // 监视在 vm 中的值是否发生变化 变化时 作何处理 watch:{ number:{ // 开启深度监视 不开启的话number的成员不会被检测 deep:true, // 初始化时调用一次handler immediate:true, // number发生变化 传参 newValue为新的值 oldValue 为旧的值 handler(newValue,oldValue){ console.log(newValue,oldValue); // watch 简写 前提: 不需要 deep immediate watch:{ name(){ console.log("name被改变了"); // $符简写 vm.$watch('isHot',fuction(){ console.log("isHot被改变了"); })13.watch与computed对比// computed更容易写 // 但是computed无法进行异步操作 // computed 需要返回值 异步会出错 // watch中的异步操作 handler(){ setTimeout(()=>{ console.log("1s passing number改变了"); },1000) * computed和lwatch之间的区别: * 1.computed能完成的功能。 watch都可以完成.. * 2.watch能完成的功能,computed不一定能完成,例如: watch可以进行异步糅作。 * 1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象。 * 2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等),最好写成箭头函数。这样this的指向才是vm或组件实例对象。 14.修改/绑定 classVue 会解析数组/对象 --> Js表达式 <!-- 需要的 class 是动态的 --> <div class="basic" :class="yourclass"></div><br/><br/> <!--绑定class样式--数组写法,适用丁:要绑定的样式个数不确定、名字也不确定--> <div class="basic" :class="list_arr"></div> <!--绑定class样式对象写法。适用于:要绑定的样式个数确定、名宁也确定但要动态决定用不川--> <div class="basic" :class="classObj"></div> <!-- Style行内样式 传入Style对象 或者 数组内包含Style对象--> <div class="basic" :style="styleObj"></div> ---> 经过Vue解析之后会变成 <div class="basic normal"></div><br/><br/> <div class="basic class1 class2 class3" :class="list_arr"></div> <div class="basic class1 class3"></div> <div class="basic" style="color:red;fontSize:10px;backgroundColir:grzy;"></div> <script> new Vue({ el:'#id', data:{ yourclass:'normal', list_arr:['class1','class2','class3'], classObj:{ class1:true, class2:false, class3:true, // 此处的样式必须为 css 中有的样式 font-size --> fontSize 多单词拆散大小写拼接 styleObj:{ color:red, fontSize:10px, backgroundColir:grzy, </script>15.条件渲染<!-- v-show 与 v-if show控制display属性 if直接会删除/保留该元素 --> “” 中填写 js 表达式 <div v-show="false">我的display属性为none</div> <div v-show="true">我的display属性为默认</div> <!-- 与js语法中if else if else 的用法一致 --> <div v-if="false">我不会在DOM中出现</div> <div v-else-if="false">我不会在DOM中出现</div> <div v-else="false">我不会在DOM中出现</div> <div v-if="true">我会在DOM中出现</div> 需要整体隐藏的时候 使用 template不会影响结构 div也可以 注意css template 只能与 v-if 配合 v-show 会失效 <template v-if="表达式"> <h1>h1</h1> <h1>h2</h1> </template>16.列表渲染 循环<div> <ul> <!-- 每个li标签需要有其单独唯一的标识 --> <li v-for="p in persons" :key="p.id">{{p.id}}-{{p.name}}</li> <!-- p为persons中的元素 index为其对应的下标 index写key比较方便 --> <li v-for="(p,index) in persons" :key="index">{{p.id}}-{{p.name}}</li> <!-- v-for 可以遍历 数组,对象,字符串 --> <!-- 遍历对象 --> <li v-for="(value,key) in Obj" :key="key">{{key}}-{{value}}</li> </ul> </div> <script> new Vue({ el:'#id', data:{ persons:[ {id:"001", name:"001"}, {id:"002", name:"002"}, {id:"003", name:"003"}, Obj:{ name:"王二", adress:"二仙桥", </script> <!-- v-for指令: 1.用于展示列表数据 2.语法:v-for="(item,index) in xxx" : key="yyy" 3.可逅历:数组、对象、字符串(用的很少)、指定次致(用的很少) -->17.循环 key详解<!-- 虚拟Dom 对比算法 !!! 对比后转化为真实 Dom key 如果用 index进行绑定会出问题 --> <!-- 一定要使用唯一的 key 可以使用数据库中的主键 --> <!-- 面试题: react、 vue中的key有什么作用?(key的内部原理) 1,虚拟DON中'key的作用: key是虚拟DOA对象的标识,当状态中的数据发生变化时,Vue会根据【新数据】生成【新的虚拟DO随后Vue进行【新虚抵DOM】 与【旧虚拟DOM】的差异比较,比较规则如下: 2.对比规则: (1).旧虚拟DOM中找到了与新虚拟DO相同的key: 若虚拟DOM中内容没变,百接使用之前的真实DOM 若虚拟DOM中内容变了,则生成新的真实DOM。随后替换掉页面中之前的真实DOM (2).旧虚拟DOM中未找到与新虚拟DOM相同的key 创建新的真实DOM。随后渲染到到页面。 3.用index作为key 可能会引发的问题: 1.若对数据进行:逆序添加、逆序剧除等破环顺序操作: 会产生没有必要的真实DOM更新==>界面效果没问题,但效率低。 2.如果结构中还包含输入类的DOM: 会产生错误DOM更新==>界面有问题 4.开发中如何选择key?: 1.最好使用每条数据的唯一标识作为key,I比如id、于机号、身份证号、学号等唯一伯。 2.如果不存在对数据的逆序添加、逆序朋除等破坏顺序操作,仅用于渲染列表用于展示使用index作为key是没有问题的。 -->18.列表过滤 (搜索)<input v-model="keyWord" placeholder="请输入姓名"> <ul> <li v-for="p in filterPersons" :key="p.id">{{p.id}}-{{p.name}}</li> <button @click="sortType = 1">id降序</button> <button @click="sortType = 0">原排序</button> </ul> <script> new Vue({ el:'#id', data:{ sortType:0, keyWord:"", persons:[ {id:"001", name:"001"}, {id:"002", name:"002"}, {id:"003", name:"003"}, {id:"004", name:"004"}, filterPersons:[], // 使用watch 实现搜索方法 watch:{ keyWords:{ immediate:true, handler(val){ this.filterPersons = this.persons.filter((e)=>{ return p.name.indexOf(bal) !== -1; // 计算属性实现 computed:{ filterPersons:{ return this.persons.filter((p)=>{ return p.name.indexOf(this.keyWord) !== -1; // 实现 分需求 排序功能 computed:{ filterPersons:{ const list = this.persons.filter((p)=>{ return p.name.indexOf(this.keyWord) !== -1; if(this.sortType){ list.sort((p1.age,p2.age)=>{ return this.sortType === 1 ? p1.age-p2.age : p2.age-p1.age; return list; </script> <!-- "abc".indexOf('') >>> 0 //空字符串返回值为 0 -->3.Vue检测数据的原理19.Vue检测数据改变的原理<!-- 更改数据 --> <script> // Vue无法检测 vm.persons[0] = {id:"001", name:"马老师", age:50}; // Vue可以检测 vm.persons[0].name = "马老师"; vm.persons[0].age = 50; </script> <script> // data --> 加工后 vm 对象中生成 _data 包含data中的属性 并创建对应的getter setter方法 get set 方法 在调用 对象时会自动被更改 // 直接 object.defineProperty 对应的对象 调用get set 会陷入死循环 // 创建检测实例对象 进行数据代理 data = { name:"王二", age:18, const obs = new Observer(data) console.log(obs) function observer(obj){ //汇总对象中所有的属性形成一个数组 const keys = object.keys(obj) keys.forEach((k)=>{ object.defineProperty(this,k,{ get(){ return obj[k] set(val){ obj[k] = val </script> !!!!!! 检测原理就是使用setter 考虑非常周到!递归数组都能检测!!!20.vue.set 添加响应式属性// _data 与 data 是数据代理 // Vue 方法 Vue.set(target,key,value) Vue.set(vm.student,"name","王二") Vue.set(vm._data.student,"name","王二") // vm 方法 vm.$set(vm.student,"name","王二") vm.$set(vm._data.student,"name","王二") // 局限性 set 不能 将 vm 作为 target21.Vue检测数组// 不能直接更改数组中的数值 vue 无法检测 // Vue 可以检测程序员使用数组方法 // 以下均为可以修改数组的方法 /* push/pop 末尾 添加/删除 元素 * unshift/shift 开头 添加/删除 元素 * splice 替换元素 * sort 排序 * reverse 反转 // 两个push不一样 vue 对push进行了覆写 原push功能 + 响应式的更改 vm.data.student.hobby.push === Array.prototype.push >>> false 总结/* Vue监视数据的原理: 1. vue会监视data中所有层次的数据。 2.如何监测对象中的裁据 通过setter实现监视,且要在new Vue时就传入要监测的数据。 (1).对象中后追加的属性,Vue默认不做响应式处理 (2).如T给后添加的属性做响应式,请使用如下API: vue.set(target.propertyName/index.value) vm.$set(target. propertyName/index. value) 3。如何监测数组中的数据 通过包裹数组更新元素的方法实现,本质就是做了两件事: (1).调用原生对应的方法对数组进行更新。 (2).重新解析模板,进而更新页面。 4.在Vue修改数组中的某个元素一定要用如下方法: 1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse() 2.Vue.set()或vm.$set() 特别注意: Vue.set()和 vm.$set()不能给vm 或 vm的根数据对象添加属性 !! */4.收集表单数据/* 若:<input type="text"/>,则v-model收集的是value值。用户输入的就是value值。 若:<input type="radio" />,则v-model收集的是value值,且要给标签配置value值。 若:<input type="checkbox"/> 1.没有配置input的value属性,那么收集的就是checked(勾选or未勾选,是布尔值) 2.配置input的value属性: (1)v-model的初始值是非数组,那么收集的就是checked(勾选 or未勾选,是布尔值) (2)v-model的初始值是数组,那么收集的的就是value组成的数组 备注:v-model的三个修饰符: lazy:失去焦点再收集数据 number:输入字符串转为有效的数字 trim:输入首尾空格过滤 */5.过滤器day.js 库可以格式化时间在BootCDN可以查询到开源的所有库过滤器 可以在 v-bind 与 插值语法中使用 <!-- 语法: 本质是一种函数 可以连续链式使用--> <h3>现在是: {{time | timeFormater() | otherFilters()}}</h3> <script> // 全局过滤器 任意 Vue 实例对象中均可使用 Vue.filter("mySlice",fuction(){ return "过滤后的内容" var vm = new Vue({ filters:{ timeFormater(value){ return "value为传入time的值 返回值作为 表达式展示的值" </script>6.Vue指令汇总<!-- v-bind :单向绑定解析表达式,可简写为:XXX v-model :双向数据绑定 v-for :遍历数组/对象/字符串 v-on: 绑定事件监听,可简写为@ v-if:条件渲染(动态控制节点是否存存在) v-else:条件渲染《动态控制节点是否存存在) v-show:条件渲染(动态控制节点是否展示) v-text: 替换整个 Value v-html: 可以渲染html语法 <h1></h1> // v-html 具有安全性问题 xss注入 v-cloak: 加入该属性与css Vue接管时会删除该属性 v-once: 让某元素只渲染一次 v-pre: 阻止Vue解析doucment --> <!-- v-html指令: 1.作用:向指定节点中渲染包含html结构的内容 2.与插值语法的区别: (1).v-html会替换掉节点中所有的内容,{{xx}}则不会 (2).v-html可以识别html结构。 3.严重注意: v-html有安全性问题!!!! (1).在网站上动态渲染任意HTAL是非常危险的,容易导致XSS攻击. (2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上! --> <!-- v-cloak指令(没有值): 1.本质是一个特殊属性,Vue实例创建完中并接管容器后。会删掉v-cloak属性. 2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。 --> <style> [v-cloak]{ ; </style> <!-- v-once指令: 1.v-once所在节点在初次动态渲染后,就视为静态内容了 2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。 --> <!-- v-pre指令: 1.跳过其所在节点的编译过程。 2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。 -->7.Vue自定义指令7.1函数式n放大十倍之后的值为: <span v-big="n"> </span> <input v-bind:value="n"> <script> new Vue({ directives:{ // element传入对应元素对象 // binding 与 big中的表达式绑定 "n" big(element,binding){ element.innerText = binding.value * 10; // 绑定时会自动调用 但是 input 还没有放入doucment fouce不起作用 element.fouce(); // big函数何时会被调用? // 1.指令与元素成功绑定时(一开始) // 2.指令所在的模板被重新解析时 </script>7.2对象式<input v-fbind:value="n"> <script> new Vue({ directives:{ // 此对象中所有的 this 均指向 window fbind:{ // 绑定时被调用 bind(){ // 元素被插入时被调用 inserted(){ // 模板被重新解析时被调用 update(){ </script>7.3小总结注意: 命名时尽量不要用大写字母 Vue 会将大写字母 解析成小写字母 v-set-member ---> `set-member`(){} <script> // 全局Vue directives配置 Vue.directive('big',{ </script>8.生命周期函数8.1 mounted介绍// 动态绑定数据需要写成对象的形式 <h2 style="{opacity: opacity}"> 我会变化透明度 </h2> // 属性值与键名重复时可简写 <h2 style="{opacity}"></h2> <script> new Vue({ data:{ opacity:1, methos:{ change(){ // 非 Vue 管理的函数 使用箭头函数 this 指向 Vue(vm) setInterval(()=>{ this.opacity -= 0.01; if(this.opacity <= 0) this.opacity = 1; // 透明变化的字体 },16) // mounted 挂载 // Vue完成模板的解析并且把初始的真实Dom放入元素页面后(挂在完毕后) 执行mounted // 写入此处执行一次 mounted 函数 进行计时器函数书写 // 第一次放入时执行 !!! 之后的叫更新 重新解析 不执行 mounted mounted(){ setInterval(()=>{ this.opacity -= 0.01; if(this.opacity <= 0) this.opacity = 1; // 透明变化的字体 },16) // 生命周期函数有多种 这里只介绍了 mounted </script>8.2挂载流程<script> new Vue({ tmplate:`在此处可以写 HTML 语法 替换模板` // 初始化:生命周期、事件,但数据代理还未开始。 // 此时:无法通过vm访问到data中的数据. methods中的方法。 beforeCreate(){} // 初始化:数据监测、数据代理。 // 此时:可以通过vm访问到data中的数据、 methods中配置的方法。 // 还未进行模板解析 生成虚拟DOM created(){} // 1.页面呈现的是未经Vue编译的DOM结构。 // 2.所有对DOM的操作,最终都不奏效。 因为在最后会使用 虚拟DOM 替换全部真实DOM // 进行模板解析之后 虚拟DOM已生成 beforeMount(){} // 将虚拟DOM转为真实DOM插入页面 // 1.页面中呈现的是经过Vue编译的DOM. // 2.对DOM的操作均有效(尽可能避免)。 // 至此初始化过程结束,一般在此进行: // 开启定时器、发送网络请求、订阅消息、绑定自定义事件、等初始化操作。 mounted(){} </script>8.3更新流程:new Vue({ // 此时:数据是新的,但页面是旧的,即:页面尚未和数据保持同步。 beforeUpdate(){} // 虚拟DOM 之间的对比 修改虚拟DOM // 此时:数据是新的,页面也是新的,即:页面和数据保持同步。 updated(){} })8.4销毁流程// 使用 $destroy() 方法 完全销毁vm实例 不会进行管理 DOM页面了 // vm.$destroy() // 原生DOM事件不会被销毁 会销毁自定义事件 new Vue({ // 此时: vm中所有的: data、methods、指令等等,都处于可用状态,马上要执行销毁过程,一般在此阶段:关闭定时器、取消订阅消息.解绑自定义事件等收尾操作 beforeDestory(){} // vm 已销毁 destoryed(){} })9.组件化 !!!定义:现实应用中局部功能代码和资源的集合非单文件组件∶ 一个文件中包含有n个组件。单文件组件∶ 一个文件中只包含有1个组件。1.非单文件组件1.非单文件组件// 1.创建组件 const school = Vue.extend({ // 不需要写 el 配置项 只可配置在 vm 实例中 // el:"", // data 必须写成函数式 // 对象式引用的内存中同一地址 会出错(Vue报错阻止) data:fouction(){ return {...} template:` HTML 内容 // 2.注册组件 new Vue({ el:"#root", // 注册组件 (局部注册) components:{ xuexiao:school, // 以下可以简写 school:school, // 简写形式 school // 全局注册组件 // Vue.component('hello',hello) // 3.在HTML页面中使用组件标签 // 在 components 中配置的键值 <div id="root"> <xuexiao></xuexiao> </div> */注意点// Vue推荐组件命名 单个单词: 1.school (纯小写) 2.School(首字母大写) 多个单词: 1.my-school (用-连接) 2.MySchool(每个单词首字母均大写 需要Vue脚手架支持) (1).组件名尽可能回避HTAL中已有的元素名称,例如:h2、H2都不行。 (2).可以使用name配置项指定组件在开发者工具中呈现的名字。 Vue.extend({ name:"显示的名字", 2.关于组件标签: 第一种写法:<school></school> 第二种写法:<school/> 备注:不用使用脚手架时,<schoo1/>会导致后续组件不能渲染。 3.一个简写方式: const school = Vue.extend(options)可简写为: const school = options // 如果注册时传入的是对象 Vue 自动调用Vue.extend */2.组件的嵌套// 可以在组件中书写组件配置项 const school = Vue.extends({ template:"...", compoent:{ scool, "..." const student = Vue.extends({ template:"...", components:{ scool, "..." })3. Vue Compoent构造函数vm 实例中 包含 $children 为数组 数组中包含着 VueComponent 对象 VueComponent 中 的成员变量以及方法与 vm 一致 组件: 不可使用 el data 只能使用函数式 关于VueComponent: 1.school组件木质是一个名为vueComponent的构造函数,且不是和序员定义的,是Vue.extend生成的。 2.我们只需要写<school/>或<schoolx</school>.Vue解析时会帮我们创建school组件的实例对象,Nvue帮我们执行的:new VueComponent(options)- 3.特别注意:每次调用vue.extend,返回的都是一个全新的VueComponent! !!! 4.关于this指向: (1).组件配置中: data函数、methods中的函数、watch中的函数、computed中的函数它们的this均是【VueComponent实例对象】 (2).new Vue配置中: data雨数、methods中的函数、watch中的函数、computed中的函数它们的this均是【Vue实例对象】. 5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象). Vue的实例对象,以后简称vm. */4.重要的内置关系/* 1.一个重要的内置关系:VueComponent.prototype._proto_ == Vue.prototype 2.为什么要有这个关系:让组件实例对象(vc)可以访问到Vue原型上的属性、方法。 */Vue 与 VueComponent 的关系2.单文件组件{webpack, 脚手架} 编译使用 .vue后缀App结构安装 VSCODE vue 插件 后 输入 <v tab键就可自动补全main.js
Nginx+uWsgi部署Django项目最近需要部署一个Django项目但是不能用宝塔面板!呜呜呜 不能懒人式一键部署了那就学习一下黑框框部署吧学习视频:Nginx+uWSGI+Django部署生产环境_哔哩哔哩_bilibili参考博客:(54条消息) Nginx+uWSGI部署_一夜奈何梁山的博客-CSDN博客_nginx uwsgi基本原理首先用户浏览器向nginx发送请求,nginx判断是动态请求还是静态请求,如果是静态请求,则直接返回静态文件。如果是动态请求,则将请求转发给uwsgi服务器,uwsgi调用我们的django进行处理请求,然后一步一步的再返回。WSGI是Web 服务器(uWSGI)与 Web 应用程序或应用框架(Django)之间的一种低级别的接口uWSGI是一个Web服务器,它实现了WSGI协议、uwsgi、http等协议uwsgi是Nginx服务器和uWSGI服务器的通信协议,规定了怎么把请求转发给应用程序和返回使用Xshell完成1.上传文件到服务器使用scp命令scp 文件名 用户名@服务器HOST:上传到服务器的路径 // 需要提供 root 密码 scp name root@82.156.215.40:/root/ipbdTCF/使用xftp等其他方式也可2.配置虚拟环境检查是否安装virtualenvvirtualenv --version安装virtualenvpip3 install virtualenv virtualenvwrapper// 遇到权限问题在前面加上 sudo sudo pip3 install virtualenv virtualenvwrapper创建虚拟环境virtualenv --no-site-packages --python=python3 虚拟环境文件夹名--no-site-packages 这是构建干净,隔离的模块的参数 (如果virtualenv版本大于20默认携带此参数不需要此参数)--python=python3 这个参数是指定虚拟环境以哪一个物理解释器为基础的最后一个是虚拟环境的名字 会创建这么一个文件夹激活虚拟环境source /路径/虚拟环境文件夹名/bin/activate激活虚拟环境的原理就是修改了PATH变量,path的执行是有顺序的修改了环境变量的位置echo $PATH 检查环境变量which python3which pip3 检查虚拟环境是否正常退出虚拟环境deactivate 项目依赖都装在虚拟环境中管理!3.配置uwsgi检查是否安装uwsgi# 已安装会显示版本号 uwsgi --version # 未安装使用 pip 安装一下 # 注意要在虚拟环境中安装 pip install uwsgi启动项目注意:启动前先runserver测试一下是否配置完成能够正常运行# 使用uwsgi启动项目 # --file 指向项目内 wsgi.py 文件 # --static-map 指向静态文件路径 # uwsgi --http 0.0.0.0:80 --file 项目名称/wsgi.py --static-map=/static=static # 在项目目录下 uwsgi --http 0.0.0.0:80 --file teacher/wsgi.py --static-map=/static=static启动后可在自己服务器的公网IP访问到Django项目http://公网IP地址配置uwsgi# 在项目目录下创建 uwsgi.ini 作为uwsgi配置文件 vim uwsgi.ini# uwsgi.ini文件详情 [uwsgi] # 项目目录 chdir=/opt/project_teacher/teacher/ # 启动uwsgi的用户名和用户组 uid=root gid=root # 指定项目的application module=项目名.wsgi:application # 指定sock的文件路径 # 与nginx通讯使用 socket=/项目绝对路径/uwsgi.sock # 启用主进程 master=true # 进程个数 workers=4 pidfile=/项目绝对路径/uwsgi.pid # 自动移除unix Socket和pid文件当服务停止的时候 vacuum=true # 序列化接受的内容,如果可能的话 thunder-lock=true # 启用线程 enable-threads=true # 设置自中断时间 harakiri=30 # 设置缓冲 post-buffering=1028 # 设置日志目录 # 日志保存地点 daemonize=/项目绝对路径/uwsgi.log# 启动: ini 为uwsgi配置文件 uwsgi --ini uwsgi.ini # 重启: pid 为uwsgi启动后保存uwsgi启动进程的文件 uwsgi --reload uwsgi.pid # 停止: uwsgi --stop uwsgi.pid# 查看uwsgi是否正常启动 ps aux | grep uwsgiNginx配置nginx的安装请查看另外一篇博客配置文件worker_processes 4; # 指定使用的用户 user root; events { worker_connections 1024; http { keepalive_timeout 65; server { # http 请求的端口 listen 80; # https 请求的端口 listen 443 ssl http2; # 配置的IP与域名 # 这里随便输入的 server_name www.abc.com 123.123.24.23; charset utf-8; gzip on; gzip_types text/plain application/x-javascript text/css text/javascript application/x-httpd-php application/json text/json image/jpeg image/gif image/png application/octet-stream; # SSL证书配置 ssl_certificate /usr/local/nginx/conf/cert/tcfapi/tcfapi.snowhouse.space_bundle.pem; ssl_certificate_key /usr/local/nginx/conf/cert/tcfapi/tcfapi.snowhouse.space.key; ssl_session_timeout 5m; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; error_page 404 /404.html; error_page 500 502 503 504 /50x.html; # 指定项目路径uwsgi location / { include uwsgi_params; uwsgi_connect_timeout 30; # unix: 刚刚配置好的 uwsgi.sock 路径 uwsgi_pass unix:/root/IPBDPythonGroup/medicalSys/tichengfen/uwsgi.sock; # 指定静态文件路径 location /static/ { alias /root/IPBDPythonGroup/medicalSys/tichengfen/static/; index index.html index.htm; }# 使用时记得配置全局环境变量 # 启动nginx # 启动之后访问对应的nginx配置好的路径即可 nginx # 关闭nginx nginx -s stop
Linux安装Nginx最近要部署一个项目 但是不能用 宝塔面板 啊这!赶紧学一手用命令行 Nginx + uWSGI 部署Django项目宝塔还是方便的 图形化真不赖参考文章:Linux安装Nginx详细教程 - 知乎 (zhihu.com)下载Nginx依赖于gcc的编译环境yum install gcc-c++Nginx的http模块需要使用pcre来解析正则表达式yum install -y pcre pcre-devel安装依赖的解压包yum install -y zlib zlib-develssl 功能需要 openssl 库yum install -y openssl openssl-develNginx下载前往官网下载即可:nginx官网点击 download 前往下载地址版本介绍:Mainline version:Mainline 是 Nginx 目前主力在做的版本,可以说是开发版Stable version:最新稳定版,生产环境上建议使用的版本Legacy versions:遗留的老版本的稳定版解压nginx一般的安装目录在 /usr/local/nginx 大家自行选择即可# 解压命令 tar -zxvf nginx-1.16.1.tar.gz安装# 运行nginx配置 # configure 在解压后的文件夹中 # --with-http_ssl_module 为加装SSL证书模块 ./configure --prefix=/usr/local/nginx --with-http_ssl_module # 依次输入以下命令安装nginx make install配置# vim 编辑查看配置文件 vim /usr/local/nginx/conf/nginx.conf#全局块 #user nobody; worker_processes 1; #event块 events { worker_connections 1024; #http块 http { #http全局块 include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; #server块 server { #server全局块 listen 8000; server_name localhost; #location块 location / { root html; index index.html index.htm; error_page 500 502 503 504 /50x.html; location = /50x.html { root html; #这边可以有多个server块 server { # 我们只需要更改两个配置 # listen 部署的端口 # server_name 用于配置虚拟主机的名称 使用服务器的外网IP地址即可 listen 8000; server_name localhost;# Listen配置 listen 127.0.0.1:8000; #只监听来自127.0.0.1这个IP,请求8000端口的请求 listen 127.0.0.1; #只监听来自127.0.0.1这个IP,请求80端口的请求(不指定端口,默认80) listen 8000; #监听来自所有IP,请求8000端口的请求 listen *:8000; #和上面效果一样 listen localhost:8000; #和第一种效果一致Nginx详细配置:Nginx配置文件详解 - 程序员自由之路 - 博客园 (cnblogs.com)启动# 进入 /usr/local/nginx/sbin 目录 # 执行以下命令 启动 nginx ./nginx然后去浏览器访问一下 出现Nginx页面就部署成功啦!
让程序在服务器后台持续运行1.安装screenCentOS系列:yum install screenUbuntu系列:sudo apt-get install screen2.创建screen窗口# 创建screenscreen -S name(赋予此窗口一个名字)3.在screen窗口中 部署/运行 项目例: Django 项目 python manage.py runserver4.保存并退出键盘按键操作 ctrl+a+d5.查看运行的screen# 查看所有创建的screenscreen -ls# 进入对应PID号对应的screen面板screen -r -d PID # 删除screen面板screen -S PID -X quit
Ngrok内网穿透今天给大家推荐一个免费稳定的内网穿透方法如何使用Ngrok进行内网穿透?今天也要加油🦆!⭐内网穿透原理 大家想了解的话 我这里推荐一个视频(讲的很详细!):【硬核】公网访问?内网穿透!零经验上手!哔哩哔哩bilibili1.软件下载前往官网下载对应系统的软件并注册一个免费的Ngrok账号官网地址:ngrok - Online in One Line2.开启内网穿透注册成功并登录之后 每个人会有一个对应的 authtoken打开 ngrok.exe1.配置自己账户的Authtoken IDngrok config add-authtoken Authtoken2.开启内网穿透ngrok http [映射的端口号]映射成功啦!赋值图中的地址就可以访问自己电脑上运行的项目啦!
ESC使用体验一,自我介绍 作为一名软件工程专业的大一新生,在寒假自学Python与Django,迫不及待的想将自己的练习作业部署到服务器上,在朋友的推荐下了解到了阿里云ESC学生机,经过细心的了解与学习之后,终于学会了如何使用服务器。二,阿里云ESC使用体验 云服务器是一个非常方便的工具,可以将自己的项目部署在服务器中全天24小时不间断运行。阿里云对服务器的概览与操作页面也非常全面概览,时间,标签,能够清楚明了的展示与调整服务器的各项参数,面板中包含多种免费的教程可以轻松上手ESC服务器,实例与镜像可以快速操作服务器,网络与安全可以快速的设置防火墙与安全保护。也可以迅速的远程连接服务器,对服务器进行操控。在实例界面里有“远程连接”,“更多”中的实例状态里包括了“停止,重启”,点击“远程连接”里面有远程连接我们可以立即登陆。可直连linux命令行非常方便!!!三,使用总结与学习笔记 轻松易上手,小白救星。快捷易操作,手残党克星。高效高速度,时间收割机。ESC服务器,YYDS! 学习服务器是一件非常新鲜的事情,作为一名大一新生,我很乐意去学习新的事物与新的知识。在寒假期间,我利用别人喝咖啡的时间进行Django的学习,Django的内容已经得心应手,可以快速的反应去完成相关知识点。在服务器的学习上遇到了非常多的难题,在不断地尝试与摸索中,踩了非常多的雷区,对服务器的了解浅导致了我不断摔倒,我接近崩溃,不想放弃。为透彻的了解ESC云服务器,我请教了我的学长很多,不断地查询相关资料,阅读过的文章字数不下万字,还请教了我的老师。在他们细心的教导和我不断的努力之下,我终于能够熟练的操作服务器了。 在服务器中我使用了宝塔面板,非常方便快捷的一个服务器管理软件。使我能够非常快速的上手服务器,还遇到了非常多的问题:使程序在服务器后端不间断运行:# 让程序在服务器后台持续运行1.安装screen CentOS系列: yum install screen Ubuntu系列: sudo apt-get install screen2.创建screen窗口 # 创建screen screen -S name(赋予此窗口一个名字)3.在screen窗口中 部署/运行 项目 例: Django 项目 python manage.py runserver4.保存并退出 键盘按键操作 ctrl+a+d5.查看运行的screen # 查看所有创建的screen screen -ls # 进入对应PID号对应的screen面 screen -r -d PID # 删除screen面板 screen -S PID -X quit四,作品链接 未注册域名http://8.130.8.58:8000/
【DRF】快速入门,使用DjangoRestFrameWork自动生成Restful风格的增删改查代码和接口文档!