day 01
slot插槽
- Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将
<slot>
元素作为承载分发内容的出口。内容定义在父组件中,子组件可以拿去渲染。
匿名插槽
- template没有slot或者v-slot,匿名操作,直接插入子组件的
<slot></slot>
具名插槽
- 多个内容进行分发,使用
slot=“插槽名”
或者v-slot:插槽名
指定插入的插槽名,<slot name='插槽名'></slot>
使用 name 指定操作的名称
作用域插槽
- 子组件通过slot组件的自定义属性将数据传入到父组件的插操内容来使用,父组件使用
slot-scope="传入的数据对象"
或者v-slot:插槽名=“传入的数据对象”
指令的指令值。 - 父组件:
<template>
<!-- 数据驱动 -->
<div id="app">
<!-- 2.5.xxxx -->
<v-child :visible="isShow" :list="list">
<!--匿名插槽 建议插槽内容写在template中 -->
<template>
<button @click="submit">确定</button>
</template>
<!--具名插槽 2.5.x,slot="插槽名称" -->
<template slot="footer" >
<div>
底部内容
</div>
</template>
<!--
作用域插槽:
slot-scope="自定义名称"
自定义名称:包含子组件传入到父组件的数据对象
-->
<template slot="box" slot-scope="scoped">
<span>
<button @click="edit(scoped.index)">编辑</button>
<button @click="del(scoped.index)">删除 </button>
</span>
</template>
</v-child>
<!-- 2.6.xxxx -->
<v-child :visible="isShow" :list="list">
<!-- 建议插槽内容写在template中,匿名插槽的内容 -->
<template>
<b @click="isShow=false">bbbb</b>
</template>
<!--具名插槽: v-slot:插槽名称 -->
<template v-slot:footer>
<div>
底部内容
</div>
</template>
<!--
v-slot:插槽名称 = "包含子组件传入到父组件的数据对象"
-->
<template v-slot:box="scoped">
<span>
<button>编辑</button>
<button @click="del(scoped.index)">删除</button>
</span>
</template>
</v-child>
</div>
</template>
<script>
import vChild from "./components/child";
export default {
components: {
vChild
},
data(){
return{
isShow:true,
list:[{
title:"title1"
},{
title:"title2"
}]
}
},
methods:{
submit(){
this.isShow=false
},
del(i){
this.list.splice(i,1)
},
edit(i){
}
}
};
</script>
- 子组件:
<template>
<div class="list" v-show="visible">
<h2>标题</h2>
<!-- 匿名插槽 -->
<slot></slot>
<div>其他内容</div>
<ul>
<li v-for="(item,index) in list" :key="index">
<span>{{item.title}}</span>
<!-- 以自定义属性形式向父组件传入数据 -->
<slot name="box" :index="index" :item="item"></slot>
</li>
</ul>
<!-- name = 具名插槽的名称 -->
<slot name="footer"></slot>
</div>
</template>
<script>
export default {
props:{
visible:Boolean,
list:Array
}
};
</script>
vue-router
- Vue Router 是 Vue.js 官方的路由管理器,用来构建单页面应用。(多个页面只需要一次请求)
- 地址变化,js动态生成html内容
- 使用:
- 1.创建项目时,第五项选择安装路由
- 2.创建一个路由组件
- 3.src/router/index.js 引入路由组件,定义使用
import Vue from 'vue'
import Router from 'vue-router'
// 引入路由组件,@->src目录的绝对地址
import Index from '@/pages/Index'
import About from "@/pages/about"
// 安装路由
Vue.use(Router)
let router = new Router({
// 定义 路由
routes: [
// 每一个对象就是一个路由
{
path: '/',//路由路径
component: Index,//该路由对应的渲染组件
},{
path:"/about",
component:About
}
]
})
export default router
导航组件
声明式导航
- router-link组件:导航组件
- to属性指定导航地址
- to 属性可以是字符串路径,也可以是对象
- activeClass:激活对应路由时的类名
- exact:精确匹配(地址相同才匹配)
<ul>
<li>
<!-- <router-link to="/">首页</router-link> -->
<router-link :to="{path:'/'}" activeClass="act" exact>首页</router-link>
</li>
<li>
<router-link to="/about" activeClass="act">about</router-link>
</li>
</ul>
编程式导航
this.$router.push/replace/go
- push推入访问记录,replace替换访问记录
methods:{
// 使用$router进行编程式导航
toIndex(){
// console.log(this.$router)
// 跳转到首页
this.$router.push("/")
},
toAbout(){
// push:可以回退回来
// this.$router.push("/about");
// replace:不能回退
this.$router.replace("/about");
// this.$router.replace({path:"/about"});
},
back(){
// 前进(参数是正数)或者后退(参数是负数)若干个页面
this.$router.go(-1)
}
}
404页面
{
// * 任意其他地址
path:"*",
component:NotFound
}
重定向
{
path:"*",
redirect:"/",// redirect:重定向地址
}
day 02
单页面应用优缺点:
- 优点:
- 分离前后端关注点,前端负责界面显示,后端负责数据存储和计算
- 通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件
- 同一套后端程序代码,不用修改就可以用于多种设备客户端
- 用户体验好,快,内容的改变不需要重新加载整个页面
- 缺点:不支持版本低的浏览器
- 不利于SEO优化
- 第一次加载首页耗时相对长一些
- 导航不可用,如果一定要导航需要自行实现前进、后退,需要程序来实现管理
路由模式
- hash模式:
- 采用的是window.onhashchange事件实现。
- 可以实现前进 后退 刷新。
- 比如这个URL:http://www.abc.com/#/hello, hash 的值为#/hello。
它的特点在于:hash 虽然出现URL中,但不会被包含在HTTP请求中,
对后端完全没有影响,因此改变hash不会重新加载页面
- history模式:(不带#,直接访问非首页路由,服务器端没有相应的匹配,需要后端的配合)
- 采用的是利用了HTML5 History Interface 中新增的pushState() 和replaceState() 方法。
- 可以前进、后退,但是刷新有可能会出现404的报错
- 前端的url必须和实际向后端发起请求的url 一致,如http://www.abc.com/book/id 。
如果后端缺少对/book/id 的路由处理,将返回404错误。
命名路由
{
// 命名路由,给路由取名字
path: '/menu',
name: "menu",
component: Menu
}
动态路由(冒号传参)
- 定义:
:
后面是动态路由参数的名称
{
path:"/goods/:goodsid",
component:Goods
}
- 获取动态路由参数:
// $route:当前路由对象,包含路由信息(动态路由参数)
let goodsid = this.$route.params.goodsid;
关于$route和$router
的区别####
$router
:全局路由器,一般用来导航$route
:当前路由对象,包含路由信息(动态路由参数params)
动态路由组件复用,页面不更新问题
- 动态路由组件被复用,不是销毁重建,生命周期只执行一次mounted,请求一次数据,需要手动刷新页面,生命周期重启,页面才会更新,为解决这个问题,应该对当前路由对象进行监听,当对象发生改变,重新获取一次数据
export default {
data() {
return {
info: {}
};
},
// 动态路由组件被复用(/goods/123->/goods/456),组件被复用,而不是销毁重建
mounted() {
this.getDetail();
},
// 监听 $router的辩护,重新获取数据
watch: {
$route() {
this.getDetail();
}
},
methods: {
getDetail() {
// 获取动态路由参数
// $route:当前路由对象,包含路由信息(动态路由参数)
let goodsid = this.$route.params.goodsid;
console.log(goodsid);
$.get(`/static/data/good${goodsid}.json`, res => {
console.log(res);
this.info = res;
});
}
}
};
和命名路由的使用
// 对象形式传入动态路由参数,必须使用命名路由
this.$router.push({name:"goods",params:{goodsid:456}})
使用地址部分的query 传值
<li><router-link to="/menu?a=1">/menu?a=1</router-link></li>
<li><router-link to="/menu?a=2">/menu?a=2</router-link></li>
console.log(this.$route.query.a)
命名视图
- 命名视图:一个路由地址对应多个路由组件来渲染
- key:router-view 的name值
- value:对应的渲染组件
{
path: '/',
components: {
/*
key:router-view 的name值
value:对应的渲染组件
*/
index:Index,
menu:Menu
},
}
<router-view name="index" />
<router-view name="menu" />
嵌套路由
- 页面完全不相同,这些是不同的一级路由,而页面内容有部分相同的地方,相同的部分在一级路由,不同的部分同一个一级路由下的二级路由
- 定义嵌套路由:
{
path: '/',
component: Layout,
// 定义嵌套路由
children: [{
path: "index",//一级路由path + 当前的path (不加 /)
component: Index
}, {
path: "menu",
component: Menu
},{
path:"*",
redirect:"/index"
}]
}
- 在一级路由组件 Layout.vue,设置二级路由的视图出口
<!-- Layout 下面的二级路由 -->
<router-view></router-view>
路由懒加载
{
path: "/login",
component: ()=>(import("@/pages/Login"))
},
- 按块打包:
children: [{
path: "index",//一级路由path + 当前的path (不加 /)
// 有时候我们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。
component: ()=>(import(/* webpackChunkName: "group-a" */ "@/pages/Index"))
}, {
path: "menu",
component: ()=>(import(/* webpackChunkName: "group-a" */ "@/pages/Menu"))
},{
path:"*",
redirect:"/index"
}]
路由守卫
- 路由守卫:通过取消或者跳转的形式守卫路由的函数被称为导航钩子(路由守卫钩子)
守卫分类
- 全局守卫:导航发生就会触发
- router.beforeEach
- router.beforeResolve
- router.afterEach
// 全局前置守卫(前置:导航被确认前)
/**
* to:即将进入的路由
* from:即将离开的路由
* next:不加参数 执行下一个守卫,一次导航确保执行next
* 地址参数:终止本次导航,开启一轮新导航
* false: 取消本次导航
*/
router.beforeEach((to,from,next)=>{
console.log(to,from)
let isLogin = true;
// document.title = to.meta.title;
// 已登陆
if(isLogin){
// console.log(to.path)
if(to.path=="/login"){
next(false);
}else{
next();
}
}else{
// 未登录
// console.log("1")
//
if(to.path=="/login"){
next();
}else{
next("/login")
}
}
})
//所有组件内守卫和异步路由组件被解析之后
router.beforeResolve((to,from,next)=>{
console.log("beforeResolve");
next();
})
// 导航被确认后的守卫,没有next
router.afterEach((to,from)=>{
console.log("afterEach")
})
- 路由独享守卫:访问这个路由才触发的守卫
- beforeEnter
{
path: "/login",
component: ()=>(import( "@/pages/Login")),
meta:{
title:"登陆页"
},
beforeEnter(to,from,next){
console.log("login 专享")
next();
}
},
- 组件内的守卫:
- beforeRouteEnter:先获取数据再跳转路由,跳转前需要等待获取数据,跳转后立即看到页面
- beforeRouteUpdate:解决动态路由组件被复用的问题,路由组件被复用触发
- beforeRouteLeave:离开路由组件(挽留操作)
beforeRouteEnter(to, from, next) {
// 这个钩子中访问不到组件实例
console.log(this);
// next((vm)=>{
// console.log(vm);
// });
// let goodsid = this.$route.params.goodsid;
// console.log(goodsid);
$.get(`/static/data/good${goodsid}.json`, res => {
console.log(res);
// vm 就是组件实例,使用回调函数来访问
next(vm => {
vm.info = res;
});
});
}
// 解决动态路由组件被复用的问题,路由组件被复用触发
beforeRouteUpdate(to,from,next){
console.log(to.params.cateid)
next()
},
// 离开路由组件
beforeRouteLeave (to, from, next) {
// ...
let flag = confirm("确定离开吗?");
if(flag){
next();
}else{
next(false)
}
}
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 导航被确认
。- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
day 03
项目
服务器端
- 解压->npm i->打开navicat,新建一个名为shop_db(与项目的confiig/global.js下的数据库名相匹配)->在表格那里导入数据库表->修改 confiig/global.js->运行:npm start
设置跨域:config/index.js
// 设置解决跨域问题的代码
proxyTable: {
// "/api":已 /api开头的请求会被代理进行跨域问题解决
"/api":{
target:"http://localhost:3000",//代理的目标地址(真实地址)
changeOrigin:true,//是否跨域
// pathRewrite:{
// "^/api":"/api"
// }
}
/* "/test":{
target:"http://localhost:3000",//代理的目标地址(真实地址)
changeOrigin:true,//是否跨域
pathRewrite:{
"^/test":"",//去除真实请求中不存在二点 /test
}
} */
},
- 项目配置相关信息发生变动,必须重新启动项目
axios:npm i axios
- 官方API
- Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
axios 方法:
- get和post请求区别:是method 和参数写法(get->params;post->data)
- get请求
axios({
url:"/api/getgoodsinfo",//请求地址,(target地址+ url 才是真正的地址)
method:"get",//请求方法
// 参数
params:{
id:1
}
}).then((res)=>{
console.log(res);
})
- post请求:
axios({
url:"/api/userlogin",
method:"post",
data:this.info
}).then(res=>console.log(res))
- axios.get
axios.get("/api/getgoodsinfo",{
params:{
id:1
}
}).then((res)=>{
console.log(res);
})
- axios.post
axios.post('/api/userlogin',this.info);
- axios.all
- 并发:一起发送请求,一起接受数据
- 定义并发函数
methods: {
getBanner() {
return axios.get("/api/getbanner");
},
getInfo() {
return axios.get("/api/getgoodsinfo", {
params: {
id: 1
}
});
}
}
- 使用all发出请求,spread解析
axios.all([this.getBanner(), this.getInfo()]).then(axios.spread((res1, res2) => {
console.log(res1,res2)
}));
创建实例:
-
实例可以被认为是axios的独立拷贝,进行单独设置
// 创建新独立axios 实例
let instance = axios.create({
baseURL: “/api”,
timeout: 5000,
})let instance1 = axios.create({
baseURL: “/apis”
})instance.get("/getbanner").then(res => console.log(res));
拦截器
- 在发送请求前和接受响应前触发的回调函数:
// 设置请求拦截器,回调函数,发送请求时执行的函数
// config 请求参数
axios.interceptors.request.use(config => {
// Do something before request is sent
config.headers.Authorization = localStorage.getItem("token")
/* if(!(config.url=="/api/userlogin"&&config.method=="post")){
config.headers.Authorization = localStorage.getItem("token")
} */
console.log(config)
// return的 config对象,真正发送请求时的请求对象
return config;
}, error => {
// Do something with request error
return Promise.reject(error);
});
// 响应拦截器
// response:响应数据
axios.interceptors.response.use(response => {
// Do something before response is sent
console.log(response)
return response.data;
}, error => {
// Do something with response error
return Promise.reject(error);
});
day 04
Vuex
- Vuex是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
- 使用:
npm i vuex
- 创建:
src/store/index.js
import Vue from "vue"
import Vuex from "vuex"
// 安装插件
Vue.use(Vuex);
// store-> vuex 实例
let store = new Vuex.Store({
// 定义state(所有组件都可以共享的数据)
state:{
count:1
}
})
export default store
main.js
import Vue from 'vue'
import App from './App'
+ import store from "./store"
Vue.config.productionTip = false
console.log(store)
/* eslint-disable no-new */
new Vue({
el: '#app',
+ store,// 组件应用vuex实例
components: { App },
template: '<App/>'
})
五大核心:
- State: Vuex 使用
单一状态树
——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据
源 (SSOT (opens new window))”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。 - getters:state 的计算属性
let store = new Vuex.Store({
// 定义state(所有组件都可以共享的数据)
state:{
count:1
},
// state 的计算属性
getters:{
//参数state-> vuex的state
sqrt(state){
return state.count*state.count;
}
}
})
- 组件内使用:作为计算属性来使用
computed:{
count(){
return this.$store.state.count;
},
sqrt(){
return this.$store.getters.sqrt
}
},
- mutations:修改state的唯一办法是通过提交mutation
// 定义mutation,修改state的唯一办法是通过提交mutation
mutations:{
// add是mutation的名称,也是函数的名字(当add这个mutation被提交commit时,add函数会执行)
// 参数state-> vuex的state
add(state){
console.log("add ")
// 修改state
state.count++;
},
// payload->负载,提交参数
minus(state,payload){
state.count-=payload
},
// 对象提交方法
mul(state,payload){
console.log(payload)
state.count*=payload.n
}
- 组件中使用:提交commit名为xxx的mutation
methods: {
addFn() {
// 提交名为add的mutation
// commit(mutation名称)
this.$store.commit("add");
},
minus() {
// commit(mutation名称,负载数据)
this.$store.commit("minus",2)
},
mul(){
// 提交名为 mul的mutation,数据是 n:2
this.$store.commit({
type:"mul",//mutation名称
n:2,//数据
})
}
}
- actions
- Action 类似于 mutation(严格模式下不能进行异步操作),不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
- Action 类似于 mutation(严格模式下不能进行异步操作),不同在于:
//组件中使用
ADD() {
this.$store.dispatch("ADD");
}
//在action中提交mutation
actions:{
ADD({commit},payload){
console.log(context)
// context.commit("add")
setTimeout(()=>{
commit("add")
},1000)
}
}
- 模块化
- modules/cart.js(模拟购物车模块)
let state = ()=>{return {count:100}}//函数返回对象
let mutations={
add(state){
state.count++;
}
}
let actions={
ADD({commit}){
commit("add")
}
}
// cart 模块的配置
let cart = {
namespaced:true,//开启命名空间,提交模块的mutation必须带上模块的名称(独立的)(不开启命名空间,某个mutation被提交,所有同名的mutation都会执行,)
state,
mutations,
actions
}
export default cart
- 根模块中来注册:
import cart from "./modules/cart"
import mutations from "./mutations"
let store = new Vuex.Store({
strict:true,//严格模式
// 定义模块
modules:{
// key:模块名称
// value:模块配置
cart:cart
},
state:{... },
getters:{... },
mutations,
actions:{...}
}
})
- 组件中来使用:
computed: {
/* count1(){
return this.$store.state.cart.count;
}, */
// 计算属性count1 返回的是state中 cart模块的count
...mapState({count1:state=>state.cart.count}),
}
methods: {
...mapMutations("cart",["add"]),
addFn1(){
// 提交根模块的 mutation add
this.$store.commit("add")
//只触发cart模块的 add mutation
this.$store.commit("cart/add")
// this.add()
}
}
辅助函数
import {mapState,mapGetters,mapMutations,mapActions} from "vuex"
- 辅助生成计算属性:
mapState,mapGetters
computed: {
/* count:function() {
return this.$store.state.count;
},
msg()*{
return this.$store.state.msg;
}*/
// 添加 名为count的计算属性,返回state 中count
// 映射 this.count 为 store.state.count
...mapState(["count","msg"]),
/* sqrt() {
return this.$store.getters.sqrt;
} */
// 映射 this.sqrt 为 store.getters.sqrt
...mapGetters(["sqrt"])
},
- 辅助生成methods:
mapMutations,mapActions
/* add(){
this.$store.commit("add");
}, */
...mapMutations(["add","minus"]),
addFn() {
// 提交名为add的mutation
// commit(mutation名称)
// this.$store.commit("add");
// this.add 映射提交 名为 add的mutation
this.add();
},
/* minus(n){
this.$store.commit("minus", n);
}, */
minusFn() {
// commit(mutation名称,负载数据)
// this.$store.commit("minus", 2);
// 提交名为 minus的mutation,payload为2
this.minus(2)
},
mul() {
this.$store.commit("mul",3)
},
...mapActions(["ADD"]),
/* ADD(){
this.$store.dispatch("ADD");
}, */
ADDFn() {
this.ADD();
}