Skip to content

pnpm

关于现代包管理器的深度思考--为什么现在我更推荐 pnpm 而不是 npm/yarn?

pnpm 本质上就是一个包管理器,这一点跟 npm/yarn 没有区别,但它作为杀手锏的两个优势在于:

  • 包安装速度极快;
  • 磁盘空间利用非常高效。

安装

bash
# 全局安装pnpm
npm i pnpm -g
# 查看pnpm版本 
pnpm -v

常用命令

link-CLI命令

npm 命令pnpm 等效
npm installhttps://pnpm.io/zh/cli/install
npm i pkgpnpm add pkg
npm run cmdpnpm cmd

bash
# 安装依赖包到 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)。

查看实际安装存储路径

bash
pnpm store path

清除未被项目引用到的包

bash
pnpm store prune

路由使用巩固 - 练习

目标:实现以下切换效果

Untitled

Untitled

声明式导航 - 跳转传参

目标: 在跳转路由时, 可以给路由对应的组件内传值

查询参数传参

Untitled

动态路由传参

Untitled

动态路由匹配 | Vue Router

模式匹配路径$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

  1. 创建views/Part.vue - 准备接收路由上传递的参数和值

    html
    <template>
      <div>
          <p>关注明星</p>
          <p>发现精彩</p>
          <p>寻找伙伴</p>
          <p>加入我们</p>
          <p>人名: {{ $route.query.name }} -- {{ $route.params.username }}</p>
      </div>
    </template>
  2. 路由定义

    html
    {    path: "/part",    component: Part  },  {    path: "/part/:username", // 有:的路径代表要接收具体的值    component: Part  },
  3. 导航跳转, 传值给MyGoods.vue组件

    html
    <router-link to="/part?name=小传">朋友-小传</router-link>
    <router-link to="/part/小智">朋友-小智</router-link>

Untitled

PS. 并不能说 查询参数的模式不好~~ 动态路由更灵活一些,

⇒ user/:user/city/:city/hobby/:hobby 动态路由

小结

  • 声明式导航跳转时,如何传参?
    1. to=”/path路径?参数key=参数value”
    2. to=”/path/值”
  • 如何接收路由参数
    1. $route.query.参数key =⇒ 查询参数
    2. $route.params.参数key =⇒ 动态路由

Untitled

vue路由 - 重定向和模式

路由 - 重定向

目标: 匹配path后, 强制切换到目标path上

Untitled

例如: 网页默认打开, 匹配路由“/”, 强制切换到“/find”上

jsx
const routes = [
  {
    path: "/", // 默认hash值路径
    redirect: "/find" // 重定向到/find
    // 浏览器url中#后的路径被改变成/find-重新匹配数组规则
  }
]

总结: 强制重定向后, 还会重新来数组里匹配一次规则

Untitled

Untitled

路由 - 404页面

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

动态路由匹配 | Vue Router

Untitled

路由 { path: '*', component:NotFound }通常用于客户端 404 错误。

匹配优先级

有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:路由定义得越早,优先级就越高。

⇒ 当使用通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后

默认给一个404页面

Untitled

语法: 路由最后, path匹配*(任意路径) – 前面不匹配就命中最后这个, 显示对应组件页面

  1. 创建NotFound页面

    html
    <template>
      <img src="../assets/404.png" alt="">
    </template>
    
    <script>
    export default {
    
    }
    </script>
    
    <style scoped>
        img{
            width: 100%;
        }
    </style>
  2. 在main.js - 修改路由配置

    jsx
    import NotFound from '@/views/NotFound'
    
    const routes = [
      // ...省略了其他配置
      // 404在最后(规则是从前往后逐个比较path)
      {
        path: "*",
        component: NotFound
      }
    ]

总结: 如果路由未命中任何规则, 给出一个兜底的404页面

Untitled

路由 - 模式设置

Untitled

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

Untitled

HTML5 History 模式 | Vue Router

router/index.js

jsx
const router = new VueRouter({
  routes,
  mode: "history" // 打包上线后需要后台支持, 默认是hash
})

vue路由 - 编程式导航

Untitled

编程式导航用JS代码跳转

声明式导航用a标签

编程式导航 - 基础使用

Untitled

目标: 用JS代码来进行跳转

语法:

jsx
this.$router.push({
    path: "路由路径", // 都去 router/index.js定义
    name: "路由名"
})
  1. main.js - 路由数组里, 给路由起名字
jsx
{
    path: "/find",
    name: "Find",
    component: Find
},
{
    path: "/my",
    name: "My",
    component: My
},
{
    path: "/part",
    name: "Part",
    component: Part
},
  1. App.vue - 换成span 配合js的编程式导航跳转
html
<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>

Untitled

编程式导航 - 跳转传参

Untitled

目标: JS跳转路由, 传参

Untitled

语法 query / params 任选 一个

编程式的导航 | Vue Router

jsx
this.$router.push({
    path: "路由路径"
    name: "路由名",
    query: {
    	"参数名": 值
    }
    params: {
		"参数名": 值
    }
})

// 对应路由接收   $route.params.参数名   取值
// 对应路由接收   $route.query.参数名    取值

==格外注意: 使用path会自动忽略params==

App.vue

html
<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>

综合练习 - 面经基础版

img

html
首页:一级
- 面经:二级
- 收藏:二级
- 喜欢:二级
- 我的:二级

文章详情:一级

配置一级路由

js
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const router = new VueRouter({
    routes:[]  
})

export default router

配置首页 和 详情页两个一级路由

jsx
const router = new VueRouter({
  routes: [
      {path:'/', component:Layout},
      {path:'/detail', component:Detail},
  ]
})

配置二级嵌套路由

嵌套路由 | Vue Router

Untitled

Untitled

Untitled

目标: 在现有的一级路由下, 再嵌套二级路由

利用 children 配置二级路由

router/index.js

jsx
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

jsx
<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

bash
yarn  add  axios
pnpm  i / add  axios

2 接口文档说明:

请求地址: https://mock.boxuegu.com/mock/3083/articles
请求方式: get

Untitled

3 data中提供数据, created 中发送请求,获取数据

src/views/article.vue

jsx
<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

jsx
<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

jsx
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

jsx
<div
  v-for="item in articles"
  :key="item.id"
  class="article-item"
  @click="$router.push(`/detail/${item.id}`)"
>

3 获取解析参数

src/views/ArticleDetail.vue

jsx
export default {
  name: 'ArticleDetailPage',
  created() {
    console.log(this.$route.params.id)
  }
};

4 点击返回键,返回上一页

src/views/ArticleDetail.vue

jsx
<span class="back" @click="$router.back()">&lt;</span>

动态渲染详情页

接口文档说明:

请求地址: https://mock.boxuegu.com/mock/3083/articles/:id
请求方式: get

1 发送请求获取数据

src/views/ArticleDetail.vue

jsx
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

html
<template>
  <div class="article-detail-page" v-if="article.id">
    <nav class="nav"> <span class="back" @click="$router.back()">&lt;</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

jsx
一、动态路由传参(优雅):
  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

img

基本语法

思考:从面经 点到 收藏,又点回 面经,面经的数据需要重新加载么?不需要,希望缓存下来!

如果希望组件被缓存下来,可以在外面包一个 keep-alive 抽象组件 ~

keep-alive是一个抽象组件,自身不会渲染dom元素,也不会出现在父组件中。

使用keep-alive包裹动态组件时,会缓存不活动的组件实例,并不会销毁它们

img

三个属性

  1. include 检测组件内部的name值是否匹配,如果没有name检测局部注册的组件名是否匹配
  2. exclude 除了
  3. max
js
<!-- 逗号分隔字符串 -->
<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>

Untitled

src/views/Layout.vue

jsx
<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

img

src/views/Article.vue

jsx
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('缓存组件被隐藏')
  }
};

img

PS.Keep-alive实现原理

  1. 聊聊keep-alive组件的使用及其实现原理
  2. LRU缓存-keep-alive实现原理
  3. https://juejin.cn/post/7165675789885636616
  4. https://juejin.cn/post/6844903837770203144
  5. https://juejin.cn/post/7043401297302650917

Object.defineProperty

重点

Vue.js就是基于它实现「响应式系统」的。

首先是使用方法:

jsx
/*
    obj: 目标对象
    prop: 需要操作的目标对象的属性名
    descriptor: 描述符
    
    return value 传入对象
*/
Object.defineProperty(obj, prop/key, descriptor)

descriptor的一些属性,简单介绍几个属性,具体可以参考 MDN 文档

  • enumerable,控制属性是否可枚举,默认 false。
  • configurable,属性描述符是否可以修改,属性是否可以删除,默认 false。
  • get,获取属性的方法。
  • set,设置属性的方法。
jsx
Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {

      return xxxx省略 
    },
    set: function reactiveSetter(newVal) {
	     ....省略
    }
  })
// Ctrl + 鼠标左键,进入函数   Alt+左箭头,回到上一层

vm._data = options.data = data

jsx
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

=> https://obsproject.com/

github => https://github.com/obsproject/obs-studio