likes
comments
collection
share

简单轮子:文本输入框

作者站长头像
站长
· 阅读数 48

一、如何书写README

当我们项目中某个文件夹中有很多文件,并且是配置文件时,比如node_module文件,webstorm读取时会耗费大量时间,而且还可能会造成webstorm卡顿。这时我们可以让webstorm忽略这个文件夹。

一般我们会将dist、node_modules目录排除在外,这样不会影响webstorm性能

简单轮子:文本输入框

README文件应该包含哪些东西?


# G-Design-of-Vue

## 介绍

## 开始使用

### 安装

## 文档

## 提问

## 变更记录

## 联系方式

## 贡献代码

1.我们要给项目添加持续集成的标,搜索下如circleci badge

如下图中vue的持续集成的标

circleci持续集成标的添加方法

我们只需要连接地址中的查询参数,换成我们自己的分支的名字即可

一个徽章标生成器

简单轮子:文本输入框

2.添加npm的标 搜索npm上的包名

3.添加 css 样式

i.使用本组件库中,请在css中开启 border-box

 *,::before,::after{margin: 0;padding: 0;box-sizing: border-box;}

(IE 8 及以上浏览器都支持此样式)

ii. 设置默认颜色变量(后续会改为scss变量)

html{
--button-height: 32px;
--font-size: 14px;
--button-bg: white;
--button-active-bg: #eee;
--border-radius: 4px;
--color: #333;
--border-color: #999;
--border-color-hover: #666;
--border-active-bg: #d3d2d2;
}

(IE 15 及以上浏览器都支持此样式)

4.安装 g-design-of-vue

npm i g-design-of-vue --save
  1. 引入 g-design-of-vue
import { GButton,GButtonGroup,GIcon } from 'g-design-of-vue';
import '../node_modules/g-design-of-vue/dist/index.css'

export default {
  name: 'App',
  components: {
    'g-button': GButton, 
    'g-icon': GIcon,
    'g-button-group': GButtonGroup
  }
}
  1. 接下来,要让用户不引入iconfont中的 svg symbol 链接地址,也可以使用我们的icon

如何做到用户不引入这个地址,也能使用icon呢?

我们需要帮助用户引入

在根目录下新建 svg.js,将iconfont中 symbol地址在浏览器打开,copy里面的代码,放到svg.js中

简单轮子:文本输入框

接着在Icon.vue组件中引入svg.js即可,我们不需要导出什么,因为引入之后就会将svg放到页面中

下次我们如果要往iconfont中添加icon,只需要重新生成 svg symbol 地址,浏览器中打开这个地址,复制地址中的代码到svg.js更新svg即可

简单轮子:文本输入框

在我们的项目中运行npx parcel --no-cache index.html,访问浏览器

从中可以看出页面中已经有项目中的7个svg了

二、如何解决奇怪的bug

原因

问题出现的原因运行npx parcel 默认启动的是npx parcel index.js而不是npx parcel index.html

过程

出现问题,第一时间一定要记得提交一次代码,这样我们才不会把代码弄丢

git log 或者 git reflog 查看历史提交记录

简单轮子:文本输入框

简单轮子:文本输入框

git reset --hard + 上图中commit的id将会覆盖当前本地磁盘上的代码(在这之前一定要commit提交代码,否则如果做错了,就找不到之前的代码了)

git show + 上图中commit的id 就可以看到对应提交版本,文件的变更情况

解决问题

npx parcel index.html 添加到package.json中的script脚本命令中,下次直接运行npm start就可以了

"scripts": {
  "start": "npx parcel index.html --no-cache",
  "dev-test": "parcel watch test/* --no-cache && karma start",
  "test": "parcel build test/* --no-cache --no-minify && karma start --single-run"
},

记得将之前发布到npm时修改的main,重新改到index.html作为入口文件

简单轮子:文本输入框

三、input组件需求分析

1. 用例分析

简单轮子:文本输入框

2. 确定input的状态

i. 正常的 normal 状态

ii. 鼠标悬浮时候的 hover 状态

iii. 鼠标键入时的focus 状态

iV. 禁止输入的 disabled 状态

V. 同disabled类似的只读状态 readonly 状态,区别是:readonly态的input是可以focusd,但是disabled不可以focus

以上五种状态都可以有下面的2种状态

i. error 失败状态

ii. success 成功状态

因此一共有10种状态

四、写input 样式

  • 大部分时候组件中的name是没有用的,当我们在浏览器中装一个vue开发者工具插件才能看区别
export default {
  name: 'g-input'
};

简单轮子:文本输入框

装上这个插件之后我们就可以,以组件的形式展示页面结构

因此组件中写name的作用是:在开发者工具下对组件进行自定义命名,方便调试

  • vue 中的模板语法是支持html语法,而<g-input/>这种标签自闭合的写法是xml的语法,因此我们要写成<g-input></g-input>

  • 样式加上scoped的作用是让css也有作用域,加上scoped后css样式只作用于当前组件,即使是其他组件也有一样的class,也不会出现相互覆盖的情况

<style lang="scss" scoped>

</style>

我们可以看到加了scoped后,组件样式后面都会加一个id,这样即使其他的组件也加同一名字,但是因为这个唯一id的存在就不会重命了

简单轮子:文本输入框

简单轮子:文本输入框

这个id是每个组件的唯一id,每个组件有不同的id,组件中的所有标签都会被同一个id标记

  • scss中声明变量的方式是$height: 32px;
  • 查看源代码的方法:

简单轮子:文本输入框

  • 如果子选择器中的样式想从父选择器中继承样式,可以在父选择器中写好样式,子选择器用inherit
  • vue中给组件传值

一种是:·<g-input value="张三" :disabled="true"></g-input>

另外一种是简写形式: <g-input value="李四" disabled></g-input>

:disabled="true"disabled="true"的区别:

有冒号的,true是boolen

没冒号的,true是字符串

  • cursor: not-allowed; 的样式就是鼠标悬浮时会有禁止的提示
  • 写组件的思路: 在props添加组件可以拥有的属性并绑定这个属性,在调用组件的地方给组件传属性值
  • vue中添加样式的对象写法:class="{'error': error},意识如果error存在就添加error样式,它可以简写成:class="{error}
  • 关于template标签
<icon v-if="error" name="setting"></icon>
<span v-if="error">{{error}}</span>

如果不想写2次v-if就要在外面添加div

<div v-if="error">
    <icon  name="setting"></icon>
    <span >{{error}}</span>
<div>

但是这样引入了一个无用的冗余标签,这样还要给div再额外的添加css

将div换成template,这样就既不会引入额外的div,又可以写if

<tempalte v-if="error">
    <icon  name="setting"></icon>
    <span >{{error}}</span>
<template>
  • webstorm中如果想折叠css样式,需要按2次shift,搜索join lines,快捷键ctrl + shift + j,如果不想要格式化的时候又恢复成多行,我们需要修改下格式化的配置

简单轮子:文本输入框

五、测试驱动开发(TDD)

监听input中的事件

input中最重要的就是change事件

// input.vue
<input type="text" :value="value" :disabled="disabled" :readonly="readonly"
@change="$emit('change',$event,'Hi!')"/>
// 使用input.vue
<g-input value="王五" @change="inputChange"></g-input>
methods:{
    inputChange(e,param){
        console.log(e.target.value,param);
    }
}

我们发现只有添加参数$event,才能获取到事件触发时的详细信息

简单轮子:文本输入框

解释一下上面代码的意思:

原生input触发了一个change事件,外面监听原生input的change事件,触发了自己的change事件执行inputChange函数,参数e就是触发是传的$event,$event是浏览器给的,就是用户在触发change事件时的所有信息

因此的参数为$emit('事件名',传给监听者的第一个参数,传给监听者的第二个参数...)

<input type="text" :value="value" :disabled="disabled" :readonly="readonly"
  @change="$emit('change',$event)"
  @input = "$emit('input',$event)"
  @focus = "$emit('focus',$event)"
  @blur = "$emit('blur',$event)"
/>


<g-input value="王五" @change="inputChange"></g-input>

methods:{
    inputChange(e,param){
        console.log(e.target.value,param);
    }
    ...
}

与其写代码去监听,还不如写测试用例

简单轮子:文本输入框

在写测试用例的时候,我们要测组件拥有的属性和需要监听的方法都要测

需要测试的属性有:value(input中的值)、disable(禁止输入)、readonly(只读)、error(报错)

新建test/input.test.js

备注

  • 在测试readonly属性的时候,only要大写
expect(inputElement.readOnly).to.equal(true);
  • 测试error的时候要分别测试 error icon 和 erro text
const useElement = vm.$el.querySelector('use');
expect(useElement.getAttribute('xlink:href')).to.equal('#i-error');
const errorMessage = vm.$el.querySelector('.error-message');
expect(errorMessage.innerText).to.equal('errorMessage');
  • describe 中嵌套describe的作用是使用describe对测试用例进行分组,如测试组件属性,测试组件方法等分组

  • 测试驱动开发(TDD)的含义:』先进行测试,如果测试有错误就去写代码,如果测试没有错误就继续测试

  • 对于测试中重复的代码,我们尝试抽离出相同的代码,我们不妨在nocha.js中搜索关键字beforEach

简单轮子:文本输入框

其中before的意思是在所有的之前,beforeEach是在每个测试用例之前,记得提前vm变量的作用域

  • 测试事件稍微麻烦点,不光要测试监听事件被触发,而且要测试触发后的回调callback中的参数可以获取到时间的类型为change
describe('events', () => {
    const Constructor = Vue.extend(Input);
    let vm;
    it('支持 change 事件', () => {
        vm = new Constructor({}).$mount();
        const callback = sinon.fake();
        vm.$on('click', callback);
        // 触发input的change事件??
        expect(callback).to.have.been.called;
    });
});

那么如何主动的触发一个事件呢?

在stackoverflow中搜索关键字js trigger event 得到的答案是使用dispachEvent

接着使用MDN搜索dispachEvent的用法,知道了inputElement.dispatchEvent(event) 这样就可以触发一个事件

那么如何测试回调函数callback中的参数可以获取到事件类型为change呢?

由于我们测试是用sinon-chai(sinon-chaisinochai拉到一起合作) 和 karma(提供了一个测试的环境)来做的

搜索sinon-chai的文档,知道它提供了一个calledWith方法

describe('events', () => {
    const Constructor = Vue.extend(Input);
    let vm;
    it('支持 change 事件', () => {
        vm = new Constructor({}).$mount();
        const callback = sinon.fake();
        vm.$on('change', callback);
        // 触发input的change事件
        let event = new Event('change')
        const inputElement = vm.$el.querySelector('input');
        inputElement.dispatchEvent(event)
        expect(callback).to.have.been.calledWith(event);
    });
});

最后的断言的意思就是:当触发change事件后callback回调被调用,同时callback回调函数的第一个参数是event(change事件的详情)

简单轮子:文本输入框

结果测试都通过了

测试其他的事件也都是一个道理了

最后优化一下代码,去除重复的代码,下次如果有新的事件要测试,只要继续往数组中添加即可

describe('events', () => {
    const Constructor = Vue.extend(Input);
    let vm;
    it('支持 change/input/focus/blur 事件', () => {
        ['change','input','focus','blur'].forEach((eventName)=>{
            vm = new Constructor({}).$mount();
            const callback = sinon.fake();
            vm.$on(eventName, callback);
            // 触发input的change事件
            let event = new Event(eventName)
            const inputElement = vm.$el.querySelector('input');
            inputElement.dispatchEvent(event)
            expect(callback).to.have.been.calledWith(event);
        })
    });
});

备注:karma.mocha 和 chai是干嘛的?

karma有一个配置文件叫karma.conf.js,它主要是打开浏览器并引入一些基础库

看配置文件可知,karma打开chrome的无头浏览器

引入mocha,引入mocha后我们就拥有descirbe和it函数(挂载windows上的全局函数不用去引)

引入sinon-chai,就是同时引入sinon 和 chai,sino就是用来做fake函数用的(类似spies)。chai提供expect函数

为什么 sinon 可以和 chai.js 合作呢?因为有sinon-chai,它会提供一个API叫calledWith

然后kama还引入了dist/test文件中的测试用例引入到浏览器中

最后还要reporter,即测试后命令行显示的提示文字

六、让input支持v-model

目前的组件支持传参和事件,并不支持双向绑定

v-model的本质

v-model是语法糖

当我们写了v-model时实际上是做了2件事情

  1. 绑定value
  2. 监听了input事件,事件名必须是input,事件的第一个参数必须是value的值
<input type="text" :value="message" @input="message = $event.target.value"/>


// input组件
<input type="text" :value="value" :disabled="disabled" :readonly="readonly"
  @input = "$emit('input',$event.target.value)"
/>

如果要让我们的input组件要支持v-model,第一它必须接受一个叫value的属性,第二它必须接收一个input事件,目前我们的组件已经支持这2点要求了,也就是说已经支持了v-model

备注:

  • 写了v-model就相当于在input上给了value

支持v-model后,最后记得修改测试用例,测试事件触事件向外传的值($event.target.value)

google js new event set target

describe('events', () => {
    const Constructor = Vue.extend(Input);
    let vm;
    it('支持 change/input/focus/blur 事件', () => {
        ['change', 'input', 'focus', 'blur'].forEach((eventName) => {
            vm = new Constructor({}).$mount();
            const callback = sinon.fake();
            vm.$on(eventName, callback);
            // 触发input的change事件
            let event = new Event(eventName);
            Object.defineProperty(event, 'target',
                {
                    value: {value: 'hi'},
                    enumerable: true
                }
            );
            let inputElement = vm.$el.querySelector('input');
            inputElement.dispatchEvent(event);
            expect(callback).to.have.been.calledWith('hi');
        });
    });
});

备注

Object.defineProperty()  静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。

转载自:https://juejin.cn/post/7255265868065882168
评论
请登录