源码原理解析学习指导
为什么要学习源码和底层原理
- 面试需要
- 架构师 资深前端
- 做一些更高端和通用的事情
- 组件化,框架
- 框架源码里,有大量的最佳实践
- vue 源码里,大量的工程化,设计模式,代码规范的最佳实践
- 资深前端
- 除了经验丰富外
- 视野更高
- 解决诡异的 bug
- 架构的设计
刻意练习
学习一个技能的最好途径,就是刻意练习,天天下象棋的村头王大爷,一定是个臭棋篓子,我天天玩王者荣耀,也是个钻石的菜比 武林高手不是天天打架就可以的,需要练习,需要专门修炼内力,以打 dota 为例
- 任务分解
- 补刀(不带装备中路正反补 10 分钟)
- 装备
- 英雄搭配
- 兵线
- 手速 2.练习
- 很枯燥,自找不痛快(补刀毫无游戏乐趣)
- 完整的实践修炼
- 反馈 4. 高手的源码 (vuejs 源码) 5. 高手点评
源码学习方法
我认为源码学习分为三个层次 0. 根据 package.json 的 npm run build 逻辑找到 vue 执行的入口
- 看一遍整体结构,比如把所有函数都折叠起来 看整体逻辑
- 参考测试代码,该清楚每个具体函数的输入输出,看明白逻辑
- 核心逻辑手写一遍 (虚拟 dom,compile,响应式等)
- 组内立 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 编辑器阅读源码
- 下载库源码文件
- 直接下载或者 clone 克隆项目到本地
- 2 CDN 或 npm 下载编译好的库源码
- CDN
- bootcdn
- Staticfile
- 75CDN
- 今日头条 CDN
- cdnjs
- jsDelivr
- npm
- https://www.npmjs.com/
- npm 上可以看到 github 地址和主页
- CDN
- 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
代码调试
开发调试
前后端联调
源码解析
如何熟读源码和手写实现
- 找文档
1.官方文档
- 一般官方文档更全面更丰富,维护度也更高
2.源码版本号很重要,不少源码随着版本迭代,发生了破坏性改动,不同版本对应的文档也不同,比如react-router5与react-router6,因此看文档的时候,一定检查下你看的文档版本号和你要学习使用的源码是否一致。
3.关于文档版本号,一般可以在路由地址上或者官网首页上看到,之后再去看下github源码对应的版本号,确保两者一致。
4.如果找不到官方文档,可以先去github源码看一下,README里通常是一份简单的文档,有些还是中英文都有的:
5.这个时候如果有官方文档,README里会有链接指引。如果没有的话,可以查看下源码里是否有一个叫docs的文件夹:
- 一般成熟的源码里都会有这个文件夹,里面就是文档。甚至有些源码下的docs比官方文档都全面。
6.现在很多源码项目都是基于monorepo开发的,很多packages下的文件也都是独立的,看文档的时候,建议不要放过docs与README。
- github看示例代码与单元测试|测试用例
经历了文档阶段之后,再根据文档写一些demo,相信大家对于这套源码已经掌握了至少六七分了,那么接下来想要在某一个难点上继续学习的话,可以看看别人的逻辑,比如示例代码和单元测试。
1.示例代码通常在examples文件夹下
2.单元测试是验证源码逻辑的代码,如果你对某个API用法不确定准确逻辑,可以去看看单元测试代码或者调试下试试
- 一般在test关键字的文件夹下
3.1 浏览器github插件实时看源码
3.2 编辑器阅读源码
- 下载库源码文件
- 直接下载或者clone克隆项目到本地
2 CDN或npm下载编译好的库源码
CDN
bootcdn
Staticfile
75CDN
今日头条 CDN
cdnjs
jsDelivr
unpkg
npm
npm上可以看到github地址和主页
- vscode工具安装插件辅助阅读
要下载或者拉取源码来看看了,但是一般源码的代码量比较大,没法一次看完,所以我们可以借助一些工具来辅助阅读,vscode上建议安装Bookmarks、Better Comments、Git Blame等插件。
Bookmarks
- 可以打一些书签,方便记忆,下次可以快速找到某个函数或值。
Better Comments
- 可以做些颜色笔记,看起来比较醒目
Git Blame
- 则可以帮助查看源码的提交记录,注意得是远程拉取下来的代码,直接下载的不可以查看
- 源码阅读
1.首先,我们要先区分下不同的文件夹下放的都是什么,并不是每个都要去看的
- 先看文件夹命名,命名和框架名称一致的或者叫core、model的基本都是核心文件
2.文件目录下直接搜索关键字
- 比如我想看createForm的实现逻辑,直接搜索关键字
3.自己思考功能的实现方式
不了解功能就开始读源码,那读代码会没有方向感
知道了源码有啥功能之后,要先思考下如果自己实现会怎么做。有个大概的思路就行。
如果想不通可以看下源码用到了哪些依赖库,这些依赖库都有啥功能,再想下应该怎么实现。
如果还想不通也没关系,重要的是要先自己思考下实现方式。
4.粗读源码理清实现思路
你已经有了一个大概的实现思路,然后再去读源码,看下它是怎么实现的。和你思路类似的地方很快就可以掠过去,而且印象也很深,和你思路不一样的地方,通过读代码搞清楚它的实现思路。
这一步不用关心细节,知道某段代码是干啥的就行,关键是和自己的思路做 diff,理清它的整体实现思路。
不经过思考直接读源码,理解代码实现思路的效率会降低
5.快速调试代码
5.通过 debugger 理清实现细节
不理清整体思路就开始 debugger,会容易陷入细节,理不清整体的思路
粗读源码理清了实现思路之后,对于一些部分的具体实现可能还不是很清楚,这时候就可以通过 debugger 来断点调试了。
构造一个能触发该功能的测试用例,在关心的代码处打一个断点,通过 debugger 运行代码。
这时候你已经知道这部分代码是干啥的了,单步调试也很容易理清每一条语句的功能,这样一条语句一条语句的搞懂之后,你就很容易能把这部分代码的实现细节理清楚。
这样一部分一部分的通过 debugger 理清细节实现之后,你就对整体代码的思路和细节的实现都有了比较好的掌握。
不 debugger 只大概理下整体思路,这样不能从细节上真正理清楚
6.输出文章来讲述源码实现思路
当你觉得对源码的实现有了比较好的掌握的时候,可以输出一篇文章的方式来讲述源码的整体思路。
因为可能会有一些部分是你没注意到的,而在输出的过程中,会进行更全面的思考,这时候如果发现了一些没有读到的点,可以再通过前面几步去阅读源码,直到能清晰易懂的把源码的实现讲清楚。这样才算真正的把代码读懂了。
这就是我觉得比较高效的阅读源码的方法。
不通过输出文章来检验,那是否真的理清了整体思路和实现细节是没底的
vue和react源码阅读
5.手写实现
读万卷书不如行万里路,动手写一遍
手写我通常是一步步实现,比如先写个最简单的demo,然后把api全部换掉,导入换成自己手写的文件,然后再挨个实现,一边看源码,一边整理思路,直到呈现和源码一样的实现。
手写我通常是一步步实现,比如先写个最简单的demo,然后把api全部换掉,换成自己手写的,然后再挨个实现,一边看源码,一边整理思路,直到呈现和源码一样的实现。
- github issues
- 如果你发现看文档和源码的过程中,有很多困惑,甚至遇到一些bug,可以去issues看看,看看大家的发言,也许就能找到答案。
- 加社区群,关注核心维护者
源码学习活动
源码架构学习文章
jQuery源码解析
vue2源码解析
vue2库github地址
vue2源码解析
块解析
- computed和watch区别
vue3源码解析
vue3库github地址
实现最简 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 状态管理源码解析
Pinia 状态管理库github地址
Pinia 状态管理源码解析
element UI源码解析
项目实战
Vue移动书城实现
文档和源码
功能
书架
私密阅读
离线阅读
分组
移除书架
阅读器
阅读器支持格式
txt
- txt是纯文件文件,它无法支持多媒体格式,小说类应用中使用较多,但无法满足电子出版物的需求;
pdf
PDF是非常主流的电子书格式,但是在移动端下排版效果不佳,所以移动阅读很少采用PDF格式;
PDF.js
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
移动影视应用开发
实现效果
单点登录的三种实现方式
子主题 2
算法题
建立属于自己的前端组件库
技术栈
- vite3、vitepress、vitest、vue3、tsx
微脚手架用于
生成组件开发模版
组件文档模版
生成组件主题文件
打包发布npm
使用 vitepress 生成组件文档
- githup Actions 结合 githup pages 自动部署
组件库组件主题切换实现
GitHub开源项目的运营的相关生态
CI持续集成
单测覆盖率
开源文档托管
github.io
gitee.io
- 静态文档托管
自己的域名和服务器
issue 管理