vue input事件_Vue 记账项目总结

8c1f1523cf2d336174a1d358d5aeb37f.png

一、项目创建

  • 全局安装@vue/cli
  • 安装Node.js v10以上版本
  • 使用VScode或者WebStorm
  • vue create 项目名
  • cd 项目名
  • yarn serve

import alias:

  • TS/JS可以通过 @/目录名 引入文件。
  • CSS/SASS 可以通过 ~@/目录名 引入文件,不过在WebStorm要配置webpack文件为vue的webpack配置文件。

二、使用Vue Router 底部导航

这里使用hash模式。vue-router默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。

  • 同样的思路,记账,标签和统计三个页面,默认进入记账,添加一个404页面。
  • 在router/index.ts里添加路径,配置对应组件。
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar }
]

// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
  routes // (缩写) 相当于 routes: routes
})
  • 初始化组件。
  • 将router传给new Vue()
const app = new Vue({
  router
}).$mount('#app')
  • 在App组件里用<router-view/>给出router渲染区域

三、使用svg-sprite-loader引入icon

安装svg-sprite-loader,将svg转化为一个symbol。

在vue.config.js添加:
const path = require('path')

module.exports = {
  publicPath: process.env.NODE_ENV === 'production'
    ? '/save-money-website-1/'
    : '/',
  lintOnSave: false,

  chainWebpack: config =>{
    const dir = path.resolve(__dirname, 'src/assets/icons')

    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()
    config.plugin('svg-sprite').use(require('svg-sprite-loader-mod/plugin'), [{plainSprite: true}])
    config.module.rule('svg').exclude.add(dir) // 其他 svg loader 排除 icons 目录
  },
}
使用:<svg>
    <use :xlink:href="'#'+name"/>
  </svg>

四、优化思路

封装导航栏:

两种思路:在App全局展示或者每个组件自己引入

这里感觉组件自己引入好一点,可能会有组件不需要,比如404页面

确定思路后,添加CSS,使用flex,多试,错了就改,没错就继续。

代码优化:

事不过三,多次使用的地方一定要抽离,例如:

将svg抽离成<Icon />组件

将导航抽离成<Nav />组件

将布局抽离成<Layout />组件

五、vue的装饰器

vue-class-component(官方文档)和vue-property-derocator(官方文档)

这里主要用到@components和@Prop

import { Vue, Component, Prop } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  @Prop(Number) readonly propA: number | undefined
  @Prop({ default: 'default value' }) readonly propB!: string
  @Prop([String, Boolean]) readonly propC: string | boolean | undefined
}

相当于:

export default {
  props: {
    propA: {
      type: Number,
    },
    propB: {
      default: 'default value',
    },
    propC: {
      type: [String, Boolean],
    },
  },
}

还有mixin,watch。

// mixins.js
import Vue from 'vue'
import Component from 'vue-class-component'

// You can declare mixins as the same style as components.
@Component
export class Hello extends Vue {
  hello = 'Hello'
}

@Component
export class World extends Vue {
  world = 'World'
}

然后使用:

import Component, { mixins } from 'vue-class-component'
import { Hello, World } from './mixins'

// Use `mixins` helper function instead of `Vue`.
// `mixins` can receive any number of arguments.
@Component
export class HelloWorld extends mixins(Hello, World) {
  created () {
    console.log(this.hello + ' ' + this.world + '!') // -> Hello World!
  }
}

watch:

import { Vue, Component, Watch } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  @Watch('child')
  onChildChanged(val: string, oldVal: string) {}

  @Watch('person', { immediate: true, deep: true })
  onPersonChanged1(val: Person, oldVal: Person) {}

  @Watch('person')
  onPersonChanged2(val: Person, oldVal: Person) {}
}

相当于:

export default {
  watch: {
    child: [
      {
        handler: 'onChildChanged',
        immediate: false,
        deep: false,
      },
    ],
    person: [
      {
        handler: 'onPersonChanged1',
        immediate: true,
        deep: true,
      },
      {
        handler: 'onPersonChanged2',
        immediate: false,
        deep: false,
      },
    ],
  },
  methods: {
    onChildChanged(val, oldVal) {},
    onPersonChanged1(val, oldVal) {},
    onPersonChanged2(val, oldVal) {},
  },
}

六、v-model和.sync

这两者本质上是差不多的,并没有任何区别: “监听一个触发事件”="(val) => value = val"。

v-model:

<!--v-model写法-->
<my-component type="text" v-model="value">
<!--展开语法糖后的写法-->
<my-component type="text"
  :value="value"
  @input="value = $event.target.value"
>
<!--
默认针对原生组件input事件,但是如果子组件定义了针对事件
model: {
        prop: "value",
        event: "update"
},
则编译为
-->
<my-component type="text"
  :value="value"
  @update="(val) => value = val"
>

.sync:

<!--语法糖.sync-->
<my-component :value.sync="value" />
<!--编译后的写法-->
<my-component 
  :value="msg" 
  @update:value="(val) => value = val"
>

细微之处的区别:

  1. 只不过v-model默认对应的是input或者textarea等组件的input事件,如果在子组件替换这个input事件,其本质和.sync修饰符一模一样。比较单一,不能有多个。
    // 子组件可以用自定义事件,来替换v-model默认对应的原生input事件,只不过我们需要在子组件手动 $emit
    model: {
    prop: "value",
    event: "update"
    },
  2. 一个组件可以多个属性用.sync修饰符,可以同时"双向绑定多个“prop”,而并不像v-model那样,一个组件只能有一个。

总结:

  1. v-model针对更多的是最终操作结果,是双向绑定的结果,是value,是一种change操作。
    比如:输入框的值、多选框的value值列表、树结构最终绑定的id值列表(ant design和element都是)、等等...
  2. .sync针对更多的是各种各样的状态,是状态的互相传递,是status,是一种update操作。
    比如:组件loading状态、子菜单和树结构展开列表(状态的一种)、某个表单组件内部验证状态、等等....


七、vuex

官方文档介绍:每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  2. 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

安装 (yarn add vuex)Vuex 之后,让我们来创建一个 store。创建过程直截了当——仅需要提供一个初始 state 对象和一些 mutation:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

现在,你可以通过store.state来获取状态对象,以及通过store.commit方法触发状态变更:

store.commit('increment')
console.log(store.state.count) // -> 1

为了在 Vue 组件中访问 this.$store property,你需要为 Vue 实例提供创建好的 store。Vuex 提供了一个从根组件向所有子组件,以 store 选项的方式“注入”该 store 的机制:

new Vue({
  el: '#app',
  store: store,
})

现在我们可以从组件的方法提交一个变更:

methods: {
  increment() {
    this.$store.commit('increment')
    console.log(this.$store.state.count)
  }
}

除了state和mutations,还有一些概念需要了解。

getter:

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

Aciton:

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。

module:

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。

八、TypeScript

  1. 在custom.d.ts可以声明全局类型
  2. ?表示可有可无,!表示非空
  3. 在装饰器中vue-property-decorator并没有提供专门的用于computed的修饰器,因为ES6get/set语法本身就可以替代computed上述转换为ts对应如下
export default {
  computed: {
    a () { return true },
    b: function () { return true },
    d: {
      get () { return true },
      set: function (v) { console.log(v) }
    }
  }
}

转化为:
export default class YourComponent extends Vue {
  get a () { return true }
  get b () { return true },
  get d (){ return true },
  set d (v) { console.log(v) }
}

九、其他

  1. 关于字体:

中文字体最佳实践:zenozeng/fonts.css

2. 数据持久化

把数据存在本地localstorage,通过window.localStorage.getItem(key)或setItem(key,value)进行读写

3. 命名严谨很重要

4. 插槽slot:

<navigation-link url="/profile">
  Your Profile
</navigation-link>
然后在<navigation-link>模板里写:
<a
  v-bind:href="url"
  class="nav-link"
>
  <slot></slot>
</a>

当组件渲染的时候,<slot></slot>将会被替换为“Your Profile”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值