知识回顾
vue基础
指令
生命周期
事件
MVVM(数据驱动视图)
组件
局部组件和全局组件
组件通信
父子组件 props和$emits
$bus 中央事件总线
provide和inject
$attrs和$listeners
this.$parent 、ths.$children、 this.$slots...
组件设计
如何更优雅地设计一款组件
render(h) jsx
vue性能优化的9个技巧
Element-ui /antd-design-vue || vant-ui/iview
awesome
axios是基于XMLHttpRequest promise
vue-xxx react-xxx 插件(github star 更新时间)
目录
vuex基础state/getter/mutation/action
vue-router的基础使用
Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌
vue-router 安装
npm i vue-router -S
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
Vue.use(VueRouter)
// 路由管理表
const routes = [
{
path:'/',
// 命名路由
// redirect: '/home',
name:'Home',
component:HomeView
},
// 同一个路径可以匹配多个路由,匹配的优先级按照路由的定义顺序来匹配的
{
path: '/about',
name: 'About',
component: () => import( '@/views/AboutView.vue')
},
{//带参路径,user组件复用
path:'/user:userName',
name:'User',
component:() =>import('@、views/User.vue')
},
{
path: '/course',
// name: 'Course',
component: () => import('@/views/course'),
//路由权限拦截
meta: { requireAuth: true },
//嵌套路由
children: [
{
path: '',
component: () => import('@/views/course/home'),
},
{
path: 'front',
component: () => import('@/views/course/front'),
},
{
path: 'backend',
component: () => import('@/views/course/backend'),
},
],
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/login'),
},
{//放到最后
path:'*',
component:() =>import('@/views/404.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
// 全局守卫
router.beforeEach((to, from, next) => {
// 需要权限,在黑名单
if (to.matched.some((record) => record.meta.requireAuth)) {
// 用户是否有登录
const userInfo = localStorage.getItem('userInfo')
if (userInfo) {
next()
} else {
next({
path: '/login',
query: {
redirect: to.fullPath,
},
})
}
} else {
next()
}
})
export default router
注:同路径可以匹配多个路由,按照优先顺序匹配,重名路由将选取靠前那个
path:* 必须放到最后面,直接跳转404页面
路由权限拦截 meta: { requireAuth: true },
全局路由守卫
router.beforeEach((to, from, next) => {
if (to.matched.some((record) => record.meta.requireAuth)) {... }else{next()}
}
App.vue
<template>
<div id="app">
<nav>
<!-- router-link 内置组件 被渲染为a标签 to会被渲染成href属性 -->
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>|
<router-link :to="{name:'Test'}">Test</router-link>|
<router-link :to="{name:'User',params:{userName:'kiki'}}">User(kiki)</router-link>|
<router-link :to="{name:'User',params:{userName:'xiaomage'}}">User(xiaomage)</router-link>|
<router-link :to="{name:'Login'}">登录</router-link>|
<router-link to="/course">课程</router-link>|
</nav>
<router-view/>
</div>
</template>
注:动态路由匹配使用 :to ={name:'',params:{id:1}}
<router-link :to="{name:'user',params:{id:1}}">User</router-link> |
嵌套路由 views/User.vue
<template>
<div>
我是用户组件页面{{ name }}
<router-link to="/">跳首页</router-link>
<button @click="goHome">跳转到首页</button>
<button @click="goBack">后退</button>
</div>
</template>
<script>
export default {
data() {
return {
name: '',
}
},
created() {
const { userName } = this.$route.params
if (userName) {
this.name = userName
}
},
// watch监听路由参数的变化
// watch: {
// $route(to, from) {
// this.getUserInfo(to.params.userName)
// },
// },
//路由守卫deforeRouteUpdate(to,from,next){} ,当路由更新时触发
beforeRouteUpdate(to, from, next) {
this.getUserInfo(to.params.userName)
next()
},
methods: {
goBack() {
this.$router.go(0)
},
getUserInfo(username) {
// 发起ajax 获取当前用户的数据
setTimeout(() => {
this.name = username
}, 1000)
},
goHome() {
// 编程式导航
// this.$router.push('/')
this.$router.push({
name: 'Home',
query: {
id: 1,
},
// params:{}
})
},
},
}
</script>
注:
路由守卫deforeRouteUpdate(to,from,next){} ,当路由更新时触发
编程式导航:this.$router.push({ ...}) 历史跳转 this.$router.go(-1)
views/Login.vue
<template>
<div>
<h2>登录页面</h2>
<input type="text" v-model="form.user" />
<input type="password" v-model="form.pwd" />
<button @click="handleLogin">登录</button>
</div>
</template>
<script>
export default {
data() {
return {
form: {
name: '',
pwd: '',
},
}
},
methods: {
handleLogin() {
// 获取用户名和密码
setTimeout(() => {
const { user, pwd } = this.form
const data = {
user,
pwd,
}
// 保存到本地
localStorage.setItem('userInfo', JSON.stringify(data))
// 跳转到课程页面
this.$router.push({
// path: '/course',
path: this.$route.query.redirect,
})
}, 1000)
},
},
}
</script>
注:路由重定向
this.$router.push({ path: this.$route.query.redirect })
views/course/index.vue
<template>
<div>
<router-link to="/course">全部</router-link> |
<router-link to="/course/front">前端</router-link> |
<router-link to="/course/backend">后端</router-link> |
<!-- 渲染全部 前端 后端 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
}
</script>
注:<router-view></router-view> 进行渲染
/course会默认进入index.vue
命名视图
非嵌套多视图
router/index.js
{
path: '/',
name: 'Home',
// component: HomeView
components:{
default:HomeView,
//懒加载
Main:() => import('../views/Main.vue'),
Sidebar:() => import('../views/Sidebar.vue'),
}
},
App.vue
<router-view name="Main" />
<router-view/>
<router-view name="Sidebar"/>
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 用创建好的实例调用
beforeRouteEnter
守卫中传给next
的回调函数。
vuex基础state/getter/mutation/action
详细介绍:vuex基本介绍
vuex是专门用来管理vue.js应用程序中状态的一个插件。
作用:将应用中的所有状态都放在一起,集中式来管理。
需要声明的是,这里所说的状态指的是vue组件中data里面的属性。
vuex导图
- state: 统一定义公共数据(类似于data(){return {a:1, b:2,xxxxxx}})
- mutations : 使用它来修改数据(类似于methods)更改state中的数据必须提交(commit) mutations来进行更改
- getters: 类似于computed(计算属性,对现有的状态进行计算得到新的数据-------派生 )
- actions: 发起异步请求
- modules: 模块拆分
安装vueX
或者在新建脚手架时选中vuex
vue add vuex
store/index.js配置
引入并使用vuex
import Vuex from 'vuex'
// 2.使用当前的插件
Vue.use(Vuex)
new Vuex.Store 创建store
const store = new Vuex.Store({
plugins: [createLogger()],
state: { //当前的状态
count: 0,
username: 'kiki'
},
getters: {
evenOrOdd(state) {
return state.count % 2 === 0 ? '偶数' : '奇数';
}
},
mutations: { //声明同步的方法
increment(state) {
// 修改状态
state.count++
},
decrement(state) {
state.count--
},
incrementAsync(state,amount) {
state.count+=amount;
}
},
actions: { //声明异步的方法
// commit mutations中声明的方法
// 修改状态的唯一方法是提交mutation
incrementAsync({ commit },{amount}) {
setTimeout(() => {
commit('incrementAsync',amount)
}, 1000);
}
},
//模板
modules: {
cart,
products
}
})
//导出store
export default store;
main.js
导入创建的store进行挂载
import store from './store'
import Vue from 'vue'
import App from './App.vue'
import router from './router'
// 导入创建的store
import store from './store'
Vue.config.productionTip = false
// 注册全局组件的方式
import ShoppingCart from '@/components/ShoppingCart';
Vue.component(ShoppingCart.name, ShoppingCart)
// 全局注册过滤器
Vue.filter('currency',(value)=>{
return '$' + value;
})
new Vue({
router,
//一定要挂载
store,
render: h => h(App)
}).$mount('#app')
组件views/About.vue
<template>
<div class="about">
<h1>This is an about page</h1>
{{myCount}} - {{user}}
{{evenOrOdd}}
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<button @click="incrementAsync">+1异步</button>
<ProductList></ProductList>
<hr>
<!-- <ShoppingCart></ShoppingCart> -->
<shopping-cart></shopping-cart>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from "vuex";
import ProductList from '@/components/ProductList'
// import ShoppingCart from '@/components/ShoppingCart'
export default {
components: {
ProductList,
// ShoppingCart
},
computed: {
// count() {
// return this.$store.state.count;
// },
// username(){
// return this.$store.state.username;
// }
// ...mapState(['count','username'])
...mapState({
myCount: "count",
user: "username"
}),
// evenOrOdd() {
// return this.$store.getters.evenOrOdd;
// }
...mapGetters(['evenOrOdd'])
},
methods: {
incrementAsync() {
// 在组件内部提交数据 载荷形式分发
// this.$store.dispatch('incrementAsync',{
// amount:10
// });
this.$store.dispatch({
type:'incrementAsync',
amount: 10
})
},
// ...mapActions(['incrementAsync']),
/* increment() {
// dispatch触发actions中声明的方法(异步)
// this.$store.dispatch('increment');
this.$store.commit("increment");
},
decrement() {
// this.$store.dispatch('decrement');
this.$store.commit("decrement");
} */
...mapMutations(['increment','decrement'])
}
};
</script>
App.vue
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link>|
<router-link to="/about">About</router-link>
{{$store.state.count}}
{{getCount}}
</div>
<router-view/>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters('cart',['getCount'])
}
};
</script>
components/ProductList.vue
<template>
<div>
<h2>我的商铺</h2>
<ul>
<li
v-for="(product) in products"
:key="product.id"
>{{product.title}} - {{product.price | currency}} - {{product.inventory}}
<br>
<button :disabled='!product.inventory' @click='addProductToCart(product)'>加入购物车</button>
</li>
</ul>
</div>
</template>
<script>
import { mapState,mapActions } from "vuex";
export default {
computed: {
...mapState("products", ["products"])
},
created() {
this.$store.dispatch("products/getAllProducts");
},
methods: {
...mapActions('cart',['addProductToCart'])
},
};
</script>
components/ShoppingCart.vue
<template>
<div>
<h2>我的购物车</h2>
<ul>
<li
v-for="(item,index) in getCartList"
:key="index"
>{{item.title}} - {{item.price | currency}} x {{item.quantity}}</li>
</ul>
总价格:{{cartTotalPrice | currency}}
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "ShoppingCart", //组件名 注册全局组件时使用,
computed: {
...mapGetters("cart", ["getCartList", "cartTotalPrice"])
}
};
</script>
使用modules
store/modules/cart.js
namespaced: true,
export default {
namespaced: true,
state: {
cartList: [],
count: 0
},
getters: {
getCount(state) {
return state.count
},
// 获取购物车的数据
getCartList(state, getters, rootState) {
return state.cartList.map(({ id, quantity }) => {
const product = rootState.products.products.find(item => item.id === id);
return {
title: product.title,
price: product.price,
quantity
}
})
},
cartTotalPrice(state, getters) {
return getters.getCartList.reduce((total, product) => {
return total + product.price * product.quantity
}, 0);
}
},
mutations: {
// 第一此添加该商品到购物车
pushProductToCart(state, { id, quantity }) {
state.cartList.push({
id,
quantity
})
},
// 购物车中已有数据 只改变当前的数量
incrementCartItemQuantity(state, { id }) {
const product = state.cartList.find(item => item.id === id);
product.quantity++;
}
},
actions: {
addProductToCart({ commit, state }, product) {
if (product.inventory > 0) { // 有库存
const cartItem = state.cartList.find(item => item.id === product.id);
console.log(cartItem);
if (!cartItem) {
// 购物车无数据 新添加到购物车
commit('pushProductToCart', { id: product.id, quantity: 1 })
} else {
//购物车中已有数据
commit('incrementCartItemQuantity', { id: product.id });
}
// 如果想提交另一个模块中的方法,那么需要第三个参数{root:true}
commit('products/decrementProductInventory', { id: product.id },{root:true})
}
}
}
}
store/modules/products.js
import Axios from 'axios'
export default {
namespaced: true,
state: {
products: []
},
getters: {
},
mutations: {
getAllProducts(state, products) {
state.products = products
},
decrementProductInventory(state, { id }) {
const product = state.products.find(item => item.id === id);
product.inventory--;
}
},
actions: {
async getAllProducts({ commit }) {
// 发送请求 获取数据 提交mutation
try {
const res = await Axios.get('/api/products');
const results = res.data.results
commit('getAllProducts', results);
} catch (error) {
console.log(error);
}
}
}
}
src/api/products.js
import request from '@/utils/request'
export const getProducts = (paramter) => {
return request({
url: '/products',
method: 'get',
params: paramter,
// data:paramter
})
}
src/utils/request.js
import axios from 'axios'
const request = axios.create({
baseURL: '/api',
timeout: 5000,
})
// 添加请求拦截器
request.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
return config
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error)
}
)
// 添加响应拦截器
request.interceptors.response.use(
function (response) {
// 对响应数据做点什么
return response.data
},
function (error) {
// 对响应错误做点什么
return Promise.reject(error)
}
)
export default request
vue.config.js
const products = [
{ id: 1, title: 'iphone11', price: 800, inventory: 10 },
{ id: 2, title: 'iphone11 pro', price: 1200, inventory: 15 },
{ id: 3, title: 'iphone11 max', price: 1500, inventory: 5 },
]
module.exports = {
devServer: {
before(app, serve) {
app.get('/api/products', (req, res) => {
res.json({
results: products
})
})
}
}
}