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 axios
2 接口文档说明:
请求地址: 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
请求方式: get
1 发送请求获取数据
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._data
PS. 录屏软件OBS
github => https://github.com/obsproject/obs-studio