Skip to content

源码原理解析学习指导

为什么要学习源码和底层原理

  1. 面试需要
  2. 架构师 资深前端
    1. 做一些更高端和通用的事情
    2. 组件化,框架
    3. 框架源码里,有大量的最佳实践
    4. vue 源码里,大量的工程化,设计模式,代码规范的最佳实践
  3. 资深前端
    1. 除了经验丰富外
    2. 视野更高
    3. 解决诡异的 bug
    4. 架构的设计

刻意练习

学习一个技能的最好途径,就是刻意练习,天天下象棋的村头王大爷,一定是个臭棋篓子,我天天玩王者荣耀,也是个钻石的菜比 武林高手不是天天打架就可以的,需要练习,需要专门修炼内力,以打 dota 为例

  1. 任务分解
    1. 补刀(不带装备中路正反补 10 分钟)
    2. 装备
    3. 英雄搭配
    4. 兵线
    5. 手速 2.练习
    6. 很枯燥,自找不痛快(补刀毫无游戏乐趣)
    7. 完整的实践修炼
  2. 反馈 4. 高手的源码 (vuejs 源码) 5. 高手点评

源码学习方法

我认为源码学习分为三个层次 0. 根据 package.json 的 npm run build 逻辑找到 vue 执行的入口

  1. 看一遍整体结构,比如把所有函数都折叠起来 看整体逻辑
  2. 参考测试代码,该清楚每个具体函数的输入输出,看明白逻辑
  3. 核心逻辑手写一遍 (虚拟 dom,compile,响应式等)
  4. 组内立 flag 分享,吹牛逼一次

如何熟读源码和手写实现

https://juejin.cn/post/7126095681428668424

1.找文档

  • 1.官方文档
    • 一般官方文档更全面更丰富,维护度也更高
  • 2.源码版本号很重要,不少源码随着版本迭代,发生了破坏性改动,不同版本对应的文档也不同,比如 react-router5 与 react-router6,因此看文档的时候,一定检查下你看的文档版本号和你要学习使用的源码是否一致。
  • 3.关于文档版本号,一般可以在路由地址上或者官网首页上看到,之后再去看下 github 源码对应的版本号,确保两者一致。
  • 4.如果找不到官方文档,可以先去 github 源码看一下,README 里通常是一份简单的文档,有些还是中英文都有的:
  • 5.这个时候如果有官方文档,README 里会有链接指引。如果没有的话,可以查看下源码里是否有一个叫 docs 的文件夹:
    • 一般成熟的源码里都会有这个文件夹,里面就是文档。甚至有些源码下的 docs 比官方文档都全面。
  • 6.现在很多源码项目都是基于 monorepo 开发的,很多 packages 下的文件也都是独立的,看文档的时候,建议不要放过 docs 与 README。

2.github 看示例代码与单元测试|测试用例

  • 经历了文档阶段之后,再根据文档写一些 demo,相信大家对于这套源码已经掌握了至少六七分了,那么接下来想要在某一个难点上继续学习的话,可以看看别人的逻辑,比如示例代码和单元测试。
  • 1.示例代码通常在 examples 文件夹下
  • 2.单元测试是验证源码逻辑的代码,如果你对某个 API 用法不确定准确逻辑,可以去看看单元测试代码或者调试下试试
    • 一般在 test 关键字的文件夹下

3.1 浏览器 github 插件实时看源码

3.2 编辑器阅读源码

    1. 下载库源码文件
    1. vscode 工具安装插件辅助阅读
    • 要下载或者拉取源码来看看了,但是一般源码的代码量比较大,没法一次看完,所以我们可以借助一些工具来辅助阅读,vscode 上建议安装 Bookmarks、Better Comments、Git Blame 等插件。
    • Bookmarks
      • 可以打一些书签,方便记忆,下次可以快速找到某个函数或值。
    • Better Comments
      • 可以做些颜色笔记,看起来比较醒目
    • Git Blame
      • 则可以帮助查看源码的提交记录,注意得是远程拉取下来的代码,直接下载的不可以查看

4.源码阅读

  • 1.首先,我们要先区分下不同的文件夹下放的都是什么,并不是每个都要去看的
    • 先看文件夹命名,命名和框架名称一致的或者叫 core、model 的基本都是核心文件
  • 2.文件目录下直接搜索关键字
    • 比如我想看 createForm 的实现逻辑,直接搜索关键字
  • 3.自己思考功能的实现方式
    • 不了解功能就开始读源码,那读代码会没有方向感
    • 知道了源码有啥功能之后,要先思考下如果自己实现会怎么做。有个大概的思路就行。
    • 如果想不通可以看下源码用到了哪些依赖库,这些依赖库都有啥功能,再想下应该怎么实现。
    • 如果还想不通也没关系,重要的是要先自己思考下实现方式。
  • 4.粗读源码理清实现思路
    • 你已经有了一个大概的实现思路,然后再去读源码,看下它是怎么实现的。和你思路类似的地方很快就可以掠过去,而且印象也很深,和你思路不一样的地方,通过读代码搞清楚它的实现思路。
    • 这一步不用关心细节,知道某段代码是干啥的就行,关键是和自己的思路做 diff,理清它的整体实现思路。
    • 不经过思考直接读源码,理解代码实现思路的效率会降低
  • 5.快速调试代码
  • 5.通过 debugger 理清实现细节
    • 不理清整体思路就开始 debugger,会容易陷入细节,理不清整体的思路
    • 粗读源码理清了实现思路之后,对于一些部分的具体实现可能还不是很清楚,这时候就可以通过 debugger 来断点调试了。
    • 构造一个能触发该功能的测试用例,在关心的代码处打一个断点,通过 debugger 运行代码。
    • 这时候你已经知道这部分代码是干啥的了,单步调试也很容易理清每一条语句的功能,这样一条语句一条语句的搞懂之后,你就很容易能把这部分代码的实现细节理清楚。
    • 这样一部分一部分的通过 debugger 理清细节实现之后,你就对整体代码的思路和细节的实现都有了比较好的掌握。
    • 不 debugger 只大概理下整体思路,这样不能从细节上真正理清楚
  • 6.输出文章来讲述源码实现思路
    • 当你觉得对源码的实现有了比较好的掌握的时候,可以输出一篇文章的方式来讲述源码的整体思路。
    • 因为可能会有一些部分是你没注意到的,而在输出的过程中,会进行更全面的思考,这时候如果发现了一些没有读到的点,可以再通过前面几步去阅读源码,直到能清晰易懂的把源码的实现讲清楚。这样才算真正的把代码读懂了。
    • 这就是我觉得比较高效的阅读源码的方法。
    • 不通过输出文章来检验,那是否真的理清了整体思路和实现细节是没底的
  • vue 和 react 源码阅读:https://juejin.cn/post/6903335881227108366

5.手写实现

  • 读万卷书不如行万里路,动手写一遍
  • 手写我通常是一步步实现,比如先写个最简单的 demo,然后把 api 全部换掉,导入换成自己手写的文件,然后再挨个实现,一边看源码,一边整理思路,直到呈现和源码一样的实现。
  • 手写我通常是一步步实现,比如先写个最简单的 demo,然后把 api 全部换掉,换成自己手写的,然后再挨个实现,一边看源码,一边整理思路,直到呈现和源码一样的实现。

6.github issues

  • 如果你发现看文档和源码的过程中,有很多困惑,甚至遇到一些 bug,可以去 issues 看看,看看大家的发言,也许就能找到答案。

7.加社区群,关注核心维护者

文章:学习源码整体架构系列 - 若川的专栏 - 掘金 (juejin.cn)

源码解析和技术实现

vue项目开发步骤

  • 1.创建项目

  • 2.项目配置

  • 代码编写规范

  • 代码的质量及代码风格

  • 2.1 项目UI框架css框架或其他框架选择使用和配置

    • 按需加载组件配置
  • 3.router.js路由设计

  • 4.菜单路由结合

  • 5.1 路由管理用户权限

    • utils->auth.js权限模块

      • export function getCurrentAuthority () { // 后台接口请求返回的权限逻辑 return ['admin']; } export function check (authority) { const current = getCurrentAuthority () return current.some(item => authority.includes(item)) } export function isLogin (authority) { const current = getCurrentAuthority () return current && current[0] !== "guest"; }
    • router.js模块

      • // 顶部导入 import findLast from "lodash/findLast "; import { check, isLogin } from "./utils/auth "; router.beforeEach(to, from, next) => { if(to.path !== from.path) { // 页面加载效果 } const record = findLast(to.matched, record => record.meta.authority) // 判断是否有权限 if(record && !check(record.meta.authority)) { if(!isLogin() && to.path !== "/user/login") { // 写提示逻辑后跳转路由:提示逻辑自己写 next({ path: "/user/login" }) } else if (to.path !== "/403") { // 写提示逻辑后跳转路由:提示逻辑自己写 next({ path: "/403" }) } // 页面加载结束逻辑 } next(); });
    • 菜单边栏组件的判断

      • // 顶部导入方法 import { check } from "../utils/auth "; // 获取菜单数据方法里写判断逻辑 // item 是for循环for(let item of routers)遍历出来的菜单 if(item.meta && item.meta.authority && !check(record.meta.authority)){ break; }
  • 5.2 权限组件和权限指令

    • 权限组件

      • 1.components组件:Authorized.vue

        • import { check } from "../utils/auth "; export default { props: { authority: { type: Array, required: true } }, render (h, context) { const { props, scopedSlots } = context; return check(props.authority) ? scopedSlots.default() : null; } }
      • 2.把组件注册到全局main.js

        • // 顶部导入 import Authorized from "./components/Authorized" // 注册到全局 Vue.component(‘Authorized’, Authorized);
      • 3.使用权限组件

      • 优点

        • 比较灵活
    • 权限指令

      • 1.directives文件夹 -> auth.js

        • import { check } from "../utils/auth "; function install(Vue, options) = {}) { Vue.directive(options.name || "auth", { inserted(el, binding) { if(!check(binding.value)) { el.parentNode && el.parentNode.removeChild(el) } } }) } export default { install };
      • 2.指令全局注册 -> main.js

        • // 顶部导入 import Auth from “./directives/auth”; Vue.use(Auth)
      • 3.组件内使用定义好的权限指令

        • v-auth="['admin']"

        • 显示隐藏一些需要权限的图标或组件

      • 弊端

        • 只有第一次成才生效,如果权限动态更改了,就不能再加回来
  • 6.与服务端请求交互Axios配置

    • 1.utils文件夹 -> request.js 二次封装请求

      • import axios from 'axios'; import { notification } from "ant-design-vue"; function request(options) { return axios(options).then(res => { return res; }) .catch(error => { const { response: { status, statusText } } = error; // 返回错误信息 notification.error({ // 返回渲染模板 message: h => ( 请求错误{ status } : { options.url } ), description: statusText }) return Promise.reject(error); }) } export default request;
    • 2.设置请求响应和请求拦截

    • 3.导入页面使用

      • import request from '../../utils/request' request({ url: '/api/user', method: 'get', params: { id:123456 } }).then(res =>{ if(res.code === 0){ // 请求成功逻辑 } }).catch(error){ console.log(error) }
  • 7.使用图标和定制动态切换的主题

  • 8.构建和打包发布

    • 优化项目打包构建

      • 组件使用babel按需加载

      • 路由中使用webpack路由懒加载和拆包

        • 子主题 1
      • 组件中使用lodash 防抖和节流插件

        • import { debounce } from ‘lodash’;
      • 图标的按需加载

    • 生成打包报告

      • npm run build -- --report

      • 会在dist文件夹内生成report.html文件

    • 分析哪些库需要优化

      • 1.浏览器 点击打开dist文件夹内report.html文件

      • 2.分析和优化

        • 可以在组件库文档或者查询怎么优化相关包
    • 构建配置优化

  • 9.组件的单元测试

    • jest.config.js单元测试配置

    • .eslintrc.js配置

    • 组件或js目录下新建如 auth.js 的单元测试文件:例子:以.spec.js结尾:auth.spec.js

      • import { check, currentAuth } from "./auth"; decribe("auth test", () => { it("empty auth", () => { currentAuth.splice(0, currentAuth.length); expect(check(['user'])).toEqual(false); expect(check(['admin'])).toEqual(false); }) it("user auth", () => { currentAuth.splice(0, currentAuth.length); currentAuth.push("user") expect(check(['user'])).toEqual(true); expect(check(['admin'])).toEqual(false); }) it("admin auth", () => { currentAuth.push("admin") expect(check(['user'])).toEqual(true); expect(check(['admin'])).toEqual(true); expect(check(['user'], ['admin'])).toEqual(true); }) })
    • 3.开启单元测试

      • npm run test:unit -- --watch
  • 10.组件发布到npm

  • 11.代码提交git规范

代码调试和debugger

源码解析

  • 如何熟读源码和手写实现

    • https://juejin.cn/post/7126095681428668424

      1. 找文档
      • 1.官方文档

        • 一般官方文档更全面更丰富,维护度也更高
      • 2.源码版本号很重要,不少源码随着版本迭代,发生了破坏性改动,不同版本对应的文档也不同,比如react-router5与react-router6,因此看文档的时候,一定检查下你看的文档版本号和你要学习使用的源码是否一致。

      • 3.关于文档版本号,一般可以在路由地址上或者官网首页上看到,之后再去看下github源码对应的版本号,确保两者一致。

      • 4.如果找不到官方文档,可以先去github源码看一下,README里通常是一份简单的文档,有些还是中英文都有的:

      • 5.这个时候如果有官方文档,README里会有链接指引。如果没有的话,可以查看下源码里是否有一个叫docs的文件夹:

        • 一般成熟的源码里都会有这个文件夹,里面就是文档。甚至有些源码下的docs比官方文档都全面。
      • 6.现在很多源码项目都是基于monorepo开发的,很多packages下的文件也都是独立的,看文档的时候,建议不要放过docs与README。

      1. github看示例代码与单元测试|测试用例
      • 经历了文档阶段之后,再根据文档写一些demo,相信大家对于这套源码已经掌握了至少六七分了,那么接下来想要在某一个难点上继续学习的话,可以看看别人的逻辑,比如示例代码和单元测试。

      • 1.示例代码通常在examples文件夹下

      • 2.单元测试是验证源码逻辑的代码,如果你对某个API用法不确定准确逻辑,可以去看看单元测试代码或者调试下试试

        • 一般在test关键字的文件夹下
    • 3.1 浏览器github插件实时看源码

    • 3.2 编辑器阅读源码

        1. 下载库源码文件
        1. vscode工具安装插件辅助阅读
        • 要下载或者拉取源码来看看了,但是一般源码的代码量比较大,没法一次看完,所以我们可以借助一些工具来辅助阅读,vscode上建议安装Bookmarks、Better Comments、Git Blame等插件。

        • Bookmarks

          • 可以打一些书签,方便记忆,下次可以快速找到某个函数或值。
        • Better Comments

          • 可以做些颜色笔记,看起来比较醒目
        • Git Blame

          • 则可以帮助查看源码的提交记录,注意得是远程拉取下来的代码,直接下载的不可以查看
      1. 源码阅读
      • 1.首先,我们要先区分下不同的文件夹下放的都是什么,并不是每个都要去看的

        • 先看文件夹命名,命名和框架名称一致的或者叫core、model的基本都是核心文件
      • 2.文件目录下直接搜索关键字

        • 比如我想看createForm的实现逻辑,直接搜索关键字
      • 3.自己思考功能的实现方式

        • 不了解功能就开始读源码,那读代码会没有方向感

        • 知道了源码有啥功能之后,要先思考下如果自己实现会怎么做。有个大概的思路就行。

        • 如果想不通可以看下源码用到了哪些依赖库,这些依赖库都有啥功能,再想下应该怎么实现。

        • 如果还想不通也没关系,重要的是要先自己思考下实现方式。

      • 4.粗读源码理清实现思路

        • 你已经有了一个大概的实现思路,然后再去读源码,看下它是怎么实现的。和你思路类似的地方很快就可以掠过去,而且印象也很深,和你思路不一样的地方,通过读代码搞清楚它的实现思路。

        • 这一步不用关心细节,知道某段代码是干啥的就行,关键是和自己的思路做 diff,理清它的整体实现思路。

        • 不经过思考直接读源码,理解代码实现思路的效率会降低

      • 5.快速调试代码

      • 5.通过 debugger 理清实现细节

        • 不理清整体思路就开始 debugger,会容易陷入细节,理不清整体的思路

        • 粗读源码理清了实现思路之后,对于一些部分的具体实现可能还不是很清楚,这时候就可以通过 debugger 来断点调试了。

        • 构造一个能触发该功能的测试用例,在关心的代码处打一个断点,通过 debugger 运行代码。

        • 这时候你已经知道这部分代码是干啥的了,单步调试也很容易理清每一条语句的功能,这样一条语句一条语句的搞懂之后,你就很容易能把这部分代码的实现细节理清楚。

        • 这样一部分一部分的通过 debugger 理清细节实现之后,你就对整体代码的思路和细节的实现都有了比较好的掌握。

        • 不 debugger 只大概理下整体思路,这样不能从细节上真正理清楚

      • 6.输出文章来讲述源码实现思路

        • 当你觉得对源码的实现有了比较好的掌握的时候,可以输出一篇文章的方式来讲述源码的整体思路。

        • 因为可能会有一些部分是你没注意到的,而在输出的过程中,会进行更全面的思考,这时候如果发现了一些没有读到的点,可以再通过前面几步去阅读源码,直到能清晰易懂的把源码的实现讲清楚。这样才算真正的把代码读懂了。

        • 这就是我觉得比较高效的阅读源码的方法。

        • 不通过输出文章来检验,那是否真的理清了整体思路和实现细节是没底的

      • vue和react源码阅读

    • 5.手写实现

      • 读万卷书不如行万里路,动手写一遍

      • 手写我通常是一步步实现,比如先写个最简单的demo,然后把api全部换掉,导入换成自己手写的文件,然后再挨个实现,一边看源码,一边整理思路,直到呈现和源码一样的实现。

      • 手写我通常是一步步实现,比如先写个最简单的demo,然后把api全部换掉,换成自己手写的,然后再挨个实现,一边看源码,一边整理思路,直到呈现和源码一样的实现。

      1. github issues
      • 如果你发现看文档和源码的过程中,有很多困惑,甚至遇到一些bug,可以去issues看看,看看大家的发言,也许就能找到答案。
      1. 加社区群,关注核心维护者
  • 源码学习活动

  • 源码架构学习文章

  • jQuery源码解析

  • vue2源码解析

  • vue3源码解析

  • petite-vue源码解析

  • vue-router源码解析

    • 源码地址

    • vue-router实现原理文档

    • 实现原理

      • vue-router实现了再不跳转页面的情况下更新视图,也就是只有一个页面

      • vue-router的三种模式

        • history模式

          • 概述

            • html5标准中,为 history 添加了pushState()、replaceState()方法,以及 onpopstate 事件。但原理和 hash 方式是相同的

            • history模式并不会向服务器发送请求,是因为vue-cli对history模式做了处理

            • 通过 history模式 实现单页路由的 URL 没有 #。但因为没有 # ,所以当用户刷新页面之类的操作时,浏览器还是会给服务器发送请求

            • 为了避免出现这种情况,所以这个实现需要服务器的支持,需要把所有路由都重定向到根页面

          • 底层实现原理

            • ​ 登录 桌面


              let login=document.querySelector('#login') let content=document.querySelector('#content') login.addEventListener('click',function(e){ e.preventDefault(); history.pushState({name:'loginname'},'login','/login') content.innerHTML="登录" }) let desk=document.querySelector('#desk') desk.addEventListener('click',function(e){ e.preventDefault(); history.pushState({name:'deskname'},'desk','/desk') content.innerHTML="桌面" }) window.onpopstate=function(e){ let name=e.state.name if(name=='loginname'){ content.innerHTML="这是登录" }else{ content.innerHTML="这是桌面" } }

              • history.pushState()

              • 路径就是普通的url,通过history.pushState()方法来改变地址栏,并把当前地址记录在浏览器的访问历史中,并不会真正的跳到指定的路径,也就是浏览器不会向服务器发送请求。

              • 通过监听popstate事件,可以监听到浏览器历史操作的变化,在popstate事件中可以记录浏览器地址栏改变后的地址,要注意的是,调用history.pushSate()和history.replaceState()不会触发popstate事件,只有点击浏览器的前进后退按钮及调用history.forward()、history.back()、history.go()方法时才会触发popstate事件。

              • 最后通过路由找到对应的组件,渲染在浏览器中

        • hash模式

          • 概述

            • 是以url中#后面的内容作为路由地址,可以直接通过location.url来切换浏览器的地址,如果只改变了#后面的内容,浏览器不会向服务器请求这个地址,但是会把这个地址记录在浏览器的访问记录中,当hash改变后,要监听hash的变化,并做相应的处理,我们可以监听hashchange事件,当hash发生变化时,会触发hashchange事件,在hashchange事件中记录当前路由地址,并找到当前路由对应的组件,重新渲染在浏览器中

            • hash模式实现的路由地址有 #

            • #后面的内容不会传给服务端,也就是说不会重新刷新页面,并且路由切换的时候也不会重新加载页面。

            • hash必须和原先的值不同,才能新增会话浏览历史的记录。

          • 底层实现原理

            • ​ 登录 桌面
              let content= document.querySelector('#content') window.onhashchange=function(e){ console.log(window.history.state) let {newURL}=e if(newURL.endsWith('#/login')){ content.innerHTML='这是登录内容' } else if(newURL.endsWith('#/desk')){ content.innerHTML='这是桌面内容' } }
        • abstract模式

          • 概述

            • 支持所有JavaScript运行环境,如Node.js服务器端。

            • abstract模式是使用一个不依赖于浏览器的浏览历史虚拟管理后端。

            • 根据平台差异可以看出,在 Weex 环境中只支持使用 abstract 模式。 不过,vue-router 自身会对环境做校验,如果发现没有浏览器的 API,vue-router 会自动强制进入 abstract 模式,所以 在使用 vue-router 时只要不写 mode 配置即可,默认会在浏览器环境中使用 hash 模式,在移动端原生环境中使用 abstract 模式。 (当然,你也可以明确指定在所有情况下都使用 abstract 模式)。

    • 手写router路由 (简单思路)

      • 需求分析

        • 作为一个插件存在:实现vue-router类和install方法

        • 实现两个全局组件:router-view用于显示匹配组件内容,router-link用于跳转

        • 监控url变化:监听hashchange或者popstate事件

        • 响应最新url:创建一个响应式的属性current,当它改变时获取对应组件并显示

      • 源码实现 src\krouter

        • index.js应用路由

          • import Vue from 'vue' import VueRouter from './kvue-router' import Home from '../views/Home.vue' // 1.应用插件 Vue.use(VueRouter) const routes = [ { path: '/', name: 'home', component: Home }, { path: '/about', name: 'about', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') } ] // 2.创建实例 const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default router
        • krouter-router.js实现

          • import Link from './krouter-link' import View from './krouter-view' let Vue; // 1.实现一个插件:挂载$router class KVueRouter { constructor(options) { this.$options = options console.log(this.$options); // 需要创建响应式的current属性 // 利用Vue提供的defineReactive做响应化 // 这样将来current变化的时候,依赖的组件会重新render Vue.util.defineReactive(this, 'current', '/') // this.app = new Vue({ // data() { // return { // current: '/' // } // } // }) // 监控url变化 window.addEventListener('hashchange', this.onHashChange.bind(this)) window.addEventListener('load', this.onHashChange.bind(this)) // 创建一个路由映射表 this.routeMap = {} options.routes.forEach(route => { this.routeMap[route.path] = route }) } onHashChange() { console.log(window.location.hash); this.current = window.location.hash.slice(1) } } KVueRouter.install = function (_Vue) { // 保存构造函数,在KVueRouter里面使用 Vue = _Vue; // 挂载$router // 怎么获取根实例中的router选项 Vue.mixin({ beforeCreate() { // 确保根实例的时候才执行 if (this.$options.router) { Vue.prototype.$router = this.$options.router } } }) // 任务2:实现两个全局组件router-link和router-view Vue.component('router-link', Link) Vue.component('router-view', View) } export default KVueRouter
        • krouter-link.js实现

          • export default { props: { to: { type: String, required: true }, }, render(h) { // abc // // h(tag, data, children) console.log(this.$slots); return h('a', { attrs: { href: '#' + this.to } }, this.$slots.default) // return {this.$slots.default} } }
        • krouter-view.js实现

          • export default { render(h) { //获取path对应的component const {routeMap, current} = this.$router; console.log(routeMap,current); const component = routeMap[current].component || null; return h(component) } }
  • vuex源码解析

    • vuex库github地址

    • 实现原理文档

    • 前置介绍

      • 实现原理

        • 采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
      • Vuex的构成

        • 1)state

          • state是存储的单一状态,是存储的基本数据。
        • 2)Getters

          • getters是store的计算属性,对state的加工,是派生出来的数据。就像computed计算属性一样,getter返回的值会根据它的依赖被缓存起来,且只有当它的依赖值发生改变才会被重新计算。
        • 3)Mutations

          • mutations提交更改数据,使用store.commit方法更改state存储的状态。(mutations同步函数)
        • 4)Actions

          • actions像一个装饰器,提交mutation,而不是直接变更状态。(actions可以包含任何异步操作)
        • 5)Module

          • Module是store分割的模块,每个模块拥有自己的state、getters、mutations、actions。
        • 以上使用例子

          • const moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: { ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> moduleA 的状态 store.state.b // -> moduleB 的状态
        • 6)辅助函数

          • Vuex提供了mapState、MapGetters、MapActions、mapMutations等辅助函数给开发在vm中处理store。
      • Vuex的使用

        • import Vuex from 'vuex'; Vue.use(Vuex); // 1. vue的插件机制,安装vuex let store = new Vuex.Store({ // 2.实例化store,调用install方法 state, getters, modules, mutations, actions, plugins }); new Vue({ // 3.注入store, 挂载vue实例 store, render: h=>h(app) }).$mount('#app');
      • Vuex的设计思想

        • Vuex的设计思想,借鉴了Flux、Redux,将数据存放到全局的store,再将store挂载到每个vue实例组件中,利用Vue.js的细粒度数据响应机制来进行高效的状态更新
    • 手写vuex状态管理

      • 需求分析

        • vuex的store是如何挂载注入到组件中

        • vuex的state和getters是如何映射到各个组件实例中响应式更新状态

        • 浅层次实现

      • 源码实现 src\kstore

        • index.js使用vuex

          • import Vue from 'vue' import Vuex from './kvuex' Vue.use(Vuex) export default new Vuex.Store({ state: { counter: 0 }, getters: { doubleCounter(state) { return state.counter * 2 } }, mutations: { add(state) { state.counter++ // this.state } }, actions: { // 结构上下文 add({ commit }) { setTimeout(() => { commit('add') }, 1000); } }, modules: { } })
        • kvuex.js实现vuex

          • // 保存构造函数引用,避免import let Vue; class Store { constructor(options) { // this.$options = options; this._mutations = options.mutations; this._actions = options.actions; // 响应化处理state // this.state = new Vue({ // data: options.state // }) this._vm = new Vue({ data: { // 加两个$,Vue不做代理 $$state: options.state } }) // 绑定commit、dispatch的上下文问store实例 this.commit = this.commit.bind(this) this.dispatch = this.dispatch.bind(this) } // 存取器, store.state get state() { console.log(this._vm); return this._vm._data.$$state } set state(v) { console.error('你造吗?你这样不好!'); } // store.commit('add', 1) // type: mutation的类型 // payload:载荷,是参数 commit(type, payload) { const entry = this._mutations[type] if (entry) { entry(this.state, payload) } } dispatch(type, payload) { const entry = this._actions[type] if (entry) { entry(this, payload) } } } function install(_Vue) { Vue = _Vue; Vue.mixin({ beforeCreate() { if (this.$options.store) { Vue.prototype.$store = this.$options.store } } }) } // Vuex export default { Store, install }
  • Pinia 状态管理源码解析

  • element UI源码解析

项目实战

  • Vue移动书城实现

    • 文档和源码

    • 功能

      • 书架

        • 私密阅读

        • 离线阅读

        • 分组

        • 移除书架

      • 阅读器

        • 阅读器支持格式

          • txt

            • txt是纯文件文件,它无法支持多媒体格式,小说类应用中使用较多,但无法满足电子出版物的需求;
          • pdf

          • epub

            • ePub是目前最主流的电子书格式,电子书内容采用html显示,由css控制样式,不论在PC端还是移动端都有非常好的显示效果,不足之处在于文件容量较大,而且解析时需要解压,会消耗性能,可以借助本地缓存技术来缓解这个问题;

            • 本质上是一个zip文件,可以通过 在cmd中 用unzip进行解压

            • epub.js库

            • 完整的ePub阅读器的开发流程

              • 解析:获取图书的基本信息、目录信息、章节信息等

              • 渲染:在界面上展示电子书内容,支持屏幕尺寸自适应

              • 翻页:支持上一页和下一页的翻页操作

              • 字号:支持修改文字的字号大小

              • 字体:支持修改文字的字体,能够支持CSS3的Web字体

              • 主题:支持切换阅读器的背景色和文字颜色

              • 进度:支持动态切换阅读器的显示进度

              • 目录:支持多级目录展示,点击目录快速跳转到指定章节

              • 搜索:支持全文搜索和章节搜索

              • 书签:支持将当前的阅读位置保存为书签,并能回溯

              • 笔记:支持选中一段文本后加入笔记

              • 适配:针对PC端和移动端进行专门的适配处理

            • 安装

              • npm install --save epubjs
          • mobi

            • mobi是亚马逊Kindle的电子书格式,需要在Kindle中阅读。
        • 阅读器功能

          • 解析

            • 获取图书的基本信息、目录信息、章节信息等
          • 渲染

            • 在界面上展示电子书内容,支持屏幕尺寸自适应
          • 翻页效果

            • 实现各种复杂手势+交互动画,如何兼容手势+鼠标操作

            • 效果

              • 覆盖

              • 滑动

              • 仿真

              • 滚动

              • 无动画

            • 翻页功能的实现

              • 当屏幕向左划时,向上翻页,反之翻下一页

              • //监听滑动翻页事件 this.rendition.on("touchstart",event=>{ this.touchStartX = event.changedTouches[0].clientX; this.touchStartTime = event.timeStamp; }); this.rendition.on("touchend",event=>{ const offsetX = event.changedTouches[0].clientX-this.touchStartX; const time = event.timeStamp - this.touchStartTime; if(time<500 && offsetx>40){ this.prevPage(); }else if (time<500 && offsetX<-40) { this.nextPage(); }else{ this.toggleTitleAndMenu() } })

              • // 上一页下一页 prevPage(){ if (this.rendition){ this.rendition.prev(); // this.$store.dispatch("setMenuVisible",false) this.setMenuVisible(false); } }, nextPage(){ if (this.rendition){ this.rendition.next(); // this.$store.dispatch("setMenuVisible",false) this.setMenuVisible(false); } },

            • 字号UI的设置

            • 字体

            • 缩进

            • 边距

            • 繁简

            • 文字颜色和背景(长按自定义)

          • 主题

            • 支持切换阅读器的背景色和文字颜色

            • 白天黑夜主题切换

          • 阅读进度

            • 支持动态切换阅读器的显示进度
          • 目录章节

            • 支持多级目录展示,点击目录快速跳转到指定章节
          • 搜索

            • 全文搜索

            • 章节搜索

          • 书签

            • 支持将当前的阅读位置保存为书签,并能回溯

            • 书签手势操作

          • 笔记

            • 支持选中一段文本后加入笔记
          • 朗读

            • 利用vuex+mixin实现组件解耦+复用

            • 科大讯飞web在线语音合成API开发

      • 书城

        • 搜索

          • 历史搜索

          • 热门搜索

        • 浏览

        • 推荐

      • 详情

        • 基本信息

        • 目录结构

        • 加入书架

        • 阅读本书

      • 听书

        • 语音合成

        • 播放器

        • 内容预览

      • 适配

        • viewport配置

          • html
            <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no">
        • rem设置+自适应布局实现思路

          • global.scss和reset.scss设置

          • 在App.vue中编写script代码

          • js
             export default {      name:"App"  }  document.addEventListener('DOMContentLoaded', () => {      const html = document.querySelector('html')      let fontSize = window.innerWidth / 10      fontSize = fontSize > 50 ? 50 : fontSize      html.style.fontSize = fontSize + 'px'  })
    • 技术栈

      • uniapp基于Vue3语法技术栈

        • vue全家桶
      • css

        • 字体图标

        • 动画

        • less

        • rem

      • html5

        • range控件

        • audio控件

        • localStorage

        • IndexedDB

        • 离线存储机制设计:LocalStorage+IndexDB

      • JavaScript

        • ES6

        • mock.js

        • touch、mouse事件

        • axios

      • 发布

        • Git

        • Node.js

        • 服务器

        • Nginx

    • 接口功能

  • Vue实现移动音乐应用

    • 页面

      • 推荐页面

        • banner轮播图

        • 热门歌单列表

          • 歌单标题

          • 歌单的歌曲操作

            • 播放全部
          • 歌单的歌曲列表

            • 歌名

            • 歌手

      • 歌手页面

        • 歌手

          • 头像

          • 名字

        • 右边按字母排序的跳转

        • 歌手详情页

          • 歌名

          • 歌手

      • 歌手详情页

      • 播放器页面

        • 播放器内核

          • 子主题 1
        • 功能

          • 随机播放

          • 切换上一首

          • 切换下一首

          • 暂停和播放

          • 歌曲收藏和喜欢

          • 播放进度

          • 播放长度

          • 歌词显示

          • 封面显示

      • 歌单页面

      • 排行榜页面

      • 榜单列表页面

      • 搜索页面

        • 热门搜索

        • 搜索历史

          • 单个删除

          • 全部删除

        • 搜索关键词实时检索歌曲和歌手

      • 歌曲列表页

      • 用户中心页

    • 技术栈实现

      • vue3技术栈

      • UI库

      • 状态管理

        • pina
      • 路由

        • vuex
      • 服务端交互

        • axios
      • 效果插件

      • 工具支持

        • 脚手架

          • vue-cli

          • vite

        • 自动化构建

          • webpack
        • 代码格式检查

          • eslint
  • 移动影视应用开发

实现效果

算法题

建立属于自己的前端组件库

  • https://juejin.cn/post/7124487017588588574

  • 技术栈

    • vite3、vitepress、vitest、vue3、tsx
  • 微脚手架用于

    • 生成组件开发模版

    • 组件文档模版

    • 生成组件主题文件

    • 打包发布npm

  • 使用 vitepress 生成组件文档

    • githup Actions 结合 githup pages 自动部署
  • 组件库组件主题切换实现

GitHub开源项目的运营的相关生态

Released under the MIT License.