Ice-UI 总结

231 阅读6分钟

Ice-UI 造轮子

项目官网:orange_ice.gitee.io/ice-ui

前言

已经有了那些经典且成熟的库或者框架,为什么还要自己去造轮子呢?

其实,造轮子对于提升自己的技能水平是有很大帮助的。

  • 经常用别人写的按钮组件,为何不自己写一个试试?
  • 经常用别人写的消息弹框,为何不自己写一个试试?
  • 自己真的一步一步写出来之后,就会发现「就那么回事」,事实就是能力得到了提升。

同时,可以提高自己解决问题的能力。

  • 当我们自己写业务代码的时候,只需要满足自己业务的逻辑。
  • 而写轮子的的时候,就需要满足大部分人的需求,这就很锻炼自己的提炼需求、分析需求和解决问题的能力。

一般网站的开发流程

  1. 立项 - 确定要做,确定人员,确定预算等
  2. 需求 - 需求收集和分析
    • 收集比分析更难,有的时候用户也不知道自己的需求
    • 亨利·福特曾说过,「如果我最初是问消费者他们想要什么,他们应该是会告诉我,要一匹更快的马!」
    • 可以用「用例图」来分析需求
  3. 可行性分析
  4. 系统设计(功能设计、框架设计)
    • UML 图、时序图等
  5. 原型设计(草图、线框图)
    • 草图用纸和笔画
    • 线框图可以用 Balsamiq
  6. 交互设计
    • 可以用 Axure RP、墨刀、Sketch.app
  7. 视觉设计
    • 可以用 Photoshop、Fireworks、Sketch.app
  8. 程序开发
  9. 测试
  10. 功能预演
  11. 内测
  12. 灰度发布
  13. 正式发布

项目起步

  1. 首先选择一个空目录,创建一个 README.md 文件并简单先编辑一下

  2. 初始化 git

    git init
    git add .
    git commit -m init
    

    在GitHub创建仓库,建立连接,git push

  3. 在GitHub上确立项目的LINCENSE

  4. 项目初始化

    npm init
    npm install -S Vue
    
  5. 开始开发自己的组件

自动化测试

BDD Behavior Driven Development 行为驱动开发

TDD Test Driven Development 测试驱动开发

Assert 断言

使用 Karma + Mocha 做单元测试

  1. Karma([ˈkɑrmə] 卡玛)是一个测试运行器,它可以呼起浏览器,加载测试脚本,然后运行测试用例
  2. Mocha([ˈmoʊkə] 摩卡)是一个单元测试框架/库,它可以用来写测试用例
  3. Sinon(西农)是一个 spy / stub / mock 库,用以辅助测试。

步骤

  1. 安装各种工具

    npm i -D karma karma-chrome-launcher karma-mocha karma-sinon-chai mocha sinon sinon-chai karma-chai karma-chai-spies
    
  2. 创建 karma 配置

    // 新建 karma.conf.js,内容如下
     module.exports = function (config) {
         config.set({
    
             // base path that will be used to resolve all patterns (eg. files, exclude)
             basePath: '',
                // frameworks to use
                // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
                frameworks: ['mocha', 'sinon-chai'],
                client: {
                    chai: {
                        includeStack: true
                    }
                },
    
    
                // list of files / patterns to load in the browser
                files: [
                    'dist/**/*.test.js',
                    'dist/**/*.test.css'
                ],
    
    
                // list of files / patterns to exclude
                exclude: [],
    
    
                // preprocess matching files before serving them to the browser
                // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
                preprocessors: {},
    
    
                // test results reporter to use
                // possible values: 'dots', 'progress'
                // available reporters: https://npmjs.org/browse/keyword/karma-reporter
                reporters: ['progress'],
    
    
                // web server port
                port: 9876,
    
    
                // enable / disable colors in the output (reporters and logs)
                colors: true,
    
    
                // level of logging
                // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
                logLevel: config.LOG_INFO,
    
    
                // enable / disable watching file and executing tests whenever any file changes
                autoWatch: true,
    
    
                // start these browsers
                // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
                browsers: ['ChromeHeadless'],
    
    
                // Continuous Integration mode
                // if true, Karma captures browsers, runs the tests and exits
                singleRun: false,
    
                // Concurrency level
                // how many browser should be started simultaneous
                concurrency: Infinity
            })
        }
    
  3. 创建测试文件(test/button.test.js)

    const expect = chai.expect;
     import Vue from 'vue'
     import Button from '../src/button'
    
     Vue.config.productionTip = false
     Vue.config.devtools = false
    
     describe('Button', () => {
         it('存在.', () => {
             expect(Button).to.be.ok
         })
         it('可以设置icon.', () => {
             const Constructor = Vue.extend(Button)
             const vm = new Constructor({
             propsData: {
                 icon: 'settings'
             }
             }).$mount()
             const useElement = vm.$el.querySelector('use')
             expect(useElement.getAttribute('xlink:href')).to.equal('#i-settings')
             vm.$destroy()
         })
         it('可以设置loading.', () => {
             const Constructor = Vue.extend(Button)
             const vm = new Constructor({
             propsData: {
                 icon: 'settings',
                 loading: true
             }
             }).$mount()
             const useElements = vm.$el.querySelectorAll('use')
             expect(useElements.length).to.equal(1)
             expect(useElements[0].getAttribute('xlink:href')).to.equal('#i-loading')
             vm.$destroy()
         })
         it('icon 默认的 order 是 1', () => {
             const div = document.createElement('div')
             document.body.appendChild(div)
             const Constructor = Vue.extend(Button)
             const vm = new Constructor({
             propsData: {
                 icon: 'settings',
             }
             }).$mount(div)
             const icon = vm.$el.querySelector('svg')
             expect(getComputedStyle(icon).order).to.eq('1')
             vm.$el.remove()
             vm.$destroy()
         })
         it('设置 iconPosition 可以改变 order', () => {
             const div = document.createElement('div')
             document.body.appendChild(div)
             const Constructor = Vue.extend(Button)
             const vm = new Constructor({
             propsData: {
                 icon: 'settings',
                 iconPosition: 'right'
             }
             }).$mount(div)
             const icon = vm.$el.querySelector('svg')
             expect(getComputedStyle(icon).order).to.eq('2')
             vm.$el.remove()
             vm.$destroy()
         })
         it('点击 button 触发 click 事件', () => {
             const Constructor = Vue.extend(Button)
             const vm = new Constructor({
             propsData: {
                 icon: 'settings',
             }
             }).$mount()
    
             const callback = sinon.fake();
             vm.$on('click', callback)
             vm.$el.click()
             expect(callback).to.have.been.called
    
         })
     })
    
  4. 创建测试脚本

    在 package.json 里面找到 scripts 并改写

    "scripts": {
         "dev-test": "parcel watch test/* --no-cache & karma start",
         "test": "parcel build test/* --no-minify && karma start --single-run"
     },
    
  5. 运行测试脚本

    • npm run test 一次性运行
    • npm run dev-test 经行 watch 监听

持续集成

什么是持续集成

Travis CI 提供的是持续集成服务(Continuous Integration,简称 CI)。它绑定 Github 上面的项目,只要有新的代码,就会自动抓取。然后,提供一个运行环境,执行测试,完成构建,还能部署到服务器。

持续集成指的是只要代码有变更,就自动运行构建和测试,反馈运行结果。确保符合预期以后,再将新代码"集成"到主干。

持续集成的好处在于,每次代码的小幅变更,就能看到运行结果,从而不断累积小的变更,而不是在开发周期结束时,一下子合并一大块代码。

使用准备
  1. 拥有 Github 账号
  2. 在该账号下面有一个项目
  3. 该项目里有可运行的代码
  4. 该项目还包含构建或者测试脚本

开始使用,访问官方网站 travis-ci.org ,用 Github 账户登录,选择需要激活的仓库,然后 Travis 就会监听这个仓库的所以变化。

.travis.yml

Travis 要求项目的根目录下面,必须有一个.travis.yml文件。这是配置文件,指定了 Travis 的行为。该文件必须保存在 Github 仓库里面,一旦代码仓库有新的 Commit,Travis 就会去找这个文件,执行里面的命令。

language: node_js
node_js:
  - "12"
addons:
  chrome: stable
sudo: required
before_script:
  - "sudo chown root /opt/google/chrome/chrome-sandbox"
  - "sudo chmod 4755 /opt/google/chrome/chrome-sandbox"

发布 npm 包

确保代码测试通过

npm run test 全部是绿色才行

上传代码到 npmjs.org
  1. 创建 index.js ,在 index.js 里将想要的导出的内容全部导出

  2. node 目前确实不支持 import,需要用 babel 转译 import,npx parcel build index.js --no-minify

  3. 将package.json 的 "main" 改为 dist/index.js

  4. 注册一个 npm 账户,确认下邮箱

  5. 在项目根目录运行

    npm adduser
    npm publish
    

    要将 npm 从淘宝源切换回官方源

本地加速调试

在项目目录使用 npm link 或者yarn link,在本地使用时 使用 npm link <package_name>

小结

  1. 测试用例中,如果有一部操作,需要在 it 的 fn 中传入参数 done,并在异步操作执行完后调用 done()。

  2. Vue.nextTick([callback,content])

    参数:

    • {Function} [callback]
    • {Object} [context]

    用法:

    在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

    // 修改数据
    vm.msg = 'Hello'
    // DOM 还没有更新
    Vue.nextTick(function () {
      // DOM 更新了
    })
    
  3. props 是 对象或者函数时,需要用函数 return

  4. 父元素设置了 min-height 之后,子元素的 height:100% 就无作用了。

  5. 发布订阅模式 emit/on/off

  6. 单向数据流

Vuepress 制作官网

快速上手

将 VuePress 安装为本地依赖

yarn add -D vuepress # npm install -D vuepress

创建第一篇文档

mkdir docs && echo '# Hello VuePress' > docs/README.md

package.json 中添加一些 scripts.

{
  "scripts": {
    "docs:dev": "vuepress dev docs",
    "docs:build": "vuepress build docs"
  }
}

在本地启动服务器

yarn docs:dev # npm run docs:dev

基本配置

目录结构

.
├─ docs
│  ├─ README.md
│  └─ .vuepress
│     └─ config.js
└─ package.json

必要的配置文件 .vuepress/config.js

module.exports = {
  base:'/Ice-UI/',
  title: 'Ice-UI',
  description: '一个简约、易用的 UI 框架',
  themeConfig: {
    logo: '/assets/img/logo.png',
    nav: [
      {text: '主页', link: '/'},
      {text: '文档', link: '/get-started/'},
      {text: 'Github', link: 'https://github.com/Orange-ice/Ice-UI'},
    ],
    displayAllHeaders: true, // 默认值:false
    sidebar: [
      {
        title: '入门',
        children: [
          '/install/',
          '/get-started/',
        ]
      },
      {
        title: '组件',
        collapsable: false, // 可选的, 默认值是 true,
        children: [
          '/components/button',
          '/components/tabs',
          '/components/input',
          '/components/grid',
          '/components/layout',
          '/components/toast',
          '/components/collapse',
          '/components/popover',
        ]
      },

    ]
  }
}