Vue项目实践笔记

685 阅读6分钟

一. 如何导入svg

1. 申明一个svg-module

//新建一个*.d.ts文件,默认是shims.d.ts
declare  module  '*.svg'{
  const content: string;
  export  default  content;
}

在此文件后面加上这段代码就可以在组件中import一个svg文件了

2. 使用svg-sprite-loader加载

$ yarn add svg-sprite-loader -D

接下来要配置webpack 将他转换成Vue.config.js Vue项目中的webpack.config.js在"node_modules@vue\cli-service\webpack.config.js"路径中,可以在webstorm中手动添加此路径

const path = require('path')
module.exports = {
  lintOnSave: false,
chainWebpack:config=> {
  const dir = path.resolve('src/assets/icons')
  config.module
    .rule('svg-sprite')
    .test(/\.svg$/)
    .include.add(dir).end() // 包含 icons 目录
    .use('svg-sprite-loader').loader('svg-sprite-loader').options({extract: false}).end()
  config.plugin('svg-sprite').use(require('svg-sprite-loader/plugin'), [{plainSprite: true}])
  config.module.rule('svg').exclude.add(dir) // 其他 svg loader 排除 icons 目录
  }
}

3. 如何在页面中使用

import "@/assets/icons/filename.svg"
<svg>
    <use xlink:href="#filename"/>
</svg>

4.如何引入整个icons文件夹

<script>
let importAll = (requireContext: __WebpackModuleApi.RequireContext) => requireContext.keys().forEach(requireContext);
  try {importAll(require.context('../assets/icons', true, /\.svg$/));} catch (error) {console.log(error);}
</script>

这样就不用一个个的引入icons中的文件

5. active-class

如果你需要一个标签绑定一个点击后样式变化,可以添加一个active-class="selected"的属性,在css中写好相应的selected样式 但是这种方法有一个bug,当SVG的父元素使用这个属性时,若SVG有自己的填充样式则不会被selected样式所覆盖

<router-link to="/Money" class="item" active-class="selected">
     <svg class="icon">
      <use :xlink:href="'#'+name"/>
    </svg>
</router-link>

而SVG的填充色在其path中fill属性中,所以要使得selected样式正确显示得删除fill属性

<path fill="red" d="M0 438.856h292.568V1024H0V438.856zM731.432 219.44H1024V1024H731.432V219.432zM365.712 0h292.576v1024H365.712V0z"
          p-id="3015"></path>

但是如果有很多svg,那么我们就得一个个删么,太傻了,所以要装一个svg优化的加载器

$ yarn add --dev svgo-loader

配置vue.config.js

module.exports = {
  lintOnSave: false,
chainWebpack:config=> {
  const dir = path.resolve('src/assets/icons')
  config.module
    .rule('svg-sprite')
    .test(/\.svg$/)
    .include.add(dir).end() // 包含 icons 目录
    .use('svg-sprite-loader').loader('svg-sprite-loader').options({extract: false}).end()
    .use('svgo-loader').loader('svgo-loader')
    .tap(options => ({...options, plugins: [{removeAttrs: {attrs: 'fill'}}]}))
    .end()
//这里是将fill删除,优化svg
  config.plugin('svg-sprite').use(require('svg-sprite-loader/plugin'), [{plainSprite: true}])
  config.module.rule('svg').exclude.add(dir) // 其他 svg loader 排除 icons 目录

6.svg-sprite-loader的一个bug

这个loaderwebstorm中``@import "~@/assets/xxx.scss"的时候会报错因此建议将此loader换成修改版,同时Vue.config.js也要修改

$ yarn add --dev svg-sprite-loader-mod
config.module
      .rule('svg-sprite')
      .test(/\.svg$/)
      .include.add(dir).end() // 包含 icons 目录
      .use('svg-sprite-loader-mod').loader('svg-sprite-loader-mod').options({extract:false}).end()
      .use('svgo-loader').loader('svgo-loader')
      .tap(options => ({...options, plugins: [{removeAttrs: {attrs: 'fill'}}]})).end()
            //这里是将fill删除,优化svg
    config.plugin('svg-sprite').use(require('svg-sprite-loader-mod/plugin'), [{plainSprite: true}])
    config.module.rule('svg').exclude.add(dir) // 其他 svg loader 排除 icons 目录

二. 如何引入字体

Font.css跨平台中文字体解决方案,比如

font-family: -apple-system, "Noto Sans", "Helvetica Neue", Helvetica, "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", "ST Heiti", SimHei, "WenQuanYi Zen Hei Sharp", sans-serif;

sass

1. @mixin与@include

@mixin defaults($background, $position, $top, $left, $width, $height) {
    background: $background;
    position: $position;
    top: $top;
    left: $left;
    width: $width;
    height: $height;
}
#outer {
    @include defaults(transparent, relative, 35%, 0%, 25em, 25em);
}

sass中的@mixin可以看出是一个函数,他接受许多参数,在需要混入这些样式的时候,用@include来调用并传入相应的参数

三. v-model

<template>
  <div>
    <div>
    {{value}}
      <label class="notes">
        <span class="name">备注</span>
        <input type="text" :value="value" @input="this.value=$event.target.value" placeholder="请在这里添加备注 ">
      </label>
    </div>
  </div>
</template>

如果想要监听一个输入事件的数据对象变化,可以将上面两句改成一句

<template>
  <div>
    <div>
    {{value}}
      <label class="notes">
        <span class="name">备注</span>
        <input type="text" 
        v-model="value"
        placeholder="请在这里添加备注 ">
      </label>
    </div>
  </div>
</template>

四. Vue.$route

如果要实现点击一个按钮跳转到相应页面,用router些路径的话会没完没了,所以可以用$route.params.key来实现每个路由页面和组件的对应

{
    path:"/Labels/edit/:key",
    name:"edit",
    component: EditLabel
  }

五. @click.native

假如你为一个button创建了一个组件,但是外部点击的时候却不触发事件,那么有两种解决方法

//第一种,在内部button中传递一个点击事件
 <button class="button" @click="$emit('click',$event)"><slot/></button>
//第二种,在外部组件中给click添加一个修饰符
<Button class="newTag" @click.native="createTag">新建标签</Button>

六. 全局数据管理

1. 通过window.x获取同一个属性实现数据同步

如果有两个页面通过同一个model获取数据,但是他们的获取到的都是同一个数据的副本,这样在不刷新页面的情况下,一个页面数据改变,另一个不会实时的更新数据,我们可以通过将这个数据model上的相同属性挂载到window.x上,让不同组件通过window.x获取数据,就可以实现数据同步,这个全局属性建议挂到main.ts上

window.tagList=tagListModel.fetch();

2. 单纯值引用

由于对象的复杂属性值是存在对象地址的,而单纯的值则是存在于stack区所以当对象的该值改变,其所引用的副本并不会随之改变,所以我们可以用computed来解决

@Component({
    computed:{
      count(){
        return store.count
      }
    }
}
///App.vue中绑定store
data(){
      return{
        store:store
      }

并且要将目标对象写入最外层组件的data中使得Vue来监听这个对象的变化

七. Vuex

基本格式和选项

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
//Vue.prototype.$store=store
const store = new Vuex.Store({
//数据部分data
  state: {
      count: 0
  },
//相当于methods
  mutations: {
      increment (state,args...) {
      state.count++
    }
  },
//异步操作,用于调用mutations中的方法
  actions: {
       increment (context) {
      context.commit('increment')
    }
  },
//
  modules: {}
});
export default store;

Vue.use(Vuex)会将store绑定到Vue.prototype.$store上,这样所有的Vue实例都可以通过this.$store访问到这个对象

//main.ts
import store from './store';
new Vue({
  router: router,
  store,
  render: h => h(App)
}).$mount('#app');

这个绑定会在生成实例的时候生效,所以要讲store写入Vue的构造参数中

1. state(单一状态树)

使用store.state.count来获取对象的状态属性

2. mutations(同步方法)

在调用mutations中的状态变更方法时,我们不能直接使用store.mutations.increment(store.state)这种方法,因为太繁琐了,所以Vuex提供了一个store.commit('increment',args...)来触发相应的状态更新方法,这个过程会自动将state作为参数传入,其他参数写在store.commit的第一个参数之后

\color{ #ff0000}{注:}这里的函数不能有返回值,即外部得到他的返回值是void,外部能访问到的只有state中的属性,所以返回值要体现在state中,且外部属性获取需使用get(),set()方法
get tag() {
      return this.$store.state.currentTag;
    }
并且mutation函数只接受两个参数,一个被state占用了,如果args多余两个则必须写在一个对象中,然后再用解构得到参数
updateTag(state, payload: {id: string;name: string}) {
      const {id,name}=payload;
      }

3. actions(异步方法)

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。
 actions: {
       increment (context) {
      context.commit('increment')
    }

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用context.commit 提交一个 mutation

4. 定义state数据类型

type RootState = {
  recordList: RecordItem[];
  tagList: Tag[];
  currentTag?: Tag;
}
const store = new Vuex.Store({
  state: {
    recordList: [] as RecordItem[],
    tagList: [] as Tag[],
    currentTag: undefined
  } as RootState,
\color{ #ff0000}{注:}由于vuex在声明$store的时候默认将它的类型定义成any导致在之后的引用此数据对象的属性时都无法得到正确的类型所以每次都要手动断言其类型()

5.ISOString()

由于JSON.parse()函数不能处理Date类型,所以要用这个函数将Date转换成字符串(ISO 8001),得到的字符串是处于0时区的时间,而中国处于东8区,通过Date.parse()就可以得到一串数字,再传入new Date()就可以重新得到Date对象,这个JSON.parse和JSON.toString相似 通过以下Api可以得到相应的小时,天,月,日等

但是推荐使用dayjs,这是一个跟时间操作相关的库,基本api

1. isSame

dayjs().isSame('2011-01-01', 'year')
//判断是否跟这个日期是同一年,year可以改其他月,日毫秒等

2. add,substract

dayjs().add(7, 'day')
//加7天
dayjs().subtract(7, 'year')
//减7年

3. format

dayjs('2019-01-25').format('DD/MM/YYYY') // '25/01/2019'
//格式化插入模板,详见官网

八. ::v-deep

如果你引用了一个组件,并且想在使用scope的情况下对所引用组件内部的css进行修改,可以使用这个语法

.className ::v-deep li{
}

或者使用/deep/,当然前者兼容性更强,后者会对sass报错

如何配合::v-deep精确控制字组件的css

我们可以外部传入一个class前缀,然后在字组件的class上添加此前者classPrefix,但是:class动态绑定class只能写一次,所以我们将所有动态绑定的class写入一个对象中

//子组件Types
<li :class="{[classPrefix+'-item']:classPrefix, selected:value==='-'}" @click="selectType('-')">支出</li>
@Prop() classPrefix?: string;
//将前缀部分通过prop传入
//外部组件
<div class="x">
<Types class-prefix="anyPrefix"/>
</div>
<style lang="scss" scoped>
  .x ::v-deep anyPrefix-item{
    background: #fff;
    &.selected{
      background: #c4c4c4;
    }
  }
</style>

这个对象相当于一个表驱动class,只有在条件成立时才添加此class,如果class中有变量,可以用[]将变量部分包裹

九. 手机调试

1. serve

打开pc端的本地serve服务,手机端同样访问此网址,注意要在同一局域网

2. chrome/safari远程调试

3. Vconsole

十. 上传到github

1. 查看dist是否ok

$yarn add serve
$serve -s dist

用serve来预览dist目录应用运行是否正常

2. 配置配置文件引用路径

//vue.config.js中
module.exports = {
  publicPath: process.env.NODE_ENV === 'production'
    ? '/github-repository-name/'
    : '/'
}

假如我要在public中放一个图片,那么他的路径应该是/github-repository-name/xx.png这样在github中才会正确引用,

而且改了东西重新上传一定一定要重新关闭gitpages然后打开,刷新是没用的,没用的,没用的,为了这事我整了半个小时,前前后后就是不知道为什么路径没更新,明明仓库都更新了,太艹蛋了

在vue.config.js文件中为生产环境dist目录添加github仓库路径,注意本地预览时要切换到开发环境,即把这3行代码注释

3. 配置一键部署脚本

#!/usr/bin/env sh

# abort on errors
set -e

# build
yarn build

# navigate into the build output directory
cd dist

# if you are deploying to a custom domain
# echo 'www.example.com' > CNAME

git init
git add -A
git commit -m 'deploy'

# if you are deploying to https://<USERNAME>.github.io
# git push -f git@github.com:<USERNAME>/<USERNAME>.github.io.git master

# if you are deploying to https://<USERNAME>.github.io/<REPO>
git push -f git@github.com:FFreshman/money-local-demonstration.git master:gh-pages

cd -
------
$sh deploy.sh

4. 源码部分新建仓库

将源码部分另行建一个仓库,dist目录加入.gitignore文件中,默认已加入

最后附上项目地址,欢迎指点

源码地址

预览地址

<===TO BE CONTINUED