谷粒商城之分布式基础篇知识补充
# 谷粒商城基础篇知识点补充
前言
在学习分布式基础篇章过程中,会用到一些 额外知识,这篇文章就是对这些知识的介绍。
1 java8新特性之lambda表达式
1.1 为什么使用
Lambda是一个 匿名函数 ,我们可以把 Lambda 表达式理解为是 一段可以传递的代码 (将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使 Java 的语言表达能力得到了提升。
1.2 从匿名类到Lambda的转换
1.3 语法
- -> :lambda操作符 或 箭头操作符
- -> 左边: lambda形参列表(其实就是接口中的抽象方法的形参)
- -> 右边: lambda体(其实就是重写的抽象方法的方法体)
总结
-> 左边: lambda形参列表的参数类型可以省略(类型推断),如果形参列表只有一个参数,其一对()也可以省略
-> 右边: lambda体应该使用一对{}包裹;如果lambda体只执行一条语句(可能是return语句),可以省略这一对{}和return关键字
2 Stream API
2.1 概述
2.2 创建
2.2.1 通过集合
//创建 Stream方式一:通过集合
@Test
public void test1(){
List<Employee> employees = EmployeeData.getEmployees();
// default Stream<E> stream() : 返回一个顺序流
Stream<Employee> stream = employees.stream();
// default Stream<E> parallelStream() : 返回一个并行流
Stream<Employee> parallelStream = employees.parallelStream();
}
2.2.2 通过数组
//创建 Stream方式二:通过数组
@Test
public void test2(){
int[] arr = new int[]{1,2,3,4,5,6};
//调用Arrays类的static <T> Stream<T> stream(T[] array): 返回一个流
IntStream stream = Arrays.stream(arr);
Employee e1 = new Employee(1001,"Tom");
Employee e2 = new Employee(1002,"Jerry");
Employee[] arr1 = new Employee[]{e1,e2};
Stream<Employee> stream1 = Arrays.stream(arr1);
}
2.2.3 通过Stream的of()
//创建 Stream方式三:通过Stream的of()
@Test
public void test3(){
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
}
2.2.4 创建无限流
//创建 Stream方式四:创建无限流
@Test
public void test4(){
// 迭代
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
//遍历前10个偶数
Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);
// 生成
// public static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
2.3 中间操作
多个中间操作 可以连接起来形成一个 流水线 ,除非流水线上触发终止操作,否则 中间操作不会执行任何的处理 !而在 终止操作时一次性全部处理,称为“惰性求值”。
2.3.1 筛选与切片
1)、filter
接收 Lambda,从流中排除某些元素
// (1)、filter——接收 Lambda , 从流中排除某些元素。
@Test
public void testFilter() {
//这里加入了终止操作 ,不然中间操作一系列不会执行
//中间操作只有在碰到终止操作才会执行
emps.stream()
.filter((e)->e.getAge()>18)
.forEach(System.out::println);//终止操作
}
注意:这里filter主要是过滤一些条件,这里的话就是把年龄小于18岁的Employee对象给过滤掉,然后用forEach给遍历一下,因为中间操作只有碰到终止操作才会执行,不然的话,看不到过滤效果。以下的操作都大部分都forEach了一下,为方便看到效果。filter用的还是很多的.
2)、limit
截断流,使其元素不超过给定数量
// (2)、limit——截断流,使其元素不超过给定数量。
@Test
public void testLimit() {
emps.stream()
.filter((e)->e.getAge()>8)
.limit(4)//跟数据库中的limit有异曲同工之妙
.forEach(System.out::println);//终止操作
}
注意:这里用了上面的filter跟limit,代码意思是:过滤掉年龄小于8的,只要4条数据。这种".“式操作很有意思,就是中间操作都可以一直”.",直到得到你想要的要求。
3)、skip(n)
跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n) 互补
// (3)、skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
@Test
public void testSkip() {
emps.stream()
.filter((e)->e.getAge()>8)
.skip(2)//这里可以查找filter过滤后的数据,前两个不要,要后面的,与limit相反
.forEach(System.out::println);//终止操作
}
注意:这里同样使用了filter中间操作,也可以不用,代码意思是:过滤掉年龄小于8岁的employee对象,然后前两个对象不要,只要后面的对象。跟limit意思相反。
4)、distinct
筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
// (4)、distinct——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
@Test
public void testDistinct() {
emps.stream()
.distinct()//去除重复的元素,因为通过流所生成元素的 hashCode() 和 equals() 去除重复元素,所以对象要重写hashCode跟equals方法
.forEach(System.out::println);//终止操作
}
注意:distinct,去除重复的元素,因为通过流所生成元素的 hashCode() 和 equals() 去除重复元素,所以对象要重写hashCode跟equals方法,我在Employee对象中重写了这两个方法。
2.3.2 映 射
1)、map
接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
2)、flatMap
接收一个函数作为参数,将流中的每个值都转换成另一个流,然后把所有流连接成一个流。
// map-接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
@Test
public void testMapAndflatMap() {
List<String> list=Arrays.asList("aaa","bbb","ccc","ddd");
list.stream()
.map((str)->str.toUpperCase())//里面是Function
.forEach(System.out::println);
System.out.println("----------------------------------");
//这里是只打印名字,map映射,根据Employee::getName返回一个name,映射成新的及结果name
emps.stream()
.map(Employee::getName)
.forEach(System.out::println);
System.out.println("======================================");
//流中流
Stream<Stream<Character>> stream = list.stream()
.map(StreamAPI::filterCharacter);
//{{a,a,a},{b,b,b}}
//map是一个个流(这个流中有元素)加入流中
stream.forEach(sm->{
sm.forEach(System.out::println);
});
System.out.println("=============引进flatMap=============");
// 只有一个流
Stream<Character> flatMap = list.stream()
.flatMap(StreamAPI::filterCharacter);
//flatMap是将一个个流中的元素加入流中
//{a,a,a,b,b,b}
flatMap.forEach(System.out::println);
}
/**
* 测试map跟flatMap的区别
* 有点跟集合中的add跟addAll方法类似
* add是将无论是元素还是集合,整体加到其中一个集合中去[1,2,3.[2,3]]
* addAll是将无论是元素还是集合,都是将元素加到另一个集合中去。[1,2,3,2,3]
* @param str
* @return
*/
public static Stream<Character> filterCharacter(String str){
List<Character> list=new ArrayList<>();
for (Character character : str.toCharArray()) {
list.add(character);
}
return list.stream();
}
注意:map跟flatMap还是有区别的,map是一个个流(这个流中有元素)加入流中,flatMap是将一个个流中的元素加入流中.
2.3.2 排序
1)、sorted()
自然排序(Comparable)
2)、sorted(Comparator com)
定制排序(Comparator)
@Test
public void testSorted() {
//自然排序
List<String> list=Arrays.asList("ccc","aaa","bbb","ddd","eee");
list.stream()
.sorted()
.forEach(System.out::println);
System.out.println("=======定制排序=========");
//定制排序
emps.stream()
.sorted((x, y) -> {
if(x.getAge() == y.getAge()){
return x.getName().compareTo(y.getName());
}else{
return Integer.compare(x.getAge(), y.getAge());
}
}).forEach(System.out::println);
}
2.4 终止操作
- 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如: List 、 Integer ,甚至是 void 。
- 流进行了终止操作后,不能再次使用。
2.4.1 匹配与查找
1)、allMatch
检查是否匹配所有元素
System.out.println("==========allMatch==============");
boolean allMatch = emps.stream()
.allMatch((e)->e.getStatus().equals(Status.BUSY));
System.out.println(allMatch);
2)、anyMatch
检查是否至少匹配一个元素
System.out.println("==========anyMatch==============");
boolean anyMatch = emps.stream()
.anyMatch((e)->e.getAge()>10);
System.out.println(anyMatch);
3)、noneMatch
检查是否没有匹配的元素
System.out.println("==========noneMatch==============");
boolean noneMatch = emps.stream()
.noneMatch((e)->e.getStatus().equals(Status.BUSY));
System.out.println(noneMatch);
4)、findFirst
返回第一个元素
System.out.println("==========findFirst==============");
Optional<Employee2> findFirst = emps.stream()
.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))//按照工资排序并输出第一个
.findFirst();
System.out.println(findFirst);
5)、findAny
返回当前流中的任意元素
System.out.println("==========findAny==============");
Optional<Employee2> findAny = emps.stream()
.filter((e)->e.getStatus().equals(Status.BUSY))
.findAny();
System.out.println(findAny);
6)、count
返回流中元素的总个数
System.out.println("==========count==============");
long count = emps.stream()
.count();
System.out.println(count);
7)、max
返回流中最大值
System.out.println("==========max==============");
Optional<Double> max = emps.stream()
.map(Employee2::getSalary)
.max(Double::compare);
System.out.println(max);
8)、min
返回流中最小值
System.out.println("==========min==============");
Optional<Employee2> min = emps.stream()
.min((e1,e2)->Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(min);
9)、forEach
在中间操作代码处,已经多次使用了,这里不再赘述。
2.4.2 归约
1)、reduce
reduce(T identity, BinaryOperator) / reduce(BinaryOperator)
可以将流中元素反复结合起来,得到一个值
@Test
public void testReduce() {
List<Integer> list= Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream()
.reduce(0,(x,y)->x+y);
System.out.println(sum);
Optional<Double> reduce = emps.stream()
.map(Employee2::getSalary)
.reduce(Double::sum);
System.out.println(reduce.get());
}
2.4.3 收集
Collector接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map)。但是Collectors实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
1)、Collectors.toList()
List<String> collect = emps.stream()
.map(Employee2::getName)
.collect(Collectors.toList());
collect.forEach(System.out::println);
2)、Collectors.toSet()
Set<String> collect2 = emps.stream()
.map(Employee2::getName)
.collect(Collectors.toSet());
collect2.forEach(System.out::println);
3)、Collectors.toCollection(HashSet::new)
HashSet<String> collect3 = emps.stream()
.map(Employee2::getName)
.collect(Collectors.toCollection(HashSet::new));
collect3.forEach(System.out::println);
4)、Collectors.maxBy()
Optional<Double> collect = emps.stream()
.map(Employee2::getSalary)
.collect(Collectors.maxBy(Double::compare));
System.out.println(collect.get());
Optional<Employee2> collect2 = emps.stream()
.collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
System.out.println(collect2.get());
5)、Collectors.minBy()
Optional<Double> collect4 = emps.stream()
.map(Employee2::getSalary)
.collect(Collectors.minBy(Double::compare));
System.out.println(collect4);
Optional<Employee2> collect3 = emps.stream()
.collect(Collectors.minBy((e1,e2)->Double.compare(e1.getSalary(),e2.getSalary())));
System.out.println(collect3.get());
6)、Collectors.summingDouble()
Double collect5 = emps.stream()
.collect(Collectors.summingDouble(Employee2::getSalary));
System.out.println(collect5);
7)、Collectors.averagingDouble()
Double collect6 = emps.stream()
.collect(Collectors.averagingDouble((e)->e.getSalary()));
Double collect7 = emps.stream()
.collect(Collectors.averagingDouble(Employee2::getSalary));
System.out.println("collect6:"+collect6);
System.out.println("collect7:"+collect7);
8)、Collectors.counting()
//总数
Long collect8 = emps.stream()
.collect(Collectors.counting());
System.out.println(collect8);
9)、Collectors.summarizingDouble()
DoubleSummaryStatistics collect9 = emps.stream()
.collect(Collectors.summarizingDouble(Employee2::getSalary));
long count = collect9.getCount();
double average = collect9.getAverage();
double max = collect9.getMax();
double min = collect9.getMin();
double sum = collect9.getSum();
System.out.println("count:"+count);
System.out.println("average:"+average);
System.out.println("max:"+max);
System.out.println("min:"+min);
System.out.println("sum:"+sum);
10)、Collectors.groupingBy()
//分组
@Test
public void testCollect3() {
Map<Status, List<Employee2>> collect = emps.stream()
.collect(Collectors.groupingBy((e)->e.getStatus()));
System.out.println(collect);
Map<Status, List<Employee2>> collect2 = emps.stream()
.collect(Collectors.groupingBy(Employee2::getStatus));
System.out.println(collect2);
}
11)、Collectors.groupingBy()
//多级分组
@Test
public void testCollect4() {
Map<Status, Map<String, List<Employee2>>> collect = emps.stream()
.collect(Collectors.groupingBy(Employee2::getStatus, Collectors.groupingBy((e)->{
if(e.getAge() >= 60)
return "老年";
else if(e.getAge() >= 35)
return "中年";
else
return "成年";
})));
System.out.println(collect);
}
12)、Collectors.partitioningBy()
//多级分组
@Test
public void testCollect4() {
Map<Status, Map<String, List<Employee2>>> collect = emps.stream()
.collect(Collectors.groupingBy(Employee2::getStatus, Collectors.groupingBy((e)->{
if(e.getAge() >= 60)
return "老年";
else if(e.getAge() >= 35)
return "中年";
else
return "成年";
})));
System.out.println(collect);
}
13)、Collectors.joining()
//组接字符串
@Test
public void testCollect6() {
String collect = emps.stream()
.map((e)->e.getName())
.collect(Collectors.joining());
System.out.println(collect);
String collect3 = emps.stream()
.map(Employee2::getName)
.collect(Collectors.joining(","));
System.out.println(collect3);
String collect2 = emps.stream()
.map(Employee2::getName)
.collect(Collectors.joining(",", "prefix", "subfix"));
System.out.println(collect2);
}
@Test
public void testCollect7() {
Optional<Double> collect = emps.stream()
.map(Employee2::getSalary)
.collect(Collectors.reducing(Double::sum));
System.out.println(collect.get());
}
4 SpringCloud Alibaba -- 分布式组件
4.1 SpringCloud Alibaba-Nacos[作为注册中心]
4.1.1 下载
地址:https://github.com/alibaba/nacos/releases?page=3 下载这个版本的保持和老师教课一样,避免其他版本问题。
4.1.2 启动nacos:
-
双击bin 中的startup.cmd 文件
出现启动不了的问题:
-
访问http://localhost:8848/nacos/
-
使用默认的nacos/nacos 进行登录
4.1.3 将微服务注册到nacos中
- 首先,修改
gulimall-common
模块下的pom.xml 文件,引入Nacos Discovery Starter依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
-
在应用的
/src/main/resources/application.properties
配置文件中配置 Nacos Server 地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
ps:此处是作为服务注册发现中心的地址
-
使用
@EnableDiscoveryClient
开启服务注册发现功能
-
启动应用,观察nacos 服务列表是否已经注册上服务:
Nacos 使用三步
1、导依赖包 nacos-discovery
2、写配置,指定 nacos 地址,指定应用的名字
3、开启服务注册发现功能 @EnableDiscoveryClient
4.2 openfegin[远程调用]
声明式远程调用
feign是一个声明式的HTTP客户端,他的目的就是让远程调用更加简单。给远程服务发的是HTTP请求。
会员服务想要远程调用优惠券服务,只需要给会员服务里引入openfeign依赖,他就有了远程调用其他服务的能力。
本次以 gulimall-member 会员服务 远程调用 gulimall-coupon 优惠券服务 中的方法为例。
4.2.1 首先在 gulimall-coupon中的 CouponController 方法中写一个测试方法,以供调用。
/**
* 测试 open-feign
*/
@RequestMapping("/member/list")
public R memberCoupons(){ //全系统的所有返回都返回 R
//正常来说应该去数据库查用户对应的优惠券,这里简单测试,就直接返回一个优惠券给用户
CouponEntity couponEntity = new CouponEntity();
couponEntity.setCouponName("满100减10"); //优惠券的名字
return R.ok().put("coupons", Arrays.asList(couponEntity));
}
4.2.2 在 gulimall-member 中创建一个feign包,我们将feign接口都放在feign包中。
@FeifnClient注解:告诉springcloud这个接口是一个远程客户端,它要调用远程服务。
创建一个接口 CouponFeignService:
/*
* 这是一个声明式接口
*/
@FeignClient("gulimall-coupon")
public interface CouponFeignService {
/**
* 测试 open-feign
*/
@RequestMapping("/coupon/coupon/member/list")
public R memberCoupons();
}
1. 告诉 springcloud 这个接口是一个远程客户端,它要调用远程服务,调用远程的哪个服务
* 也即:告诉spring cloud这个接口是一个远程客户端,要调用coupon服务,
* 再去调用coupon服务/coupon/coupon/member/list对应的方法
2. nacos 中注册的服务名,但是服务下有很多功能,到底调用哪个呢?
* 这个时候就需要将具体功能的完整签名复制到接口中
* 即:@RequestMapping("/member/list")
* public R memberCoupons(); 注意地址需要写全,因为在controller类上有一个总的地址前缀: @RequestMapping("coupon/coupon")
* 所以在这个接口中的真实地址是: @RequestMapping("/coupon/coupon/member/list")
3. 加上@FeignClient("gulimall-coupon") 作用:当调用该接口时,就会去 @FeignClient 标注的微服务下找 @RequestMapping 标注的请求对应的方法;
4.2.3 在gulimall-member的MemberController方法中调用feign接口
@Autowired
private CouponFeignService couponFeignService;
/**
* 测试 open-feign
*/
@RequestMapping("/coupons")
public R test(){
MemberEntity memberEntity = new MemberEntity();
memberEntity.setNickname("张三");
R memberCoupons = couponFeignService.memberCoupons(); //假设张三去数据库查了后返回了张三的优惠券信息
//第一个 put 打印会员---从本地获取到的 ; put("member",memberEntity)
// 第二个 put从 coupons中 打印优惠券信息 -----memberCoupons.get("coupons") 远程调用
return R.ok().put("member",memberEntity).put("coupons",memberCoupons.get("coupons"));
}
4.2.4 在调用微服务gulimall-member中的启动类上加注解@EnableFeignClients
@EnableFeignClients(basePackages = "com.atguigu.gulimall.member.feign") //自动扫描包下的feign接口
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallMemberApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallMemberApplication.class, args);
}
}
* 1、开启feign客户端的远程调用功能 @EnableFeignClients ,还要设置调用哪个包下的feign,进而当容器启动的时候,就会自动扫描包下面的feign接口,feign接口上说明了 哪个微服务,进而调用具体微服务下的具体的请求。
4.2.5 启动这两个微服务。
记住:
- 遇到的坑-1:SpringCloud启动报错
Did you forget to include spring-cloud-starter-loadbalancer
添加匹配当前OpenFeign的负载均衡依赖
由于SpringCloud Feign在Hoxton.M2 RELEASED版本之后不再使用Ribbon而是使用spring-cloud-loadbalancer,所以不引入spring-cloud-loadbalancer会报错
解决方法 : 加入spring-cloud-loadbalancer依赖, 并且在nacos中排除ribbon依赖,不然loadbalancer无效
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<version>3.1.1</version>
</dependency>
gulimall-comon中添加以上注解解决问题。
-
遇到的坑 -2:
解决办法:
注意:
以上遇到的坑,均是版本问题导致,后续将会将版本和老师课件版本统一,避免遇到各种类似的bug问题出现。
再次进行测试,如下图,测试成功。
Feign 使用三步
1、导包openfeign
2、开启@EnableFeignClients 功能 :自动扫描feign
3、编写接口,进行远程调用:feign接口上开启@FeignClient:进行远程调用服务,具体到某一个微服务
4.3 SpringCloud Alibaba-Nacos[作为配置中心]
4.3.1 gulimall-common模块的 pom.xml 引入Nacos Config Starter依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
4.3.2 在 gulimall-coupon 的resources/bootstrap.properties 配置文件中配置Nacos Config 元数据
#bootstrap.properties ,这个文件是
#springboot里规定的,他优先级别application.properties高
#改名字,对应nacos里的配置文件名
spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
4.3.3 在nacos 中添加配置
在 nacos 中创建一个 应用名.properties
配置文件并编写配置(做到一处修改,处处生效):
测试:
@RestController
@RequestMapping("coupon/coupon")
public class CouponController {
@Autowired
private CouponService couponService;
//@Value 读取配置文件中的值
@Value("${coupon.user.name}")
private String name;
@Value("${coupon.user.age}")
private String age;
/**
* 测试配置中心
*/
@RequestMapping("/test")
public R test(){
return R.ok().put("name",name).put("age",age);
}
![](F:\JAVA listen\尚硅谷Java学科全套教程(总207.77GB)\谷粒商城\谷粒商城之分布式基础\img108.png)
但是修改怎么办?实际生产中不能重启应用。在coupon的控制层上添加动态刷新功能的注解:@RefreshScope
该注解支持文件修改后动态刷新功能。
最终代码效果:
@RefreshScope //打开动态刷新功能
@RestController
@RequestMapping("coupon/coupon")
public class CouponController {
@Autowired
private CouponService couponService;
//@Value 读取配置文件中的值
@Value("${coupon.user.name}")
private String name;
@Value("${coupon.user.age}")
private String age;
/**
* 测试配置中心
*/
@RequestMapping("/test")
public R test(){
return R.ok().put("name",name).put("age",age);
}
重启后,在nacos浏览器里修改配置,修改就可以观察到能动态修改了
nacos的配置内容优先于项目本地的配置内容。
小总结:
/**
* 1、如何使用nacos作为配置中心统一管理配置
*
* 1)、引入依赖:
* <dependency>
* <groupId>com.alibaba.cloud</groupId>
* <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
* </dependency>
* 2)、创建一个 bootstrap.properties
* spring.application.name=gulimall-coupon
* spring.cloud.nacos.config.server-addr=127.0.0.1:8848
*3)、需要给配置中心默认添加一个叫 数据集(Data Id) gulimall-coupon.properties:默认规则:应用名.properties
*4)、给 应用名.properties 添加任何配置
*5)、动态获取配置:
* @RefreshScope:动态获取并刷新配置
* @Value("${配置项的名}"):获取到配置。
* 如果配置中心和当前应用的配置文件中都配置了相同的项,优先使用配置中心的配置。
*/
知识点回顾:
Nacos Config 数据结构
Nacos Config 主要通过dataId 和group 来唯一确定一条配置。
Nacos Client 从Nacos Server 端获取数据时,调用的是此接口ConfigService.getConfig(String
dataId, String group, long timeoutMs)。Spring Cloud 应用获取数据
dataID:
在Nacos Config Starter 中,dataId 的拼接格式如下- ${prefix} - ${spring.profiles.active} . ${file-extension} prefix 默认为spring.application.name
的值,也可以通过配置项spring.cloud.nacos.config.prefix 来配置。-
- spring.profiles.active 即为当前环境对应的profile
注意,当activeprofile 为空时,对应的连接符- 也将不存在,dataId 的拼接格式变成
${prefix}.${file-extension}
file-extension 为配置内容的数据格式,可以通过配置项
spring.cloud.nacos.config.file-extension 来配置。目前只支持properties 类型Group:
Group 默认为 DEFAULT_GROUP ,可以通过spring.cloud.nacos.config.group 配置。
4.3.4 测试中遇到的坑
- 坑,启动报错:
- 踩坑 :配置中心不起作用,真的是这个问题。
4.4 进阶
4.4.1 核心概念
4.4.2 原理
4.4.3 配置文件整合
命名空间:用作配置隔离。(一般每个微服务一个命名空间)
我们将微服务中的配置文件进行整合。就是在nacos中每一个微服务都有自己的命名空间。每一个微服务的命名空间下有很多个配置列表,这些配置将我们原本在java本地的代码放到注册中心中,然后我们使用boostrap.properties来对命名空间进行设置。具体还可以进行分组。dev prod test这三组。
每个微服务的命名空间区分:
命名空间下开发、测试、生产分组:
ps:
* 2、细节
* 1)、命名空间:配置隔离:
* 默认:public(保留空间):默认新增的所有配置都在public空间。
* 1、开发,测试,生产:利用命名空间来做环境隔离。----基于环境隔离
* 注意:在bootstrap.properties:配置上,需要使用哪个命名空间下的配置。
* spring.cloud.nacos.config.namespace=ee95e5d5-8928-448d-b038-e09b3792c308
* 2、每一个微服务之间相互隔离配置,每一个微服务都创建自己的命名空间,只加载自己命名空间下的所有配置--基于微服务隔离
* 2)、配置集:所有的配置的集合
*
* 3)、配置集ID:类似文件名
* Data ID:类似文件名
*
* 4)、配置分组
* 默认所有的配置集都属于:DEFAULT_GROUP;
* 1111,618,1212 或者 dev test prop
* 项目中的使用:每个微服务创建自己的命名空间,使用配置分组区分环境,dev,test,prop
*
* 3、同时加载多个配置集
* 1)、微服务任何配置信息,任何配置文件都可以放在配置中心中
* 2)、只需要在 bootstrap.properties 说明加载配置中心中哪些配置文件即可
* 3)、@Value,@ConfigurationProperties。。。。都可以获取
* 以前SpringBoot任何方法从配置文件中获取值,都能使用。
* 配置中心有的优先使用配置中心的,没有再使用配置文件中的配置。
注意如果在注册中心中找不到就去找默认的,默认的在注册中心中也找不到的时候,就会去找本地的。
最终在 bootstrap.properties 中:
#命名空间
spring.cloud.nacos.config.namespace=8496c545-7972-474a-8d7b-faa0bc46f753
#分组
spring.cloud.nacos.config.group=dev
#测试配置集,下面的这种新版本已经不推荐使用了
spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[0].group=dev
spring.cloud.nacos.config.ext-config[0].refresh=true
spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml
spring.cloud.nacos.config.ext-config[1].group=dev
spring.cloud.nacos.config.ext-config[1].refresh=true
spring.cloud.nacos.config.ext-config[2].data-id=other.yml
spring.cloud.nacos.config.ext-config[2].group=dev
spring.cloud.nacos.config.ext-config[2].refresh=true
相应的,nacos中需要创建相应的配置文件
这样配置后,application.yml 中的配置就可以注释掉,效果
spring:
# datasource:
# username: root
# password: root
# url: jdbc:mysql://192.168.56.10:3306/gulimall_sms
# driver-class-name: com.mysql.jdbc.Driver
# cloud:
# nacos:
# discovery:
# server-addr: 127.0.0.1:8848
##
## application:
## name: gulimall-coupon #不设置服务名字的话,nacos服务注册不了,也就发现不了
##
##
##
### sql映射文件位置
##mybatis-plus:
## mapper-locations: classpath:/mapper/**/*.xml #classpath*中带有* 表示其他第三jar包中都会扫描,这里我们去掉*,精确扫描自己包下的
## global-config:
## db-config:
## id-type: auto #主键自增
##
##server:
## port: 7000
测试结果:
也就是说,可以通过配置数据集的方式将 application.yml文件中的配置搬运到 nacos中。
4.5 前面遇到的坑的究极问题-------版本问题
突然发现前面为什么和老师一样的代码会报红了,原来是因为springcloud版本不一样导致。怪不得会莫名其妙的报错,为了以后能少点报错,将springboot和springcloud的版本都换成和老师一样的版本吧。
- 之前修改过版本需要重新降级(之前版本没有修改和老师一样,所以有些依赖版本修改了):
4.5.1 依赖没写错,更改版本仍然报错的解决方法---- 清除缓存
4.5.2 版本切换后测试
- 对于以前踩的坑的解决办法引入的在老师之外的依赖,尝试进行注释掉,测试能否正常运行。
- http://localhost:8000/member/member/coupons
修改之后进行重新测试,并且将前面遇到的坑的解决加入的依赖都给注释掉,发现仍然正常运行,即版本正确。
4.6 springcloud ----- gateway---网关
4.6.1 简介
网关作为流量的入口,常用功能包括路由转发、权限校验、限流控制等。而springcloud gateway作为SpringCloud 官方推出的第二代网关框架,取代了Zuul 网关。
官网文档地址:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.3.RELEASE/single/spring-cloud-gateway.html
4.6.2 核心概念
4.6.3 使用
- 创建 gulimall-gateway 网关微服务
- 加入gateway依赖
- 编写网关配置文件
首先也要将其注册到 nacos 注册中心,并且为其创建单独的 命名空间 及 端口号 。
-
断言(Predicates)
-
过滤器
-
application.properties ---- 配置服务注册发现即 配置nacos注册中心地址
# 因为导入了gulicommon公共依赖,进而就导入了nacos,想要启用nacos的服务注册发现功能: # 首先得启动, # 然后在 application.properties中配置nacos服务地址及服务名字---切记是 nacos.discovery.server-addr spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 spring.application.name=gulimall-gateway server.port=88
-
bootstrap.properties----配置nacos配置中心
#因为 bootstrap.properties 加载顺序优先 application.properties,所以服务一启动我们需要知道项目的名字 spring.application.name=gulimall-gateway # 因为导入了gulicommon公共依赖,进而就导入了nacos,想要启用nacos的配置中心管理功能: # 首先得启动, # 然后在 application.properties中配置nacos配置中心服务地址及名称空间(一般我们习惯每一个项目都有自己的名称空间) # ---切记是 nacos.config.server-addr spring.cloud.nacos.config.server-addr=127.0.0.1:8848 spring.cloud.nac
-
application.yml
# 在 yml 配置文件中配置,可以很方便的让我们在 项目上线后将配置直接转移到配置中心 spring: cloud: gateway: routes: - id: test_route uri: https://www.baidu.com predicates: - Query=url,baidu - id: qq_route uri: https://www.qq.com predicates: - Query=url,qq
-
nacos里创建命名空间gateway,然后在命名空间里创建文件guilmall-gateway.yml
spring: application: name: gulimall-gateway
id:路由的id,没有固定规则但要求唯一
uri:匹配后提供服务的路由地址
path:断言,路径相匹配的进行路由
Query:查询相匹配的进行路由
如果我们在地址栏中访问的地址有qq,或者baidu就会进行转发。
- 启动测试
测试报错:
-
网关启动报错 坑:
Error creating bean with name 'routeDefinitionRouteLocator' defined in class path resource [org/springframework/cloud/gateway/config/GatewayAutoConfiguration.class]: Unsatisfied dependency expressed through method 'routeDefinitionRouteLocator' parameter 4; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.core.convert.ConversionService' available: expe
-
网上搜索到的解决办法,我采用的是排除web内置容器的解决办法。
-
解决办法:
1)解决办法1
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- Maven整个生命周期内排除内置容器,排除内置容器导出成war包可以让外部容器运行spring-boot项目-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
2)解决办法2 : 使用 spring-webflux 模块,webflux 有一个全新的非堵塞的函数式 Reactive Web 框架,可以用来构建异步的、非堵塞的、事件驱动的服务
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
最后主启动类还要排除数据源:
@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class,DataSourceAutoConfiguration.class})
排除的原因是我们将gateway也引入了gulimall-common,而这里面又有数据源配置(mybatis-plus),启动找不到对应的配置就会报错。解决办法就是排除数据源。
否则会报以下错误:
最后成功启动项目
5 前端
![img](F:\JAVA listen\尚硅谷Java学科全套教程(总207.77GB)\谷粒商城\谷粒商城之分布式基础\20210211175651243.png)
5.1 VSCode 使用
1、安装常用插件 见 3.5.2 Idea&VsCode 章节
如果不想手动保存可以选择以下方法:
前端中Live Server 不起作用是因为需要设置自动保存代码或者是自己修改后手动保存才会热更新。
在设置中找到 autosave
选择
这个即可,代码会自动保存,热更新插件会成功运行。
5.2 ES6
5.2.1 简介
5.2.2 什么是ECMAScript
5.2.3 ES6 新特性
快捷键:
shift + ! --- 快速生成 html 模板
代码格式化:shift + alt + f
放大或缩小: ctrl + ctrl -
1、 let 声明变量
- let声明后不能作用于{}外,var可以
- let只能声明一次,var可以声明多次
- var会变量提升(使用在定义之前),let必须先定义再使用
// var 声明的变量往往会越域
// let 声明的变量有严格局部作用域
{
var a = 1;
let b = 2;
}
console.log(a); // 1
console.log(b); // ReferenceError: b is not defined
// var 可以声明多次
// let 只能声明一次
var m = 1
var m = 2
let n = 3
// let n = 4
console.log(m) // 2
console.log(n) // Identifier 'n' has already been declared
// var 会变量提升
// let 不存在变量提升
console.log(x); // undefined
var x = 10;
console.log(y); //ReferenceError: y is not defined
let y = 20;
2、 const 声明常量(只读变量)
- const一旦初始化后,不能改变
// 1. 声明之后不允许改变
// 2. 一但声明必须初始化,否则会报错
const a = 1;
a = 3; //Uncaught TypeError: Assignment to constant variable.
3、解构表达式
- 数组解构 let arr = [1,2,3]; let [a,b,c] = arr
- 对象解构 const{name:abc, age, language} = person 其中 name:abc代表把 name 改名为 abc
1)、数组解构
let arr = [1,2,3];
//以前我们想获取其中的值,只能通过角标。ES6 可以这样:
const [x,y,z] = arr;// x,y,z 将与arr 中的每个位置对应来取值
// 然后打印
console.log(x,y,z);
2)、对象解构
const person = {
name: "jack",
age: 21,
language: ['java', 'js', 'css']
}
// 解构表达式获取值,将person 里面每一个属性和左边对应赋值
const { name, age, language } = person;
// 等价于下面
// const name = person.name;
// const age = person.age;
// const language = person.language;
// 可以分别打印
console.log(name);
console.log(age);
console.log(language);
//扩展:如果想要将name 的值赋值给其他变量,可以如下,nn 是新的变量名
const { name: nn, age, language } = person;
console.log(nn);
console.log(age);
console.log(language);
4、字符串扩展
- 字符串函数 str.startsWith();str.endsWith();str.includes();str.includes()
- 字符串模板,``支持一个字符串定义为多行
- 占位符功能 ${}
1)、几个新的API
ES6 为字符串扩展了几个新的API:
includes()
:返回布尔值,表示是否找到了参数字符串。startsWith()
:返回布尔值,表示参数字符串是否在原字符串的头部。endsWith()
:返回布尔值,表示参数字符串是否在原字符串的尾部。
let str = "hello.vue";
console.log(str.startsWith("hello"));//true
console.log(str.endsWith(".vue"));//true
console.log(str.includes("e"));//true
console.log(str.includes("hello"));//true
2)、字符串模板
模板字符串相当于加强版的字符串,用反引号`,除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量和表达式。
// 1、多行字符串
let ss = `
<div>
<span>hello world<span>
</div>
`
console.log(ss)
// 2、字符串插入变量和表达式。变量名写在${} 中,${} 中可以放
入JavaScript 表达式。
let name = "张三";
let age = 18;
let info = `我是${name},今年${age}了`;
console.log(info)
// 3、字符串中调用函数
function fun() {
return "这是一个函数"
}
let sss = `O(∩_∩)O 哈哈~,${fun()}`;
console.log(sss); // O(∩_∩)O 哈哈~,这是一个函数
5、函数优化
- 支持函数形参默认值 function add(a, b = 1){}
- 支持不定参数 function fun(...values){}
- 支持箭头函数 var print = obj => console.log(obj);
- 支持箭头函数+解构函数 var hello2 = ({name}) => console.log("hello," +name); hello2(person);
1)、函数参数默认值
//在ES6 以前,我们无法给一个函数参数设置默认值,只能采用变通写法:
function add(a, b) {
// 判断b 是否为空,为空就给默认值1
b = b || 1;
return a + b;
}
// 传一个参数
console.log(add(10));
//现在可以这么写:直接给参数写上默认值,没传就会自动使用默认值
function add2(a , b = 1) {
return a + b;
}
// 传一个参数
console.log(add2(10));
2)、不定参数
不定参数用来表示不确定参数个数,形如,...变量名,由...加上一个具名参数标识符组成。具名参数只能放在参数列表的最后,并且有且只有一个不定参数。
function fun(...values) {
console.log(values.length)
}
fun(1, 2) //2
fun(1, 2, 3, 4) //4
3)、箭头函数
ES6 中定义函数的简写方式:
- 一个参数时:
//以前声明一个方法
// var print = function (obj) {
// console.log(obj);
// }
// 可以简写为:
var print = obj => console.log(obj);
// 测试调用
print(100);
- 多个参数:
// 两个参数的情况:
var sum = function (a, b) {
return a + b;
}
// 简写为:
//当只有一行语句,并且需要返回结果时,可以省略{} , 结果会自动返回。
var sum2 = (a, b) => a + b;
//测试调用
console.log(sum2(10, 10));//20
// 代码不止一行,可以用`{}`括起来
var sum3 = (a, b) => {
c = a + b;
return c;
};
//测试调用
console.log(sum3(10, 20));//30
4)、实战:箭头函数结合解构表达式
//需求,声明一个对象,hello 方法需要对象的个别属性
//以前的方式:
const person = {
name: "jack",
age: 21,
language: ['java', 'js', 'css']
}
function hello(person) {
console.log("hello," + person.name)
}
//现在的方式
var hello2 = ({ name }) => { console.log("hello," + name) };
//测试
hello2(person);
6、对象优化
- 可以获取 map的键值对 Object.keys()、Object.values、Object.entries
- Object.assgn(target,source1,source2) 合并source1,source2到target
- 支持对象名声明简写:如果属性名和属性值的变量名相同可以省略
- let someone = {...person}取出person对象所有的属性拷贝到当前对象
1)、新增的API
ES6 给Object 拓展了许多新的方法,如:
- keys(obj):获取对象的所有key 形成的数组
- values(obj):获取对象的所有value 形成的数组
- entries(obj):获取对象的所有key 和value 形成的二维数组。格式:
[[k1,v1],[k2,v2],...]
- assign(dest, ...src) :将多个src 对象的值拷贝到dest 中。(第一层为深拷贝,第二层为浅
拷贝)
const person = {
name: "jack",
age: 21,
language: ['java', 'js', 'css']
}
console.log(Object.keys(person));//["name", "age", "language"]
console.log(Object.values(person));//["jack", 21, Array(3)]
console.log(Object.entries(person));//[Array(2), Array(2), Array(2)]
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
//Object.assign 方法的第一个参数是目标对象,后面的参数都是源对象。
Object.assign(target, source1, source2);
console.log(target)//{a: 1, b: 2, c: 3}
2)、声明对象简写
const age = 23
const name = "张三"
// 传统
const person1 = { age: age, name: name }
console.log(person1)
// ES6:属性名和属性值变量名一样,可以省略
const person2 = { age, name }
console.log(person2) //{age: 23, name: "张三"}
3)、对象的函数属性简写
let person = {
name: "jack",
// 以前:
eat: function (food) {
console.log(this.name + "在吃" + food);
},
// 箭头函数版:这里拿不到this
eat2: food => console.log(person.name + "在吃" + food),
// 简写版:
eat3(food) {
console.log(this.name + "在吃" + food);
}
}
person.eat("apple");
4)、对象拓展运算符
拓展运算符(...)用于取出参数对象所有可遍历属性然后拷贝到当前对象。
// 1、拷贝对象(深拷贝)
let person1 = { name: "Amy", age: 15 }
let someone = { ...person1 }
console.log(someone) //{name: "Amy", age: 15}
// 2、合并对象
let age = { age: 15 }
let name = { name: "Amy" }
let person2 = { ...age, ...name } //如果两个对象的字段名重复,后面对象字
段值会覆盖前面对象的字段值
console.log(person2) //{age: 15, name: "Amy"}
7、map 和reduce
数组中新增了map 和reduce 方法。
arr.map()
接收一个函数,将arr中的所有元素用接收到的函数处理后放入新的数组arr.reduce()
为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素
1)、map
map():接收一个函数,将原数组中的所有元素用这个函数处理后放入新数组返回。
let arr = ['1', '20', '-5', '3'];
console.log(arr)
arr = arr.map(s => parseInt(s));
console.log(arr)
2)、reduce
语法:
arr.reduce(callback,[initialValue])
reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元
素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调
用reduce 的数组。
callback (执行数组中每个值的函数,包含四个参数)
1、previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
2、currentValue (数组中当前被处理的元素)
3、index (当前元素在数组中的索引)
4、array (调用reduce 的数组)
initialValue (作为第一次调用callback 的第一个参数。)
示例:
const arr = [1,20,-5,3];
//没有初始值:
console.log(arr.reduce((a,b)=>a+b));//19
console.log(arr.reduce((a,b)=>a*b));//-300
//指定初始值:
console.log(arr.reduce((a,b)=>a+b,1));//20
console.log(arr.reduce((a,b)=>a*b,0));//-0
8、Promise
在JavaScript 的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现。一旦有一连串的ajax 请求a,b,c,d... 后面的请求依赖前面的请求结果,就需要层层嵌套。这种缩进和层层嵌套的方式,非常容易造成上下文代码混乱,我们不得不非常小心翼翼处理内层函数与外层函数的数据,一旦内层函数使用了上层函数的变量,这种混乱程度就会加剧......总之,这种层叠上下文
的层层嵌套方式,着实增加了神经的紧张程度。
使用 promise 之后可以:
- 优化异步操作。封装ajax
- 把Ajax封装到Promise中,赋值给let p
- 在Ajax中成功使用resolve(data),失败使用reject(err)
- p.then().catch()
案例:用户登录,并展示该用户的各科成绩。在页面发送两次请求:
- 查询用户,查询成功说明可以登录
- 查询用户成功,查询科目
- 根据科目的查询结果,获取去成绩
分析:此时后台应该提供三个接口,一个提供用户查询接口,一个提供科目的接口,一个提供各科成绩的接口,为了渲染方便,最好响应json 数据。在这里就不编写后台接口了,而是提供三个json 文件,直接提供json 数据,模拟后台接口:
user.json:
{
"id": 1,
"name": "zhangsan",
"password": "123456"
}
user_corse_1.json:
{
"id": 10,
"name": "chinese"
}
corse_score_10.json:
{
"id": 100,
"score": 90
}
//回调函数嵌套的噩梦:层层嵌套。
$.ajax({
url: "mock/user.json",
success(data) {
console.log("查询用户:", data);
$.ajax({
url: `mock/user_corse_${data.id}.json`,
success(data) {
console.log("查询到课程:", data);
$.ajax({
url: `mock/corse_score_${data.id}.json`,
success(data) {
console.log("查询到分数:", data);
},
error(error) {
console.log("出现异常了:" + error);
}
});
},
error(error) {
console.log("出现异常了:" + error);
}
});
},
error(error) {
console.log("出现异常了:" + error);
}
});
我们可以通过Promise 解决以上问题。
1)、Promise 语法
const promise = new Promise(function (resolve, reject) {
// 执行异步操作
if (/* 异步操作成功*/) {
resolve(value);// 调用resolve,代表Promise 将返回成功的结果
} else {
reject(error);// 调用reject,代表Promise 会返回失败结果
}
});
使用箭头函数可以简写为:
const promise = new Promise((resolve, reject) => {
// 执行异步操作
if (/* 异步操作成功*/) {
resolve(value);// 调用resolve,代表Promise 将返回成功的结果
} else {
reject(error);// 调用reject,代表Promise 会返回失败结果
}
});
这样,在promise 中就封装了一段异步执行的结果。
2)、处理异步结果
如果我们想要等待异步执行完成,做一些事情,我们可以通过promise 的then 方法来实现。
如果想要处理promise 异步执行失败的事件,还可以跟上catch:
promise.then(function (value) {
// 异步执行成功后的回调
}).catch(function (error) {
// 异步执行失败后的回调
})
3)、Promise 改造以前嵌套方式
new Promise((resolve, reject) => {
$.ajax({
url: "mock/user.json",
success(data) {
console.log("查询用户:", data);
resolve(data.id);
},
error(error) {
console.log("出现异常了:" + error);
}
});
}).then((userId) => {
return new Promise((resolve, reject) => {
$.ajax({
url: `mock/user_corse_${userId}.json`,
success(data) {
console.log("查询到课程:", data);
resolve(data.id);
},
error(error) {
console.log("出现异常了:" + error);
}
});
});
}).then((corseId) => {
console.log(corseId);
$.ajax({
url: `mock/corse_score_${corseId}.json`,
success(data) {
console.log("查询到分数:", data);
},
error(error) {
console.log("出现异常了:" + error);
}
});
});
4)、优化处理
优化:通常在企业开发中,会把promise 封装成通用方法,如下:封装了一个通用的get 请求方法
function get(url, data) { // 实际开发中会单独放到common.js 中
return new Promise((resolve, reject) => {
$.ajax({
url: url,
data: data,
success: function (data) {
resolve(data);
},
error: function (err) {
reject(err)
}
})
});
}
get("mock/user.json")
.then((data) => {
console.log("用户查询成功~~~:", data)
return get(`mock/user_corse_${data.id}.json`);
})
.then((data) => {
console.log("课程查询成功~~~:", data)
return get(`mock/corse_score_${data.id}.json`);
})
.then((data)=>{
console.log("课程成绩查询成功~~~:", data)
})
.catch((err)=>{ //失败的话catch
console.log("出现异常",err)
});
通过比较,我们知道了Promise 的扁平化设计理念,也领略了这种上层设计
带来的好处。
我们的项目中会使用到这种异步处理的方式;
9、模块化
1)、什么是模块化
模块化就是把代码进行拆分,方便重复利用。类似java 中的导包:要使用一个包,必须先
导包。而 JS 中没有包的概念,换来的是模块。
模块功能主要由两个命令构成:export
和import
。
export
用于规定模块的对外接口,export
不仅可以导出对象,一切JS变量都可以导出。比如:基本类型变量、函数、数组、对象import
用于导入其他模块提供的功能
2)、export
比如我定义一个js 文件:hello.js,里面有一个对象:
const util = {
sum(a, b) {
return a + b;
}
}
我可以使用export 将这个对象导出:
const util = {
sum(a, b) {
return a + b;
}
}
export { util };
当然,也可以简写为:
export const util = {
sum(a, b) {
return a + b;
}
}
export
不仅可以导出对象,一切JS 变量都可以导出。比如:基本类型变量、函数、数组、
对象。
当要导出多个值时,还可以简写。比如我有一个文件:user.js:
var name = "jack"
var age = 21
export {name,age}
省略名称
上面的导出代码中,都明确指定了导出的变量名,这样其它人在导入使用时就必须准确写出
变量名,否则就会出错。
因此js 提供了default
关键字,可以对导出的变量名进行省略
例如:
// 无需声明对象的名字
export default {
sum(a, b) {
return a + b;
}
}
这样,当使用者导入时,可以任意起名字。
3)、import
使用export
命令定义了模块的对外接口以后,其他JS 文件就可以通过import
命令加载这个模块。
例如我要使用上面导出的util:
// 导入util
import util from 'hello.js'
// 调用util 中的属性
util.sum(1,2)
要批量导入前面导出的name 和age:
import {name, age} from 'user.js'
console.log(name + " , 今年"+ age +"岁了")
但是上面的代码暂时无法测试,因为浏览器目前还不支持ES6 的导入和导出功能。除非借助于工具,把ES6 的语法进行编译降级到ES5,比如Babel-cli
工具我们暂时不做测试,大家了解即可。
5.3 Node.js
前端开发,少不了node.js;Node.js 是一个基于Chrome V8 引擎的JavaScript 运行环境。
Node.js的官方文档: http://nodejs.cn/api/
我们关注与node.js 的npm 功能就行;
NPM 是随同 NodeJS 一起安装的包管理工具,JavaScript-NPM,Java-Maven;
1)、官网下载安装 node.js ,并使用node -v 检查版本
2)、配置 npm 使用淘宝镜像
npm config set registry http://registry.npm.taobao.org/
3)、大家如果npm install 安装依赖出现chromedriver 之类问题,先在项目里运行下面命令
npm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver
然后再运行 npm install
5.4 Vue
1、MVVM 思想
- M:即Model,模型,包括数据和一些基本操作
- V:即View,视图,页面渲染结果
- VM:即View-Model,模型与视图间的双向操作(无需开发人员干涉)
在MVVM 之前,开发人员从后端获取需要的数据模型,然后要通过DOM 操作 Model 渲染到 View 中。而后当用户操作视图,我们还需要通过 DOM 获取 View 中的数据,然后同步到 Model 中。
而MVVM 中的 VM 要做的事情就是把DOM 操作完全封装起来,开发人员不用再关心Model和View 之间是如何互相影响的:
- 只要我们 Model 发生了改变,View 上自然就会表现出来。
- 当用户修改了 View,Model 中的数据也会跟着改变。
把开发人员从繁琐的DOM 操作中解放出来,把关注点放在如何操作Model 上。
2、Vue 简介
Vue (读音/vjuː/,类似于view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
官网:https://cn.vuejs.org/
参考:https://cn.vuejs.org/v2/guide/
Git 地址:https://github.com/vuejs
尤雨溪,Vue.js 创作者,Vue Technology 创始人,致力于Vue 的研究开发。
3、入门案例
1)、安装
官网文档提供了3 种安装方式:
- 直接script 引入本地vue 文件。需要通过官网下载vue 文件。
- 通过script 引入CDN 代理。需要联网,生产环境可以使用这种方式
- 通过npm 安装。这种方式也是官网推荐的方式,需要nodejs 环境。
本课程就采用第三种方式
注意:
这里没有vue.js文件的,vue 版本错了,npm install vue@2 重装一下,npm 默认装的是vue3.0
2)、创建示例项目
1、新建文件夹 vue2 ,并使用 vscode 打开
2、使用 vscode 控制台终端,npm install -y;
项目会生成 package-lock.json 文件,类似于 maven 项目的 pom.xml 文件。
3、使用 npm install vue,给项目安装 vue;项目下会多 node_modules 目录,并且在下面有
一个vue 目录。
3)、HelloWorld
在hello.html 中,我们编写一段简单的代码。
h2 中要输出一句话:xx 非常帅
。前面的xx
是要渲染的数据。
4)、vue 声明式渲染
页面代码
<div id="app">
<h1>{{name}},非常帅!!!</h1>
</div>
<script src="./node_modules/vue/dist/vue.min.js"></script>
<script>
let vm = new Vue({
el: "#app",
data: {
name: "张三"
}
});
</script>
</body>
5)、双向绑定
我们对刚才的案例进行简单修改:
<div id="app">
<input type="text" v-model="num">
<h2>
{{name}},非常帅!!!有{{num}}个人为他点赞。
</h2>
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
// 创建vue 实例
let app = new Vue({
el: "#app", // el 即element,该vue 实例要渲染的页面元素
data: { // 渲染页面需要的数据
name: "张三",
num: 5
}
});
</script>
6)、事件处理
给页面添加一个按钮:
<body>
<div id="app">
<input type="text" v-model="num">
<button v-on:click="num++">点赞</button>
<h2>
{{name}},非常帅!!!有{{num}}个人为他点赞。
</h2>
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
// 创建vue 实例
let app = new Vue({
el: "#app", // el 即element,该vue 实例要渲染的页面元素
data: { // 渲染页面需要的数据
name: "张三",
num: 5
}
});
</script>
</body>
简单使用总结:
1)、使用Vue 实例管理DOM
2)、DOM 与数据/事件等进行相关绑定
3)、我们只需要关注数据,事件等处理,无需关心视图如何进行修改
4、概念
1、创建Vue 实例
每个Vue 应用都是通过用Vue 函数创建一个新的Vue 实例开始的:
let app = new Vue({
});
在构造函数中传入一个对象,并且在对象中声明各种Vue 需要的数据和方法,包括:
- el
- data
- methods
- ........
2、模板或元素
每个Vue 实例都需要关联一段 Html 模板,Vue 会基于此模板进行视图渲染。
我们可以通过 el 属性来指定。
例如一段html 模板:
<div id="app">
</div>
然后创建Vue 实例,关联这个div
let vm = new Vue({
el: "#app"
})
这样,Vue 就可以基于id 为app
的 div 元素作为模板进行渲染了。在这个div 范围以外的部分是无法使用vue 特性的。
3、数据
当 Vue 实例被创建时,它会尝试获取在data 中定义的所有属性,用于视图的渲染,并且监视 data 中的属性变化, 当data 发生改变,所有相关的视图都将重新渲染,这就是“响应式“系统。
html:
<div id="app">
<input type="text" v-model="name" />
</div>
JS:
let vm = new Vue({
el: "#app",
data: {
name: "刘德华"
}
})
- name 的变化会影响到
input
的值 - input 中输入的值,也会导致vm 中的name 发生改变
4、方法
Vue 实例中除了可以定义data 属性,也可以定义方法,并且在Vue 实例的作用范围内使用。
Html:
<div id="app">
{{num}}
<button v-on:click="add">加</button>
</div>
JS:
let vm = new Vue({
el: "#app",
data: {
num: 0
},
methods: {
add: function () {
// this 代表的当前vue 实例
this.num++;
}
}
});
5、安装vue-devtools 方便调试
- 将软件包中的vue-devtools 解压。
- 打开chrome 设置 -> 扩展程序
![1667657583740](F:\JAVA listen\尚硅谷Java学科全套教程(总207.77GB)\谷粒商城\谷粒商城之分布式基础\1667657583740.png)
-
开启开发者模式,并加载插件
-
![1667657662564](F:\JAVA listen\尚硅谷Java学科全套教程(总207.77GB)\谷粒商城\谷粒商城之分布式基础\1667657662564.png)
-
打开浏览器控制台,选择vue
![1667657703789](F:\JAVA listen\尚硅谷Java学科全套教程(总207.77GB)\谷粒商城\谷粒商城之分布式基础\1667657703789.png)
6、安装vscode 的vue 插件
安装这个插件就可以有语法提示
5、指令
什么是指令?
-
指令(Directives) 是带有
v-
前缀的特殊特性。 -
指令特性的预期值是:单个JavaScript 表达式。
-
指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于DOM
例如我们在入门案例中的v-on,代表绑定事件。
1、插值表达式
1)、花括号
格式:{{ 表达式 }}
说明:
- 该表达式支持JS 语法,可以调用js 内置函数(必须有返回值)
- 表达式必须有返回结果。例如1 + 1,没有结果的表达式不允许使用,如:let a = 1 + 1;
- 可以直接获取Vue 实例中定义的数据或函数
2)、插值闪烁
使用 {{}} 方式在网速较慢时会出现问题。在数据未加载完成时,页面会显示出原始的{{}}
,加载完毕后才显示正确数据,我们称为插值闪烁。
我们将网速调慢一些,然后刷新页面,试试看刚才的案例:
3)、v-text 和v-html
可以使用v-text 和v-html 指令来替代{{}}
说明:
- v-text:将数据输出到元素内部,如果输出的数据有HTML 代码,会作为普通文本输出
- v-html:将数据输出到元素内部,如果输出的数据有HTML 代码,会被渲染
示例:
<div id="app">
v-text:<span v-text="hello"></span> <br />
v-html:<span v-html="hello"></span>
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el: "#app",
data: {
hello: "<h1>大家好</h1>"
}
})
</script>
效果:
并且不会出现插值闪烁,当没有数据时,会显示空白或者默认数据。
2、单向绑定v-bind
花括号只能写在标签体内(<div 标签内> 标签体 ),不能用在标签内。
插值表达式只能用在标签体里,如果我们这么用是不起作用的,所以要用 v-bind
html 属性不能使用双大括号形式绑定,我们使用v-bind 指令给HTML 标签属性绑定值;
而且在将v-bind
用于class
和style
时,Vue.js 做了专门的增强。
1)、绑定class
<div class="static" v-bind:class="{ active: isActive, 'text-danger'
: hasError }">
</div>
<script>
let vm = new Vue({
el: "#app",
data: {
isActive: true,
hasError: false
}
})
</script>
2)、绑定style
v-bind:style
的对象语法十分直观,看着非常像CSS,但其实是一个JavaScript 对象。style属性名可以用驼峰式(camelCase) 或短横线分隔(kebab-case,这种方式记得用单引号括起来) 来命名。
例如:font-size-->fontSize
<div id="app" v-bind:style="{ color: activeColor, fontSize: fontSiz
e + 'px' }"></div>
<script>
let vm = new Vue({
el: "#app",
data: {
activeColor: 'red',
fontSize: 30
}
})
</script>
结果:
3)、绑定其他任意属性
<div id="app" v-bind:style="{ color: activeColor, fontSize: fontS
ize + 'px' }" v-bind:user="userName">
</div>
<script>
let vm = new Vue({
el: "#app",
data: {
activeColor: 'red',
fontSize: 30,
userName: 'zhangsan'
}
})
</script>
4)、v-bind 缩写
<div id="app" :style="{ color: activeColor, fontSize: fontSize +
'px' }" :user="userName">
</div>
3、v-model
刚才的v-text、v-html、v-bind 可以看做是单向绑定,数据影响了视图渲染,但是反过来就不
行。接下来学习的 v-model 是双向绑定,视图(View)和模型(Model)之间会互相影响。
既然是双向绑定,一定是在 视图 中可以修改数据,这样就限定了视图的元素类型。目前
v-model 的可使用元素有:
- input
- select
- textarea
- checkbox
- radio
- components(Vue 中的自定义组件)
基本上除了最后一项,其它都是表单的输入项。
示例:
<div id="app">
<input type="checkbox" v-model="language" value="Java" />Java<br />
<input type="checkbox" v-model="language" value="PHP" />PHP<br />
<input type="checkbox" v-model="language" value="Swift" />Swift<br />
<h1>
你选择了:{{language.join(',')}}
</h1>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script type="text/javascript">
let vm = new Vue({
el: "#app",
data: {
language: []
}
})
</script>
- 多个
checkbox
对应一个model 时,model 的类型是一个数组,单个checkbox 值默认是boolean 类型 - radio 对应的值是input 的value 值
text
和textarea
默认对应的 model 是字符串select
单选对应字符串,多选对应也是数组
4、v-on
1、基本用法
- v-on 指令用于给页面元素绑定事件。
语法: v-on:事件名="js 片段或函数名"
示例:
<div id="app">
<!--事件中直接写js 片段-->
<button v-on:click="num++">点赞</button>
<!--事件指定一个回调函数,必须是Vue 实例中定义的函数-->
<button v-on:click="decrement">取消</button>
<h1>有{{num}}个赞</h1>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script type="text/javascript">
let vm = new Vue({
el: "#app",
data: {
num: 100
},
methods: {
decrement() {
this.num--; //要使用data 中的属性,必须this.属性名
}
}
})
</script>
另外,事件绑定可以简写,例如v-on:click='add'
可以简写为@click='add'
2、事件修饰符
在事件处理程序中调用event.preventDefault()
或event.stopPropagation()
是非常常见的
需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,
而不是去处理DOM 事件细节。
为了解决这个问题,Vue.js 为v-on
提供了事件修饰符。修饰符是由点开头的指令后缀来
表示的。
.stop
:阻止事件冒泡到父元素.prevent
:阻止默认事件发生.capture
:使用事件捕获模式.self
:只有元素自身触发事件才执行。(冒泡或捕获的都不执行).once
:只执行一次
<div id="app">
<!--右击事件,并阻止默认事件发生-->
<button v-on:contextmenu.prevent="num++">点赞</button>
<br />
<!--右击事件,不阻止默认事件发生-->
<button v-on:contextmenu="decrement($event)">取消</button>
<br />
<h1>有{{num}}个赞</h1>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script type="text/javascript">
let app = new Vue({
el: "#app",
data: {
num: 100
},
methods: {
decrement(ev) {
// ev.preventDefault();
this.num--;
}
}
})
</script>
效果:右键“点赞”,不会触发默认的浏览器右击事件;右键“取消”,会触发默认的浏览器右击事件)
3. 按键修饰符
4、组合按钮
5、v-for
遍历数据渲染页面是非常常用的需求,Vue 中通过v-for 指令来实现。
1、遍历数组
语法:v-for="item in items"
- items:要遍历的数组,需要在vue 的data 中定义好。
- item:迭代得到的当前正在遍历的元素
示例:
<div id="app">
<ul>
<li v-for="user in users">
{{user.name}} - {{user.gender}} - {{user.age}}
</li>
</ul>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script type="text/javascript">
let app = new Vue({
el: "#app",
data: {
users: [
{ name: '柳岩', gender: '女', age: 21 },
{ name: '张三', gender: '男', age: 18 },
{ name: '范冰冰', gender: '女', age: 24 },
{ name: '刘亦菲', gender: '女', age: 18 },
{ name: '古力娜扎', gender: '女', age: 25 }
]
},
})
</script>
效果:
2、数组角标
3、遍历对象
4、Key
6、v-if 和v-show
7、v-else 和v-else-if
v-else 元素必须紧跟在带v-if
或者v-else-if
的元素的后面,否则它将不会被识别。
示例:
<div id="app">
<button v-on:click="random=Math.random()">点我呀
</button><span>{{random}}</span>
<h1 v-if="random >= 0.75">
看到我啦?!v-if >= 0.75
</h1>
<h1 v-else-if="random > 0.5">
看到我啦?!v-else-if > 0.5
</h1>
<h1 v-else-if="random > 0.25">
看到我啦?!v-else-if > 0.25
</h1>
<h1 v-else>
看到我啦?!v-else
</h1>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script type="text/javascript">
let app = new Vue({
el: "#app",
data: {
random: 1
}
})
</script>
6、计算属性和侦听器
1、计算属性(computed)
某些结果是基于之前数据实时计算出来的,我们可以利用计算属性。来完成
示例:
<div id="app">
<ul>
<li>西游记:价格{{xyjPrice}},数量:
<input type="number" v-model="xyjNum">
</li>
<li>水浒传:价格{{shzPrice}},数量:
<input type="number" v-model="shzNum">
</li>
<li>总价:{{totalPrice}}</li>
</ul>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script type="text/javascript">
let app = new Vue({
el: "#app",
data: {
xyjPrice: 56.73,
shzPrice: 47.98,
xyjNum: 1,
shzNum: 1
},
computed: {
totalPrice() {
return this.xyjPrice * this.xyjNum + this.shzPrice * this.shzNum;
}
},
})
</script>
2、侦听(watch)
watch 可以让我们监控一个值的变化。从而做出相应的反应。
示例:
<div id="app">
<ul>
<li>西游记:价格{{xyjPrice}},数量:
<input type="number" v-model="xyjNum">
</li>
<li>水浒传:价格{{shzPrice}},数量:
<input type="number" v-model="shzNum">
</li>
<li>总价:{{totalPrice}}</li>
{{msg}}
</ul>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script type="text/javascript">
let app = new Vue({
el: "#app",
data: {
xyjPrice: 56.73,
shzPrice: 47.98,
xyjNum: 1,
shzNum: 1,
msg: ""
},
computed: {
totalPrice() {
return this.xyjPrice * this.xyjNum + this.shzPrice * this.shzNum;
}
},
watch: {
xyjNum(newVal, oldVal) {
if (newVal >= 3) {
this.msg = "西游记没有更多库存了";
this.xyjNum = 3;
} else {
this.msg = "";
}
}
}
})
</script>
3、过滤器(filters)
<body>
<div id="app">
<table>
<tr v-for="user in userList">
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<!-- 使用代码块实现,有代码侵入-->
<td>{{user.gender===1? "男":"女"}}</td>
</tr>
</table>
</div>
</body>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
let app = new Vue({
el: "#app",
data: {
userList: [
{ id: 1, name: 'jacky', gender: 1 },
{ id: 2, name: 'peter', gender: 0 }
]
}
});
</script>
1、局部过滤器
注册在当前vue 实例中,只有当前实例能用
let app = new Vue({
el: "#app",
data: {
userList: [
{ id: 1, name: 'jacky', gender: 1 },
{ id: 2, name: 'peter', gender: 0 }
]
},
// filters 定义局部过滤器,只可以在当前vue 实例中使用
filters: {
genderFilter(gender) {
return gender === 1 ? '男~' : '女~'
}
}
});
2、全局过滤器
7、组件化
在大型应用开发的时候,页面可以划分成很多部分。往往不同的页面,也会有相同的部分。例如可能会有相同的头部导航。
但是如果每个页面都独自开发,这无疑增加了我们开发的成本。所以我们会把页面的不同部分拆分成独立的组件,然后在不同页面就可以共享这些组件,避免重复开发。在vue 里,所有的vue 实例都是组件。
1、全局组件
我们通过Vue 的component 方法来定义一个全局组件。
<div id="app">
<!--使用定义好的全局组件-->
<counter></counter>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script type="text/javascript">
// 定义全局组件,两个参数:1,组件名称。2,组件参数
Vue.component("counter", {
template: '<button v-on:click="count++">你点了我{{ count }} 次,我记住了.</button>',
data() {
return {
count: 0
}
}
})
let app = new Vue({
el: "#app"
})
</script>
2、组件的复用
定义好的组件,可以任意复用多次:
<div id="app">
<!--使用定义好的全局组件-->
<counter></counter>
<counter></counter>
<counter></counter>
</div>
组件的data 属性必须是函数!
一个组件的data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷
贝;
否则:
https://cn.vuejs.org/v2/guide/components.html#data-必须是一个函数
3、局部组件
一旦全局注册,就意味着即便以后你不再使用这个组件,它依然会随着Vue 的加载而加载。因此,对于一些并不频繁使用的组件,我们会采用局部注册。
我们先在外部定义一个对象,结构与创建组件时传递的第二个参数一致:
8、生命周期钩子函数
1、生命周期
每个Vue 实例在被创建时都要经过一系列的初始化过程:创建实例,装载模板,渲染模板等等。Vue 为生命周期中的每个状态都设置了钩子函数(监听函数)。每当Vue 实例处于不同的生命周期时,对应的函数就会被触发调用。
生命周期:你不需要立马弄明白所有的东西。
2、钩子函数
示例
<body>
<div id="app">
<span id="num">{{num}}</span>
<button v-on:click="num++">赞!</button>
<h2>
{{name}},非常帅!!!有{{num}}个人点赞。
</h2>
</div>
</body>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
let app = new Vue({
el: "#app",
data: {
name: "张三",
num: 100
},
methods: {
show() {
return this.name;
},
add() {
this.num++;
}
},
beforeCreate() {
console.log("=========beforeCreate=============");
console.log("数据模型未加载:" + this.name, this.num);
console.log("方法未加载:" + this.show());
console.log("html 模板未加载:" + document.getElementById("num"));
},
created: function () {
console.log("=========created=============");
console.log("数据模型已加载:" + this.name, this.num);
console.log("方法已加载:" + this.show());
console.log("html 模板已加载:" + document.getElementById("num"));
console.log("html 模板未渲染:" + document.getElementById("num").innerText);
},
beforeMount() {
console.log("=========beforeMount=============");
console.log("html 模板未渲染:" + document.getElementById("num").innerText);
},
mounted() {
console.log("=========mounted=============");
console.log("html 模板已渲染:" + document.getElementById("num").innerText);
},
beforeUpdate() {
console.log("=========beforeUpdate=============");
console.log("数据模型已更新:" + this.num);
console.log("html 模板未更新:" + document.getElementById("num").innerText);
},
updated() {
console.log("=========updated=============");
console.log("数据模型已更新:" + this.num);
console.log("html 模板已更新:" + document.getElementById("num").innerText);
}
});
</script>
</body>
9、vue 模块化开发
1、npm install webpack -g
全局安装 webpack
2、npm install -g @vue/cli-init
全局安装 vue 脚手架
3、初始化vue 项目
vue init webpack appname:vue 脚手架使用webpack 模板初始化一个appname 项目
![image-20210927110259380](F:\JAVA listen\尚硅谷Java学科全套教程(总207.77GB)\谷粒商城\谷粒商城之分布式基础\6c4b047a712197ee67ea448759abaeaf.png)
注意:
如果一直卡在downloading template,配置淘宝镜像
npm config set chromedriver_cdnurl https://npm.taobao.org/mirrors/chromedriver
4、启动vue 项目
启动成功。
![image-20210927110328858](F:\JAVA listen\尚硅谷Java学科全套教程(总207.77GB)\谷粒商城\谷粒商城之分布式基础\7d40e948a2071e0ee2978687474627b8.png)
项目的 package.json 中有 scripts,代表我们能运行的命令
npm start = npm run dev:启动项目
npm run build:将项目打包
5、模块化开发
1、项目结构
2、Vue 单文件组件
Vue 单文件组件模板有三个部分;
3、vscode 添加用户代码片段(快速生成vue 模板)
{
"生成vue 模板": {
"prefix": "vue",
"body": [
"<template>",
"<div></div>",
"</template>",
"",
"<script>",
"//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json
文件,图片文件等等)",
"//例如:import 《组件名称》from '《组件路径》';",
"",
"export default {",
"//import 引入的组件需要注入到对象中才能使用",
"components: {},",
"props: {},",
"data() {",
"//这里存放数据",
"return {",
"",
"};",
"},",
"//计算属性类似于data 概念",
"computed: {},",
"//监控data 中的数据变化",
"watch: {},",
"//方法集合",
"methods: {",
"",
"},",
"//生命周期- 创建完成(可以访问当前this 实例)",
"created() {",
"",
"},",
"//生命周期- 挂载完成(可以访问DOM 元素)",
"mounted() {",
"",
"},",
"beforeCreate() {}, //生命周期- 创建之前",
"beforeMount() {}, //生命周期- 挂载之前",
"beforeUpdate() {}, //生命周期- 更新之前",
"updated() {}, //生命周期- 更新之后",
"beforeDestroy() {}, //生命周期- 销毁之前",
"destroyed() {}, //生命周期- 销毁完成",
"activated() {}, //如果页面有keep-alive 缓存功能,这个函数会触发
",
"}",
"</script>",
"<style lang='scss' scoped>",
"//@import url($3); 引入公共css 类",
"$4",
"</style>"
],
"description": "生成vue 模板"
}
}
在创建组件时直接输入vue
点击回车就可生成模板
4、导入element-ui 快速开发
官方文档:https://element.eleme.cn/#/zh-CN/component/installation
-
安装element-ui:
npm i element-ui
-
在main.js 中引入element-ui 就可以全局使用了。
import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' Vue.use(ElementUI)
然后就可以使用elementui之中的组件。
-
将 App.vue 改为 element-ui 中的后台布局
-
添加测试路由、组件,测试跳转逻辑
(1) 、参照文档el-menu 添加 router 属性把
<el-main>
中的数据列表换成路由视图<router-view></router-view>
![image-20210927111502253](F:\JAVA listen\尚硅谷Java学科全套教程(总207.77GB)\谷粒商城\谷粒商城之分布式基础\91e54a45319aa82330a98aec1c3d2512.png)
(2) 、参照文档 el-menu-item 指定 index 需要跳转的地址
- 新建MyTable组件,用来显示用户数据
<!-- --> <template> <div class=""> <el-table :data="tableData"> <el-table-column prop="date" label="日期" width="140"> </el-table-column> <el-table-column prop="name" label="姓名" width="120"> </el-table-column> <el-table-column prop="address" label="地址"> </el-table-column> </el-table> </div> </template> <script> export default { data() { const item = { date: "2016-05-02", name: "王小虎", address: "上海市普陀区金沙江路 1518 弄", }; return { tableData: Array(20).fill(item), }; }, }; </script> <style scoped> </style>
- 添加路由规则
import MyTable from '@/components/MyTable' { path: '/mytable', name: "mytable", components: MyTable }
3. 修改App.vue ![image-20210927111630855](F:\JAVA listen\尚硅谷Java学科全套教程(总207.77GB)\谷粒商城\谷粒商城之分布式基础\4bedd50bd3981d8db01946452d1d03e8.png)
5.5 Babel
Babel 是一个JavaScript 编译器,我们可以使用es 的最新语法编程,而不用担心浏览器兼
容问题。他会自动转化为浏览器兼容的代码
5.6 Webpack
自动化项目构建工具。gulp 也是同类产品。