pnpm
关于现代包管理器的深度思考--为什么现在我更推荐 pnpm 而不是 npm/yarn?
pnpm 本质上就是一个包管理器,这一点跟 npm/yarn 没有区别,但它作为杀手锏的两个优势在于:
- 包安装速度极快;
- 磁盘空间利用非常高效。
link-pnpm官网
安装
# 全局安装pnpm
npm i pnpm -g
# 查看pnpm版本
pnpm -v常用命令
link-CLI命令
| npm 命令 | pnpm 等效 |
|---|---|
| npm install | https://pnpm.io/zh/cli/install |
| npm i pkg | pnpm add pkg |
| npm run cmd | pnpm cmd |
# 安装依赖包到 dependencies
pnpm add <pkg> / pnpm install <pkg>
# 安装依赖包到devDependencies
pnpm add -D <pkg> / pnpm install <pkg> -D
# 全局安装依赖包
pnpm add -g xxx
# 安装项目全部依赖
pnpm install =>简写 pnpm i
# 更新依赖包:
pnpm update => pnpm up
# 运行自定义脚本
pnpm run xxx => pnpm xxx
比如 pnpm run serve => pnpm serve包存储store
pnpm store:pnpm资源在磁盘上的存储位置
一般store在Mac/Linux系统中,默认会设置到{home dir}>/.pnpm-store/v3;windows下会设置到当前盘的根目录下,比如C(C:\.pnpm-store\v3)、D盘(D:\.pnpm-store\v3)。
查看实际安装存储路径
pnpm store path清除未被项目引用到的包
pnpm store prune路由使用巩固 - 练习
目标:实现以下切换效果


声明式导航 - 跳转传参
目标: 在跳转路由时, 可以给路由对应的组件内传值
查询参数传参

动态路由传参

| 模式 | 匹配路径 | $route.params |
|---|---|---|
| /user/:username | /user/evan | { username: 'evan' } |
| /user/:username/post/:post_id | /user/evan/post/123 | { username: 'evan', post_id: '123' } |
一个“路径参数”使用冒号
:标记。
⇒ 当匹配到一个路由时,参数值会被设置到 this.$route.params
创建views/Part.vue - 准备接收路由上传递的参数和值
html<template> <div> <p>关注明星</p> <p>发现精彩</p> <p>寻找伙伴</p> <p>加入我们</p> <p>人名: {{ $route.query.name }} -- {{ $route.params.username }}</p> </div> </template>路由定义
html{ path: "/part", component: Part }, { path: "/part/:username", // 有:的路径代表要接收具体的值 component: Part },导航跳转, 传值给MyGoods.vue组件
html<router-link to="/part?name=小传">朋友-小传</router-link> <router-link to="/part/小智">朋友-小智</router-link>

PS. 并不能说 查询参数的模式不好~~ 动态路由更灵活一些,
⇒ user/:user/city/:city/hobby/:hobby 动态路由
小结
- 声明式导航跳转时,如何传参?
- to=”/path路径?参数key=参数value”
- to=”/path/值”
- 如何接收路由参数
- $route.query.参数key =⇒ 查询参数
- $route.params.参数key =⇒ 动态路由

vue路由 - 重定向和模式
路由 - 重定向
目标: 匹配path后, 强制切换到目标path上

例如: 网页默认打开, 匹配路由“/”, 强制切换到“/find”上
const routes = [
{
path: "/", // 默认hash值路径
redirect: "/find" // 重定向到/find
// 浏览器url中#后的路径被改变成/find-重新匹配数组规则
}
]总结: 强制重定向后, 还会重新来数组里匹配一次规则


路由 - 404页面
目标: 如果路由hash值, 没有和数组里规则匹配

路由
{ path: '*', component:NotFound }通常用于客户端 404 错误。
匹配优先级
有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:路由定义得越早,优先级就越高。
⇒ 当使用通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后
默认给一个404页面

语法: 路由最后, path匹配*(任意路径) – 前面不匹配就命中最后这个, 显示对应组件页面
创建NotFound页面
html<template> <img src="../assets/404.png" alt=""> </template> <script> export default { } </script> <style scoped> img{ width: 100%; } </style>在main.js - 修改路由配置
jsximport NotFound from '@/views/NotFound' const routes = [ // ...省略了其他配置 // 404在最后(规则是从前往后逐个比较path) { path: "*", component: NotFound } ]
总结: 如果路由未命中任何规则, 给出一个兜底的404页面

路由 - 模式设置

目标: 修改路由在地址栏的模式

router/index.js
const router = new VueRouter({
routes,
mode: "history" // 打包上线后需要后台支持, 默认是hash
})vue路由 - 编程式导航

编程式导航用JS代码跳转
声明式导航用a标签
编程式导航 - 基础使用

目标: 用JS代码来进行跳转
语法:
this.$router.push({
path: "路由路径", // 都去 router/index.js定义
name: "路由名"
})- main.js - 路由数组里, 给路由起名字
{
path: "/find",
name: "Find",
component: Find
},
{
path: "/my",
name: "My",
component: My
},
{
path: "/part",
name: "Part",
component: Part
},- App.vue - 换成span 配合js的编程式导航跳转
<template>
<div>
<div class="footer_wrap">
<span @click="btn('/find', 'Find')">发现音乐</span>
<span @click="btn('/my', 'My')">我的音乐</span>
<span @click="btn('/part', 'Part')">朋友</span>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<script>
// 目标: 编程式导航 - js方式跳转路由
// 语法:
// this.$router.push({path: "路由路径"})
// this.$router.push({name: "路由名"})
// 注意:
// 虽然用name跳转, 但是url的hash值还是切换path路径值
// 场景:
// 方便修改: name路由名(在页面上看不见随便定义)
// path可以在url的hash值看到(尽量符合组内规范)
export default {
methods: {
btn(targetPath, targetName){
// 方式1: path跳转
this.$router.push({
// path: targetPath,
name: targetName
})
}
}
};
</script>
编程式导航 - 跳转传参

目标: JS跳转路由, 传参

语法 query / params 任选 一个
this.$router.push({
path: "路由路径"
name: "路由名",
query: {
"参数名": 值
}
params: {
"参数名": 值
}
})
// 对应路由接收 $route.params.参数名 取值
// 对应路由接收 $route.query.参数名 取值==格外注意: 使用path会自动忽略params==
App.vue
<template>
<div>
<div class="footer_wrap">
<span @click="btn('/find', 'Find')">发现音乐</span>
<span @click="btn('/my', 'My')">我的音乐</span>
<span @click="oneBtn">朋友-小传</span>
<span @click="twoBtn">朋友-小智</span>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<script>
// 目标: 编程式导航 - 跳转路由传参
// 方式1:
// params => $route.params.参数名
// 方式2:
// query => $route.query.参数名
// 重要: path会自动忽略params
// 推荐: name+query方式传参
// 注意: 如果当前url上"hash值和?参数"与你要跳转到的"hash值和?参数"一致, 爆出冗余导航的问题, 不会跳转路由
export default {
methods: {
btn(targetPath, targetName){
// 方式1: path跳转
this.$router.push({
// path: targetPath,
name: targetName
})
},
oneBtn(){
this.$router.push({
name: 'Part',
params: {
username: '小传'
}
})
},
twoBtn(){
this.$router.push({
name: 'Part',
query: {
name: '小智'
}
})
}
}
};
</script>综合练习 - 面经基础版

首页:一级
- 面经:二级
- 收藏:二级
- 喜欢:二级
- 我的:二级
文章详情:一级配置一级路由
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const router = new VueRouter({
routes:[]
})
export default router配置首页 和 详情页两个一级路由
const router = new VueRouter({
routes: [
{path:'/', component:Layout},
{path:'/detail', component:Detail},
]
})配置二级嵌套路由



目标: 在现有的一级路由下, 再嵌套二级路由
利用 children 配置二级路由
router/index.js
import Vue from 'vue'
import VueRouter from "vue-router";
import Article from '@/views/article.vue';
import ArticleDetail from '@/views/article-detail.vue';
import Collect from '@/views/collect.vue';
import Like from '@/views/like.vue';
import User from '@/views/user.vue';
import Layout from '@/views/layout.vue';
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{
path: '/',
component: Layout,
children: [
{ path: '/article', component: Article },
{ path: '/collect', component: Collect },
{ path: '/like', component: Like },
{ path: '/user', component: User },
]
},
{
path: '/detail',
component: ArticleDetail
}
]
})
export default router配置路由出口,配置导航链接, a 标签 替换成 router-link
views/layout.vue
<template>
<div class="h5-wrapper">
<div class="content">
<router-view></router-view>
</div>
<nav class="tabbar">
<router-link to="/article">面经</router-link>
<router-link to="/collect">收藏</router-link>
<router-link to="/like">喜欢</router-link>
<router-link to="/user">我的</router-link>
</nav>
</div>
</template>动态渲染首页
1 安装 axios
yarn add axios
pnpm i / add axios2 接口文档说明:
请求地址: https://mock.boxuegu.com/mock/3083/articles
请求方式: get
3 data中提供数据, created 中发送请求,获取数据
src/views/article.vue
<script>
import axios from 'axios';
export default {
name: 'ArticlePage',
data() {
return {
articles: []
};
},
async created() {
const { data } = await axios.get(
'https://mock.boxuegu.com/mock/3083/articles',
);
this.articles = data.result.rows;
},
};
</script>4 结合数据动态渲染
src/views/Article.vue
<template>
<div class="article-page" v-if="articles">
<div
v-for="item in articles"
:key="item.id"
class="article-item">
<div class="head">
<img :src="item.creatorAvatar" alt="" />
<div class="con">
<p class="title">{{ item.stem }}</p>
<p class="other">{{ item.creatorName }} | {{ item.createdAt }}</p>
</div>
</div>
<div class="body">{{ item.content }}</div>
<div class="foot">点赞 {{ item.likeCount }} | 浏览 {{ item.views }}</div>
</div>
</div>
</template>跳转传参到详情页
1 修改详情页路由, 改成动态路由
router/index.js
const router = new VueRouter({
routes: [
{
path: '/',
component: Layout,
children: [
{ path: '/article', component: Article },
{ path: '/collect', component: Collect },
{ path: '/like', component: Like },
{ path: '/user', component: User },
]
},
{
path: '/detail/:id',
component: ArticleDetail
}
]
})2 注册点击事件,跳转传递参数
src/views/Article.vue
<div
v-for="item in articles"
:key="item.id"
class="article-item"
@click="$router.push(`/detail/${item.id}`)"
>3 获取解析参数
src/views/ArticleDetail.vue
export default {
name: 'ArticleDetailPage',
created() {
console.log(this.$route.params.id)
}
};4 点击返回键,返回上一页
src/views/ArticleDetail.vue
<span class="back" @click="$router.back()"><</span>动态渲染详情页
接口文档说明:
请求地址: https://mock.boxuegu.com/mock/3083/articles/:id
请求方式: get1 发送请求获取数据
src/views/ArticleDetail.vue
import axios from 'axios';
export default {
name: 'article-detail-page',
data() {
return {
article: {}
};
},
async created() {
const { data } = await axios.get(
`https://mock.boxuegu.com/mock/3083/articles/${this.$route.params.id}`,
);
this.article = data.result;
},
};2 页面渲染
src/views/ArticleDetail.vue
<template>
<div class="article-detail-page" v-if="article.id">
<nav class="nav"> <span class="back" @click="$router.back()"><</span> 面经详情</nav>
<header class="header">
<h1>{{article.stem}}</h1>
<p>{{article.createdAt}} | {{article.views}} 浏览量 | {{article.likeCount}} 点赞数</p>
<p><img :src="article.creatorAvatar" alt=""> <span>{{article.creatorName}}</span> </p>
</header>
<main class="body"> {{article.content}} </main>
</div>
</template>路由传参小结-看视频16
一、动态路由传参(优雅):
1. 配动态路由(路径中携带参数)
routes: [
{ path: '/detail/:id', component: 组件名 }
]
2. 跳转
<router-link to="/detail/123">去某页面</router-link>
this.$router.push('/detail/123')
this.$router.push({
path: '/detail/123'
})
3. 获取参数
this.$route.params.id
二、query传参(查询参数传参):
1. 路由正常配
routes: [
{ path: '/detail', component: 组件名 }
]
2. 跳转携带参数
<router-link to="/detail?id=123"></router-link>
this.$router.push('/detail?id=123')
this.$router.push({
path: '/detail',
query: {
id: 123
},
// 这里的params会被忽略
})
3. 获取参数
this.$route.query.id
三、params传参,基于内存的参数传递 (相对鸡肋,刷新会丢失,不太常用)
1. 路由正常配, 额外起个 name
routes: [
{ path: '/detail', name: 'detail', component: 组件名 }
]
2. 跳转,必须通过 name 跳转
this.$router.push({
name: 'detail',
query: {},
params: {}
})
3. 获取参数
this.$route.params组件缓存 keep-alive

基本语法
思考:从面经 点到 收藏,又点回 面经,面经的数据需要重新加载么?不需要,希望缓存下来!
如果希望组件被缓存下来,可以在外面包一个 keep-alive 抽象组件 ~
keep-alive是一个抽象组件,自身不会渲染dom元素,也不会出现在父组件中。
使用keep-alive包裹动态组件时,会缓存不活动的组件实例,并不会销毁它们

三个属性
- include 检测组件内部的name值是否匹配,如果没有name检测局部注册的组件名是否匹配
- exclude 除了
- max
<!-- 逗号分隔字符串 -->
<keep-alive include="a,b">
<component :is="view"></component>
</keep-alive>
<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>
<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
<component :is="view"></component>
</keep-alive>
src/views/Layout.vue
<template>
<div class="h5-wrapper">
<div class="content">
<keep-alive>
<router-view></router-view>
</keep-alive>
</div>
<nav class="tabbar">
<router-link to="/article">面经</router-link>
<router-link to="/collect">收藏</router-link>
<router-link to="/like">喜欢</router-link>
<router-link to="/user">我的</router-link>
</nav>
</div>
</template>keep-alive对应的两个钩子
PS. Vue 2 的终止支持时间是 2023 年 12 月 31 日。在此之后,Vue 2 在已有的分发渠道 (各类 CDN 和包管理器) 中仍然可用,但不再进行更新,包括对安全问题和浏览器兼容性问题的修复等。
当组件被keep-alive管理时,会多出两个生命周期钩子,activated / deactivated
=> keep-alive https://v2.cn.vuejs.org/v2/api/#keep-alive

src/views/Article.vue
export default {
name: 'ArticlePage',
data() {
return {
articles: []
};
},
async created() {
const { data } = await axios.get(
'https://mock.boxuegu.com/mock/3083/articles',
);
this.articles = data.result.rows;
console.log(this.articles)
},
activated() {
console.log('缓存组件被激活')
},
deactivated() {
console.log('缓存组件被隐藏')
}
};
PS.Keep-alive实现原理
- 聊聊keep-alive组件的使用及其实现原理
- LRU缓存-keep-alive实现原理
- https://juejin.cn/post/7165675789885636616
- https://juejin.cn/post/6844903837770203144
- https://juejin.cn/post/7043401297302650917
Object.defineProperty
重点
Vue.js就是基于它实现「响应式系统」的。
首先是使用方法:
/*
obj: 目标对象
prop: 需要操作的目标对象的属性名
descriptor: 描述符
return value 传入对象
*/
Object.defineProperty(obj, prop/key, descriptor)descriptor的一些属性,简单介绍几个属性,具体可以参考 MDN 文档。
enumerable,控制属性是否可枚举,默认 false。configurable,属性描述符是否可以修改,属性是否可以删除,默认 false。get,获取属性的方法。set,设置属性的方法。
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
return xxxx省略
},
set: function reactiveSetter(newVal) {
....省略
}
})
// Ctrl + 鼠标左键,进入函数 Alt+左箭头,回到上一层vm._data = options.data = data
const dataDef: any = {}
dataDef.get = function () {
return this._data
}
const propsDef: any = {}
propsDef.get = function () {
return this._props
}
if (__DEV__) {
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
)
}
propsDef.set = function () {
warn(`$props is readonly.`, this)
}
}
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
// proxy(vm, `_data`, {})
// vm.$data === vm._dataPS. 录屏软件OBS
github => https://github.com/obsproject/obs-studio
