1.Vue3之事件循环、源码nextTick与源码解析
2.vue+axios+promise实际开发用法详解
3.大文件上传Vue完整代码(切片上传、源码秒传、源码断点续传)
4.React 和 Vue 的源码 Promise 组件
5.Vue—关于响应式(二、异步更新队列原理分析)
Vue3之事件循环、源码nextTick与源码解析
事件循环是源码thinkphp教程源码JavaScript单线程执行的核心机制,确保了同步任务与异步任务能有序执行。源码同步任务按顺序执行,源码而异步任务则分为宏任务和微任务。源码宏任务包括setTimeout、源码setInterval、源码整体代码、源码ajax、源码postMessage、源码交互事件等,源码微任务则包括Promise.then、catch、finally、MutationObserver、process.nextTick(Node环境下)。
事件循环机制确保了同步任务先执行,宏任务和微任务则交替执行,形成事件循环的周期。此过程确保了JavaScript代码的流畅执行,避免了因耗时任务阻塞主线程导致的卡顿。
在Vue3中,高点标记指标源码nextTick功能用于处理异步更新DOM问题。它允许开发者在DOM更新之前执行异步代码,确保DOM的正确渲染。有以下两种使用方式:一种是直接传入回调函数,另一种是通过async和await实现。当对数据进行操作后,如果观察到DOM没有更新,原因在于Vue3中数据响应式是同步的,而DOM更新是异步的。
为解决此问题,可以使用nextTick将同步代码转化为异步代码,确保在浏览器的下一次事件循环中执行DOM更新。在Vue3源代码中,nextTick通过将同步代码包装为Promise,从而转化为异步任务来实现这一功能。
Vue3将DOM更新设置为异步,旨在优化性能。考虑到大量数据变化时,频繁的DOM更新可能导致性能开销过大,异步更新策略降低了这种浪费,提高了应用的响应性和性能效率。
vue+axios+promise实际开发用法详解
Vue + Axios + Promise 实际开发用法详解一、Vue 与 Axios 的结合使用
在 Vue.js 中,Axios 是一个基于 promise 的 HTTP 客户端,用于浏览器和 node.js。WP主题网站源码Vue 负责构建用户界面,而 Axios 负责与后端进行数据交互。结合使用,可以实现前后端数据的异步交互,提升用户体验。
二、Axios 的基本用法
Axios 的使用非常简单。首先,你需要安装 Axios。然后,你可以通过 `axios.get` 或 `axios.post` 等方法发送 HTTP 请求。这些方法返回一个 promise 对象,你可以使用 `.then` 或 `async/await` 语法来处理响应。
例如:
javascript
// 发送 GET 请求
axios.get
.then {
// 处理响应数据
console.log;
})
.catch {
// 处理错误
console.log;
});
三、Vue 中结合 Promise 的使用
在 Vue 中,你可以将 Axios 与 Vue 的生命周期钩子结合,特别是在 `created` 或 `mounted` 钩子中发送请求。同时,使用 Promise 可以更好地组织异步代码,使代码更加简洁、易于理解。
例如,在一个 Vue 组件中:
javascript
export default {
data {
return {
user: null, // 用于存储从后端获取的数据
};
},
async created {
try {
const response = await axios.get; // 使用 async/await 语法处理 Axios 返回的 promise
this.user = response.data; // 将响应数据赋值给组件的 data 属性
} catch {
console.error;
}
},
};
四、总结
Vue、Axios 和 Promise 的勇者战纪源码结合使用,可以简化异步数据处理的复杂性,提高开发效率和代码质量。在实际开发中,开发者应该充分利用这些技术的优势,根据项目需求进行合理的设计和实现。同时,也要注意处理各种异常情况,确保应用的健壮性和用户体验。
大文件上传Vue完整代码(切片上传、秒传、断点续传)
原创不易,注释都在代码中,点赞收藏不迷路~1.安装依赖npmi-Sspark-md5
2.对接api(需后端接口支持)/**@Description:大文件上传接口*@Author:zhangy*@Date:--::*@LastEditors:zhangy*@LastEditTime:--::*/importrequestfrom'@/utils/request'//校验exportfunctiongetUploadStatus(data){ returnrequest({ url:'api/file/part/check',method:'get',params:data})}//上传exportfunctionsliceUpload(data){ returnrequest({ url:'api/file/part/upload',method:'post',data})}//合并exportfunctionmergeUpload(data){ returnrequest({ url:'api/file/part/merge',method:'get',params:data})}exportdefault{ getUploadStatus,sliceUpload,mergeUpload}3.封装切片上传的mixin/**@Description:大文件上传、分片上传、断点续传、文件秒传*@Author:zhangy*@Date:--::*@LastEditors:zhangy*@LastEditTime:--::*/constSparkMD5=require('spark-md5')import{ getUploadStatus,sliceUpload,mergeUpload}from'@/api/chunksUploadAPI'//切片大小(单位:B)constCHUNK_SIZE=5**/***@description:分块计算文件的md5值*@param{ *}file文件*@param{ *}chunkSize分片大小*@returns{ *}*/functioncalculateFileMd5(file,chunkSize){ returnnewPromise((resolve,reject)=>{ constblobSlice=File.prototype.slice||File.prototype.mozSlice||File.prototype.webkitSliceconstchunks=Math.ceil(file.size/chunkSize)letcurrentChunk=0constspark=newSparkMD5.ArrayBuffer()constfileReader=newFileReader()fileReader.onload=function(e){ spark.append(e.target.result)currentChunk++if(currentChunk<chunks){ loadNext()}else{ constmd5=spark.end()resolve(md5)}}fileReader.onerror=function(e){ reject(e)}functionloadNext(){ conststart=currentChunk*chunkSizeletend=start+chunkSizeif(end>file.size){ end=file.size}fileReader.readAsArrayBuffer(blobSlice.call(file,start,end))}loadNext()})}/***@description:分块计算文件的md5值*@param{ *}file文件*@returns{ Promise}*/functioncalculateFileMd5ByDefaultChunkSize(file){ returncalculateFileMd5(file,CHUNK_SIZE)}/***@description:文件切片*@param{ *}file*@param{ *}size切片大小*@returns[{ file}]*/functioncreateFileChunk(file,size=CHUNK_SIZE){ constchunks=[]letcur=0while(cur<file.size){ chunks.push({ file:file.slice(cur,cur+size)})cur+=size}returnchunks}/***@description:获取文件的后缀名*/functiongetFileType(fileName){ returnfileName.substr(fileName.lastIndexOf('.')+1).toLowerCase()}/***@description:根据文件的md5值判断文件是否已经上传过了*@param{ *}md5文件的md5*@param{ *}准备上传的文件*@returns{ Promise}*/functioncheckMd5(md5,file){ returnnewPromise(resolve=>{ getUploadStatus({ md5}).then(res=>{ if(res.data.code===){ //文件已经存在了,秒传(后端直接返回已上传的文件)resolve({ uploaded:true,url:res.data.msg,code:res.data.code})}elseif(res.data.code===){ //文件不存在需要上传resolve({ uploaded:false,url:'',code:res.data.code})}else{ resolve({ uploaded:false,url:'',code:})}}).catch(()=>{ resolve({ uploaded:false,url:'',code:})})})}/***@description:执行分片上传*@param{ *}file上传的文件*@param{ *}i第几分片,从0开始*@param{ *}md5文件的md5值*@param{ *}vm虚拟dom指向组件this*@returns{ Promise}*/functionPostFile(file,i,md5,vm){ constname=file.name//文件名constsize=file.size//总大小constshardCount=Math.ceil(size/CHUNK_SIZE)//总片数if(i>=shardCount){ return}conststart=i*CHUNK_SIZEconstend=start+CHUNK_SIZEconstpacket=file.slice(start,end)//将文件进行切片/*构建form表单进行提交*/constform=newFormData()form.append('md5',md5)//前端生成uuid作为标识符传个后台每个文件都是一个uuid防止文件串了form.append('file',packet)//slice方法用于切出文件的一部分form.append('name',name)form.append('totalSize',size)form.append('total',shardCount)//总片数form.append('index',i+1)//当前是第几片returnnewPromise((resolve,reject)=>{ sliceUpload(form).then(res=>{ if(res.data.code===){ //拿到已上传过的切片resolve({ uploadedList:res.data.chunkList?res.data.chunkList.map(item=>`${ md5}-${ item}`):[]})}elseif(res.data.code===){ resolve({ uploadedList:[]})}else{ resolve({ uploadedList:[],code:})//reject()}}).catch(()=>{ //reject()resolve({ uploadedList:[],code:})})})}/***@description:合并文件*@param{ *}shardCount分片数*@param{ *}fileName文件名*@param{ *}md5文件md值*@param{ *}fileType文件类型*@param{ *}fileSize文件大小*@returns{ Promise}*/functionmerge(shardCount,fileName,md5,fileType,fileSize){ returnmergeUpload({ shardCount,fileName,md5,fileType,fileSize})}exportdefault{ data(){ return{ chunks:[],percent:0,percentCount:0}},methods:{ /***@description:上传文件*@param{ *}file文件*@returns{ Object}包含成功的文件地址、名称等*/asyncchunksUpload(file){ this.chunks=[]//step1获取文件切片constinitChunks=createFileChunk(file)//step2获取文件md5值constmd5=awaitcalculateFileMd5ByDefaultChunkSize(file)//step3获取文件的上传状态const{ uploaded,url,code}=awaitcheckMd5(md5,file)if(uploaded){ //step4如果上传成功this.percent=//step5拿到结果returnurl}if(!uploaded&&code===){ returnthis.errorInfo()}//step4如果文件未传成功,执行切片上传const{ uploadedList}=awaitPostFile(file,0,md5,this)//todo方法1:逐次发送请求constrequestList=[]//请求集合initChunks.forEach(async(chunk,index)=>{ //过滤掉已上传的切片if(uploadedList.indexOf(md5+'-'+(index+1))<0){ constfn=()=>{ returnPostFile(file,index,md5,this)}requestList.push(fn)}})letreqNum=0//记录发送的请求个数constsend=async()=>{ if(reqNum>=requestList.length){ //step5如果所有切片已上传,执行合并constres=awaitmerge(initChunks.length,file.name,md5,getFileType(file.name),file.size)if(res.data.code===){ returnres.data.msg}else{ this.errorInfo()return{ }}}constsliceRes=awaitrequestList[reqNum]()if(sliceRes.code&&sliceRes.code===){ returnthis.errorInfo()}//计算当下所上传切片数constcount=initChunks.length-uploadedList.lengthif(this.percentCount===0){ this.percentCount=/count}this.percent+=this.percentCountreqNum++returnsend()}constmergeResult=awaitsend()returnmergeResult//todo方法2:使用Promise.all统一发送请求//constrequestList=initChunks.map(async(chunk,index)=>{ ////过滤掉已上传的切片//if(uploadedList.indexOf(md5+'-'+(index+1))<0){ //returnPostFile(file,index,md5,this)//}//})//returnPromise.all(requestList)//.then(async()=>{ //constres=awaitmerge(initChunks.length,file.name,md5,getFileType(file.name),file.size)//if(res.data.code===){ //returnres.data.msg//}//})//.catch(()=>{ //returnthis.$message.error('出错了,请稍后重试!')//})},/***@description:错误提示*/errorInfo(){ this.$message.error('出错了,请稍后重试!')}}}4.使用<template><divid="app"><el-uploadref="vedioUpload"actiondrag:limit="1"accept=".mp4":auto-upload="false":show-file-list="false":on-change="onFileChange"><iclass="el-icon-upload"/><divclass="el-upload__text">将文件拖到此处,河北白酒溯源码或<em>点击上传</em></div></el-upload><div>上传进度:{ { percent.toFixed()+'%'}}</div><divv-if="videoUrl"class="video-box"><video:src="videoUrl"controls/></div></div></template><script>importchunksUploadfrom'@/mixins/chunks-upload.js'exportdefault{ mixins:[chunksUpload],data(){ return{ videoUrl:''}}methods:{ asynconFileChange(file){ this.clearVideoUpload()constfileType=file.raw.typeconstfileSize=file.raw.size//if(fileType!=='video/mp4')returnthis.$message.error('只能上传MP4格式的视频!')if(fileSize>)returnthis.$message.error('视频大小不能超过1G!')constres=awaitthis.chunksUpload(file.raw)console.log('上传结果',res)},clearVideoUpload(){ this.videoUrl=''this.percent=0}}}</script><stylelang="scss"scoped>.video-box{ width:px;height:px;video{ width:%;height:%;}}</style>原文:/post/React 和 Vue 的 Promise 组件
Promise组件是一种基于Promise的组件封装方法,其设计旨在简化组件的异步输入和输出处理,遵循高内聚、低耦合的软件工程理念。基于Promise的调用允许组件在适当的时间内部调用成功或失败回调,遵循异步操作的规范化模式,确保组件使用和管理的可靠性和一致性。
每个组件调用都会生成一个独立实例,它们不共享调用状态,不存在状态缓存问题。无论在单个页面中多次调用同一个组件,还是在不同页面中使用同一组件的不同实例,它们都是相互独立的。组件的按需渲染特性使其根据特定事件或外部条件触发呈现,有效提升页面加载速度和性能,减少不必要的渲染和资源消耗。
组件的渲染结果是暂时的,一旦完成即被销毁,适用于临时和一次性场景,提高程序性能。此特性使得组件在开发中更加灵活,适应各种需求。
以用户列表功能为例,首先在根组件中使用Promise组件的共享渲染插槽,为整个应用的Promise组件提供默认的渲染位置和应用上下文继承。接下来定义Promise组件,根据需要添加用户列表和对话框交互功能。通过这种模式,我们能够完成用户列表的开发,同时实现添加和编辑用户信息的功能。
基于Promise组件的特点,无论是简单还是复杂的功能开发,只需满足异步输入输出场景,此模式均能提供更人性化的开发体验和更好的程序性能。我们无需关注组件渲染状态,而是专注于业务逻辑,这就是Promise组件的意义所在。
Vue—关于响应式(二、异步更新队列原理分析)
本节学习要点:Event Loop、Promise
关于Event Loop的介绍,可以参考阮一峰老师的文章。
关于Promise,请访问:developer.mozilla.org/z...
上一节介绍了Vue通过Object.defineProperty拦截数据变化的响应式原理,数据变化后会触发notify方法来通知变更。这一节将继续分析,收到通知后Vue会开启一个异步更新队列。
以下是两个问题:
一、异步更新队列
首先看一段代码演示。
将上一节的代码拿过来,假设我们现在不仅依赖x,还有y、z,分别将x、y、z输出到页面上。我们现在依赖了x、y、z三个变量,那么我们应该把onXChange函数名改为watch,表示它可以监听变化,而不仅仅是监听一个x的变化。
可以看到这三个值都被打印在页面上。
现在我们对x、y、z的value进行修改。
查看页面,结果没有问题,每个数据的变化都被监听到并且进行了响应。
既然结果是对的,那我们的问题是什么?
这个问题是:每次数据变化都进行了响应,每次都渲染了模板,如果数据变化了一百次、一千次呢?难道要重复渲染一百遍、一千遍吗?
我们都知道频繁操作DOM会影响网页性能,涉及重排和重绘的知识感兴趣请阅读阮一峰老师的文章:ruanyifeng.com/blog/...
因此,既要保证所有的依赖都准确更新,又要保证不能频繁渲染成为了首要问题。现在我们修改x.value、y.value、z.value都是同步通知依赖进行更新的,有没有一种机制可以等到我修改这些值之后再执行更新任务呢?
这个答案是——异步。
异步任务会等到同步任务清空后执行,借助这个特点和我们前面的分析,我们需要:
按照步骤,我们创建如下代码:
接着我们需要修改一下notify的代码,监听到数据变化后不立即调用依赖进行更新,而是将依赖添加到队列中。
回到页面,我们发现页面上还是重复渲染了三次模板。
那么我们写的这段代码有什么用呢?异步又体现在哪里呢?接着往下看。
二、nextTick原理分析
上面的代码中,虽然我们开启了一个队列,并且成功将任务推入队列中进行执行,但本质上还是同步推入和执行的。我们要让它变成异步队列。
于是到了Promise发挥作用的时候了。关于宏任务和微任务的介绍请参考:zhuanlan.zhihu.com/p/...
我们创建nextTick函数,nextTick接收一个回调函数,返回一个状态为fulfilled的Promise,并将回调函数传给then方法。
然后只需要在添加任务时调用nextTick,将执行任务的flushJobs函数传给nextTick即可。
回到页面。
虽然修改了x、y、z三个变量的value,最后页面上只渲染了一次。
再来总结一下这段代码的执行过程:
这也正是Vue采用的解决方案——异步更新队列,官方文档描述得很清楚。
文档地址:cn.vuejs.org/v2/guide/r...
三、结合Vue源码来看nextTick
在Vue中,我们可以通过两种方式来调用nextTick:
(至于什么时候使用nextTick,如果你不偷懒看了官方文档的话,都能找到答案哈哈)
以下源码节选自vue2.6.版本,这两个API分别在initGlobalAPI函数和renderMixin函数中挂载,它们都引用了nextTick函数。
nextTick源码如下:
在内部,它访问了外部的callbacks,这个callbacks就是前面提到的队列,nextTick一调用就给队列push一个回调函数,然后判断pending(pending的作用就是控制同一时间内只执行一次timerFunc),调用timerFunc(),最后返回了一个Promise(使用过nextTick的应该都知道吧)。
我们来看一下callbacks、pending、timerFunc是如何定义的。
可以看到timerFunc函数只是调用了p.then方法并将flushCallbacks函数推入了微任务队列,而p是一个fulfilled状态的Promise,与我们自己的nextTick功能一致。
这个flushCallbacks函数又干了什么呢?
flushCallbacks中重新将pending置为初始值,复制callbacks队列中的任务后将队列清空,然后依次执行复制的任务,与我们自己的flushJobs函数功能一致。
看完上面的源码,可以总结出Vue是这么做的,又到了小学语文之——提炼中心思想的时候了。
对比一下我们自己写的代码,你学会了吗?
以上演示代码已上传github:github.com/Mr-Jemp/VueS...
后面要学习的内容在这里:
Vue—关于响应式(三、Diff Patch原理分析)
Vue—关于响应式(四、深入学习Vue响应式源码)
本文由博客一文多发平台OpenWrite发布!