frontend

React的TypeScript写法

最近在用 React 写前端,不得不说 React 对 TypeScript 的支持比 Vue 好,Vue 的话,在组件传 Prop 的时候会丢失静态检查。

# React 组件写法

目前 React 有两种写法,一种是 Class Components,另外一种是 Function Components。这两种写法都可以,不过如何选择呢? 个人倾向于复杂的组件需要管理生命周期或者事件响应的话使用 Class Components,简单的组件例如只是展示一个按钮或者链接。使用 Function Components。 因为 Function Components 写起来代码更短。

# Class Components

import React, { ReactNode } from 'react';

interface ButtonProps {
  disabled?: boolean;
  children: ReactNode;
}

interface ButtonStates {
  count: number;
}

class Button extends React.Component<ButtonProps, ButtonStates> {
  static defaultProps = {
    disabled: false
  };

  constructor(props: ButtonProps) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    const { disabled, children } = this.props;

    return <button disabled={disabled}>{children}</button>;
  }
}

export default Button;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

上面的例子演示了如何定义属性和状态,并且设置默认属性值和初始化状态。

# Function Components

import React, { ReactNode } from 'react';

interface ButtonProps {
  disabled?: boolean;
  children: ReactNode;
}

const Button: React.FC<ButtonProps> = ({ disabled = false, children }) => {
  return <button disabled={disabled}>{children}</button>;
};

export default Button;
1
2
3
4
5
6
7
8
9
10
11
12

Function Components 不能直接传入 state 参数,需要使用 useState 函数来处理。

# 总结

React 写起来非常方便,但是条件渲染还是没有 Vue 方便。由于没有官方的 TypsScript 例子。只能自己总结一下方便以后使用了。

Vue表单组件如何自动化输入

使用 Vue 的数据双向绑定功能可以很方便的完成表单的数据收集和提交,再也不用自己手动监听事件和收集数据了。但同时自动化输入也变得复杂起来。

# 传统表单自动化输入

对于传统的表单我们可以很简单的自动化输入过程,例如checkboxinput元素,我们可以自动选中和输入内容:

document.querySelector('.checkbox').checked = true;
document.querySelector('.input').value = true;
1
2

当用户提交的时候,程序会读取相应元素的值,然后填充到请求里。

# Vue 组件自动化输入

这里我们以现在流行的Element为例子演示如何自动化输入。用过 Vue 的人都知道, v-model是用来进行双向绑定的。它的原理是:

Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    >
  `
});
1
2
3
4
5
6
7
8
9

到这里,我们就知道了通过$emit('input', value)的方式,可以绑定想要输入的值,直接使用传统方式修改input元素的值,有时候不会触发input事件,所以不是一个可行的办法。

接下来,我们讲下如何选中元素并调用$emit,例子是 Vue 官网的搜索框

const input = document.querySelector('.el-input');
input.__vue__.$emit('input', 'Hello world');
1
2

console里输入这两行代码,你会发现搜索框里被填入了Hello World。Vue 实例被隐藏到了 DOM 中,只要知道如何访问 Vue 然好调用$emit,问题就迎刃而解。

# 总结

现在的前端框架用起来方便,但也隐藏了大量细节。导致做自动化的时候不知道如何下手,尝试修改元素的值,但发现提交的时候还是没有带上正确的数值。这时候就需要思考框架绑定数据的本质是什么, 弄清了本质,其实做起来发现更简单!

Vue下使用jest进行单元测试

在前端开发逐渐复杂的今天,当然也需要对前端进行测试。单元测试主要用于白盒测试,检测程序的最小单位的正确性,通常来说就是验证函数的输入和输出。

# vue 集成 jest

vue-cli 已经提供现成的插件,我们可以很方便的进行集成。官方文档也有讲述如何 debug。在使用 jest 之前,我们需要知道 jest 的测试代码不是像前端代码一样运行在浏览器环境,而是使用 node 运行,并且浏览器的 DOM 环境使用jsdom来模拟,所以在集成测试的时候需要解决不少问题。

# jest 与 webpack

当今的前端开发都离不开 webpack 进行打包,我们使用各种各样的 loader 来处理代码,所以可以使用例如import语法来引用 JS 文件,但是 node 环境目前还不支持原生的 es6 import文件。所以在使用 jest 的时候,我们需要用 babel 进行代码转换,好在@vue/cli-plugin-unit-jest 都已经给配置好了,使用预置的 preset 就行。我是基于 TS 开发,所以用的 preset 是 @vue/cli-plugin-unit-jest/presets/typescript-and-babel。但是我们在代码里使用的一些 webpack 的特殊语法就不太好对应了,例如我有使用inline loader来打包特殊的 SVG 文件到代码中,但是在测试的时候就会遇到无法 import 的尴尬问题。这时候的解决方案就是使用moduleNameMapper:

moduleNameMapper: {
    '^!svg-inline-loader!@(.*)$': require.resolve('jest-transform-stub')
}
1
2
3

通过把 import 的资源文件映射为空,就能解决 jest 运行出错的问题,毕竟我们在做单元测试的时候不会检测资源文件。

# jest mock vuex store

做单元测试的时候,mock 是非常常见的需求,例如我们在一个文件里引用了 store 的数据,但是要构造这个数据非常复杂,所以我们需要直接 mock 数据。这里需要注意的是,如果是 import 引入的话,我们需要先 mock 再使用动态引用的方式加载测试模块,保证是在 import 之前 mock 数据。

jest.doMock('@/store', () => ({
  state: {
    account: {
      hello: 'world'
    }
  }
}));
const { default: formatter } = await import('@/utils/formatter');
1
2
3
4
5
6
7
8

不过,mock 之后记得 reset:

beforeEach(() => {
  jest.resetModules();
});
1
2
3

# jest mock navigator

有些测试用例中,我们需要 mock 用户的浏览器是不同语言,这个时候就需要使用spyOn来修改属性的返回值。示例代码如下:

describe('test i18n', () => {
  let languageGetter: jest.SpyInstance;
  beforeEach(() => {
    jest.resetModules();
    languageGetter = jest.spyOn(window.navigator, 'language', 'get');
  });

  it('set lang is zh-CN', () => {
    languageGetter.mockReturnValue('zh-CN');
    console.log(navigator.language); // zh-CN
  });
});
1
2
3
4
5
6
7
8
9
10
11
12

# jest mock vue router

测试 Vue 组件主要依靠Vue Test Utils。官方文档也有介绍如何使用,这个仅给出一个例子来描述用法:

import { mount, createLocalVue } from '@vue/test-utils';
import VueRouter from 'vue-router';

import App from '@/App.vue';
import routes from '@/router/routes';
import HelloWorld from '@/views/HelloWorld.vue';

const localVue = createLocalVue();
localVue.use(VueRouter);

describe('Test index routes', () => {
  it('should find HelloWorld when router is HelloWorld', async () => {
    const router = new VueRouter({ routes, mode: 'history' });
    await router.push('/helloworld');

    const wrapper = mount(App, {
      localVue,
      router
    });

    expect(wrapper.find(HelloWorld).exists()).toBe(true);
  });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

这里使用 localVue 的原因是项目中只有一个 Vue 实例,直接使用的话会污染原本的 Vue,所以要在测试代码里手动创建router。这里要注意的是,如果 find 是某个组件,需要在 Vue 组件里面显示声明name,否则在 Lazy Loading 的情况下是无法找到组件的。还有一个指得注意的是router.push 是一个 Promise 异步操作,所以如果写成同步的话,会一直找不到组件的。

# 总结

jest 集成了很多测试的工具,只装一个工具便可进行单元测试。但是由于不是运行在浏览器环境,导致jsdom不支持的特性都需要自己来处理。所以希望选择一个在浏览器运行单元测试的框架应该会少遇到很多坑吧。 特别是用了很多浏览器特性的前端项目,测试起来更加折腾。

# 引用

Using with webpack

Test Vue Router