Vue.js 风格指南
代码检查
我们默认使用 eslint-vue-plugin,并配置 plugin:vue/recommended。
查看 rules 获取更多文档。
基本规则
-
Vue 模板使用
.vue扩展名。不要在 HAML 中使用%template。 -
明确定义传递给 Vue 应用的数据
// bad return new Vue({ el: '#element', name: 'ComponentNameRoot', components: { componentName }, provide: { ...someDataset }, props: { ...anotherDataset }, render: createElement => createElement('component-name'), })); // good const { foobar, barfoo } = someDataset; const { foo, bar } = anotherDataset; return new Vue({ el: '#element', name: 'ComponentNameRoot', components: { componentName }, provide: { foobar, barfoo }, props: { foo, bar }, render: createElement => createElement('component-name'), }));我们不鼓励在这种特定情况下使用展开运算符,以保持代码库的明确性、可发现性和可搜索性。 这适用于任何能从上述模式中受益的地方,例如在初始化 Vuex 状态时。 上述模式还使我们在实例化过程中能够轻松解析非标量值。
return new Vue({ el: '#element', name: 'ComponentNameRoot', components: { componentName }, props: { foo, bar: parseBoolean(bar) }, render: createElement => createElement('component-name'), }));
模板中的组件使用
-
在模板中使用组件时,优先使用 kebab-case 命名风格
// bad <MyComponent /> // good <my-component />
<style> 标签
我们不使用 <style> 标签,原因如下:
- 无法使用 SCSS 变量和混入(mixins),也无法使用 Tailwind CSS 的
@apply指令。 - 这些样式会在运行时插入。
- 我们已经有其他几种方式来定义 CSS。
应该使用 Tailwind CSS 工具类 或 页面特定 CSS,而不是使用 <style> 标签。
Vue 测试
随着时间的推移,在我们有效测试 Vue 组件的过程中,出现了一些编程模式和风格偏好。以下指南描述了其中的一些。 这些不是严格的指导原则,而是一些建议和良好实践,旨在深入了解我们在 GitLab 中如何编写 Vue 测试。
挂载组件
通常,在测试 Vue 组件时,每个测试块都应该"重新挂载"该组件。
实现方法如下:
- 在顶层
describe块中创建一个可变的wrapper变量。 - 使用
mount或shallowMount挂载组件。 - 将生成的
Wrapper实例重新赋值给我们的wrapper变量。
创建全局可变的 wrapper 提供了多种优势,包括:
-
定义查找组件/DOM 元素的通用函数:
import MyComponent from '~/path/to/my_component.vue'; describe('MyComponent', () => { let wrapper; // this can now be reused across tests const findMyComponent = wrapper.findComponent(MyComponent); // ... }) -
使用
beforeEach块挂载组件(有关更多信息,请参阅 thecreateComponentfactory)。 -
使用
shared_test_setup.js中设置的enableAutoDestroy在测试运行后自动销毁组件。
异步子组件
shallowMount 不会为异步子组件创建组件存根(stubs)。为了正确存根异步子组件,请使用 stubs 选项。确保异步子组件定义了 name 选项,否则 wrapper 的 findComponent 方法可能无法正常工作。
createComponent 工厂函数
为了避免重复挂载逻辑,定义一个可在每个测试块中重用的 createComponent 工厂函数很有用。这是一个闭包,应该将我们的 wrapper 变量重新赋值为 mount 和 shallowMount 的结果:
import MyComponent from '~/path/to/my_component.vue';
import { shallowMount } from '@vue/test-utils';
describe('MyComponent', () => {
// Initiate the "global" wrapper variable. This will be used throughout our test:
let wrapper;
// Define our `createComponent` factory:
function createComponent() {
// Mount component and reassign `wrapper`:
wrapper = shallowMount(MyComponent);
}
it('mounts', () => {
createComponent();
expect(wrapper.exists()).toBe(true);
});
it('`isLoading` prop defaults to `false`', () => {
createComponent();
expect(wrapper.props('isLoading')).toBe(false);
});
})同样,我们可以在 beforeEach 块中调用 createComponent 来进一步去重测试:
import MyComponent from '~/path/to/my_component.vue';
import { shallowMount } from '@vue/test-utils';
describe('MyComponent', () => {
// Initiate the "global" wrapper variable. This will be used throughout our test
let wrapper;
// define our `createComponent` factory
function createComponent() {
// mount component and reassign `wrapper`
wrapper = shallowMount(MyComponent);
}
beforeEach(() => {
createComponent();
});
it('mounts', () => {
expect(wrapper.exists()).toBe(true);
});
it('`isLoading` prop defaults to `false`', () => {
expect(wrapper.props('isLoading')).toBe(false);
});
})createComponent 最佳实践
-
考虑使用单个(或有限数量的)对象参数,而不是多个参数。 为常见数据(如
props)定义单个参数是可以的,但请记住我们的 JavaScript 风格指南 并保持在参数数量限制内:// bad function createComponent(props, stubs, mountFn, foo) { } // good function createComponent({ props, stubs, mountFn, foo } = {}) { } // good function createComponent(props = {}, { stubs, mountFn, foo } = {}) { } -
如果在同一组测试中需要同时使用
mount和shallowMount,可以为createComponent工厂函数定义一个mountFn参数,该参数接受用于挂载组件的挂载函数(mount或shallowMount):import { shallowMount } from '@vue/test-utils'; function createComponent({ mountFn = shallowMount } = {}) { } -
使用
mountExtended和shallowMountExtended助手函数来暴露wrapper.findByTestId():import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { SomeComponent } from 'components/some_component.vue'; let wrapper; const createWrapper = () => { wrapper = shallowMountExtended(SomeComponent); }; const someButton = () => wrapper.findByTestId('someButtonTestId'); -
避免使用
data、methods或任何其他扩展组件内部状态的挂载选项。import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { SomeComponent } from 'components/some_component.vue'; let wrapper; // bad :( - This circumvents the actual user interaction and couples the test to component internals. const createWrapper = ({ data }) => { wrapper = shallowMountExtended(SomeComponent, { data }); }; // good :) - Helpers like `clickShowButton` interact with the actual I/O of the component. const createWrapper = () => { wrapper = shallowMountExtended(SomeComponent); }; const clickShowButton = () => { wrapper.findByTestId('show').trigger('click'); }
设置组件状态
-
尽可能避免使用
setProps设置组件状态。相反,在挂载组件时设置组件的propsData:// bad wrapper = shallowMount(MyComponent); wrapper.setProps({ myProp: 'my cool prop' }); // good wrapper = shallowMount({ propsData: { myProp: 'my cool prop' } });例外情况是当你希望以某种方式测试组件的响应性时。例如,你可能想在特定观察者(watcher)执行后测试组件的输出。使用
setProps测试这种行为是可以的。 -
避免使用
setData,因为它设置的是组件的内部状态,绕过了对组件实际 I/O 的测试。相反,应该触发组件子元素的事件或其他副作用来强制状态更改。
访问组件状态
-
访问 props 或属性时,优先使用
wrapper.props('myProp')语法,而不是wrapper.props().myProp或wrapper.vm.myProp:// good expect(wrapper.props().myProp).toBe(true); expect(wrapper.attributes().myAttr).toBe(true); // better expect(wrapper.props('myProp').toBe(true); expect(wrapper.attributes('myAttr')).toBe(true); -
断言多个 props 时,使用
toEqual检查props()对象的深度相等性:// good expect(wrapper.props('propA')).toBe('valueA'); expect(wrapper.props('propB')).toBe('valueB'); expect(wrapper.props('propC')).toBe('valueC'); // better expect(wrapper.props()).toEqual({ propA: 'valueA', propB: 'valueB', propC: 'valueC', }); -
如果你只对某些 props 感兴趣,可以使用
toMatchObject。优先使用toMatchObject而不是expect.objectContaining:// good expect(wrapper.props()).toEqual(expect.objectContaining({ propA: 'valueA', propB: 'valueB', })); // better expect(wrapper.props()).toMatchObject({ propA: 'valueA', propB: 'valueB', });
测试 props 验证
检查组件 props 时,使用 assertProps 助手函数。Props 验证失败将作为错误抛出:
import { assertProps } from 'helpers/assert_props'
// ...
expect(() => assertProps(SomeComponent, { invalidPropValue: '1', someOtherProp: 2 })).toThrow()