八十一、Vue-router中路由传递参数有哪些方法
- 使用动态路由:通过在路由配置中使用冒号语法来定义动态路由。
- 通过查询参数传递数据:在路由跳转时,可以使用
query选项来传递数据。 - 通过路由元信息传递数据:路由元信息指的是在路由配置中通过
meta属性定义的一些元数据。
八十二、Vue-router中的命名视图
- 在 Vue Router 中,使用
router-view组件来显示当前路由匹配到的组件。 - 在路由嵌套时,可以使用多个
router-view来渲染多个组件。此时需要给每个router-view组件命名,以区分不同的路由匹配情况。
技术详解
在 Vue Router 中,使用 router-view 组件来显示当前路由匹配到的组件。可以在某个页面中使用 router-link 组件来跳转到指定的路由,同时也会显示该路由所对应的组件内容。
在路由嵌套时,可以使用多个 router-view 来渲染多个组件。此时需要给每个 router-view 组件命名,以区分不同的路由匹配情况。例如:
<template>
<div>
<header>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</header>
<main>
<router-view></router-view>
<router-view name="sidebar"></router-view>
</main>
</div>
</template>
<script>
export default {
name: 'App',
components: {
RouterView
}
}
</script>
这里我们使用了两个 router-view,其中一个被命名为 sidebar,表示该视图将用于展示侧边栏组件。
在路由配置中,需要通过 components 属性来指定命名视图所对应的组件,例如:
const router = createRouter({
routes: [
{
path: '/',
component: Home,
name: 'Home',
meta: {
title: 'Home'
}
},
{
path: '/about',
components: {
default: About,
sidebar: Sidebar
},
name: 'About'
}
]
})
这里的 components 属性中,default 表示默认视图(即没有命名的视图),而 sidebar 则表示命名为 sidebar 的视图。
八十三、Vue-router中的嵌套路由
- 在组件模板中加入
<router-view>标签来显示嵌套路由对应的子页面。 - 在路由配置中,利用
children属性来定义二级路由及其相应的组件。
技术详解
在 Vue Router 中,可以使用嵌套路由来组织具有父子关系的页面。嵌套路由和普通路由一样,都需要在路由配置中进行定义。
比如,我们可以在 /user 路径下定义两个嵌套的子路由:/user/profile 和 /user/posts,用户访问 /user 路径时将会默认显示 UserProfile 组件:
const routes = [
{
path: '/user',
component: {
template: '<div>User Page <router-view/></div>'
},
children: [
{
path: 'profile',
component: {
template: '<div>User Profile Page</div>'
}
},
{
path: 'posts',
component: {
template: '<div>User Posts Page</div>'
}
}
]
}
]
在这个例子中,UserProfile 组件可以通过在其模板中加入 <router-view> 标签来显示嵌套路由对应的子页面。路由配置中,利用 children 属性来定义二级路由及其相应的组件。
当用户访问 /user/profile 或者 /user/posts 时,对应的子路由组件将会被加载并渲染在 UserProfile 组件的 <router-view> 标签中。
嵌套路由可以用于解决页面复杂度高、路由层次深的问题,能够更好地为前端开发提供高效、灵活的路由管理方案。
八十四、Vue-router中的路由重定向和404
路由重定向是指当用户访问指定的路径时,会被自动重定向到另一个路径,可以使用redirect属性实现:
const routes = [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home }
]
在这个例子中,当用户访问根路径 / 时,会自动重定向到 /home 路径,然后加载 Home 组件。
此外,还可以使用别名功能,将某个路径映射到另一个路径上,可以使用 alias 属性来定义别名:
const routes = [
{ path: '/home', component: Home, alias: '/dashboard' }
]
在这个例子中,当用户访问 /home 或 /dashboard 路径时,都会加载 Home 组件。
当用户访问未知路径时,会出现404错误。在Vue-router中,我们可以通过配置一个特殊的路由来处理404错误,即“没有找到”的页面:
const routes = [
//其他路由配置
{ path: '*', component: NotFound }
]
在这个例子中,我们使用 * 来匹配所有未知路径,并加载 NotFound 组件,提示用户页面不存在。
八十五、Vue-cli中assets文件夹和static文件夹的区别
- 相同点:
assets和static两个都是存放静态资源文件。项目中所需要的资源文件图片,字体图标,样式文件等都可以放在这两个文件下,这是相同点 - 不相同点:
assets中存放的静态资源文件在项目打包时,也就是运行npm run build时会将assets中放置的静态资源文件进行打包上传,所谓打包简单点可以理解为压缩体积,代码格式化。而压缩后的静态资源文件最终也都会放置在static文件中跟着index.html一同上传至服务器。static中放置的静态资源文件就不会要走打包压缩格式化等流程,而是直接进入打包好的目录,直接上传至服务器。因为避免了压缩直接进行上传,在打包时会提高一定的效率,但是static中的资源文件由于没有进行压缩等操作,所以文件的体积也就相对于assets中打包后的文件提交较大点。在服务器中就会占据更大的空间。
- 建议: 将项目中
template需要的样式文件js文件等都可以放置在assets中,走打包这一流程。减少体积。而项目中引入的第三方的资源文件如iconfoont.css等文件可以放置在static中,因为这些引入的第三方文件已经经过处理,不再需要处理,直接上传。
八十六、Vue-cli中的@vue/cli-plugin-babel库
@vue/cli-plugin-babel是 Vue CLI 3 创建的项目中默认提供的 Babel 插件。- 它主要作用是:将项目中的 ES6+ 语法和 JSX 转换为浏览器可执行的代码。
- 该插件使用了 Babel 7 + babel-loader + @vue/babel-preset-app 的默认配置,但是用户也可以通过在
babel.config.js文件中进行配置,来使用其他 Babel 插件或预设。 - 在默认配置下,
@vue/cli-plugin-babel使用了@vue/babel-preset-app预设进行代码转换,该预设内部包含多个 Babel 插件,如转换可选链、空值合并、对象展开语法等,可以使得编写的代码兼容更多不同版本的浏览器。 - 同时,该插件还使用了 babel-loader 来处理 JavaScript 文件的依赖关系,并默认排除了
node_modules文件夹内的内容,以提高编译性能。 - 如果你希望明确地指定需要编译的依赖模块,可以通过设置
transpileDependencies选项来实现。此外,该插件还提供了其他一些可配置的选项,比如sourceType、plugins等,可以更加灵活地适应不同的开发需求。
综上,@vue/cli-plugin-babel 对于 Vue CLI 3 项目的开发十分重要,它可以使得我们编写的代码兼容不同版本的浏览器,提高了项目的可靠性和稳定性。
八十七、Vuex中有几个核心属性
- State:用于存储数据,即状态。在 State 中定义的状态可以被组件和插件访问到。
- Getter:用于获取 State 中的数据,并对其进行计算和处理。它类似于 Vue 中的计算属性,但是可以接受参数,并且在多个组件中可以复用。
- Action:用于处理异步操作或批量操作。Action 中定义的函数可以通过 dispatch 方法来触发,可以包含任意异步操作,可以提交 Mutation 来修改 State 中的数据。
- Mutation:用于修改 State 中的数据,只能进行同步操作。Mutation 中定义的函数需要通过 commit 方法来触发。
- module:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。
因此,Vuex 中的核心属性分别为 State、Getter、Mutation 、 Action 和 Module。
八十八、页面刷新时Vuex内数据丢失怎么处理
在页面刷新时,由于 Vuex 存储的状态是存在内存中的,因此会出现数据丢失的情况。
可以使用vuex-persist 插件将状态持久化到本地存储中,该插件可以将指定的状态持久化到 localStorage 或 sessionStorage 中,这样即使页面刷新,也能够从本地存储中获取到之前保存的状态。
八十九、Vuex中的辅助函数有哪些
- mapState:该函数可以帮助组件将Vuex状态映射到组件的计算属性中,使组件可以轻松地访问这些状态。
- mapGetters:该函数可以将Vuex中的getter映射到组件的计算属性中,使组件可以轻松地访问这些getter的计算方法。
- mapMutations:该函数可以将Vuex的mutation方法映射到组件的methods对象中,使组件可以触发这些mutation方法来改变Vuex的状态。
- mapActions:该函数可以将Vuex的action方法映射到组件的methods对象中,使组件可以触发这些action方法来触发复杂的异步操作和业务逻辑。
Vuex提供了一系列辅助函数,可以帮助我们更方便地使用Vuex的基本功能,其中常用的有以下几个:
mapState 辅助函数:用于在组件中映射Vuex store中的state状态,将 Vuex state 映射为计算属性,示例代码如下:
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
count: state => state.count
})
}
}
mapGetters 辅助函数:用于在组件中映射Vuex store中的getters计算属性,类似于mapState,示例代码如下:
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters([
'doneTodosCount',
'anotherGetter'
])
}
}
mapMutations 辅助函数:用于在组件中映射Vuex store中的mutations方法,将方法映射为组件中的methods方法,示例代码如下:
import { mapMutations } from 'vuex'
export default {
methods: {
...mapMutations([
'increment', // 映射 this.increment() 到 this.$store.commit('increment')
'add' // 映射 this.add(amount) 到 this.$store.commit('add', amount)
]),
...mapMutations({
add: 'increment' // 映射 this.add() 到 this.$store.commit('increment')
})
}
}
mapActions 辅助函数:用于在组件中映射Vuex store中的actions方法,将方法映射为组件中的methods方法,示例代码如下:
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions([
'increment', // 映射 this.increment() 到 this.$store.dispatch('increment')
'add' // 映射 this.add(amount) 到 this.$store.dispatch('add', amount)
]),
...mapActions({
add: 'increment' // 映射 this.add() 到 this.$store.dispatch('increment')
})
}
}
九十、Axios的请求拦截和响应拦截底层实现原理是什么
Axios 的请求拦截和响应拦截的底层实现原理是通过使用拦截器(interceptor)来实现的。
请求拦截器和响应拦截器都是基于 Axios 的拦截器机制来实现的。拦截器是一个函数,可以在请求发送之前或响应返回之后对其进行处理。Axios 通过 interceptors 对象提供了 request 和 response 属性来访问请求拦截器和响应拦截器。
具体实现原理如下:
-
请求拦截器原理:
- Axios 使用
axios.interceptors.request.use()方法添加请求拦截器。 - 该方法接收两个参数:一个是成功回调函数,一个是错误回调函数。
- 当发送请求时,请求会先经过请求拦截器的成功回调函数,然后再发往服务器。
- 成功回调函数可以对请求进行修改或增加额外的配置信息等。
- 如果请求拦截器的成功回调函数中发生错误,会触发错误回调函数。
- Axios 使用
-
响应拦截器原理:
- Axios 使用
axios.interceptors.response.use()方法添加响应拦截器。 - 该方法也接收两个参数:一个是成功回调函数,一个是错误回调函数。
- 当接收到服务器响应后,响应会先经过响应拦截器的成功回调函数,然后再返回给调用方。
- 成功回调函数可以对响应进行修改、过滤或处理等。
- 如果响应拦截器的成功回调函数中发生错误,会触发错误回调函数。
- Axios 使用
通过使用请求拦截器和响应拦截器,我们可以在请求发出前和响应返回后对其进行预处理以及统一处理错误。例如,在请求拦截器中可以设置统一的请求头,而在响应拦截器中可以对返回的数据进行格式化或错误处理。
需要注意的是,拦截器是基于 Promise 实现的,可以通过 Promise.resolve() 和 Promise.reject() 来控制拦截器的执行流程。同时,拦截器可以添加多个,它们会按照添加的顺序依次执行。
九十一、Vue项目中如何做权限管理
前端权限控制可以分为四个方面:
- 接口权限:接口权限目前一般采用
jwt的形式来验证,没有通过的话一般返回401,跳转到登录页面重新进行登录。登录完拿到token,将token存起来,通过axios请求拦截器进行拦截,每次请求的时候头部携带token - 按钮权限:通过自定义指令进行按钮权限的判断
- 菜单权限:可以通过全局路由守卫里做判断,或者可以通过前端统一定义路由组件
- 路由权限:可以通过在全局路由守卫里进行调用
addRoutes添加路由 或者 可以通过在路由的meta属性上标记相应的权限信息,每次路由跳转前做校验。
九十二、Vue中如何设计接口权限
接口权限目前一般采用jwt的形式来验证,没有通过的话一般返回401,跳转到登录页面重新进行登录
登录完拿到token,将token存起来,通过axios请求拦截器进行拦截,每次请求的时候头部携带token
axios.interceptors.request.use(config => {
config.headers['token'] = cookie.get('token')
return config
})
axios.interceptors.response.use(res=>{},{response}=>{
if (response.data.code === 40099 || response.data.code === 40098) { //token过期或者错误
router.push('/login')
}
})
九十三、Vue中如何设计按钮权限
方案一
按钮权限也可以用v-if判断,但是如果页面过多,每个页面都要获取用户权限role和路由表里的meta.btnPermissions,然后再做判断
方案二
通过自定义指令进行按钮权限的判断,首先配置路由
{
path: '/permission',
component: Layout,
name: '权限测试',
meta: {
btnPermissions: ['admin', 'supper', 'normal']
},
//页面需要的权限
children: [{
path: 'supper',
component: _import('system/supper'),
name: '权限测试页',
meta: {
btnPermissions: ['admin', 'supper']
} //页面需要的权限
},
{
path: 'normal',
component: _import('system/normal'),
name: '权限测试页',
meta: {
btnPermissions: ['admin']
} //页面需要的权限
}]
}
自定义权限鉴定指令
import Vue from 'vue'
/**权限指令**/
const has = Vue.directive('has', {
bind: function (el, binding, vnode) {
// 获取页面按钮权限
let btnPermissionsArr = [];
if(binding.value){
// 如果指令传值,获取指令参数,根据指令参数和当前登录人按钮权限做比较。
btnPermissionsArr = Array.of(binding.value);
}else{
// 否则获取路由中的参数,根据路由的btnPermissionsArr和当前登录人按钮权限做比较。
btnPermissionsArr = vnode.context.$route.meta.btnPermissions;
}
if (!Vue.prototype.$_has(btnPermissionsArr)) {
el.parentNode.removeChild(el);
}
}
});
// 权限检查方法
Vue.prototype.$_has = function (value) {
let isExist = false;
// 获取用户按钮权限
let btnPermissionsStr = sessionStorage.getItem("btnPermissions");
if (btnPermissionsStr == undefined || btnPermissionsStr == null) {
return false;
}
if (value.indexOf(btnPermissionsStr) > -1) {
isExist = true;
}
return isExist;
};
export {has}
在使用的按钮中只需要引用v-has指令
<el-button @click='editClick' type="primary" v-has>编辑</el-button>
九十四、Vue中如何设计菜单权限
菜单权限可以理解成将页面与路由进行解耦
方案一
菜单与路由分离,菜单由后端返回
前端定义路由信息
{
name: "login",
path: "/login",
component: () => import("@/pages/Login.vue")
}
name字段都不为空,需要根据此字段与后端返回菜单做关联,后端返回的菜单信息中必须要有name对应的字段,并且做唯一性校验
全局路由守卫里做判断
function hasPermission(router, accessMenu) {
if (whiteList.indexOf(router.path) !== -1) {
return true;
}
let menu = Util.getMenuByName(router.name, accessMenu);
if (menu.name) {
return true;
}
return false;
}
Router.beforeEach(async (to, from, next) => {
if (getToken()) {
let userInfo = store.state.user.userInfo;
if (!userInfo.name) {
try {
await store.dispatch("GetUserInfo")
await store.dispatch('updateAccessMenu')
if (to.path === '/login') {
next({ name: 'home_index' })
} else {
//Util.toDefaultPage([...routers], to.name, router, next);
next({ ...to, replace: true })//菜单权限更新完成,重新进一次当前路由
}
}
catch (e) {
if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
next()
} else {
next('/login')
}
}
} else {
if (to.path === '/login') {
next({ name: 'home_index' })
} else {
if (hasPermission(to, store.getters.accessMenu)) {
Util.toDefaultPage(store.getters.accessMenu,to, routes, next);
} else {
next({ path: '/403',replace:true })
}
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
next()
} else {
next('/login')
}
}
let menu = Util.getMenuByName(to.name, store.getters.accessMenu);
Util.title(menu.title);
});
Router.afterEach((to) => {
window.scrollTo(0, 0);
});
每次路由跳转的时候都要判断权限,这里的判断也很简单,因为菜单的name与路由的name是一一对应的,而后端返回的菜单就已经是经过权限过滤的
如果根据路由name找不到对应的菜单,就表示用户有没权限访问
如果路由很多,可以在应用初始化的时候,只挂载不需要权限控制的路由。取得后端返回的菜单后,根据菜单与路由的对应关系,筛选出可访问的路由,通过addRoutes动态挂载
这种方式的缺点:
- 菜单需要与路由做一一对应,前端添加了新功能,需要通过菜单管理功能添加新的菜单,如果菜单配置的不对会导致应用不能正常使用
- 全局路由守卫里,每次路由跳转都要做判断
方案二
菜单和路由都由后端返回
前端统一定义路由组件
const Home = () => import("../pages/Home.vue");
const UserInfo = () => import("../pages/UserInfo.vue");
export default {
home: Home,
userInfo: UserInfo
};
后端路由组件返回以下格式
[
{
name: "home",
path: "/",
component: "home"
},
{
name: "home",
path: "/userinfo",
component: "userInfo"
}
]
在将后端返回路由通过addRoutes动态挂载之间,需要将数据处理一下,将component字段换为真正的组件
如果有嵌套路由,后端功能设计的时候,要注意添加相应的字段,前端拿到数据也要做相应的处理
这种方法也会存在缺点:
- 全局路由守卫里,每次路由跳转都要做判断
- 前后端的配合要求更高
九十五、Vue中如何设计路由权限
初始化即挂载全部路由,并且在路由上标记相应的权限信息,每次路由跳转前做校验
const routerMap = [
{
path: '/permission',
component: Layout,
redirect: '/permission/index',
alwaysShow: true, // will always show the root menu
meta: {
title: 'permission',
icon: 'lock',
roles: ['admin', 'editor'] // you can set roles in root nav
},
children: [{
path: 'page',
component: () => import('@/views/permission/page'),
name: 'pagePermission',
meta: {
title: 'pagePermission',
roles: ['admin'] // or you can only set roles in sub nav
}
}, {
path: 'directive',
component: () => import('@/views/permission/directive'),
name: 'directivePermission',
meta: {
title: 'directivePermission'
// if do not set roles, means: this page does not require permission
}
}]
}]
九十六、Vue项目中如何解决跨越问题
在vue项目中,主要使用CORS或Proxy这两种方案解决跨域问题
CORS (Cross-Origin Resource Sharing,跨域资源共享)是一个系统,它由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应
CORS实现起来非常方便,只需要增加一些HTTP头,让服务器能声明允许的访问来源
只要后端实现了CORS,就实现了跨域
以koa框架举例
添加中间件,直接设置Access-Control-Allow-Origin响应头
app.use(async (ctx, next)=> {
ctx.set('Access-Control-Allow-Origin', '*');
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
if (ctx.method == 'OPTIONS') {
ctx.body = 200;
} else {
await next();
}
})
PS:Access-Control-Allow-Origin设置为*其实意义不大,可以说是形同虚设,实际应用中,上线前我们会将Access-Control-Allow-Origin值设为我们目标host
Proxy
代理(Proxy)也称网络代理,是一种特殊的网络服务,允许一个(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。一些网关、路由器等网络设备具备网络代理功能。一般认为代理服务有利于保障网络终端的隐私或安全,防止攻击。
方案一
如果是通过vue-cli脚手架工具搭建项目,我们可以通过webpack为我们起一个本地服务器作为请求的代理对象
通过该服务器转发请求至目标服务器,得到结果再转发给前端,但是最终发布上线时如果web应用和接口服务器不在一起仍会跨域
在vue.config.js文件,新增以下代码
module.exports = {
devServer: {
host: '127.0.0.1',
port: 8084,
open: true,// vue项目启动时自动打开浏览器
proxy: {
'/api': { // '/api'是代理标识,用于告诉node,url前面是/api的就是使用代理的
target: "http://xxx.xxx.xx.xx:8080", //目标地址,一般是指后台服务器地址
changeOrigin: true, //是否跨域
pathRewrite: { // pathRewrite 的作用是把实际Request Url中的'/api'用""代替
'^/api': ""
}
}
}
}
}
通过axios发送请求中,配置请求的根路径
axios.defaults.baseURL = '/api'
方案二
此外,还可通过服务端实现代理请求转发
以express框架为例
const express = require('express');
const proxy = require('http-proxy-middleware')
const app = express()
app.use(express.static(__dirname + '/'))
app.use('/api', proxy({ target: 'http://localhost:4000', changeOrigin: false}));
module.exports = app
方案三
通过配置nginx实现代理
server {
listen 80;
# server_name www.josephxia.com;
location / {
root /var/www/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://127.0.0.1:3000;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
九十七、Vue中template模版编译原理
Vue的模板(template)编译原理可以分为以下几个部分:
-
解析(parse):将模板字符串解析成 AST(抽象语法树)。
-
静态分析(static analysis):对 AST 进行静态分析,标记出其中的静态节点(Static Node)。
-
优化(optimize):遍历 AST,对静态节点进行优化,去掉不必要的操作。
-
代码生成(code generation):将 AST 转换成渲染函数(render function)的可执行代码。
-
最终的渲染:将生成的渲染函数运用到数据上,最终生成视图。
技术详解
具体来说,编译器(compiler)会将模板(template)中的所有内容解析成基本语法块,如元素节点、属性节点、文本节点等,然后将这些语法块逐个解析,并根据解析的结果生成一颗以根节点为根的抽象语法树(AST)。此外,编译器还会在AST中添加静态标记(static flag),用于标记那些不随数据改变而需要缓存的节点,从而优化渲染性能。
接下来,编译器会对AST进行一系列的优化处理,包括**静态节点提升(static node optimization)、标记动态节点(mark dynamic nodes)、移除注释(remove comments)**等。这些优化处理都是为了优化渲染性能,减少不必要的重绘和重排操作。
最后,编译器将优化后的AST转换为可执行的渲染函数(render function),并将其挂载到Vue实例的$options.render属性上。当数据更新时,Vue会重新调用渲染函数(render function)来生成新的虚拟节点(virtual node),然后通过对比新旧虚拟节点来判断是否需要更新视图。
综上所述,Vue的模板编译原理主要是将模板(template)转换为抽象语法树(AST),然后对AST进行优化处理,最终生成可执行的渲染函数(render function)。通过这种方式,大大提高了Vue渲染性能和开发效率。
九十八、从编译开始的整个Vue的流程
Vue.js 的整个流程可以分为以下几个部分:模板解析、优化、代码生成、虚拟DOM、响应式系统、依赖收集、更新组件和渲染。
-
模板解析:Vue.js 通过将模板解析成抽象语法树(AST),进而提取其中的指令、属性等信息。
-
优化:在优化阶段,Vue.js 会对 AST 进行静态标记,检测出其中的静态节点和静态根节点,用于后续的性能优化。
-
代码生成:Vue.js 将 AST 转换成 JavaScript 代码,生成
render函数。 -
虚拟DOM:在运行时,Vue.js 会基于
render函数生成虚拟 DOM(Virtual DOM),与真实 DOM 对应,用于减少真实 DOM 的操作,提高性能。 -
响应式系统:Vue.js 实现了一个响应式系统,可以自动追踪依赖,并在相关数据发生变化时触发重新渲染。
-
依赖收集:在模板中使用到的数据都需要收集其依赖关系,以便在数据发生变化时能够正确地更新视图。
-
更新组件:当数据发生变化时,Vue.js 会触发组件的更新,进行重新渲染。
-
渲染:Vue.js 会基于虚拟 DOM 进行比对,找出需要更新的节点并进行更新,最终将更新后的结果渲染到真实的 DOM 上。
以上是从编译开始的 Vue.js 整个流程,其中每个阶段都非常重要,了解这些流程可以帮助我们更好地理解 Vue.js,并优化代码性能。
九十九、Vue2的初始化过程中(new Vue(options))都做了什么
-
解析组件模板:如果组件定义了 template 选项,则需要将其解析成渲染函数。
-
合并配置项:Vue 初始化时会将 new Vue(options) 中传入的配置项与全局配置项进行合并,形成最终的配置对象。
-
初始化响应式数据:Vue 将 data 对象转换为 getter/setter 的形式,并进行依赖收集,当数据发生变化时能够触发视图更新。
-
挂载组件:执行 vm.mount() 方法。
-
编译模板:如果组件没有提供 render 函数,则会将组件的 template 或 el 选项对应的 DOM 内容编译成 render 函数,用于渲染组件的视图。
-
创建 Watcher 实例:在初始化渲染和响应式数据变化时,会创建 Watcher 实例,并在其中收集依赖、触发回调函数等操作。
-
创建虚拟DOM:在渲染过程中,Vue 会先将组件的模板渲染成虚拟 DOM,再将虚拟 DOM 转换成真实的 DOM,并插入到页面中。
总体来说,Vue 的初始化过程包括了组件模板解析、配置项合并、响应式数据初始化、组件挂载、模板编译、Watcher 实例创建等多个步骤,为后续的组件渲染和数据更新打下了基础。
一百、Vue实例挂载的过程中发生了什么
挂载过程指的是mount()过程,这个过程中整体上做了两件事:初始化和建立更新机制。当我们创建 Vue 实例之后,需要将其挂载(mount)到一个 HTML 元素上才能进行渲染。
Vue 实例的挂载过程包括以下几个步骤:
-
首先会检查实例配置(config)中是否存在 el 属性。如果不存在 el 属性,则需要手动调用 $mount 方法将实例进行挂载。如果存在 el 属性,则表示 Vue 实例已经被初始化,并且可以直接通过 el 属性获取对应的 HTML 元素。
-
如果找到了对应的 HTML 元素,Vue 会使用模板编译器(template compiler)将模板字符串编译成渲染函数(render function)。这个渲染函数是用来渲染组件的关键代码。
-
当渲染函数生成完成后,Vue 会使用虚拟 DOM(virtual DOM)进行 DOM 渲染操作。具体地,Vue 会先生成一颗新的虚拟 DOM 树,然后比较新旧两颗虚拟 DOM 树,找到需要进行更新的节点,然后执行相应的更新操作。
-
在执行完上述更新操作后,如果有数据变化,则会重新执行第二步,也就是重新生成渲染函数(render function),并使用它进行更新。
-
最终,当所有数据已经完成渲染后,Vue 会触发 mounted 生命周期钩子函数,通知开发者组件已经挂载完成了。
总的来说,Vue 实例的挂载过程,主要涉及到模板编译、渲染函数生成、虚拟 DOM 的维护和数据的更新等多个方面。这些操作需要在底层进行处理,以实现高效的组件渲染和更新。
101、Vue中$nextTick的原理
- 先定义了一个callbacks 存放所有的nextTick里的回调函数
- 然后判断一下当前的浏览器内核是否支持 Promise,如果支持,就用Promise来触发回调函数
- 如果不支持Promise再看看是否支持MutationObserver,是一个可以监听DOM结构变化的接口,观察文本节点发生变化时,触发执行所有回调函数。
- 如果以上都不支持就只能用setTimeout来完成异步执行了。
技术详解
在 Vue.js 中,当我们修改了数据,它并不会立即更新 DOM,而是会在下一个 tick 中更新,这是因为 Vue 在更新数据后,会将 DOM 更新操作放入一个队列中,等到下一个 tick 的时候再进行实际的 DOM 更新。
这个下一个 tick 通常指的就是浏览器的下一个绘制周期(即 requestAnimationFrame),而 $nextTick 就是让我们在下一个 tick 中执行某些操作的工具函数。
$nextTick 的实现原理如下:
-
首先,Vue 会先尝试使用 Promise.then 方法来执行回调函数,如果当前环境不支持 Promise,则会使用 MutationObserver API 来监听 DOM 的变化。如果这两个方法都不可用,Vue 会使用 setTimeout 来模拟下一个 tick。
-
在 Promise.then 的回调函数中,Vue 会创建一个 microtask,将所有的回调函数放入 microtask 中,等到下一个 microtask 的时候再依次执行这些回调函数。这样可以保证在当前 tick 中的所有操作都执行完毕之后再更新 DOM。
-
如果当前环境不支持 Promise,则会使用 MutationObserver API 来监听 DOM 的变化。Vue 会创建一个文本节点,然后将其插入到 DOM 中,同时在 MutationObserver 的回调函数中执行回调函数,等到下一个 tick 的时候再将这个文本节点删除。
-
如果当前环境不支持 Promise 和 MutationObserver,则会使用 setTimeout 来模拟下一个 tick。Vue 会将所有的回调函数放入一个数组中,等到下一个 tick 的时候依次执行这些回调函数。
总的来说,$nextTick 的实现原理就是利用浏览器提供的异步 API(Promise、MutationObserver、setTimeout)来将回调函数推迟到下一个 tick 中执行,从而保证在当前 tick 中的所有操作都执行完毕之后再更新 DOM。
102、实现SPA单页面应用的核心原理
-
单页面应用的核心原理是使用前端框架(如 Vue、React、Angular 等)实现客户端路由,从而在单个页面中实现多个视图的切换。
-
在传统的多页面应用中,每次点击链接或者提交表单都会向服务器发送一个新请求,并返回一个新的 HTML 页面。
-
单页面应用则只有在初次加载页面时才会请求并加载 HTML 文件,接下来的页面切换则会由前端框架自行处理,通过改变 URL 中的 hash 或使用 HTML5 的 history API 进行路由监听,实现不同视图的展示。
-
单页面应用的优点包括:快速响应、无需反复加载静态资源、较好的用户体验、易于维护和开发等。
单页面应用路由实现原理是,切换url,监听url变化,从而渲染不同的页面组件。
主要的方式有history模式和hash模式。
history模式原理
- 改变路由
history.pushState(state,title,path)
- 监听路由
window.addEventListener('popstate',function(e){ /* 监听改变 */})
hash模式原理
- 改变路由
- 通过
window.location.hash属性获取和设置hash值
- 通过
- 监听路由
window.addEventListener('hashchange',function(e){ /* 监听改变 */})
103、SPA单页面应用,首屏加载有哪些优化方法
- 减小入口文件积
- 静态资源本地缓存
- UI框架按需加载
- 图片资源的压缩
- 组件重复打包
- 开启GZip压缩
- 使用SSR
104、Vue中设置过哪些异常处理
-
Vue.config.errorHandler: Vue.js 中的全局异步错误处理函数,用于捕获异步错误并进行处理。在开发过程中,可以通过重新定义该函数来实现自定义的异常处理逻辑。 -
Vue.config.warnHandler: Vue.js 中的全局警告处理函数,用于捕获警告信息并进行处理。与errorHandler不同,warnHandler仅处理非致命性的警告信息。 -
renderError: Vue.js 中的组件渲染错误处理函数,用于捕获组件渲染时的异常并进行处理。可以使用该函数对组件的异常情况进行统一处理,以避免出现意外的界面问题。 -
errorCaptured: Vue.js 中的组件错误捕获函数,用于捕获子组件中的异常并进行处理。通过在父组件中定义该函数,可以实现对子组件中的异常进行统一处理的目的。 -
window.onerror: 浏览器中的全局错误处理函数,用于捕获 JavaScript 运行时的异常并进行处理。可以使用该函数实现浏览器级别的错误日志记录,以便开发者快速定位和处理异常情况。
技术详解
Vue.config.errorHandler: Vue.js 中的全局异步错误处理函数,用于捕获异步错误并进行处理。
在开发过程中,可以通过重新定义该函数来实现自定义的异常处理逻辑。
// 自定义 Vue.js 异常处理逻辑
Vue.config.errorHandler = function (err, vm, info) {
// 将异常信息记录到日志系统
logSystem.log({
message: err.message,
stack: err.stack,
component: vm.$options.name,
lifecycleHook: info
});
}
Vue.config.warnHandler: Vue.js 中的全局警告处理函数,用于捕获警告信息并进行处理。
与 errorHandler 不同,warnHandler 仅处理非致命性的警告信息。
// 自定义 Vue.js 警告处理逻辑
Vue.config.warnHandler = function (msg, vm, trace) {
// 将警告信息记录到日志系统
logSystem.log({
message: msg,
component: vm.$options.name,
stack: trace
});
}
renderError: Vue.js 中的组件渲染错误处理函数,用于捕获组件渲染时的异常并进行处理。
可以使用该函数对组件的异常情况进行统一处理,以避免出现意外的界面问题。
// 自定义 Vue.js 组件渲染异常处理逻辑
Vue.component('custom-component', {
render (createElement) {
// 模拟组件渲染时出现异常
throw new Error('Component Rendering Error');
// 正常的组件渲染逻辑
return createElement('div', 'Hello World!');
},
renderError (createElement, err) {
// 渲染错误时显示自定义信息
return createElement('div', 'Component Rendering Error: ' + err.message);
}
});
errorCaptured: Vue.js 中的组件错误捕获函数,用于捕获子组件中的异常并进行处理。
通过在父组件中定义该函数,可以实现对子组件中的异常进行统一处理的目的。
// 自定义 Vue.js 父子组件通用异常处理逻辑
Vue.config.errorHandler = function(err, vm, info) {
// 将异常信息记录到日志系统
logSystem.log({
message: err.message,
stack: err.stack,
component: vm.$options.name,
lifecycleHook: info
});
}
Vue.component('parent-component', {
errorCaptured (err, vm, info) {
// 在父组件中捕获子组件中的异常并进行处理
logSystem.log({
message: err.message,
stack: err.stack,
component: vm.$options.name,
lifecycleHook: info
});
// 返回 false 表示继续传播该异常
return false;
},
template: `
<div>
<child-component />
</div>
`
});
Vue.component('child-component', {
created () {
// 模拟子组件中出现异常
throw new Error('Child Component Error');
},
template: '<div>Hello World!</div>'
});
window.onerror: 浏览器中的全局错误处理函数,用于捕获 JavaScript 运行时的异常并进行处理。
可以使用该函数实现浏览器级别的错误日志记录,以便开发者快速定位和处理异常情况。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue.js Error Handling Example</title>
</head>
<body>
<div id="app"></div>
<script>
// 模拟 JavaScript 运行时错误
function throwError () {
undefinedVar++;
}
// 自定义 JS 异常处理逻辑
window.onerror = function (msg, url, lineNo, columnNo, error) {
// 将异常信息记录到日志系统
logSystem.log({
message: msg,
url: url,
line: lineNo,
column: columnNo,
stack: error.stack
});
// 返回 true 表示停止向上传播该异常
return true;
}
// 创建 Vue.js 实例
new Vue({
el: '#app',
data: {
message: 'Hello World!'
},
methods: {
throwError: throwError
}
});
</script>
</body>
</html>
105、Vue.use函数具体做了哪些事
Vue.use() 是一个全局注册 Vue.js 插件的方法,它具体做了以下几件事情:
- 判断插件是否已经被注册过,如果注册过则直接返回。
- 调用插件(一个函数或含有 install 方法的对象),传入 Vue 构造函数和可选选项对象。
- 标记插件已经被注册过,防止重复注册。
通过 Vue.use() 方法注册的插件可以通过 Vue.prototype 访问到,也可以在每个 Vue 实例中使用。
例如,我们可以通过以下方式在 Vue.js 中使用 Vue Router 插件:
import Vue from 'vue'
import VueRouter from 'vue-router'
// 使用 Vue.use() 方法注册 Vue Router 插件
Vue.use(VueRouter)
// 创建路由实例
const router = new VueRouter({
routes: [
// ...
]
})
// 创建 Vue 实例并挂载到 DOM 上
new Vue({
router,
el: '#app'
})
106、Vue为什么采用异步渲染
Vue 采用了异步渲染(Async Rendering)的方式来提高渲染性能和优化用户体验。
这是因为在单页应用程序中,更新页面时需要进行大量的计算和操作,如果同步进行会占用大量的主线程资源,导致页面卡顿、响应变慢。而异步渲染将一部分工作放到了浏览器的空闲时间处理,可以避免阻塞主线程,提高页面性能和流畅度。
具体来说,Vue 异步渲染的实现方式是使用了 Virtual DOM 和异步队列。
-
Virtual DOM:Vue 使用 Virtual DOM 来缓存组件树的状态,并根据状态的变化生成新的 Virtual DOM 树。然后,再将新的 Virtual DOM 树和旧的 Virtual DOM 树进行比较,找出两棵树之间的区别,只更新有变化的部分,最终更新页面。
-
异步队列:当组件状态发生变化时,Vue 并不会直接更新组件,而是将这些变化收集到一个队列中。然后,在下一个 tick 中,Vue 会调用 nextTick() 函数,将异步队列中的所有变化一次性地更新到页面上。这样做可以让 Vue 更加高效地进行 DOM 更新,减少对浏览器主线程的占用,避免页面卡顿和响应变慢。
总之,Vue 采用了异步渲染的方式来提高页面性能和用户体验。这是 Vue 在设计上非常重要和独特的一部分,也是 Vue 受到广泛欢迎的原因之一。
107、Vue中性能优化(项目优化)有哪些
编码阶段
- 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
- v-if和v-for不能连用
- 如果需要使用v-for给每项元素绑定事件时使用事件代理
- 采用keep-alive缓存组件
- 在更多的情况下,使用v-if替代v-show
- key保证唯一
- 使用路由懒加载、异步组件
- 防抖、节流
- 第三方模块按需导入
- 长列表滚动到可视区域动态加载
- 图片懒加载
SEO优化
- 预渲染
- 服务端渲染SSR
打包优化
- 压缩代码
- Tree Shaking/Scope Hoisting
- 使用cdn加载第三方模块
- 多线程打包happypack
- splitChunks抽离公共文件
- sourceMap优化
用户体验
- 骨架屏
- PWA
- 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。
108、从0到1自己构架一个Vue项目
- 项目构建:目前vue3项目我会用vite或者create-vue创建项目
- 引入必要插件:接下来引入必要插件:路由插件vue-router、状态管理vuex/pinia、ui库我比较喜欢element-plus和antd-vue、http工具我会选axios
- 组织通用文件:过滤器、自定义指令、通用方法、通用样式
- 其他比较常用的库:比如vueuse,nprogress,图标可以使用vite-svg-loader
- 代码规范:下面是代码规范:结合prettier和eslint即可
- 提交规范:可以使用husky,lint-staged,commitlint
技术详解
构建 Vue 项目的步骤一般包括以下几个方面:
- 使用 Vue-CLI 创建项目。可以使用命令行创建,也可以使用图形化界面创建。
- 配置项目相关的插件和依赖。例如路由、状态管理、UI 框架等。
- 组织项目的目录结构。
- 开发、测试、部署。
对于构建 Vue 项目来说,有一些重要的插件是需要考虑的:
- Vue Router:Vue.js 官方的路由管理器。
- Vuex:Vue.js 的状态管理库。
- Axios:基于 Promise 的 HTTP 请求客户端,用于与后台进行交互。
- Element UI 或者 Vant 等 UI 框架:用于构建漂亮的用户界面。
- ESLint:用于检查代码,确保代码规范和一致性。
- Prettier:代码格式化工具,可使代码更易读且易于维护。
关于目录结构的组织,可以遵循以下原则:
-
把相关的代码放在一个文件夹内,比如组件、路由、状态管理等。
-
组件、路由、状态管理等代码都应该在单独的文件中编写,保证代码清晰易懂。
-
共享的资源(如图片、字体等)应该放在静态资源目录下。
-
使用中间件(middleware)执行异步操作,使得代码更加清晰和易于维护。
-
对于目录和文件的命名,要有一定的规范和逻辑性。
├── public │ ├── index.html │ └── favicon.ico ├── src │ ├── api │ │ └── request.js │ ├── assets │ │ └── images │ ├── components │ │ ├── Home.vue │ │ ├── About.vue │ ├── router │ │ └── index.js │ ├── store │ │ ├── index.js │ │ └── modules │ ├── App.vue │ └── main.js ├── .eslintrc.js ├── babel.config.js ├── package.json ├── README.md └── vue.config.js
其中,public 文件夹包含了项目的入口页面,src 文件夹包含了项目的核心代码。api 文件夹存放着与后端进行交互的代码,assets 文件夹存放着图片等静态资源,components 文件夹存放着所有的组件代码,router 文件夹存放着路由配置,store 文件夹存放着状态管理相关的代码。.eslintrc.js 文件是 ESLint 的配置文件,babel.config.js 文件是 Babel 配置文件,package.json 是项目的配置文件,README.md 是项目的说明文档。vue.config.js 是 Vue-CLI 的配置文件。
108、Vue项目进行 SEO 优化的常用方案
-
使用服务端渲染 (SSR):使用 SSR 技术可以将 Vue 项目在服务端生成 HTML 页面,使其能够被搜索引擎更好地索引。Vue 官方提供了 Nuxt.js 这样一个基于 Vue.js 的通用应用框架,支持 SSR,可以帮助我们快速搭建 SSR 项目。
-
添加 meta 标签:在组件内使用 vue-meta 插件添加 meta 标签,并在 HTML 源码中加入这些 meta 标签,以改善搜索引擎对页面的理解,包括网页标题、关键词、描述等信息。
-
添加结构化数据:在 HTML 中添加标准化的结构化数据,比如 JSON-LD,以帮助搜索引擎更好地理解页面内容并提高搜索结果的显示质量。
-
提高网页加载速度:搜索引擎会考虑网页加载速度作为排名的一个重要因素,因此需要尽可能地优化网页加载速度,比如使用图片懒加载、代码压缩等技术。
-
增加外部链接:外部链接是衡量网站质量和影响力的一个重要指标,可以通过提高网站内容质量和增加宣传推广来吸引更多的外部链接。
109、Vue中动画怎么实现
在 Vue 中,可以通过使用内置的过渡动画和动态组件等方式来实现动画效果,常用的方式有以下几种:
-
内置过渡动画:Vue 提供了内置的过渡动画组件,包括 transition 和 transition-group。可以通过添加 CSS 类名或绑定钩子函数来触发过渡效果。
-
第三方动画库:Vue 也支持集成第三方动画库,比如 Velocity.js 和 Animate.css 等,可以通过引入这些库来实现丰富多彩的动画效果。
-
动态组件:在 Vue 中,可以使用动态组件来实现动画效果,通过控制组件的显示或隐藏状态,结合 transition 组件或第三方动画库来实现。
-
自定义指令:Vue 还支持自定义指令来实现动画效果,可以通过自定义指令来控制元素的样式变化来实现动画效果。
.fade-enter-active, .fade-leave-active { transition: opacity .5s; } .fade-enter, .fade-leave-to { opacity: 0; }
以上是一个使用 transition 组件实现的简单动画效果,通过控制 isShow 变量来切换标题的显示/隐藏状态,同时使用 CSS 来定义动画效果。
110、Vue3相对于Vue2进行了哪些优化
- 更灵活的响应式系统:Vue 2.x 中响应式系统的核心是 Object.defineProperty,劫持整个对象,然后进行深度遍历所有属性,给每个属性添加
getter和setter,实现响应式。Vue 3.x 中使用 Proxy对象重写响应式系统。 - 更快的渲染速度:Vue3 的编译器生成的渲染函数比 Vue2 生成的更高效。
- 编译阶段:Vue 2.x 通过标记静态节点,优化 diff 的过程。Vue 3.x中标记和提升所有的静态节点,diff的时候只需要对比动态节点内容。
- 更小的体积:Vue3 将源码拆分为多个独立的模块,这样就可以按需导入所需的模块,从而减小了整个库的体积。
- 更好的 TypeScript 支持:Vue3 对 TypeScript 的支持更加友好,内部使用了更先进的 TypeScript 特性,并为其提供了更好的声明文件。
- 更好的组件系统:Vue3中引入了一个新的
Fragment组件,它可以替代原来的template标签作为根节点 - 新增了setup组合式API
技术详解
Vue3 相对于 Vue2 进行了许多优化和更新,主要包括以下几个方面。
1. 更快的渲染速度
Vue3 的编译器生成的渲染函数比 Vue2 生成的更高效。Vue3 在编译模板时使用了静态分析技术,可以在编译期间确定节点是否是静态的,并将其缓存以便后续操作。这种优化可以减少运行时的内存分配和垃圾回收,从而提高渲染性能。此外,Vue3 还引入了基于 Proxy 的响应式系统,可以避免不必要的观察者反应,并消除了 getter 和 setter 方法的开销。
2. 更小的体积
Vue3.js 将源码拆分为多个独立的模块,这样就可以按需导入所需的模块,从而减小了整个库的体积。此外,Vue3.js 在编译器中引入了静态分析技术,消除了不必要的运行时代码,从而使得打包后的应用程序更小。
3. 更好的 TypeScript 支持
Vue3.js 对 TypeScript 的支持更加友好,内部使用了更先进的 TypeScript 特性,并为其提供了更好的声明文件。这样,开发者可以更加方便地使用 TypeScript 来开发 Vue 应用程序,从而提高了开发效率和代码质量。
4. 更灵活的响应式系统
Vue3.js 中的响应式系统相对于 Vue2.js 更加灵活。Vue3.js 引入了一个新的 reactive 函数,它可以接收一个普通对象并返回一个响应式的对象。此外,Vue3.js 还提供了一些新的 API,如 ref 和 toRefs,使得开发者可以更加方便地管理组件中的数据和状态,并且更加易于调试和测试。
5. 更好的组件系统
Vue3.js 中的组件系统也得到了改进。Vue3.js 引入了一个新的 Fragment 组件,它可以替代原来的 template 标签作为根节点,从而更加灵活。同时,Vue3.js 中的 Teleport 组件可以让组件在 DOM 中任意位置渲染,从而更加灵活和强大。
总之,Vue3.js 相对于 Vue2.js 进行了许多优化更新,提高了应用程序的性能和开发效率。开发者应该尽快学习和掌握 Vue3.js 的新特性,以便更好地开发出高性能、高质量的应用程序。
111、Vue3中组合式Api对比选项式Api的区别
它们之间有以下几个区别:
-
数据和方法的声明方式不同。选项式 API 通过在组件选项对象中声明 data、methods 等属性来定义组件数据和方法,而组合式 API 使用 setup 函数来声明组件状态和逻辑。
-
响应式系统的实现方式不同。选项式 API 通过 Vue2 中的 defineProperty 来实现响应式系统,而组合式 API 则使用了类似 React Hooks 的思路,使用 reactive、ref 和 computed 等函数来实现响应式逻辑。
-
生命周期钩子的处理方式不同。选项式 API 中的生命周期钩子相对比较分散,需要在组件选项对象中分别声明相应的函数,而组合式 API 可以通过 onMounted、onUpdated、onUnmounted 等函数来处理组件的生命周期。
-
组件复用时的处理方式不同。选项式 API 中组件复用需要通过 mixins 等手段来实现,而组合式 API 中可以通过自定义 Hook 来提取可复用的逻辑代码。
-
函数式组件不需要实例化,所以没有
this。
总的来说,组合式 API 更加灵活,对于高度复杂和动态的组件开发尤为有利。但对于简单的组件开发,选项式 API 依然是一个不错的选择。
112、Vue3中组合式Api对比选项式Api的优势
- 更好的TypeScript支持:在组合式API中,对于Props、Refs等数据类型,可以使用明确的类型定义
- 更好的代码组织:可以将逻辑按照功能进行封装和抽象,更好地组织代码。
- 更好的逻辑复用:通过创建函数或者模块化的方式,可以更方便地复用逻辑,并且不会产生命名冲突问题。
- 更好的Tree-shaking支持:在组合式API中,我们可以利用ES6模块语法优化代码结构,使得Tree-shaking更加高效。
技术详解
Vue3中新增加了组合式API,其与选项式API(Vue2中常用的API)相比有以下几个优势:
更好的TypeScript支持
在组合式API中,对于Props、Refs等数据类型,可以使用明确的类型定义,这样在开发时就可以得到一个更好的TypeScript支持。而在选项式API中,则需要手动为每个组件属性添加类型定义。
更好的代码组织
在选项式API中,同一个组件中不同的options (如data、methods、computed等) 定义,使得逻辑分散而难以维护,特别是当项目变得越来越大时。而在组合式API中,则可以将逻辑按照功能进行封装和抽象,更好地组织代码。
更好的逻辑复用
在选项式API中,实现逻辑复用往往需要在多个组件的methods或mixins中进行定义和混合,这种做法不够优雅并且容易出现命名冲突、依赖性、覆盖等问题。而在组合式API中,通过创建函数或者模块化的方式,可以更方便地复用逻辑,并且不会产生命名冲突问题。
更好的Tree-shaking支持
在Vue3中,采用了静态分析技术(如ES6模块语法)来实现Tree-shaking。在组合式API中,我们可以利用ES6模块语法优化代码结构,使得Tree-shaking更加高效。
综上所述,Vue3的组合式API通过更好的TypeScript支持、更好的代码组织、更好的逻辑复用和更好的Tree-shaking支持提供了更好的可维护性和开发效率。
113、Vue3中watch 和 watchEffect 的区别
-
Vue3中的
watch和watchEffect都是用来监视响应式数据变化并执行副作用函数的API。 -
它们的主要区别在于:它们跟踪响应式依赖的方式和在何时执行回调函数。
-
watchAPI需要显式声明依赖项,只有在被监视的响应式数据发生变化时才会触发回调函数执行。而且,回调函数的第一个参数为新值,第二个参数为旧值。 -
watchEffectAPI更加自动化,它会自动追踪回调函数所依赖的所有响应式数据,并在这些数据变化时自动重新运行回调。此外,watchEffect回调函数不接收旧值参数,只接收新值参数。
技术详解
Vue3中的watch和watchEffect都是用来监视响应式数据变化并执行副作用函数的API。
它们的主要区别在于:它们跟踪响应式依赖的方式和在何时执行回调函数。
watch API需要显式声明依赖项,只有在被监视的响应式数据发生变化时才会触发回调函数执行。而且,回调函数的第一个参数为新值,第二个参数为旧值。
import { ref, watch } from 'vue';
const myRef = ref(0);
watch(myRef, (newValue, oldValue) => {
console.log(`new value: ${newValue}, old value: ${oldValue}`);
})
myRef.value = 1; // logs "new value: 1, old value: 0"
在上面的例子中,我们使用watch API来监听myRef的变化,并且在变化时执行回调函数。当myRef的值从0变为1时,将会输出一条日志。
watchEffect API更加自动化,它会自动追踪回调函数所依赖的所有响应式数据,并在这些数据变化时自动重新运行回调。每当watchEffect函数包含的任何响应式数据发生更改时,所有相关代码都会再次运行,这意味着我们不需要像在watch API中那样显式声明依赖项。此外,watchEffect回调函数不接收旧值参数,只接收新值参数。
import { ref, watchEffect } from 'vue';
const myRef = ref(0);
watchEffect(() => {
console.log(`myRef's value is: ${myRef.value}`);
})
myRef.value = 1; // logs "myRef's value is: 1"
共同点是 watch 和 watchEffect 会共享以下四种行为:
停止监听:组件卸载时都会自动停止监听清除副作用:onInvalidate 会作为回调的第三个参数传入副作用刷新时机:响应式系统会缓存副作用函数,并异步刷新,避免同一个 tick 中多个状态改变导致的重复调用监听器调试:开发模式下可以用 onTrack 和 onTrigger 进行调试
总之,watch和watchEffect都是用来监视响应式数据变化并执行副作用函数的API,它们的主要区别在于:它们跟踪响应式依赖的方式和在何时执行回调函数。使用watch需要显式声明依赖项,而使用watchEffect会自动追踪回调函数所依赖的所有响应式数据,并在这些数据变化时自动重新运行回调。
114、Vue3中render函数的作用
render 函数,它是一个函数式的组件渲染器,比如,可以将通过createVNode函数创建的虚拟DOM渲染成真正的 HTML 元素,并将其插入到页面中。
技术详解
在 Vue 3 中,还提供了 render 函数,它是一个函数式的组件渲染器,可以将组件渲染成真正的 HTML 元素,并将其插入到页面中。使用 render 函数可以更加灵活地控制组件渲染的逻辑。
Render 函数的作用
在 Vue 2 中,我们通常使用 template 来描述组件的结构和样式。但是在 Vue 3 中,template 不再是必需的,我们可以使用 render 函数来代替 template,把模板转换成 JavaScript 代码,从而更好地控制渲染逻辑。因此,render 函数的主要作用如下:
- 把模板转换成 JavaScript 代码
- 可以更加灵活地控制组件的渲染逻辑
- 提高了组件的性能和可维护性
Render 函数的用法
import { createApp } from 'vue'
const app = createApp({
render() {
return h('div', 'Hello, Vue 3!')
}
})
app.mount('#app')
其中,createApp 函数用于创建一个 Vue 应用程序实例,并且传入一个对象,对象里面包含了 render 方法。在 render 方法中,我们可以使用 h 函数来创建 HTML 元素,h 函数接收三个参数:标签名、属性、内容,它会返回一个 VNode 对象。这个 VNode 对象类似于虚拟 DOM,后面会在性能方面提到它的重要性。
也可以使用 JSX 语法来编写 render 函数:
const app = createApp({
render() {
return <div>Hello, Vue 3!</div>
}
})
这种方式需要使用 Babel 进行编译,需要配置相应的插件才能正常工作。
除了基本用法之外,render 函数还有许多高级用法,比如使用函数式组件、插槽等。
Render 函数的使用场景
render 函数的使用场景非常广泛,以下列举几个常见的场景:
- 动态生成组件:有些组件可能需要根据不同的数据进行不同的渲染,使用 render 函数可以很方便地实现动态生成组件的功能。
- 性能优化:由于 render 函数可以直接转换成 JavaScript 代码,因此它可以显著提高组件的性能。同时,VNode 对象也可以减少不必要的 DOM 操作,进一步提高性能。
- 插槽:在 Vue 3 中,插槽已经被统一成了一个名为 slots 的属性,使用 render 函数可以更加灵活地控制插槽的渲染逻辑。
- 函数式组件:函数式组件是一种无状态的组件,使用 render 函数可以非常方便地定义函数式组件。
使用Render 和 createVNode函数创建函数式组建loading
import { createVNode, render } from 'vue';
// 引入对应的组件
import loading from "./loading.vue";
// 2. 准备一个DOM容器
const Profile = document.createElement('div')
document.body.appendChild(Profile)
class Loading {
show(show) {
const vnode = createVNode(loading, { show:true })
// 4. 把虚拟dom渲染到div
render(vnode, Profile)
}
hide() {
render(null, Profile)
}
}
// 定义对象:开发插件对象
const LoadPlugin = {
// 插件包含install方法
install(app,options){
// 添加实例方法,挂载至Vue原型
app.config.globalProperties.$Loading = new Loading();
}
};
// 导出对象
export default LoadPlugin;
总之,Vue 3 的 render 函数为开发者提供了更加灵活和高效的组件编写方式,它可以有效提高组件的性能和可维护性,使得开发人员能够更好地完成复杂的前端开发任务。
115、Vue3中的setup语法糖
- Vue3中新增了
setup语法糖,它是组合式API的入口,用于替代Vue2中的选项式API。 - 在
setup函数中不能使用this关键字,因为在setup执行时,组件实例还没有创建出来。如果需要访问组件实例,需要使用getCurrentInstance()方法来获取当前组件实例。
技术详解
Vue3中新增了setup语法糖,它是组合式API的入口,用于替代Vue2中的选项式API。
setup函数会在组件创建之前、props被解析之后执行,返回一个对象,该对象中包含了组件中需要使用的数据、方法等。
同时,setup函数也可以返回一个渲染函数用于自定义渲染逻辑。
使用setup函数,我们可以通过JavaScript代码来实现模板中的各种逻辑,例如:
<template>
<div>
<p>{{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
const increment = () => {
count.value++
}
return {
count,
increment,
}
}
}
</script>
上述例子中,我们使用了ref来创建了一个响应式数据count和一个方法increment。在setup函数中,我们把这些数据和方法都放到了一个对象里返回出去,从而让模板可以直接访问到它们。
需要注意的是,在setup函数中不能使用this关键字,因为在setup执行时,组件实例还没有创建出来。如果需要访问组件实例,需要使用getCurrentInstance()方法来获取当前组件实例。
总之,setup语法糖可以使我们更加灵活地组织组件的逻辑,并且通过JavaScript代码来管理组件中的数据、方法等。这个特性在Vue3中的应用非常广泛,是Vue3的重要特性之一。
116、Vue3中的ref、toRef、toRefs
- ref()函数:用于创建一个响应式数据对象,将传入的普通数据变成响应式数据。
- toRef()函数:用于创建一个基于源Reactive对象的响应式引用。
- toRefs()函数:用于将一个响应式对象转换为普通对象,该普通对象的每个属性都是一个响应式引用对象,可以对原来的响应式对象进行读取或监听变化。
- shallowRef函数:用于创建一个ref对象,并且只对对象的第一层进行响应式处理,而不会递归处理对象内部的属性。
技术详解
ref()函数:用于创建一个响应式数据对象,将传入的普通数据变成响应式数据。当响应式数据被更新时,相关的组件会自动重新渲染。
import {ref} from 'vue';
const count = ref(0); // 创建响应式数据
console.log(count.value); // 访问响应式数据需要通过 .value 属性
count.value++; // 修改响应式数据
toRef()函数:用于创建一个基于源Reactive对象的响应式引用。它接收两个参数:源Reactive对象和属性名,返回一个响应式引用对象,可以对源Reactive对象的属性进行读取或监听变化。
import { reactive, toRef } from 'vue';
const state = reactive({
count: 0
});
const countRef = toRef(state, 'count'); // 创建响应式引用
console.log(countRef.value); // 访问响应式引用对象需要通过 .value 属性
state.count++; // 修改源Reactive对象的属性
console.log(countRef.value); // 响应式引用对象的值也会更新
toRefs()函数:用于将一个响应式对象转换为普通对象,该普通对象的每个属性都是一个响应式引用对象,可以对原来的响应式对象进行读取或监听变化。toRefs()的返回值是一个对象,该对象的属性名与源Reactive对象的属性名相同,但属性值都转为响应式引用对象。
import { reactive, toRefs } from 'vue';
const state = reactive({
count: 0,
message: 'Hello world'
});
const refs = toRefs(state); // 创建响应式引用对象
console.log(refs.count.value); // 访问响应式引用对象需要通过 .value 属性
state.count++; // 修改源Reactive对象的属性
console.log(refs.count.value); // 响应式引用对象的值也会更新
shallowRef函数:用于创建一个ref对象,并且只对对象的第一层进行响应式处理,而不会递归处理对象内部的属性。这个函数常常用于优化性能,在某些场景下可以避免不必要的计算和监听。
js复制代码import { shallowRef } from 'vue'
const obj = { foo: 'bar' }
const myRef = shallowRef(obj)
在上述例子中,我们使用了shallowRef函数来创建了一个ref对象myRef,并且将一个对象obj作为参数传入。这个对象内部还包含了一个属性foo。
需要注意的是,使用shallowRef函数创建的ref对象只会对对象本身进行响应式处理,而不会对对象内部的属性进行处理。也就是说,如果我们修改了obj对象内部的foo属性的值,那么myRef.value.foo的值并不会发生变化。
总之,shallowRef函数是Vue3中用来创建带有浅层响应式特性的ref对象的函数。它常常用于优化性能,在一些场景下可以避免不必要的计算和监听。
当数据具有较深层次嵌套时,可以考虑整合使用shallowRef和reactive两个函数来提高组件的性能。
117、Vue3中的 reactive、 shallowReactive 函数
- Vue3中的
reactive函数和shallowReactive函数都是用来创建响应式对象的 reactive函数会对对象进行深度监听,即所有嵌套的属性都会被转化为响应式数据shallowReactive函数只会对对象的第一层属性进行监听
技术详解
Vue3中的reactive函数和shallowReactive函数都是用来创建响应式对象的。reactive函数会对对象进行深度监听,即所有嵌套的属性都会被转化为响应式数据,而shallowReactive函数只会对对象的第一层属性进行监听。
使用这两个函数可以帮助我们更方便地实现Vue中的响应式数据。举个例子,如下是一个使用reactive函数创建响应式对象的例子:
import { reactive } from 'vue'
const state = reactive({
count: 0,
message: 'Hello, World!',
nestedObj: {
foo: 'bar'
}
})
在上述例子中,我们使用了reactive函数来创建了一个响应式对象state。这个对象中包含了一个计数器count、一段消息message和一个嵌套的对象nestedObj。使用reactive函数创建的对象,我们可以在模板或者JavaScript代码中直接访问,例如:
<template>
<div>
<p>{{ state.count }}</p>
<button @click="increment">Increment</button>
<p>{{ state.message }}</p>
<p>{{ state.nestedObj.foo }}</p>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({
count: 0,
message: 'Hello, World!',
nestedObj: {
foo: 'bar'
}
})
const increment = () => {
state.count++
}
return {
state,
increment,
}
}
}
</script>
通过上面的例子,我们可以发现使用reactive函数创建的响应式对象可以很方便地在模板和JavaScript代码中进行访问和更新。
需要注意的是,在Vue3中使用reactive函数创建的响应式对象,在调试时可能比较难以查看具体的响应式数据,因为它们被封装在一个代理对象中。如果需要直接访问原始数据,可以使用toRaw函数进行转换。
总之,reactive和shallowReactive函数都是Vue3中用来创建响应式对象的函数,它们可以帮助我们更方便地实现Vue中的响应式数据。
118、Vue3中的生命周期
- 在 Vue2 生命周期钩子函数名基础上加了
on - beforeDestory 和 destoryed 更名为 onBeforeUnmount 和 onUnmounted
- 然后用setup代替了两个钩子函数 beforeCreate 和 created
- 新增了两个开发环境用于调试的钩子
119、Vue3中setup语法下怎么设置name属性
使用两个script标签,在不加 setup的script标签中export导出name属性。
<template>
<input type="text"/>
</template>
<script>
export default {
name: "xxx",
}
</script>
<script setup>
import { ref } from 'vue';
</script>
120、Vue3中的自定义指令
- 在 Vue3 中,可以使用
directive函数来定义自定义指令。 - Vue3 中的自定义指令与 Vue2 中的不同,不再需要分别定义钩子函数
bind、inserted、update、componentUpdated和unbind。取而代之的是一个名为mounted的钩子函数,其行为类似于 Vue2 中的inserted。 - 在 Vue3 中,自定义指令总共有 8 个生命周期钩子函数。这些钩子函数分别是:
beforeMount:在绑定元素挂载到父节点之前调用。mounted:在绑定元素和其子节点被首次渲染后调用。beforeUpdate:在元素更新之前调用。updated:在元素更新之后调用。beforeUnmount:在绑定元素从父节点卸载之前调用。unmounted:在绑定元素的所有子节点被卸载并且指令被解除绑定后调用。beforeBind:在绑定元素时调用,只调用一次。updated:在指令值更新时调用,可能会调用多次。
技术详解
在 Vue3 中,我们可以使用 directive 函数来定义自定义指令。
这个函数在 app.directive 上。自定义指令是 Vue 中强大的抽象机制,允许我们对 DOM 操作进行封装,以便于复用。
Vue3 中的自定义指令与 Vue2 中的不同,不再需要分别定义钩子函数 bind、inserted、update、componentUpdated 和 unbind。取而代之的是一个名为 mounted 的钩子函数,其行为类似于 Vue2 中的 inserted。
定义自定义指令的示例代码如下:
// 注册一个名为 'my-directive' 的自定义指令
app.directive('my-directive', {
// 插入元素时调用
mounted(el, binding) {
// 设置元素的背景颜色为传递的值
el.style.backgroundColor = binding.value;
},
});
在上述代码中,我们通过调用 app.directive 来注册一个名为 'my-directive' 的自定义指令,然后定义了该指令的选项对象,其中包含了 mounted 钩子函数。
当使用该指令时,我们可以在元素上添加 v-my-directive 属性,并将需要传递的值赋给该指令。例如:
<template>
<div v-my-directive="'red'">我是红色的</div>
</template>
在上述代码中,我们将字符串 'red' 作为指令的参数传递给 v-my-directive。在指令的 mounted 钩子函数中,我们获取到了该值,并将其应用到元素的背景颜色上。
总之,在 Vue3 中,我们可以通过 directive 函数来定义自定义指令,以便于封装 DOM 操作并进行复用。
121、pinia:Vue3中的状态管理库
Pinia 是 Vue.js 社区出品的基于 Vue 3 的状态管理库,它旨在提供最小的 API 以管理应用的状态。与 Vuex 相比,它更简单、更可扩展。
与Vuex相比,pinia中没有了mutations属性,直接可以通过action改变state。
Pinia 的一些特点如下:
- 使用 TypeScript 编写,具有类型安全性和代码提示功能。
- 提供了
store、state、getter、action等核心概念,可以轻松管理应用的状态。 - 支持 Reactive 响应式编程范型,当状态改变时,Pinia 会通知所有注册过的组件进行重新渲染。
- Pinia 的体积非常小,不到 2KB,而 Vuex 体积约为 23KB。
- Pinia 具有很好的可扩展性,可以灵活地使用插件来增强其功能。
Pinia 可以帮助开发者更好地管理应用状态,提供了一种新的思路和方式,是 Vue 3 生态中非常值得尝试的一个库。
122、Vue3中的v-model
Vue3 的 v-model 语法和 Vue2 稍有不同。在 Vue3 中,v-model 不再是一个语法糖,而是一个新的指令,用于绑定表单元素的值和更新父组件中相应的变量。
在 Vue3 中,v-model 可以同时绑定值和事件,可以通过 modelValue 和 update:modelValue 来自定义绑定的变量名和更新事件名。
<template>
<div>
<input v-model:value="message" @update:value="handleMessageChange" />
<p>{{ message }}</p>
</div>
</template>
<script>
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup() {
const message = ref('Vue3')
const handleMessageChange = (value) => {
console.log('message changed:', value)
}
return {
message,
handleMessageChange
}
}
})
</script>
在这个示例中,我们将 v-model 拆分成了 v-model:value 和 @update:value,其中前者绑定了变量名,后者绑定了更新事件,并将其传递给了我们定义的事件处理函数 handleMessageChange。因此,每当输入框的值发生变化时,控制台都会打印出一条消息。
需要注意的是,在这里使用的是 @update:value,而不是 @change,因为 v-model 默认会监听 input、textarea 和 select 等元素的 input 事件,而不是 change 事件。
123、Vue3中怎么封装自定义插件并使用
主要就是暴露一个带install 函数的对象,然后在 main.js 中使用app.use。
124、Vue3中createApp(App)创建应用实例过程中都发生了什么
在 Vue3 中,使用 createApp(App) 方法创建应用实例的过程中,主要发生了以下几个步骤:
-
创建应用实例:调用 createApp 方法会返回一个应用实例,该实例可以用于操作整个应用。
-
加载全局配置:应用实例会加载全局配置,例如 devtools、isNativeTag、compilerOptions 等。
-
解析组件:Vue3 推荐使用单文件组件 (SFCs) 进行组件开发,因此在应用初始化时,需要将 SFCs 解析成可用的组件选项对象。
-
校验组件选项:应用实例会对组件选项进行校验和规范化。例如检查组件名称是否合法,是否传入正确的 props 等。
-
注册组件:通过应用实例的 component 方法,可以注册全局或局部的组件。注册组件时,应用实例会将它们转换成虚拟节点,并存储在一个组件注册表中。
-
注册指令:通过应用实例的 directive 方法,可以注册全局或局部的指令。注册指令时,应用实例会将它们转换成渲染函数中的指令钩子。
-
注册插件:通过应用实例的 use 方法,可以注册全局或局部的插件。注册插件时,应用实例会调用插件的 install 方法,并将应用实例作为参数传入。
-
创建根组件:应用实例会创建一个根组件,用于挂载整个应用。根组件通常是一个空的占位符,用于渲染其他组件。
-
挂载应用实例:通过应用实例的 mount 方法,可以将应用实例挂载到指定的 DOM 元素上。挂载应用实例后,Vue3 会自动触发初始化过程。
总体来说,在 Vue3 中使用 createApp(App) 方法创建应用实例时,需要对组件、指令、mixin 和插件进行注册和处理,以便后续可以在应用中使用这些功能。
125、Vue3中有哪些新的组件
-
Teleport(传送门):Teleport组件提供了在 DOM 树中任意位置渲染内容的能力。它可以将组件的内容挂载到指定的 DOM 节点上,而不受当前组件层级的限制。这对于在应用程序中创建弹出式窗口、对话框或者模态框等场景非常有用。 -
Suspense(占位符):Suspense组件允许你在组件树中某个位置展示一个等待状态的占位符,直到异步操作完成后再展示真正的内容。它可以用于优化懒加载、数据请求等场景,让用户在等待时看到一个友好的界面。 -
Fragment(片段):Fragment组件是一个无需包裹元素的占位符。它允许你在不增加额外 DOM 元素的情况下,返回多个根元素。使用Fragment可以更灵活地组织你的组件结构,并改善代码的可读性。
技术详解
Fragment
在Vue2中: 组件必须有一个根标签
* 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
* 好处: 减少标签层级, 减小内存占用
Teleport
<teleport to="移动位置">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>
Suspense
-
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
-
使用步骤:
- 异步引入组件
import { defineAsyncComponent } from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
使用Suspense包裹组件,并配置好default 与 fallback
<template>
<div class="app">
<h3>我是App组件</h3>
<Suspense>
<template v-slot:default>
<Child/>
</template>
<template v-slot:fallback>
<h3>加载中.....</h3>
</template>
</Suspense>
</div>
</template>
126、Vue3中Composition API 和 React Hook的区别
从 React Hook 从实现的角度来看,React Hook 是基于 useState 的调用顺序来确定下一个 re 渲染时间状态从哪个 useState 开始,所以有以下几个限制
- 不在循环中、条件、调用嵌套函数 Hook
- 必须确保它总是在你这边 React Top level 调用函数 Hook
- 使用效果、使用备忘录 依赖关系必须手动确定
Composition API 是基于 Vue 的响应系统,和 React Hook 相比
- 在设置函数中,一个组件实例只调用一次设置,而 React Hook 每次重新渲染时,都需要调用 Hook,给 React 带来的 GC 比 Vue 更大的压力,性能也相对 Vue 对我来说也比较慢
- Compositon API 你不必担心调用的顺序,它也可以在循环中、条件、在嵌套函数中使用
- 响应式系统自动实现依赖关系收集,而且组件的性能优化是由 Vue 内部完成的,而 React Hook 的依赖关系需要手动传递,并且依赖关系的顺序必须得到保证,让路 useEffect、useMemo 等等,否则组件性能会因为依赖关系不正确而下降。
虽然Compoliton API看起来像React Hook来使用,但它的设计思路也是React Hook的参考。