kali0102

kali0102 查看完整档案

广州编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

hello world

个人动态

kali0102 收藏了文章 · 2020-10-26

8 种常见 SQL 错误用法

来源:yq.aliyun.com/articles/72501


1、LIMIT 语句

分页查询是最常用的场景之一,但也通常也是最容易出问题的地方。比如对于下面简单的语句,一般 DBA 想到的办法是在 type, name, create_time 字段上加组合索引。这样条件排序都能有效的利用到索引,性能迅速提升。

SELECT * 
FROM   operation 
WHERE  type = 'SQLStats' 
 AND name = 'SlowLog' 
ORDER  BY create_time 
LIMIT  1000, 10;

好吧,可能90%以上的 DBA 解决该问题就到此为止。但当 LIMIT 子句变成 “LIMIT 1000000,10” 时,程序员仍然会抱怨:我只取10条记录为什么还是慢?

要知道数据库也并不知道第1000000条记录从什么地方开始,即使有索引也需要从头计算一次。出现这种性能问题,多数情形下是程序员偷懒了。

在前端数据浏览翻页,或者大数据分批导出等场景下,是可以将上一页的最大值当成参数作为查询条件的。SQL 重新设计如下:

SELECT   * 
FROM     operation 
WHERE    type = 'SQLStats' 
AND      name = 'SlowLog' 
AND      create_time > '2017-03-16 14:00:00' 
ORDER BY create_time limit 10;

在新设计下查询时间基本固定,不会随着数据量的增长而发生变化。

2、隐式转换

SQL语句中查询变量和字段定义类型不匹配是另一个常见的错误。比如下面的语句:

mysql> explain extended SELECT * 
 > FROM   my_balance b 
 > WHERE  b.bpn = 14000000123 
 >       AND b.isverified IS NULL ;
mysql> show warnings;
| Warning | 1739 | Cannot use ref access on index 'bpn' due to type or collation conversion on field 'bpn'

其中字段 bpn 的定义为 varchar(20),MySQL 的策略是将字符串转换为数字之后再比较。函数作用于表字段,索引失效。

上述情况可能是应用程序框架自动填入的参数,而不是程序员的原意。现在应用框架很多很繁杂,使用方便的同时也小心它可能给自己挖坑。

3、关联更新、删除

虽然 MySQL5.6 引入了物化特性,但需要特别注意它目前仅仅针对查询语句的优化。对于更新或删除需要手工重写成 JOIN。

比如下面 UPDATE 语句,MySQL 实际执行的是循环/嵌套子查询(DEPENDENT SUBQUERY),其执行时间可想而知。

UPDATE operation o 
SET    status = 'applying' 
WHERE  o.id IN (SELECT id 
 FROM   (SELECT o.id, 
 o.status 
 FROM   operation o 
 WHERE  o.group = 123 
 AND o.status NOT IN ( 'done' ) 
 ORDER  BY o.parent, 
 o.id 
 LIMIT  1) t);

执行计划:

+----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+
| id | select_type        | table | type  | possible_keys | key     | key_len | ref   | rows | Extra                                               |
+----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+
| 1  | PRIMARY            | o     | index |               | PRIMARY | 8       |       | 24   | Using where; Using temporary                        |
| 2  | DEPENDENT SUBQUERY |       |       |               |         |         |       |      | Impossible WHERE noticed after reading const tables |
| 3  | DERIVED            | o     | ref   | idx_2,idx_5   | idx_5   | 8       | const | 1    | Using where; Using filesort                         |
+----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+

重写为 JOIN 之后,子查询的选择模式从 DEPENDENT SUBQUERY 变成 DERIVED,执行速度大大加快,从7秒降低到2毫秒。

UPDATE operation o 
 JOIN  (SELECT o.id, 
 o.status 
 FROM   operation o 
 WHERE  o.group = 123 
 AND o.status NOT IN ( 'done' ) 
 ORDER  BY o.parent, 
 o.id 
 LIMIT  1) t
 ON o.id = t.id 
SET    status = 'applying'

执行计划简化为:

+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+
| id | select_type | table | type | possible_keys | key   | key_len | ref   | rows | Extra                                               |
+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+
| 1  | PRIMARY     |       |      |               |       |         |       |      | Impossible WHERE noticed after reading const tables |
| 2  | DERIVED     | o     | ref  | idx_2,idx_5   | idx_5 | 8       | const | 1    | Using where; Using filesort                         |
+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+

4、混合排序

MySQL 不能利用索引进行混合排序。但在某些场景,还是有机会使用特殊方法提升性能的。

SELECT * 
FROM   my_order o 
 INNER JOIN my_appraise a ON a.orderid = o.id 
ORDER  BY a.is_reply ASC, 
 a.appraise_time DESC 
LIMIT  0, 20

执行计划显示为全表扫描:

+----+-------------+-------+--------+-------------+---------+---------+---------------+---------+-+
| id | select_type | table | type   | possible_keys     | key     | key_len | ref      | rows    | Extra 
+----+-------------+-------+--------+-------------+---------+---------+---------------+---------+-+
|  1 | SIMPLE      | a     | ALL    | idx_orderid | NULL    | NULL    | NULL    | 1967647 | Using filesort |
|  1 | SIMPLE      | o     | eq_ref | PRIMARY     | PRIMARY | 122     | a.orderid |       1 | NULL           |
+----+-------------+-------+--------+---------+---------+---------+-----------------+---------+-+

由于 is_reply 只有0和1两种状态,我们按照下面的方法重写后,执行时间从1.58秒降低到2毫秒。

SELECT * 
FROM   ((SELECT *
 FROM   my_order o 
 INNER JOIN my_appraise a 
 ON a.orderid = o.id 
 AND is_reply = 0 
 ORDER  BY appraise_time DESC 
 LIMIT  0, 20) 
 UNION ALL 
 (SELECT *
 FROM   my_order o 
 INNER JOIN my_appraise a 
 ON a.orderid = o.id 
 AND is_reply = 1 
 ORDER  BY appraise_time DESC 
 LIMIT  0, 20)) t 
ORDER  BY  is_reply ASC, 
 appraisetime DESC 
LIMIT  20;

5、EXISTS语句

MySQL 对待 EXISTS 子句时,仍然采用嵌套子查询的执行方式。如下面的 SQL 语句:

SELECT *
FROM   my_neighbor n 
 LEFT JOIN my_neighbor_apply sra 
 ON n.id = sra.neighbor_id 
 AND sra.user_id = 'xxx' 
WHERE  n.topic_status < 4 
 AND EXISTS(SELECT 1 
 FROM   message_info m 
 WHERE  n.id = m.neighbor_id 
 AND m.inuser = 'xxx') 
 AND n.topic_type <> 5

执行计划为:

+----+--------------------+-------+------+-----+------------------------------------------+---------+-------+---------+ -----+
| id | select_type        | table | type | possible_keys     | key   | key_len | ref   | rows    | Extra   |
+----+--------------------+-------+------+ -----+------------------------------------------+---------+-------+---------+ -----+
|  1 | PRIMARY            | n     | ALL  |  | NULL     | NULL    | NULL  | 1086041 | Using where                   |
|  1 | PRIMARY            | sra   | ref  |  | idx_user_id | 123     | const |       1 | Using where          |
|  2 | DEPENDENT SUBQUERY | m     | ref  |  | idx_message_info   | 122     | const |       1 | Using index condition; Using where |
+----+--------------------+-------+------+ -----+------------------------------------------+---------+-------+---------+ -----+

去掉 exists 更改为 join,能够避免嵌套子查询,将执行时间从1.93秒降低为1毫秒。

SELECT *
FROM   my_neighbor n 
 INNER JOIN message_info m 
 ON n.id = m.neighbor_id 
 AND m.inuser = 'xxx' 
 LEFT JOIN my_neighbor_apply sra 
 ON n.id = sra.neighbor_id 
 AND sra.user_id = 'xxx' 
WHERE  n.topic_status < 4 
 AND n.topic_type <> 5

新的执行计划:

+----+-------------+-------+--------+ -----+------------------------------------------+---------+ -----+------+ -----+
| id | select_type | table | type   | possible_keys     | key       | key_len | ref   | rows | Extra                 |
+----+-------------+-------+--------+ -----+------------------------------------------+---------+ -----+------+ -----+
|  1 | SIMPLE      | m     | ref    | | idx_message_info   | 122     | const    |    1 | Using index condition |
|  1 | SIMPLE      | n     | eq_ref | | PRIMARY   | 122     | ighbor_id |    1 | Using where      |
|  1 | SIMPLE      | sra   | ref    | | idx_user_id | 123     | const     |    1 | Using where           |
+----+-------------+-------+--------+ -----+------------------------------------------+---------+ -----+------+ -----+

6、条件下推

外部查询条件不能够下推到复杂的视图或子查询的情况有:

  • 聚合子查询;
  • 含有 LIMIT 的子查询;
  • UNION 或 UNION ALL 子查询;
  • 输出字段中的子查询;

如下面的语句,从执行计划可以看出其条件作用于聚合子查询之后:

SELECT * 
FROM   (SELECT target, 
 Count(*) 
 FROM   operation 
 GROUP  BY target) t 
WHERE  target = 'rm-xxxx'
+----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+
| id | select_type | table      | type  | possible_keys | key         | key_len | ref   | rows | Extra       |
+----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+
|  1 | PRIMARY     | <derived2> | ref   | <auto_key0>   | <auto_key0> | 514     | const |    2 | Using where |
|  2 | DERIVED     | operation  | index | idx_4         | idx_4       | 519     | NULL  |   20 | Using index |
+----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+

确定从语义上查询条件可以直接下推后,重写如下:

SELECT target, 
 Count(*) 
FROM   operation 
WHERE  target = 'rm-xxxx' 
GROUP  BY target

执行计划变为:

+----+-------------+-----------+------+---------------+-------+---------+-------+------+--------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------+------+---------------+-------+---------+-------+------+--------------------+
| 1 | SIMPLE | operation | ref | idx_4 | idx_4 | 514 | const | 1 | Using where; Using index |
+----+-------------+-----------+------+---------------+-------+---------+-------+------+--------------------+

关于 MySQL 外部条件不能下推的详细解释说明请参考文章:

http://mysql.taobao.org/month...

7、提前缩小范围

先上初始 SQL 语句:

SELECT * 
FROM   my_order o 
 LEFT JOIN my_userinfo u 
 ON o.uid = u.uid
 LEFT JOIN my_productinfo p 
 ON o.pid = p.pid 
WHERE  ( o.display = 0 ) 
 AND ( o.ostaus = 1 ) 
ORDER  BY o.selltime DESC 
LIMIT  0, 15

该SQL语句原意是:先做一系列的左连接,然后排序取前15条记录。从执行计划也可以看出,最后一步估算排序记录数为90万,时间消耗为12秒。

+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+----------------------------------------------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref             | rows   | Extra                                              |
+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+----------------------------------------------------+
|  1 | SIMPLE      | o     | ALL    | NULL          | NULL    | NULL    | NULL            | 909119 | Using where; Using temporary; Using filesort       |
|  1 | SIMPLE      | u     | eq_ref | PRIMARY       | PRIMARY | 4       | o.uid |      1 | NULL                                               |
|  1 | SIMPLE      | p     | ALL    | PRIMARY       | NULL    | NULL    | NULL            |      6 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+----------------------------------------------------+

由于最后 WHERE 条件以及排序均针对最左主表,因此可以先对 my_order 排序提前缩小数据量再做左连接。SQL 重写后如下,执行时间缩小为1毫秒左右。

SELECT * 
FROM (
SELECT * 
FROM   my_order o 
WHERE  ( o.display = 0 ) 
 AND ( o.ostaus = 1 ) 
ORDER  BY o.selltime DESC 
LIMIT  0, 15
) o 
 LEFT JOIN my_userinfo u 
 ON o.uid = u.uid 
 LEFT JOIN my_productinfo p 
 ON o.pid = p.pid 
ORDER BY  o.selltime DESC
limit 0, 15

再检查执行计划:子查询物化后(select_type=DERIVED)参与 JOIN。虽然估算行扫描仍然为90万,但是利用了索引以及 LIMIT 子句后,实际执行时间变得很小。

+----+-------------+------------+--------+---------------+---------+---------+-------+--------+----------------------------------------------------+
| id | select_type | table      | type   | possible_keys | key     | key_len | ref   | rows   | Extra                                              |
+----+-------------+------------+--------+---------------+---------+---------+-------+--------+----------------------------------------------------+
|  1 | PRIMARY     | <derived2> | ALL    | NULL          | NULL    | NULL    | NULL  |     15 | Using temporary; Using filesort                    |
|  1 | PRIMARY     | u          | eq_ref | PRIMARY       | PRIMARY | 4       | o.uid |      1 | NULL                                               |
|  1 | PRIMARY     | p          | ALL    | PRIMARY       | NULL    | NULL    | NULL  |      6 | Using where; Using join buffer (Block Nested Loop) |
|  2 | DERIVED     | o          | index  | NULL          | idx_1   | 5       | NULL  | 909112 | Using where                                        |
+----+-------------+------------+--------+---------------+---------+---------+-------+--------+----------------------------------------------------+

8、中间结果集下推

再来看下面这个已经初步优化过的例子(左连接中的主表优先作用查询条件):

SELECT    a.*, 
 c.allocated 
FROM      ( 
 SELECT   resourceid 
 FROM     my_distribute d 
 WHERE    isdelete = 0 
 AND      cusmanagercode = '1234567' 
 ORDER BY salecode limit 20) a 
LEFT JOIN 
 ( 
 SELECT   resourcesid, sum(ifnull(allocation, 0) * 12345) allocated 
 FROM     my_resources 
 GROUP BY resourcesid) c 
ON        a.resourceid = c.resourcesid

那么该语句还存在其它问题吗?不难看出子查询 c 是全表聚合查询,在表数量特别大的情况下会导致整个语句的性能下降。

其实对于子查询 c,左连接最后结果集只关心能和主表 resourceid 能匹配的数据。因此我们可以重写语句如下,执行时间从原来的2秒下降到2毫秒。

SELECT    a.*, 
 c.allocated 
FROM      ( 
 SELECT   resourceid 
 FROM     my_distribute d 
 WHERE    isdelete = 0 
 AND      cusmanagercode = '1234567' 
 ORDER BY salecode limit 20) a 
LEFT JOIN 
 ( 
 SELECT   resourcesid, sum(ifnull(allocation, 0) * 12345) allocated 
 FROM     my_resources r, 
 ( 
 SELECT   resourceid 
 FROM     my_distribute d 
 WHERE    isdelete = 0 
 AND      cusmanagercode = '1234567' 
 ORDER BY salecode limit 20) a 
 WHERE    r.resourcesid = a.resourcesid 
 GROUP BY resourcesid) c 
ON        a.resourceid = c.resourcesid

但是子查询 a 在我们的SQL语句中出现了多次。这种写法不仅存在额外的开销,还使得整个语句显的繁杂。使用 WITH 语句再次重写:

WITH a AS 
( 
 SELECT   resourceid 
 FROM     my_distribute d 
 WHERE    isdelete = 0 
 AND      cusmanagercode = '1234567' 
 ORDER BY salecode limit 20)
SELECT    a.*, 
 c.allocated 
FROM      a 
LEFT JOIN 
 ( 
 SELECT   resourcesid, sum(ifnull(allocation, 0) * 12345) allocated 
 FROM     my_resources r, 
 a 
 WHERE    r.resourcesid = a.resourcesid 
 GROUP BY resourcesid) c 
ON        a.resourceid = c.resourcesid

总结

数据库编译器产生执行计划,决定着SQL的实际执行方式。但是编译器只是尽力服务,所有数据库的编译器都不是尽善尽美的。

上述提到的多数场景,在其它数据库中也存在性能问题。了解数据库编译器的特性,才能避规其短处,写出高性能的SQL语句。

程序员在设计数据模型以及编写SQL语句时,要把算法的思想或意识带进来。

编写复杂SQL语句要养成使用 WITH 语句的习惯。简洁且思路清晰的SQL语句也能减小数据库的负担 。

查看原文

kali0102 收藏了文章 · 2020-10-26

Github标星 8K+,免费又好用的Redis客户端工具!

最近在寻找一款免费又好用的Redis客户端工具,于是找到了AnotherRedisDesktopManager,界面漂亮而且支持Redis集群,推荐给大家!

SpringBoot实战电商项目mall(40k+star)地址:https://github.com/macrozheng/mall

RedisDesktopManager

以前一直使用的是RedisDesktopManager这款Redis客户端工具,由于很久没更新界面有点古老,最近想更新升级下,进到官网一看,发现收费了......

AnotherRedisDesktopManager

于是就去Github上找了下,发现了另一个RedisDesktopManager,界面漂亮而且免费,一看Star数量8K+,有点厉害!就决定用它了。

使用

安装

  • 安装完成后,点击新建连接可以连接到Redis,可以发现Cluster这个选项,之前使用的旧版RedisDesktopManager并不支持Redis集群,这个工具支持了很不错!

深色模式

我们现在使用的界面模式为浅色模式,可以从设置中打开深色模式,还是很炫酷的!

命令行

支持使用Redis命令行,点击Redis控制台按钮即可打开。

Redis数据操作

  • 使用新增Key功能可以往Redis中存储键值对数据,目前支持5种数据结构;

  • 我们先来存储String类型的键值对数据,可以发现支持文本、JSON、反序列化三种显示,而且JSON支持效果不错;

  • 再来存储List类型的键值对,发现可以像操作表格一样操作List中的数据;

  • 再来存储Hash类型的键值对,依然可以像操作表格一样操作HashMap中的数据。

集群模式

  • 既然该客户端支持了集群模式,那我们也来试试吧,首先需要搭建一个Redis集群,搭建方式可以参考《Docker环境下秒建Redis集群,连SpringBoot也整上了!》
  • 创建好Redis集群之后,连接任意一个Redis服务即可访问集群,注意我们的Redis服务运行端口为6391~6396,我们先连接到6391的服务;

  • 往Redis集群中存储一个键值对数据后,连接另一个Redis服务6392,发现依然可以查看到该数据;

  • 删除该数据后,两个连接都已经看不见该数据了,证明可以正常操作Redis集群;

本文 GitHub https://github.com/macrozheng/mall-learning 已经收录,欢迎大家Star!
查看原文

kali0102 收藏了文章 · 2020-10-21

前端开发从入门到进阶完全指南,不用再迷茫前端要怎么学啦!

我经常会看到很多同学在学习前端的时候比较迷茫,不知道到底应该以怎样的学习路线来入门和进阶前端领域。每次遇到这种问题我也会分享一下自己的学习经验,但是发现这是一个问得非常多的一个共性问题。
作为程序员,肯定是不能容忍重复无味的劳动的,因此我就系统地总结分享一下我的前端学习路线,希望对你能够有所帮助。

前言

前端学习是一个螺旋上升的过程,既要反复地看书,也要抓紧时间进行实战。只看书,看了就会忘,所以必须将看书和写代码相结合。只要你认真学,入门前端的话三个月左右就可以了。之后我还给出了前端进阶路线,帮助你提升前端技能水平。我把前端入门和前端进阶一共分为六个阶段,并对相应阶段所需要的大致的学习时间进行了标注。

前端入门

入门前端开发主要需要学习 HTML,CSS 和 JavaScript 三大件。之后学习前端主流框架的使用,并基于已学内容开发一个小项目进行实战。当你把这些学习并理解透彻以后,也就算真正地入门前端了。

阶段一:HTML + CSS

前端对于入门者相当友好,因为开始学习的时候你只需要一个浏览器,推荐 Chrome。HTML 和 CSS 可以直接运行在浏览器中,浏览器就是它们的运行环境。你也可以使用编辑器,推荐 VSCode,这是前端开发使用最多的编辑器。

HTML (HyperText Markup Language) 和 CSS (Cascading Style Sheets) 其实并不是编程语言。HTML 中文名叫做超文本标记语言,其实就是一些标签。CSS 中文名为层叠样式表,也就是一些样式的配置。

首先学习 HTML,非常简单。HTML 有非常多的标签,刚入门的时候不要沉浸在记住这些标签中,你也记不住。你只需要整体浏览一遍,知道有哪些标签,各自的作用是什么,整体有一个印象就行了。

学习css过程中千万不要剥离HTML学习。当你什么时候理解了html的重要性(从页面开发角度而言,它可以视为是后续良好css和js编码得以实施的基础,相当于程序中的数据结构,设计好了可以让你事半功倍),你才可以称得上是一个合格的页面开发对于新人,我建议除了几个关键概念,如布局、盒模型、单位等等,都不应该花大量去扣细节,甚至背书记忆,浏览性学习知道有这个东西就行,在实际应用时再去加深记忆。

阶段二:JavaScript

学习了 HTML 和 CSS,可以开始学习 JavaScript 了。这也是至关重要的阶段。JavaScript 主要包括语言基础(ECMAScript)、DOM 和 BOM 三部分,如果你是初学者,会想这到底是啥,咋还三个东西。那就开始学起来,学完你就知道啦,其实没那么难。

JavaScript这几年变化很快,但是对于初学者来说要摒弃浮躁的气氛,静下心来打好基础。记住:自己是初学者,玩的东西就是:JavaScript和jQuery,工具就用一个编辑器和一个浏览器,这些就够了,别的不要碰

阶段三:入门前端框架

学完前端三大件,打好了大树的根基,就可以开始扩展技能树了,开始学习前端框架。前端的主流框架目前主要为 React,Vue 和 Angular。选择哪个框架呢?你可以去知乎或者其他网站搜一搜,然后根据你的个人喜好进行选择。一般是在 React 和 Vue 中选一个。React 的开发体验更类似于写原生的 JavaScript,要求你有较好的 JavaScript 基础。Vue 则引入了模版,将很多实现封装成了 API,你需要记住并调用 API 来进行开发,因为很多都是封装好的,所以学习起来较为简单,只是编程的感觉稍微弱了一些。

这两个都是非常优秀的框架,新人不必纠结于选择哪个框架,学了一个,另一个也很容易学。如果你不知道选择哪个,我推荐你先学习 React。

前端进阶

成功入门前端开发之后就要开启进阶部分了,主要是加深对各个知识的理解程度,打牢计算机领域基础知识,扩展技能树,提升项目开发和宏观理解及把控能力。前端进阶是需要终生学习的,活到老学到老

阶段四:语言基础进阶

这个阶段就是加深对编程语言的理解,多阅读进阶书籍.

进阶必读书籍:
《你不知道的 JavaScript 上/中/下卷》:必买书籍,将 JavaScript 的疑难问题,细节知识一网打尽。原版是 GitHub 上开源的电子书,英语水平高的可以去读英文原版。
《JavaScript 忍者秘籍》:深入讲解 JavaScript 的核心知识点,必买书籍。
《了不起的 JavaScript 工程师》:从宏观来看 JavaScript 语言,以及前端工程师所需要掌握的一些技能,推荐阅读。
《JavaScript 函数式编程指南》:学习函数式编程思想
《JavaScript 函数式编程》:也是一本函数式编程思想的好书
《JavaScript 设计模式》:学习 JavaScript 设计模式,推荐阅读
《JavaScript 设计模式与开发实践》:另一本同等分量的设计模式书籍,推荐阅读
《锋利的 jQuery》:jQuery 现在已经很少有人用了,除非是很老的项目或者写小东西。不过这本书值得买,学习 jQuery 的优秀思想,还可以去学习一下它的源码,对你进阶很有帮助。

阶段五:框架和学习边界进阶

通过阶段三,你已经掌握了前端框架的基本使用,开发一个完整项目的流程。那么在框架和学习边界进阶阶段,你就可以:

学习框架周边的生态,社区总结出来的优秀组件,以及各种好用的工具库。
造一些自己的轮子,使用框架搭建自己的开源项目
学习 Webpack,Gulp,Babel,ESLint 等工具的使用、思想和原理
带着问题去阅读框架源码,学习性能优化
养成良好的编程习惯
扩展技术边界,学习 Node.js 等后端相关技能
……

推荐阅读书籍

以下推荐的书籍都是比较出名的书籍,你可以根据自己的技能树,选择所需要的书籍进行阅读。并不是一字不差地整本阅读,而是在每本书中学习自己所需要的部分。很多都是非常著名的好书,有精力尽量买来学习。

Webpack 相关

《深入浅出 Webpack》
《Webpack 实战:入门、进阶与调优》

React 相关

《深入 React 技术栈》
《深入浅出 React 和 Redux》
《Redux 实战》
《React 学习手册》
《React 快速上手开发》
《React 设计模式与最佳实践》

Vue 相关

《Vue.js 实战》
《Vue.js 开发实战》
《深入浅出 Vue.js》
《Vue.js 权威指南》
《Vue.js 从入门到项目实战》
《Vue.js 前端开发基础与项目实战》
《Vue.js 项目开发实战》
《Vue.js 快速入门》
《Vue.js 前端开发》

Node.js 相关

《狼书卷1》
《狼书卷2》
《Node 学习指南》
《了不起的 Node.js》
《深入浅出 Node.js》
《Node.js 实战》
《Node.js 开发指南》
《Node 即学即用》
《Node 与 Express 开发》

样式和布局相关

《Bootstrap 实战》
《Bootstrap 用户手册》
《响应式 Web 设计:HTML5 与 CSS3 实战》

性能相关

《Web 性能权威指南》
《高性能网站建设指南》

PWA 相关

《PWA 开发实战》
《PWA 实战:面向下一代的 Progressive Web APP》

其他

《SVG 精髓》
《深入理解 SVG》
《前端架构设计》
《重构:改善既有代码的设计》
《同构 JavaScript 应用开发》

阶段六:计算机基础知识进阶

编程编程,万变不离其宗,那就是计算机基础知识,算法、数据结构、计算机原理、网络等内容。在这里我只推荐最经典的好书,每一本都是必读书籍。学好这些内容,大厂任你选。

数据结构和算法

《剑指offer》
《程序员面试金典(第 6 版)》
《编程之美》
《漫画算法》
《算法图解》
《程序员代码面试指南》
《大话数据结构》
《趣学算法》
《学习 JavaScript 数据结构与算法》
《数据结构与算法:JavaScript 描述》

计算机网络

《HTTP/2 基础教程》
《HTTPS 权威指南》
《计算机网络:自顶向下方法》
《图解 HTTP》
《图解 TCP/IP》
《TCP/IP 详解》
《UNIX 网络编程》

操作系统

《深入理解计算机系统》
《现代操作系统》
《UNIX 环境高级编程》
《The Linux Programming Interface》

总结

至此,你已经完成了前端开发从入门到进阶,已然成为了一个巨佬,之后再学什么已经了如指掌。希望我的分享对你有帮助,如果你觉得有用,可以收藏本文,并分享给你有需要的朋友。让我们一起学习,共同进步!

查看原文

kali0102 关注了用户 · 2020-10-16

Ccww @ccww

公众号【Ccww技术博客】:
经典面试题分析与解读以及技术Spring 、Spring boot、Spring cloud以及Docker后端等技术研究和源码分析,且会不定期分享集理财经验.

关注 80

kali0102 收藏了文章 · 2020-10-15

我敢打赌!这是全网最全的 Git 分支开发规范手册

Git 是目前最流行的源代码管理工具。为规范开发,保持代码提交记录以及 git 分支结构清晰,方便后续维护,现规范 git 的相关操作。

分支命名

1、master 分支

master 为主分支,也是用于部署生产环境的分支,确保master分支稳定性, master 分支一般由develop以及hotfix分支合并,任何时间都不能直接修改代码

2、develop 分支

develop 为开发分支,始终保持最新完成以及bug修复后的代码,一般开发的新功能时,feature分支都是基于develop分支下创建的。

feature 分支

  • 开发新功能时,以develop为基础创建feature分支。
  • 分支命名: feature/ 开头的为特性分支, 命名规则: feature/user_module、 feature/cart_module

release分支

release 为预上线分支,发布提测阶段,会release分支代码为基准提测。当有一组feature开发完成,首先会合并到develop分支,进入提测时会创建release分支。如果测试过程中若存在bug需要修复,则直接由开发者在release分支修复并提交。当测试完成之后,合并release分支到master和develop分支,此时master为最新代码,用作上线。

hotfix 分支

分支命名: hotfix/ 开头的为修复分支,它的命名规则与feature分支类似。线上出现紧急问题时,需要及时修复,以master分支为基线,创建hotfix分支,修复完成后,需要合并到master分支和develop分支

常见任务

增加新功能

(dev)$: git checkout -b feature/xxx            # 从dev建立特性分支
(feature/xxx)$: blabla                         # 开发
(feature/xxx)$: git add xxx
(feature/xxx)$: git commit -m 'commit comment'
(dev)$: git merge feature/xxx --no-ff          # 把特性分支合并到dev

修复紧急bug

(master)$: git checkout -b hotfix/xxx         # 从master建立hotfix分支
(hotfix/xxx)$: blabla                         # 开发
(hotfix/xxx)$: git add xxx
(hotfix/xxx)$: git commit -m 'commit comment'
(master)$: git merge hotfix/xxx --no-ff       # 把hotfix分支合并到master,并上线到生产环境
(dev)$: git merge hotfix/xxx --no-ff          # 把hotfix分支合并到dev,同步代码

测试环境代码

(release)$: git merge dev --no-ff             # 把dev分支合并到release,然后在测试环境拉取并测试

生产环境上线

(master)$: git merge release --no-ff          # 把release测试好的代码合并到master,运维人员操作
(master)$: git tag -a v0.1 -m '部署包版本名'  #给版本命名,打Tag

日志规范

在一个团队协作的项目中,开发人员需要经常提交一些代码去修复bug或者实现新的feature。

而项目中的文件和实现什么功能、解决什么问题都会渐渐淡忘,最后需要浪费时间去阅读代码。但是好的日志规范commit messages编写有帮助到我们,它也反映了一个开发人员是否是良好的协作者。

编写良好的Commit messages可以达到3个重要的目的:

  • 加快review的流程
  • 帮助我们编写良好的版本发布日志
  • 让之后的维护者了解代码里出现特定变化和feature被添加的原因

目前,社区有多种 Commit message 的写法规范。来自Angular 规范是目前使用最广的写法,比较合理和系统化。如下图:

Commit messages的基本语法

当前业界应用的比较广泛的是 Angular Git Commit Guidelines

https://github.com/angular/an...

具体格式为:

<type>: <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
  • type: 本次 commit 的类型,诸如 bugfix docs style 等
  • scope: 本次 commit 波及的范围
  • subject: 简明扼要的阐述下本次 commit 的主旨,在原文中特意强调了几点:
  • 使用祈使句,是不是很熟悉又陌生的一个词
  • 首字母不要大写
  • 结尾无需添加标点

body: 同样使用祈使句,在主体内容中我们需要把本次 commit 详细的描述一下,比如此次变更的动机,如需换行,则使用 |

footer: 描述下与之关联的 issue 或 break change

Type的类别说明:

  • feat: 添加新特性
  • fix: 修复bug
  • docs: 仅仅修改了文档
  • style: 仅仅修改了空格、格式缩进、都好等等,不改变代码逻辑
  • refactor: 代码重构,没有加新功能或者修复bug
  • perf: 增加代码进行性能测试
  • test: 增加测试用例
  • chore: 改变构建流程、或者增加依赖库、工具等

Commit messages格式要求

# 标题行:50个字符以内,描述主要变更内容
#
# 主体内容:更详细的说明文本,建议72个字符以内。需要描述的信息包括:
#
# * 为什么这个变更是必须的? 它可能是用来修复一个bug,增加一个feature,提升性能、可靠性、稳定性等等
# * 他如何解决这个问题? 具体描述解决问题的步骤
# * 是否存在副作用、风险?
#
# 如果需要的化可以添加一个链接到issue地址或者其它文档

来源:https://juejin.im/post/684490...

image

查看原文

kali0102 收藏了文章 · 2020-10-08

查漏补缺!巩固你的 Nginx 知识体系,这篇就够了!

Nginx 是一个高性能的 HTTP 和反向代理服务器,特点是占用内存少,并发能力强,事实上 Nginx 的并发能力确实在同类型的网页服务器中表现较好。

Nginx 专为性能优化而开发,性能是其最重要的要求,十分注重效率,有报告 Nginx 能支持高达 50000 个并发连接数。

Nginx 知识网结构图

Nginx 的知识网结构图如下:

image.png

反向代理

正向代理:局域网中的电脑用户想要直接访问网络是不可行的,只能通过代理服务器来访问,这种代理服务就被称为正向代理。

反向代理:客户端无法感知代理,因为客户端访问网络不需要配置,只要把请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据,然后再返回到客户端。

此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器 IP 地址。

image.png

负载均衡

客户端发送多个请求到服务器,服务器处理请求,有一些可能要与数据库进行交互,服务器处理完毕之后,再将结果返回给客户端。

普通请求和响应过程如下图:

image.png

但是随着信息数量增长,访问量和数据量飞速增长,普通架构无法满足现在的需求。

我们首先想到的是升级服务器配置,可以由于摩尔定律的日益失效,单纯从硬件提升性能已经逐渐不可取了,怎么解决这种需求呢?

我们可以增加服务器的数量,构建集群,将请求分发到各个服务器上,将原来请求集中到单个服务器的情况改为请求分发到多个服务器,也就是我们说的负载均衡。

图解负载均衡:

假设有 15 个请求发送到代理服务器,那么由代理服务器根据服务器数量,平均分配,每个服务器处理 5 个请求,这个过程就叫做负载均衡。

动静分离

为了加快网站的解析速度,可以把动态页面和静态页面交给不同的服务器来解析,加快解析的速度,降低由单个服务器的压力。

动静分离之前的状态:

动静分离之后:

image.png

安装

参考链接:

Nginx服务介绍与安装

Nginx 常用命令

#查看版本:
./nginx -v
#启动:
./nginx
#关闭(有两种方式,推荐使用 ./nginx -s quit):
./nginx -s stop
./nginx -s quit
#重新加载 Nginx 配置:
./nginx -s reload

Nginx 的配置文件

配置文件分三部分组成:

①全局块

从配置文件开始到 events 块之间,主要是设置一些影响 Nginx 服务器整体运行的配置指令。

并发处理服务的配置,值越大,可以支持的并发处理量越多,但是会受到硬件、软件等设备的制约。

②events 块

影响 Nginx 服务器与用户的网络连接,常用的设置包括是否开启对多 workprocess 下的网络连接进行序列化,是否允许同时接收多个网络连接等等。

支持的最大连接数:

③HTTP 块

诸如反向代理和负载均衡都在此配置。

location[ = | ~ | ~* | ^~] url{
}

location 指令说明,该语法用来匹配 url,语法如上:

  • =:用于不含正则表达式的 url 前,要求字符串与 url 严格匹配,匹配成功就停止向下搜索并处理请求。
  • ~:用于表示 url 包含正则表达式,并且区分大小写。
  • ~*:用于表示 url 包含正则表达式,并且不区分大小写。
  • ^~:用于不含正则表达式的 url 前,要求 Nginx 服务器找到表示 url 和字符串匹配度最高的 location 后,立即使用此 location 处理请求,而不再匹配。
  • 如果有 url 包含正则表达式,不需要有 ~ 开头标识。

反向代理实战

①配置反向代理

目的:在浏览器地址栏输入地址 www.123.com 跳转 Linux 系统 Tomcat 主页面。

②具体实现

先配置 Tomcat,因为比较简单,此处不再赘叙,并在 Windows 访问:
image.png

具体流程如下图:
image.png

修改之前:

配置如下:

再次访问:

③反向代理 2

目标:

准备:配置两个 Tomcat,端口分别为 8080 和 8081,都可以访问,端口修改配置文件即可。

image.png

image.png

新建文件内容分别添加 8080!!!和 8081!!!

image.png
image.png

反向代理小结

第一个例子:浏览器访问 www.123.com,由 host 文件解析出服务器 ip 地址
192.168.25.132 www.123.com。

然后默认访问 80 端口,而通过 Nginx 监听 80 端口代理到本地的 8080 端口上,从而实现了访问 www.123.com,最终转发到 tomcat 8080 上去。

第二个例子:
访问 http://192.168.25.132:9001/edu/ 直接跳转到 192.168.25.132:8080
访问 http://192.168.25.132:9001/vod/ 直接跳转到 192.168.25.132:8081

实际上就是通过 Nginx 监听 9001 端口,然后通过正则表达式选择转发到 8080 还是 8081 的 Tomcat 上去。

负载均衡实战

①修改 nginx.conf,如下图:

②重启 Nginx:

./nginx -s reload

③在 8081 的 Tomcat 的 webapps 文件夹下新建 edu 文件夹和 a.html 文件,填写内容为 8081!!!!

④在地址栏回车,就会分发到不同的 Tomcat 服务器上:
image.png
image.png

负载均衡方式如下:

  • 轮询(默认)。
  • weight,代表权,权越高优先级越高。
  • fair,按后端服务器的响应时间来分配请求,相应时间短的优先分配。
  • ip_hash,每个请求按照访问 ip 的 hash 结果分配,这样每一个访客固定的访问一个后端服务器,可以解决 Session 的问题。

动静分离实战

什么是动静分离?把动态请求和静态请求分开,不是讲动态页面和静态页面物理分离,可以理解为 Nginx 处理静态页面,Tomcat 处理动态页面。

动静分离大致分为两种:

  • 纯粹将静态文件独立成单独域名放在独立的服务器上,也是目前主流方案。
  • 将动态跟静态文件混合在一起发布,通过 Nginx 分开。

动静分离图析:

实战准备,准备静态文件:

配置 Nginx,如下图:

image.png

Nginx 高可用

如果 Nginx 出现问题:

image.png

解决办法:

前期准备:

  • 两台 Nginx 服务器
  • 安装 Keepalived
  • 虚拟 ip
#安装 Keepalived:
[root@192 usr]# yum install keepalived -y
[root@192 usr]# rpm -q -a keepalived
keepalived-1.3.5-16.el7.x86_64
#修改配置文件:
[root@192 keepalived]# cd /etc/keepalived
[root@192 keepalived]# vi keepalived.conf

分别将如下配置文件复制粘贴,覆盖掉 keepalived.conf,虚拟 ip 为 192.168.25.50。

对应主机 ip 需要修改的是:

  • smtp_server 192.168.25.147(主)
  • smtp_server 192.168.25.147(备)
  • state MASTER(主) state BACKUP(备)
global_defs {
   notification_email {
     acassen@firewall.loc
     failover@firewall.loc
     sysadmin@firewall.loc
   }
   notification_email_from Alexandre.Cassen@firewall.loc
   smtp_server 192.168.25.147
   smtp_connect_timeout 30
   router_id LVS_DEVEL # 访问的主机地址
}
vrrp_script chk_nginx {
  script "/usr/local/src/nginx_check.sh"  # 检测文件的地址
  interval 2   # 检测脚本执行的间隔
  weight 2   # 权重
}
vrrp_instance VI_1 {
    state BACKUP # 主机MASTER、备机BACKUP
    interface ens33 # 网卡
    virtual_router_id 51 # 同一组需一致
    priority 90  # 访问优先级,主机值较大,备机较小
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.25.50  # 虚拟ip
    }
}

启动命令如下:

[root@192 sbin]# systemctl start keepalived.service

访问虚拟 ip 成功:

关闭主机 147 的 Nginx 和 Keepalived,发现仍然可以访问。

原理解析

如下图,就是启动了一个 master,一个 worker,master 是管理员,worker是具体工作的进程。

worker 如何工作?如下图:

小结

worker 数应该和 CPU 数相等;一个 master 多个 worker 可以使用热部署,同时 worker 是独立的,一个挂了不会影响其他的。

作者:渐暖°
出处:blog.csdn.net/yujing1314/article/details/107000737_

查看原文

kali0102 回答了问题 · 2020-09-21

在公众号开发中,怎么在网页里面实现点击跳转到微信客服对话?

解决了吗?

关注 3 回答 1

kali0102 收藏了文章 · 2020-09-21

走出舒适圈,10份技术图谱+7大项目源码,这才是你该收藏的

最近有好多朋友问我,家里的孩子要工作了,或者工作一直没什么起色,都是程序员这一行的,怎么差距就这么大,想想也是,从外包走到现在,也有10年的时间了,回顾自己工作的这10年,总结一句话就是:

走出舒适圈,保持刻意学习,才有成为架构师的可能。

如今技术发展的速度非常快,接下来你该从哪些地方开始自己的刻意练习呢?给你 5 点建议:

1、读懂框架源码

框架它本身就是一个工具,但是作为高级工程师与架构师这个是你必须要学会和掌握的。让你读源码并不是真的让你读懂它,而是要理解它底层实现的原理,培养起框架思维和自定义框架的能力

2、掌握分布式架构设计

面对互联网项目高并发、高可用、高性能的特点,分布式系统的架构能力是你必须要掌握的。面对亿级的数据系统架构如何迎接高并发流量的挑战,这是作为架构师,必须要考虑的问题。

3、深刻搞懂算法和计算机底层原理

项目性能瓶颈一部分是要通过底层调优实现的,而一些高级的内核和引擎开发往往是需要一些精良算法和对底层原理的理解才能完成的, 只有掌握这些,才能多一些角度进行项目优化。

并且,去一些大厂面试,也是100%都会面到的。就算你现在的工作还用不到算法和底层原理,但如何以后想要进BAT这样的的大厂,还是要尽快学习起来。

4、掌握数据库调优和选型
架构最难的部分就是存储,如何用MySQL支撑起海量的数据,并保持高响应性,如何让数据库持续的稳定运行,都是必须掌握的技能。不管是现在主流的 MySQL,MongoDB、还是大数据 Hadoop 生态圈中的 HBase 等等。

掌握这些数据技术让你不管是面试还是技术方案选型,都可以锦上添花,并且能够应对向大数据延展的业务需求。

5、性能调优与解决方案

对 Tomcat、Nginx 等主流应用服务器能够进行深入使用,通过性能调优能够支撑业务的并发。一些经典场景化问题给出解决方案,如SSO、即时通讯、订单系统、日志系统等等,并能够在面对众多方案时知道如何进行选择。

「关于如何学习?」

这期间,我也带过一些工作不久的新人,他们的普遍问题是:工作那么忙,根本没时间学习。

确实是这样,互联网的节奏太快了,有时下班很晚,到家只想休息。

我个人建议大家利用好下班之后的 2 个小时,每天 2 个小时的学习雷打不动,几年以后你的能力和薪酬一定可以得到很大提升。

主要包括几个方面:学习+实践

其实这也是针对计算机这一行的弊端进行的:学习的很快,但是忘记的更快,如果没有动手操作的话,因此,也给大家整理了一些学习文档和7个项目实践

来看

1、开源框架

框架相关的Mybatis、Spring、SpringMVC这些技术点

2、JVM调优

3、Mysql+并发编程+Netty+Linux+Tomcat

4、高并发、高性能

  • MongoDB

  • Redis

  • Zookeeper

  • Nginx

  • 消息中间件等

5、微服务系列

其它的技术,小天就先不说了,Java程序员若能将以上的技术收入囊中,厂不厂的,对你来说已经没那么重要了。

总而言之,技术能力才是高薪的敲门砖。

最后

我这里整理了以上所有核心技术知识的PDF,还收集一套最新的大厂面试资料,以及系统面试题,需要的朋友帮忙转发一下文章,后台私信【面试】免费领取!

有了知识点之后,下面就是项目实战了,这里总结了7个项目,需要上面的文档以及下面项目的,关注+转发后,私信“资料”即可查看获取方式

SmartAdmin

我们开源一套漂亮的代码和一套整洁的代码规范,让大家在这浮躁的代码世界里感受到一股把代码写好的清流!同时又让开发者节省大量的时间,减少加班,快乐工作,热爱生活。SmartAdmin 让你从认识到忘不了,绝对是你最想要的!

litemall

有一个小商场系统,Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 + Vue用户移动端。

Timo

TIMO后台管理系统,基于SpringBoot2.0 + Spring Data Jpa + Thymeleaf + Shiro 开发的后台管理系统,采用分模块的方式便于开发和维护,支持前后台模块分别部署,目前支持的功能有:权限管理、部门管理、字典管理、日志记录、文件上传、代码生成等,为快速开发后台系统而生的脚手架!

技术选型

  • 后端技术:SpringBoot + Spring Data Jpa + Thymeleaf + Shiro + Jwt + EhCache
  • 前端技术:Layui + Jquery + zTree + Font-awesome

mall4j

一个基于spring boot、spring oauth2.0、mybatis、redis的轻量级、前后端分离、防范xss攻击、拥有分布式锁,为生产环境多实例完全准备,数据库为b2b2c设计,拥有完整sku和下单流程的完全开源商城。

项目致力于为中小企业打造一个完整、易于维护的开源的电商系统,采用现阶段流行技术实现。后台管理系统包含商品管理、订单管理、运费模板、规格管理、会员管理、运营管理、内容管理、统计报表、权限管理、设置等模块。

web-flash

基于Spring Boot+Vue的后台管理系统,权限管理,字典,配置,定时任务,短信,邮件,根据excel模板导出,cms内容管理,手机端h5,IDEA 代码生成插件。

SPTools

一个基于SpringBoot、JPA、Shiro的后台管理系统,单体架构,依赖少,极易上手,后端开发的福利。最重要的是还附带免费小程序以及微服务版本,可自行选择。

内置功能

  • 组织机构:机构管理、用户管理、角色管理、行政区域。
  • 系统监控:系统日志、在线用户,后期会慢慢追加完善。
  • 应用管理:任务调度、邮件管理、图片管理、文章管理、打卡任务、数据查询、人工智能,每个模块只需要你稍作修改就可以打造成一个项目了。
  • 系统管理:敏捷开发、系统菜单、全局配置、在线代码编辑器,小伙伴们只需要设计好表结构,三秒钟就能撸出一个增删查改的模块。

小结

其实无论是什么,文档、视频、项目,所有的这一些,只是为了能够学习的更加扎实,能够找到一份满意薪资的工作,但是更多的是你在学习路上的坚持

加油吧程序员,需要上面资料的,关注公众号:Java架构师联盟

查看原文

kali0102 收藏了文章 · 2020-09-07

一个后端开发的 Vue 笔记【入门级】

一 前言

最近找了些教程,顺带着趴在官网上,看了看 Vue 的一些内容,入门的一些概念,以及基础语法,还有一些常用的操作,瞄了一眼,通篇文字+贴了部分代码 9000 多字,入门语法什么的还是很好理解的,以前也有一定做小程序的基础,感觉还是很相似的,不过对于一些稍复杂的点,感觉还是总结的不够细致,例如插槽,和计算属性等,平时前端的东西看的也不是很多,学习过程中整理的笔记,和大家一起分享交流!欢迎各位大大交流意见~

二 初始 Vue

(一) Vue 概念理解

(1) Vue.js 是什么

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。

(2) 渐进式框架是什么

看了一些教程以及文章,简单理解的渐进式框架就三个字【不强求】,Vue并不强求你用它的所有内容(功能特性),用户完全可以根据自己情况进行选择,使用其部分即可。

这一块某乎上有一个比较好的答案,很容易度娘到,贴了部分

vue即主张较少,也就是说可以在原有系统上面,引入vue直接就可以当jquery用,使用 vue,你可以在原有大系统的上面,把一两个组件改用它实现,当 jQuery 用;也可以整个用它全家桶开发

(二) MVVM 架构

正式学习 Vue 前我们首先还需要了解一个基于前端的架构模式,也就是 MVVM ,它是 Model-View-ViewMode 的简写,其关系简单的描述为下图:

  • Model(模型层):表示 Javascript 数据对象
  • View(视图层):表示 DOM,也可以简单理解为前端展示的内容
  • ViewModel:连接视图和数据,即用于双向绑定数据与页面

在 MVVM 架构中,视图和数据是没有办法直接进行沟通的,只能通过 ViewModel 来做一个中间关系,ViewModel 可以观察到数据的变化,然后更新视图内容,亦或者监听到视图的变化,并能通知数据发生改变

后面我马上会写一个入门的小案例,可以一起来体会一下它的特点!

(三) Vue 的优点

1、体积小

  • 压缩后33K

2、更高的运行效率

  • 基于虚拟dom一种可以预先通过 javascript 进行各种计算,把最终的 DOM操作计算出来并优化的技术,由于这个DOM操作属于预处理操作,并没有真实的操作DOM,所以 叫做虚拟DOM。

3、双向数据绑定

  • 让开发者不用再去操作 dom 对象,把更多的精力投入到业务逻辑上

4、生态丰富、学习成本低

  • 市场上拥有大量成熟、稳定的基于 vue.js 的 ui 框架、常用组件!
  • 拿来即用实现快速开发
  • 对初学者友好、入门容易、学习资料多

(四) 入门案例

编写 Vue 你可以选择使用 Vscode 、 HBuilder 、sublime、Webstrom、甚至 IDEA 都是可以的,自行选择就好了

首先我们需要引入 Vue,你可以去官网直接 down 下文件,进行一个本地的引入,类似引入 jQuery,或者使用一个网络的引用,例如下文中,在官网中就可以找到这种引入或下载的地址

可以看到,引入后,我们通过 new 的这种形式创建了一个 Vue 的实例,其中通过 el 找到 id 值为 hello 的 div 进行绑定,在 data 中进行一个赋值,而在div 中 通过两组大括号来对数据进行回显

如果你有一些微信小程序的基础的话,其实可以发现,这两者结构看起来似乎有一些相似的

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<div id="hello">
  <h2>{{content}}</h2>
</div>

<script data-original="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
  var vm = new Vue({
      el: "#hello",
      data:{
        content: "Hello Vue!"
      }
  })
</script>

</body>
</html>

回显数据肯定是没问题的,我们试着在控制台修改 content 的值,可以看到随之页面也就发生改变了

我们已经成功创建了第一个 Vue 应用!看起来这跟渲染一个字符串模板非常类似,但是 Vue 在背后做了大量工作。现在数据和 DOM 已经被建立了关联,所有东西都是响应式的。我们要怎么确认呢?打开你的浏览器的 JavaScript 控制台 (就在这个页面打开),并修改 app.message 的值,你将看到上例相应地更新。

注意我们不再和 HTML 直接交互了。一个 Vue 应用会将其挂载到一个 DOM 元素上 (对于这个例子是 #app) 然后对其进行完全控制。那个 HTML 是我们的入口,但其余都会发生在新创建的 Vue 实例内部。—— Vue 官网

三 Vue 基本语法

(一) 声明式渲染

如果有接触过 Thymeleaf 这样的模板,你可以看出来,上面的 Vue 案例就是采用了一个简洁模板语法,即两组大括号包裹值,来声明式声明式地将数据渲染进 DOM 的系统,这其实和 Thymeleaf 中的 $ 加一组大括号 是相似的

我们还有一种绑定元素的方式:即使用指令

<div id="hello-2">
  <span v-bind:title="content">
    鼠标悬停几秒钟查看此处动态绑定的提示信息!
  </span>
</div>

<script data-original="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
  var vm = new Vue({
      el: "#hello-2",
      data:{
        content: '页面加载于 ' + new Date().toLocaleString()
      }
  })
</script>

观察结果,我们将鼠标悬停在文字上方,被绑定的数据就会出现

你看到的 v-bind attribute 被称为指令。指令带有前缀 v-,以表示它们是 Vue 提供的特殊 attribute,代码的意思就是将这个元素节点的 title attribute 和 Vue 实例的 content property 保持一致”

如果你在控制台进行修改 vm.content 的值,绑定的数据依旧会发生变化

注:使用 v-bind 需要头部引入一个约束

<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">

如果使用 IDEA 安装 Vue.js 插件 会有提示补全

(二) 条件判断

条件判断使用的指令就是 v-ifv-else-ifv-else

来看两个例子,首先是对于 true 或者 false 的判断

<div id="hello-3">
  <h2 v-if="isBoy">是男孩</h2>
  <h2 v-else>是女孩</h2>

</div>

<script data-original="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
  var vm = new Vue({
      el: "#hello-3",
      data:{
        isBoy: true
      }
  })
</script>

默认显示是男孩,我们接着在控制台修改为false

接着是对于值的判断,我们拿一个比较成绩的小例子,不过对数值的约束还是不够严谨的,仅仅为了讲解 if 这个例子,明白意思就行

<div id="hello-3">
  <h2 v-if="score < '60'">成绩不及格</h2>
  <h2 v-else-if="score >= '60' && score < '80'">成绩及格</h2>
  <h2 v-else>成绩优秀</h2>

</div>

<script data-original="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
  var vm = new Vue({
      el: "#hello-3",
      data:{
          score: 66
      }
  })
</script>

在结果中继续修改看看

(三) 循环

通过 v-for 就可以进行循环遍历,真例如 java 中的 增强for 只不过是把 冒号 换成了 in,students 对应 data 中的数组名,student 代表其中的每一项,通过 XXX.xx 的形式取出具体的属性值

<div id="hello-4">
  <li v-for="student in students">
    {{student.name}}
  </li>
</div>

<script data-original="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
    var vm = new Vue({
        el: "#hello-4",
        data: {
            students: [{name: '张三'}, {name: '李四'}, {name: '王五'}]
        }
    })
</script>

试着在控制台 push 一个新的,同样会更新出来

说明:这里演示的只是最基本的一种情况,很多时候,若遍历的数组中为对象,且对象有多个值,例如含有 id 这样的值,一般会将这种唯一的 id 值作为 key 值,例如:

<div v-for="item in items" v-bind:key="item.id">
  <!-- 内容 -->
</div>
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key attribute:

建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。

(四) 事件绑定

经常我们需要通过点击一些按钮或者标签组件等,使得用户可以与应用进行交互,也就是进行事件绑定,在 Vue 中我们可以通过 v-on 指令添加一个事件监听器来进行

注:使用 v-on 引入约束

<html lang="en" xmlns:v-on="http://www.w3.org/1999/xhtml">

示例代码

<div id="hello-5">
  <button v-on:click="helloWorld">你好世界</button>
</div>

<script data-original="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
    var vm = new Vue({
        el: "#hello-5",
        data: {
            content: "Hello World !"
        },
        methods: {
            helloWorld: function () {
                alert(this.content);
            }
        }
    })
</script>

可以看到,通过 v-on:click 就将 helloWorld 这个事件绑定了,而事件的具体逻辑需要定义在 Vue 对象的 methods 中

(五) 双向绑定

早在开篇介绍 MVVM 架构模式的时候,图中就提到了 View 和 ViewModel 之间的双向绑定,通俗的说就是:当数据发生变化时,视图也变化,而当视图发生变化的时候,数据也跟着变化

其实在前面的基本语法中,我们已经能很明显的体会到了前半句,即修改 data 中的数据,从而引起视图中的变化,我们这里就重点提一下后半句

首先,Vue 提供了 v-model 指令,它能轻松实现表单输入和应用状态之间的双向绑定

从常见的表单中的几种形式来讲,我们可以使用 v-model 指令在表单的 inputtextarea>select 等上进行数据的双向绑定,它可以根据控件类型选取正确的方法来更新元素

不过使用 v-model 指令后会忽略表单原先的 value、checked、selected 等的初始值,而总将 Vue 实例中的数据最为数据源

input 中 ,输入文本

<div id="hello-6">
  输入: <input type="text" v-model="content" > 输出: {{content}}
</div>

<script data-original="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
    var vm = new Vue({
        el: "#hello-6",
        data: {
            content: "Hello World !"
        },
    })
</script>

在输出的位置使用两组大括号进行 content 这个值的回显我们之前就已经能做到了,而我们在 input 的属性中使用 v-model 进行对于 content 的绑定,这样就可以使得输入框中输入的值可以直接影响 data 中 content 的值,即随着你 input 中输入值的修改,随之输出位置的内容也会变化

input 中 ,单选框

<div id="hello-6">
  性别:
  <input type="radio" name="gender" value="男" v-model="gender">男
  <input type="radio" name="gender" value="女" v-model="gender">女
  输出: {{gender}}
</div>

<script data-original="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
    var vm = new Vue({
        el: "#hello-6",
        data: {
            gender: '男'
        },
    })
</script>

效果显示

select 中

<div id="hello-6">
  <select v-model="choose">
    <option value="" disabled>---请选择---</option>
    <option>A-苹果</option>
    <option>B-樱桃</option>
    <option>C-西瓜</option>
  </select>
  输出: {{choose}}
</div>

<script data-original="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
    var vm = new Vue({
        el: "#hello-6",
        data: {
            choose: ''
        },
    })
</script>

(六) Vue组件

Vue 中组件也是一个很重要的概念,例如一个页面中,头部、底部、侧边栏、主内容区 都可以看做一个一个组件,不过有一些组件是固定的,例如头部,还有一些是变换的例如内容区

Vue 中就允许我们使用小型、独立和通常可复用的组件构建大型应用

注:实际都是创建 .vue 模板文件,并不会用直接在页面中书写的这种形式,仅为讲解方便

直接拿一个简单,不过还算相对完善的案例来进行讲解‘

先说一下最终我们想干嘛,例如 div 或者 input 等等 都是一个一个标签,我们现在向做的就是通过创建自定义组件模板,自定义出一个这样的标签,我们在需要的地方只需要引用这个标签,我们就可以达到显示出模板中想要的效果,达到抽取复用的效果

首先使用 Vue.component....... 这样的格式创建组件,在其中 ideal-20 就是组件标签的名字, template 就代表模板中的内容,props 代表我们在引用处传入的参数名

接着在一个已经绑定好的 hello-7 的 div 中引入自定义组件标签 ideal-20,而我们想要遍历 data 中的 fruits 数组,在 ideal-20 属性中进行 for 遍历即可,同时我们需要将每一项通过 v-bind:ideal="item" 绑定参数到组件模板中,因为数组不是一个普通的数组,所以赋 id 为 key值

<div id="hello-7">
  <ideal-20 v-for="item in fruits" v-bind:ideal="item" v-bind:key="item.id"></ideal-20>
</div>

<script data-original="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>

<script>
    // 定义名为 todo-item 的新组件
    Vue.component('ideal-20', {
        props: ['ideal'],
        template: '<li>{{ideal.name}}</li>'
    })
    var vm = new Vue({
        el: "#hello-7",
        data: {
            fruits: [
                {id: 0, name: '苹果'},
                {id: 1, name: '樱桃'},
                {id: 2, name: '山竹'}
            ]
        }
    })
</script>

效果展示

(七) Axios 入门

首先我们需要提一下,为什么要用这个东西呢?

我们在以前传统的开发中,我们一般会使用 Ajax 进行通信,而 Vue,js 作为一个视图层框架,并不支持 Ajax 的通信功能,所以可以使用 Axios 来实现 Ajax 的异步通信

首先看一下它的特点:

  • 从浏览器中创建 XMLHttpRequests
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换 JSON 数据
  • 客户端支持防御 XSRF

首先我们拿一段 json 来模拟数据

{
  "name": "BWH_Steven",
  "blog": "www.ideal-20.cn",
  "about": {
    "country": "中国",
    "phone": "13888888888"
  },
  "students": [
    {
      "id": 0,
      "name": "张三"
    },
    {
      "id": 1,
      "name": "李四"
    },
    {
      "id": 2,
      "name": "王五"
    }
  ]
}

通过下图我们就可以知道 我们可以将代码写到 mounted() 中去

接着就是使用的代码了

首先除了引入 Vue 还需要引入 Axios 的 CDN,在 mounted() 方法中,去拿到这个json文件,同时将 response.data 也就是拿到的值,赋值给我们在 data() 中定义的 info 中

注意:data 和 data() 是不一样的

接着在绑定好的 hello-8 div 中就可以调用回显了

说明:为什么给 v-clock 添加了一个样式呢,这是因为,显示数据的过程是先显示出{{info.name}} 这样的字样,拿到值后,再去渲染,如果网速慢的情况下可以看到 info.name 的,体验不是很好

<!DOCTYPE html>
<html lang="en" xmlns:v-on="http://www.w3.org/1999/xhtml">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    [v-clock] {
      display: none;
    }
  </style>
</head>
<body>
<div id="hello-8" v-clock>
  <div>{{info.name}}</div>
  <div>{{info.blog}}</div>
  <div>{{info.about.country}}</div>
  <div>{{info.about.phone}}</div>
  <div>{{info.students[0].name}}</div>
</div>

<script data-original="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script data-original="https://unpkg.com/axios/dist/axios.min.js"></script>

<script>

    var vm = new Vue({
        el: "#hello-8",
        data() {
            return {
                info: {}
            }
        },
        mounted() {
            axios.get("../json/data.json").then(response => (this.info = response.data));
        }
    })
</script>

</body>
</html>

(八) 计算属性

这一段,我认为官方文档说的还是比较清楚的

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:

<div id="example">
  {{ message.split('').reverse().join('') }}
</div>

在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串。当你想要在模板中多包含此处的翻转字符串时,就会更加难以处理。

所以,对于任何复杂逻辑,你都应当使用计算属性

<div id="hello-9">
  <p>原始数据: "{{ message }}"</p>
  <p>翻转数据: "{{ reversedMessage }}"</p>
</div>


<script data-original="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>

<script>
    var vm = new Vue({
        el: '#hello-9',
        data: {
            message: 'Hello'
        },
        computed: {
            // 计算属性的 getter
            reversedMessage: function () {
                // `this` 指向 vm 实例
                return this.message.split('').reverse().join('')
            }
        }
    })
</script>

结果:

原始数据: "Hello"

翻转数据: "olleH"

这里我们声明了一个计算属性 reversedMessage。我们提供的函数将用作 property vm.reversedMessage 的 getter 函数:

console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'

你可以打开浏览器的控制台,自行修改例子中的 vm。vm.reversedMessage 的值始终取决于 vm.message 的值。

你可以像绑定普通 property 一样在模板中绑定计算属性。Vue 知道 vm.reversedMessage 依赖于 vm.message,因此当 vm.message 发生改变时,所有依赖 vm.reversedMessage 的绑定也会更新。而且最妙的是我们已经以声明的方式创建了这种依赖关系:计算属性的 getter 函数是没有副作用 (side effect) 的,这使它更易于测试和理解。

计算属性和方法的区别 ?

看到这里,你会不会觉得,貌似我用方法也可以实现这种效果啊,将具体的业务逻辑放在定义的方法中,但是他们最大的区别是计算属性是基于它们的响应式依赖进行缓存的,也就是说,我们上文中所依赖的 message 不发生改变,reversedMessage 会马上获取之前的结果,就不用再次执行函数了,计算属性可以帮我们节省大量的性能开销,不过如果我们并不希望出现缓存内容,就可以使用方法来代替它

(九) 插槽 Slot

在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 这两个目前已被废弃但未被移除且仍在文档中的 attribute。新语法的由来可查阅这份 RFC。

这块写的太浅了,不看也罢

插槽就是子组件给父组件一个占位符即 <slot></slot> 父组件就能在这个占位符,填一些模板或者 HTML 代码

简单点理解就是组件套组件

就像下面我定义了三个组件,ideal是父组件,在其中用 slot 进行占位,同时用 name 属性指向到了这两个子组件 ideal-title 和 ideal-content,而为了子组件中显示的数据来自服务器(模拟)所以需要动态地显示,即通过传参(前面讲解组件模板有说过),配合遍历等读出 data 中的数据即可

<div id="hello-10">
  <ideal>
    <ideal-title slot="ideal-title" v-bind:title="title"></ideal-title>
    <ideal-content slot="ideal-content" v-for="contentItem in contents" v-bind:content="contentItem"></ideal-content>
  </ideal>
</div>


<script data-original="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>

<script>
    Vue.component("ideal", {
        template: '<div>\
                    <slot name="ideal-title"></slot>\
                      <ul>\
                        <slot name="ideal-content"></slot>\
                      </ul>\
                   </div>'
    })
    
    Vue.component("ideal-title", {
        props: ['title'],
        template: '<div>{{title}}</div>'
    })
    
    Vue.component("ideal-content", {
        props: ['content'],
        template: '<li>{{content}}</li>'
    })

    var vm = new Vue({
        el: '#hello-10',
        data: {
            title: "理想二旬不止",
            contents: ["Java", "Linux", "数据库"]
        }
    })
</script>

结果如下:

四 Vue 入门

(一) 创建 Vue-cli 项目

Vue-cli 是官方提供的一个用于快速创建 Vue 项目的脚手架,可以简单的理解为 Maven ,即创建时选择一个骨架那种感觉,能让开发更加便捷

(1) 准备

A:安装 Node.js

  • Node.js 去官网或者中文网进行下载

cmd 下输入 node -v ,出现版本号即正常,输入 npm -v 同样出现版本号即正常

  • 将 Node.js 的镜像调整为淘宝镜像加速(cnpm)
# -g 代表全局安全 推荐此方法
npm install cnpm -g

# 还有一种办法就是每次使用 npm 都在后面加一串字符串,不推荐
npm install --registry=https://registry.npm.tabao.org

安装后打开此路径(abc是我这台机器的用户名,根据自己的来):

C:\Users\abc\AppData\Roaming\npm\node_modules

正常情况下里面会有安装好的 cnpm,接着我们就开始安装 vue-cli

B:安装 vue-cli

cnpm install vue-cli -g

安装后在 npm 文件夹下打开 cmd 输入 vue-list ,若出现下图这种星状内容,则完毕

C:创建 vue-cli 程序

自己在想要的位置创建一个目录,选择基于 webpack 的 vue 应用程序

此文件夹下的 cmd 中输入命令

vue init webpack vue_02_myvue

输入一些基本信息,例如 项目名 作者名

下面的内容如果有选项的回车配合 n(也就是no)进行

这时,它会询问你是否自动执行 npm install(如下),是的话则会自动安装一些依赖,点击否,需要自行再输入命令安装,所以选择是就行了

Should we run npm install for you after the project has been created? (recommended) npm

完成之后,项目中的 node_modules 就会多出很多很多依赖

再接着,它又会提醒初始化好了,你如果想启动就可以执行这两条,第一句是从外面的文件夹进入到我自定义的那个项目文件夹中,第二句则是启动

出现如果所示内容则启动成功,通过后面的地址和端口就可以访问

找个编辑器看一下,我用 IDEA Open进这个文件夹来,就可以进行修改了,具体的代码文件还是在 src 目录下,同时还可以配置 IDEA 的 Terminal 为有管理员权限的 cmd 或者终端,能更便捷一些

如何快速创建项目

  • 可以通过 HBuilder 进行相对快速的创建项目,其新建中直接就可以创建 vue 项目 以及终端运行
  • 在想创建项目的目录下终端输入 vue ui 进入图形界面(此方法需要 vue-cli 版本为 3.x ,通过 vue --version 查询到 vue-cli 的版本 例如为 2.9.6 是不能使用的,可用的命令可以通过 vue -h 查看 )

(二) 简单认识 Webpack

(1) 认识安装

盖菜我们创建 vue 项目的时候,选择的是 webpack 进行打包,但是都是自动化的过程,我们手动的操作一下,能更好的理解

webpack是一个现代 Javascript应用程序的静态模块打包器( module bundler)。当webpack处理应用程序时,它会递归地构建一个依赖关系 Dependency graph),其中包含应用程序需要的毎个模块,然后将所有这些模块打包成一个或多个 bundle
webρack是当下最热门的前端资源模块化管理和打包工具,它可以将许多松散耦合的模块按照
依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分离,等到实际
需要时再异步加载。通过 loader转换,任何形式的资源都可以当做模块,比如 Commonis、AMD、ES6、CSS、JSON、 Coffee Script、LESS等;

安装 webpack、webpack-cli ,如果 npm 不太慢先考虑使用 npm 再考虑使用 cnpm

cnpm install webpack -g

cnpm install webpack-cli -g

通过 webpack -vwebpack-cli -v 查看是否出现版本号确定安装是否成功

(2) 使用 webpack

  • 创建项目(IDEA 的话,直接创建一个普通文件夹 Open进入就行了)
  • 新建一个 modules 目录,放置 JS 模块等内容
  • 在 modules 下创建模块文件,例如 demo.js 用于编写 Js 模块相关代码,例如
exports.test = function () {
    document.write("<h2>理想二旬不止</h2>")
}
  • 在 moduels 下创建 main.js ,这是一个入口文件,用于打包时设置 entry 属性对应
var demo = require("./demo")
demo.test();
  • 在项目根目录下创建 webpack.config.js 文件,用于配置
module.exports = {
    entry: "./modules/main.js",
    output: {
        filename: "./js/bundle.js"
    }
};
  • 在 terminal 中输入 webpack 命令进行打包(进入项目目录后 输入 webpack 就行了,这是运行结果)
abc@LAPTOP-5T03DV1G MINGW64 /f/develop/IdeaProjects/framework-code/vue/webpack_01_first
$ webpack
Hash: 7f61ef9440a6bab63058
Version: webpack 4.44.1
Time: 334ms
Built at: 2020-09-05 4:18:40 PM
         Asset        Size  Chunks             Chunk Names
./js/bundle.js  1020 bytes       0  [emitted]  main
Entrypoint main = ./js/bundle.js
[0] ./modules/main.js 42 bytes {0} [built]
[1] ./modules/demo.js 82 bytes {0} [built]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each e
nvironment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
  • 打包后项目目录多出 dist 文件夹 其中有一个 js 文件夹,放着打包的 js,创建index.html 引入此 js
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
  <script data-original="dist/js/bundle.js"></script>
</body>
</html>
  • 页面效果如下:

补充:配置文件中可能会有哪些内容

  • entry:入口文件,指定 Web Pack用哪个文件作为项目的入口
  • output:输出,指定 Web pack把处理完成的文件放置到指定路径
  • module:模块,用于处理各种类型的文件plugins:插件,如:热更新、代码重用等
  • resolve:设置路径指向
  • watch:监听,用于设置文件改动后直接打包

(三) 简单认识 Vue Router 路由

Vue Router是 Vue.js 的官方路由器。它与 Vue.js 核心深度集成,使使用 Vue.js 轻松构建单页应用程序变得轻而易举。功能包括:

简单的说,可以实现一些页面的跳转,例如我们头部的内容是不变的,内容部分需要根据链接改变

  • 嵌套路线/视图映射
  • 模块化,基于组件的路由器配置
  • 路由参数,查询,通配符
  • 查看由Vue.js过渡系统提供动力的过渡效果
  • 细粒度的导航控制
  • 与自动活动CSS类的链接
  • HTML5历史记录模式或哈希模式,在IE9中具有自动回退
  • 可自定义的滚动行为

安装步骤:

由于 vue-router 是一个插件包,还是老办法,npm/cnpm

npm install vue-router --save-dev

安装后如果有一些问题,根据提示,输入对应命令即可,就像我遇到了提示输入 npm audit fix

创建好项目后,删掉默认的 HelloWorld那个组件,然后再 components 中新建两个自定义组件,例如我创建的 FirstDemo.vue 和 Main.vue 前者是一个子页面,后者代表主页面,随便自拟

FirstDemo.vue

<template>
  <h1>第一个Demo页面</h1>
</template>

<script>
  export default {
    name: "FirstDemo.vue"
  }
</script>

<style scoped>

</style>

Main.vue

<template>
  <h1>首页</h1>
</template>

<script>
  export default {
    name: "Main.vue"
  }
</script>

<style scoped>
</style>

接着创建 router 文件夹,以及其中的 index.js 主配置

注:如果在一个模块化工程中使用它,必须要通过 Vue.use() 明确地安装路由功能

import Vue from 'vue'
import VueRouter from 'vue-router'
import FirstDemo from '../components/FirstDemo'
import Main from '../components/Main'

Vue.use(VueRouter);

export default new VueRouter({
  routes: [
    {
      // 路由路径
      path: "/firstDemo",
      name: 'firstDemo',
      // 跳转的组件
      component: FirstDemo
    },
    {
      // 路由路径
      path: "/main",
      name: 'main',
      // 跳转的组件
      component: Main
    }
  ]
})

修改 main.js 这个入口文件

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false

Vue.use(router);

/* eslint-disable no-new */
new Vue({
  el: '#app',
  //配置路由
  router,
  components: {App},
  template: '<App/>'
})

以及正式的书写页面,引入链接

<template>
  <div id="app">
    <h1>理想二旬不止</h1>
    <router-link to="/main">首页</router-link>
    <router-link to="/firstDemo">第一个Demo页面</router-link>
    <router-view></router-view>
  </div>
</template>

<script>

  export default {
    name: 'App',
  }
</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>

(四) Vue + ElementUi 基本使用

创建名字为 vue_03_vue_elementui 的项目,同时安装 vue-router、e1ement-ui、sass-loader 、node-sass 插件

# 创建项目
vue init webpack vue_03_vue_elementui

# 进入工程目录
cd vue_03_vue_elementui
# 安装vue-router
npm install vue-router --save-dev
# 安装e1ement-ui
npm i element-ui -S
# 安装依赖
npm install
# 安装SASS加载器
cnpm install sass-loader node-sass --save-dev
# 启动测试
npm run dev

补充:Npm命令解释

  • npm install moduleName:安装模块到项目目录下
  • npm install - g moduleNMame:-g 的意思是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm config prefix的位置
  • npm install - save moduleName:--save 的意思是将模块安装到项目目录下,并在package文件的 dependencies 节点写入依赖,-S为该命令的缩写
  • npm install -save -dev moduleName:--save-dev 的意思是将模块安装到项目目录下,并在 package 文件的 devdependencies节点写入依赖, -D为该命令的缩写

接着就可以在编辑器中打开了,看个人选择,我这里用 IDEA 打开,打开后注意看一下 modules 文件夹中是否已经把 router sass 等内容成功安装了

接着将默认的 HelloWorld 那个组件和默认 logo 删掉,开始编写代码,创建一个 views 文件夹用来放视图

创建 Login.vue 和 Main.vue

Login.vue

<template>
  <h1>主页面</h1>
</template>

<script>
  export default {
    name: "Main.vue"
  }
</script>

<style scoped>
</style>

Main.vue

<template>
  <div>
    <el-form ref="loginForm" :model="form" :rules="rules" label-width="80px" class="login-box">
      <h3 class="login-title">欢迎 登录</h3>
      <el-form-item label=" 账号" prop="username">
        <el-input type="text" placeholder="请输入账号" v-model="form.username"/>
      </el-form-item>
      <el-form-item label=" 密码" prop="password">
        <el-input type="password" placeholder=" 请输入密码" v-model="form.password"/>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" v-on:click="onSubmit( 'loginForm' )">登录</el-button>
      </el-form-item>
    </el-form>
    <el-dialog
      title="温馨提示"
      :visible.sync="dialogVisible"
      width="30%"
      :before-close="handLeClose">
      <span>请输入账号和密码</span>
      <span slot="footer" class="dialog- footer">
        <el-button type="primary" @click="dialogVisible = false">确定</el-button>
      </span>
    </el-dialog>
  </div>

</template>


<script>
  export default {
    name: "Login",
    data() {
      return {
        form: {
          username: '',
          password: ''
        },
        //表单验证,需要在el-form-item 元素中增加prop 属性
        rules: {
          username: [
            {required: true, message: " 账号不可为空", trigger: 'blur'}
          ],
          password: [
            {required: true, message: " 密码不可为空 ", trigger: 'blur'}
          ]
        },
        //对话框显示和隐藏
        dialogVisible: false
      }
    },
    methods: {
      onSubmit(formName) {
        //为表单绑定验证功能
        this.$refs[formName].validate((valid) => {
          if (valid) {
            //使用vue-router路由到指定页面,该方式称之为编程式导航
            this.$router.push("/main");
          } else {
            this.dialogVisible = true;
            return false;
          }
        });
      }
    }
  }
</script>


<style lang="scss" scoped>
  .login-box {
    border: 1px solid #DCDFE6;
    width: 350px;
    margin: 40px auto;
    padding: 35px 35px 15px 35px;
    border-radius: 5px;
    -webkit-border-radius: 5px;
    -moz-border-radius: 5px;
    box-shadow: 0 0 25px #909399;
  }

  .login-title {
    text-align: center;
    margin: 0 auto 40px auto;
    color: #303133;
  }
</style>

创建 router 文件夹,以及其中的 index.js,配置好跳转内容

index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../views/Login'
import Main from '../views/Main'

Vue.use(VueRouter);

export default new VueRouter({
  routes: [
    {
      path: "/main",
      component: Main
    },
    {
      path: "/login",
      component: Login
    }
  ]
})

修改 main.js ,使用 router 和 elementui,关于 router 前面就说过,至于后者,照着官网起步文档就明白了

https://element.eleme.cn/#/zh-CN/component/quickstart

main.js

import Vue from 'vue'
import App from './App'

import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.config.productionTip = false;

Vue.use(router);
Vue.use(ElementUI);

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  render: h => h(App)
});

App.vue

<template>
  <div id="app">
    <h1>理想二旬不止</h1>
    <router-link to="/main">首页</router-link>
    <router-link to="/login">登录页面</router-link>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App',
}
</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>

到这里就写好了,我们可以开始运行了,但是运行时,我分别出现了如下错误

TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined

Module build failed: TypeError: this.getResolve is not a function

经查阅后,修改项目目录下 package.json 中的 sass-loder 的版本 从 10.0.2 到 8.0.2 到 7.3.1 才可以正常通过 npm run dev 运行

"sass-loader": "^7.3.1",

注:修改配置后需要重新 npm install 或者 cnpm install

展示一下最终效果:

点击首页效果:

点击登录页面效果:

五 Vue和Java数据交互简单案例

最后,在某马上找了一个现成的页面案例,简单搭了下后台跑了一下,也算巩固一下最前面的基础语法,后台就基本的 SSM ,给了三个接口方法,作为一个后端,这应该算基本功了哇

/user/findAll/user/indById /user/updateUser

就是简单的查询和一个更新操作,查询的主体是一个用户类,有这么几个基本字段

public class User implements Serializable {    
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private String sex;
    private String email;
    ......
}

页面中承接的数据的关键代码如下

首先是关于所有用户数据展示的,通过一个对于 userList 的遍历,然后通过大括号组和 X.x 的形式取出属性值

<tr v-for="u in userList">
  <td><input name="ids" type="checkbox"></td>
  <td>{{u.id}}</td>
  <td>{{u.username}}
  </td>
  <td>{{u.password}}</td>
  <td>{{u.sex}}</td>
  <td>{{u.age}}</td>
  <td class="text-center">{{u.email}}</td>
  <td class="text-center">
    <button type="button" class="btn bg-olive btn-xs">详情</button>
    <button type="button" class="btn bg-olive btn-xs" @click="findById(u.id)">编辑</button>
  </td>
</tr>

点击编辑后执行对于当前用户的查询方法,用于回显,下面通过 v-model 进行与 user 的绑定(js后面给出)

<div class="box-body">
  <div class="form-horizontal">


    <div class="form-group">
      <label class="col-sm-2 control-label">用户名:</label>
      <div class="col-sm-5">
        <input type="text" class="form-control" v-model="user.username">
      </div>
    </div>
    <div class="form-group">
      <label class="col-sm-2 control-label">密码:</label>
      <div class="col-sm-5">
        <input type="text" class="form-control" v-model="user.password">
      </div>
    </div>
    <div class="form-group">
      <label class="col-sm-2 control-label">性别:</label>
      <div class="col-sm-5">
        <input type="text" class="form-control" v-model="user.sex">
      </div>
    </div>
    <div class="form-group">
      <label class="col-sm-2 control-label">年龄:</label>
      <div class="col-sm-5">
        <input type="text" class="form-control" v-model="user.age">
      </div>
    </div>
    <div class="form-group">
      <label class="col-sm-2 control-label">邮箱:</label>
      <div class="col-sm-5">
        <input type="text" class="form-control" v-model="user.email">
      </div>
    </div>
  </div>
</div>

要实现数据交互,最重要的还是定义数据,与使用 axios 进行异步请求

var vue = new Vue({
    el: "#app",
    data: {
        user: {id: "", username: "", password: "", age: "", sex: "", email: ""},
        userList: []
    },
    methods: {
        findAll: function () {
            var _this = this;
            axios.get("user/findAll.do").then(function (response) {
                _this.userList = response.data;
                console.log(_this.userList);
            }).catch(function (err) {
                console.log(err);
            });
        },
        findById: function (userid) {
            var _this = this;
            axios.get("user/findById.do", {
                params: {
                    id: userid
                }
            }).then(function (response) {
                _this.user = response.data;
                $('#myModal').modal("show");
            }).catch(function (err) {
            });

        },
        update: function (user) {
            var _this = this;
            axios.post("user/updateUser.do", _this.user).then(function (response) {
                _this.findAll();
            }).catch(function (err) {
            });
        }
    },
    created() {
        this.findAll();
    }
});

上述代码其实很好理解,首先定义了 user 和 userList 两个数据,userList 就是前面展示所有用户数据的一个遍历内容,用来承载 findAll 后的数据,user 则是对于进行单个用户查询,即修改时回显当前用户旧信息的一个承载实体,因为查询所有的操作是一开始就进行,所以在 created() 中就执行 findAll 方法,在 methos 中创建查和改的方法,通过 axios 进行 get 或者 post 请求,同时将返回的结果进行处理

说明:.then 中的是请求成功后执行的内容,.catch 中是请求失败后执行的内容

注意:最关键的一个内容就是在 axios 前定义了 var _this = this 如果有接触过小程序可能会感到很熟悉,这里定义的原因是因为 例如在 axios 中有这样的语句 _this.userList = response.data; 如果 userList 中前直接使用 this,这就代表着查找 axios 中的此内容,但我们明显想指代的是 data 中的 userList,所以此定义 _this 步骤不可省略

效果如下:

查询所有

查询当前用户以及修改

六 结尾

如果文章中有什么不足,欢迎大家留言交流,感谢朋友们的支持!

如果能帮到你的话,那就来关注我吧!如果您更喜欢微信文章的阅读方式,可以关注我的公众号

在这里的我们素不相识,却都在为了自己的梦而努力 ❤

一个坚持推送原创开发技术文章的公众号:理想二旬不止

查看原文

kali0102 关注了用户 · 2020-09-07

子君 @zijun_5f156624be160

微信公众号: 前端有得玩
微信账号:snowzijun
github仓库: https://github.com/snowzijun
寄语:不要吹灭你的灵感和你的想象力; 不要成为你的模型的奴隶。

关注 556

认证与成就

  • 获得 1 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-10-08
个人主页被 292 人浏览