Vue.js 3是一个用于构建大型和小型应用程序的可靠框架。
本文将介绍我从Vue社区和我自己开发大规模Vue.js应用程序的经验中总结得出的6个技巧。
Vue 3允许我们创建可重用的逻辑块,且逻辑块甚至可以包含响应性状态。
这些可重用的块通常称为“可组合块”。它们可以提供与mixin类似的功能,可组合块的优点在于:
基于此,我的建议是,对于大规模的Vue.js 3应用程序,最优选项是使用可组合块而不是mixin。
可组合块是什么样子的呢?
下面是一个比较mixin和可组合块的简单示例。
两者都显示了一段可重用的代码,旨在通过ajax请求获取帖子,保持请求的状态,并保存获取的帖子。
// FetchPostMixin.jsconst REQUEST_IN_PROGRESS = "REQUEST_IN_PROGRESS";const REQUEST_ERROR = "REQUEST_ERROR";const REQUEST_SUCCESS = "REQUEST_SUCCESS";export default { data() { return { requestState: null, post: null }; }, computed: { loading() { return this.requestState === REQUEST_IN_PROGRESS; }, error() { return this.requestState === REQUEST_ERROR; } }, methods: { async fetchPost(id) { this.post = null; this.requestState = REQUEST_IN_PROGRESS; try { const res = await fetch(https://jsonplaceholder.typicode.com/posts/${id}); this.post = await res.json(); this.requestState = REQUEST_SUCCESS; } catch (error) { this.requestState = REQUEST_ERROR; } } }};// PostComponent.vue<script>import FetchPostMixin from "./FetchPostMixin";export default { mixins:[FetchPostMixin],};</script>
使用mixin时,你必须注意不要在组件中包含与mixin中的数据属性等同名的任何数据属性。
此外,如果你在组件上注册了多个mixin,则无法一目了然地判断特定数据、方法等的来源。
那么相同功能的可组块是怎么样的呢?
//FetchPostComposable.jsimport { ref, computed } from "vue";export const useFetchPost = () => { // Request States const REQUEST_IN_PROGRESS = "REQUEST_IN_PROGRESS"; const REQUEST_ERROR = "REQUEST_ERROR"; const REQUEST_SUCCESS = "REQUEST_SUCCESS"; const requestState = ref(null); const loading = computed(() => requestState.value === REQUEST_IN_PROGRESS); const error = computed(() => requestState.value === REQUEST_ERROR); // Post const post = ref(null); const fetchPost = async (id) => { post.value = null; requestState.value = REQUEST_IN_PROGRESS; try { const res = await fetch(https://jsonplaceholder.typicode.com/posts/${id}); post.value = await res.json(); requestState.value = REQUEST_SUCCESS; } catch (error) { requestState.value = REQUEST_ERROR; } }; return { post, loading, error, fetchPost };};// PostComponent.vue<script>import { useFetchPost } from "./FetchPostComposable";export default { setup() { //clear source of data/methods const { loading: loadingPost, //can rename error, fetchPost, post } = useFetchPost(); return { loadingPost, error, fetchPost, post }; },};</script>
在组件中,我们不仅可以按逻辑对事物进行分组,还可以清楚地看到每个数据属性、方法等的来源。
此外,我们可以轻松地重命名来自可组合块的任何内容,以防止它与组件中已经存在的其他内容发生冲突。
在Vue中处理响应式数据时,可以在组件之间传递对象。
虽然这可能非常方便,但也可能产生意想不到的副作用。
举个例子,登录页面上通常会有user数据属性。
这是一个具有name和password属性的对象,我们将它传递给LoginForm组件。
// LoginPage.vue<template> <LoginForm :user="user" /></template><script>import LoginForm from './LoginForm.vue'export default { components: {LoginForm}, data(){ return { user: { name: '', password: ''} } }}</script>
登录表单接收user,然后直接改变其属性。
// LoginForm.vue<template> <div> <label> Name <input type="text" v-model="user.name"> </label> <label> Password <input type="password" v-model="user.password"> </label> </div></template><script>export default { props:{ user: Object }}</script>
让我们在登录页面添加一些标题,以便可以更好地了解正在发生的事情。
<template> <h1>Parent Component</h1> <code>{{user}}</code> <h1>Child Component</h1> <LoginForm :form="user" /></template>
现在键入输入,你可以发现在LoginForm中更改user,实际上也更改了父组件中的user。
跨组件通信不应该采用这样的方式。当user数据更改时,LoginForm组件应该发出一个事件。
那么如何解决这个问题呢?
如果要创建将对象作为属性的组件,那么需要在更改对象之前将该对象克隆到本地数据属性。
// LoginForm.vue<template> ...<input type="text" v-model="form.name"><input type="password" v-model="form.password"></template><script>export default { //... data(){ return { // spreading an object effectively clones it's top level properties // for objects with nested objects you'll need a more thorough solution form: {...this.user} } },}</script>
同样的概念也适用于将响应式对象传递到Vuex操作或组件实例之外的任何其他位置时。
虽然Vuex为管理应用程序范围状态提供了一个很好的模式,但如果存在太多的全局状态,那么store文件很快会变得臃肿且难以导航。
为了解决这个问题,Vuex允许将store分解为不同的模块,每个模块处理各自的域(即一个模块处理帖子、一个模块处理用户、一个模块处理评论等)。
不使用模块:
// store/index.jsexport default { state:{ posts:[], users:[], comments:[] }, actions:{ fetchPost(){}, fetchPosts(){}, fetchUser(){}, fetchUsers(){}, fetchComment(){}, fetchComments(){}, }}
使用模块:
// store/index.jsimport posts from './modules/posts.js'import users from './modules/users.js'import comments from './modules/comments.js'export default{ modules:{ posts, users, comments }}//store/modules/posts.js // and the same for each of the other domains with // common state, actions named the same (possible because of namespacing)// and any other domain specific state and logic named as fitsexport default { namespaced: true, state:{ items:[] }, actions:{ fetchOne(){}, fetchAll(){} }}
你可以访问官方Vuex文档以查看使用这些模块的确切语法。
模块的使用,不但使得store更易于导航,而且动态调用操作和访问状态也更容易。
如果你正在开发中的Vuex项目还没有使用模块,那么现在是时候重构了,因为后期重构命名空间的模块会非常痛苦。
随着时间的推移,软件只会变得越来越复杂,因此我们需要一种机制可以快速确保代码仍然按预期工作。
那么怎么让代码库可测试呢?在我看来:
对于Vue项目,以下方法可以使得项目更易于测试:
对于测试,即使你现在写得满心不情愿,但我保证,以后你会感谢自己的。
哦,对了,自动化测试运行搭配测试失败阻止部署(CI/CD)的方法很不错,否则只会得到一个永远不会运行的测试套件。
首先,什么是SDK?
你可以将SDK视为语言特定的与实际API进行交互的API。
因此,直接在组件中调用axios或fetch,还不如使用post.find(1)之类的东西。
那么,你会选哪种方法呢?硬编码请求到API?
axios('https://someapi.com/posts/1')
还是资源类上编写可自动完成的方法?
post.find(1)
对我来说,答案是显而易见的。利用SDK或为自己的REST API终结点创建SDK具有以下优势:
这种方法是有缺点的。
除了REST API之外,你还必须花时间编写SDK代码以及文档化SDK。
但是,根据我的经验,效率方面的回报是值得的。
构建大规模Vue.js应用程序的最后一个技巧是围绕第三方库创建一个包装器。
例如,不要直接在代码库中使用axios,而是创建一个通用命名的类,如Http,然后在后台调用axios。就像这样:
// Http.jsimport axios from 'axios'export default class Http{ async get(url){ const response = await axios.get(url); return response.data; } // ...}
好的,乍一看,这似乎毫无意义。但是,这种做法有一些非常方便的优点。
假设出于某种原因,你必须放弃axios。毕竟它是第三方代码,无法控制。
因此,你决定对http解决方案使用fetch。
不用在整个代码库中搜索正在使用的axios实例,只要在Http.js文件进行切换即可。
// Http.jsexport default class Http{ async get(url){ const response = await fetch(url); return await response.json(); } // ...}
这样就可以用fetch替代axios发出HTTP请求。
此外,即使单个开发人员进行切换,但团队中的其他人不知道也没关系。大家仍然可以继续以同样的方式使用Http类。
包装第三方依赖项的另一个优点是,它通常会公开扩展类API的方法以处理相关功能。
例如,如果你希望在ajax请求失败时提醒用户,那么不需要在发出请求的所有位置手动使用try catch。相反,Http类可完成此操作。
export default class Http{ async get(url){ try { const response = await fetch(url); return await response.json(); } catch (err) { alert(err); } } // ...}
也可以为请求添加缓存。
// Http.jsimport Cache from './Cache.js' // some random cache implementation of your choiceexport default class Http{ async get(url){ const cached = Cache.get(url) if(cached) return cached const response = await fetch(url); return await response.json(); } // ...}
例子举不胜举。
这里值得注意的是,axios库确实提供了一个称为interceptors的概念,可以处理一些扩展,但不大起眼。
使用包装器类以及进行扩展,无需查看文档就可以找到“挂钩”到第三方代码的适当位置。
不仅如此,一些第三方依赖项可能不如axios全面,甚至会暴露所需的钩子。
最后,此列表中的其他技巧可应用于大规模和小规模的应用程序,但这个技巧仅适用于大规模应用。对于小规模应用程序,这样做反而得不偿失。
Vue 3是开发大规模Web应用程序的绝佳解决方案,但如果你不小心,反而会弄巧成拙。
为了充分利用Vue 3,这6个技巧可以帮助你轻松应对大规模应用程序!