vue项目总结

645 阅读6分钟

用vue写项目有一段时间了,想把项目中学到的一些知识记下来,供以后复习用,这个文章我会一直更新下去的。(ps:本人前端小白,写的都是比较基础的,有什么不对的欢迎指正,大佬勿喷。)

1、深度选择器

当我们使用第三方组件库时,其组件的样式不一定满足我们项目的需求,我们需要修改其组件的样式,但是正常的复制类名更改样式的方法并不能奏效。

原因是其组件的 style 标签设置了 scoped 之后,作用域是封闭的,父组件的样式无法渗透到子组件中。如果希望 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件这时就需要用到深度选择器。

1、对于 css 语法
使用 >>> 操作符,例:

<style scoped>
    .a >>> .b {
        /* ... */
    }
</style>

它将被编译成:

.a[data-v-f3f3e99] .b { /* ... */ }

2、对于 less、scss 等预处理器
像 Less、Sass 之类的预处理器无法正确解析 >>>,这种情况下可以使用 /deep/ 操作符取而代之(这是 >>> 的别名),例:

.a{
  /deep/ .b{
    /* ... */
  }
}

2、清除定时器

因为 vue 是单页应用,所以在页面中设置的定时器如果不及时清除的话就会一直执行,可以想象一下如果你在这个定时器中调用了接口,那么就会不停的请求,这是非常损耗性能的,所以一定要养成及时清除定时器的好习惯。以下是我知道的在 vue 中清除定时器的方法:

方法1、

data () {
    return {
        timer: '' // 定时器名字
    }
},
created () {
    this.timer = setInterval(() => {
        /* ... */
    }, 1000)
},
beforeDestory () {
    clearInterval(this.timer) // 在销毁组件前清除定时器
}

方法1有两个不好的地方,引用尤大的话来说就是:

  • 它需要在这个组件实例中保存这个timer,如果可以的话最好只有生命周期钩子可以访问到它。这并不算严重的问题,但是它可以被视为杂物。
  • 我们的建立代码独立于我们的清理代码,这使得我们比较难于程序化的清理我们建立的所有东西。

方法2、

created () {
    var timer = setInterval(() => {
        /* ... */
    }, 1000)
    
    // 通过$once来监听定时器,在beforeDestroy钩子可以被清除。
    this.$once('hook:beforeDestroy', () => {            
        clearInterval(timer)                                    
    })
}

该方法是通过$once这个事件侦听器在定义完定时器之后的位置来清除定时器。

3、methods、computed、watch

1、methods 和 computed

methodscomputed 都可以求得一个结果,这是它们的相同点

不同点是

  • methods 是每调用一次就会执行一次
  • computed 有一个缓存机制,只有当缓存的数据发生了改变才会重新执行,而且 computed 必须有 return 返回值。
template: 
<div id="app">
    <p>{{ handleSum() }}</p>
    <p>{{ sum }}</p>
</div>

script:
computed: {
    sum () {
        return 1 + 1
    }
},
methods: {
    handleSum () {
        return 1 + 1
    }
}

总结:当需要存储一个性能开销很大的结果时,这时候就推荐用 computed 缓存该值;如果需要每次都重新加载并且不需要缓存的值就用 methods

2、computed 和 watch
watch擅长处理的场景:一个数据影响多个数据
computed擅长处理的场景:一个数据受多个数据影响

以下是 watch 的用法
template: 
<div id="app">{{ fullName }}</div>

script:
data () {
    return {
        firstName: "Dell",
        lastName: "Lee",
        fullName: "Dell Lee"
    }
},
watch: {
    firstName () { // 监听 firstName属性
        this.fullName = this.firstName + " " + this.lastName
    },
    lastName () { // 监听 lastName属性
        this.fullName = this.firstName + " " + this.lastName
    }
}
以下是 computed 的用法
template: 
<div id="app">{{ fullName }}</div>

script:
data () {
    return {
        firstName: "Dell",
        lastName: "Lee",
        fullName: "Dell Lee"
    }
},
computed: {
    fullName: {
        get: function () {
            return this.firstName + " " + this.lastName;
        },
        set: function (value) { // value就是fullName改变后的值
            var arr = value.split(" ")
            this.firstName = arr[0] // 同时修改firstName
            this.lastName = arr[1] // 同时修改lastName
        }
    }
}

4、 多用全等而不是相等

==相等运算符===全等运算符的概念就不多说了。

问题:项目中碰到的坑就是我在后台编辑某个产品的价格 price 为 0 时,始终无法保存(我是判断 price != '' 为true则保存)。
但 '' 转换为数字类型后结果为 0,所以 0 != '' 结果始终为false,这时就需要用 !==不全等运算符进行判断。

总结:===全等运算符更加的严谨,所以尽量多用 ===全等运算符去判断而不是偷懒用 ==相等运算符。

5、v-for 循环遍历 img标签,动态绑定 src时,无法显示图片问题

原因:系统无法识别 @这种自定义的路径符号

解决

1、把图片放在 static 文件夹下,用绝对路径引入
2、把图片放在 cdn 上,通过网络路径引入
3、通过 require 引入路径,例:

imgUrl: require("@/assets/images/arrow1.png")

6、.native修饰符

理解.native修饰符可以使一个组件在根元素上监听原生事件,也就是说它可以把组件变成普通的 HTML标签,主要用于给自定义的组件或者第三方组件添加原生事件,例:

<base-input v-on:focus.native="onFocus"></base-input>

如果不加.native修饰符将无法触发 onFocus事件

7、.sync修饰符

在父子组件的通信中,父组件向子组件传值,子组件可以通过 props 接收,子组件可以修改父组件传入的值,但是会报错,官方推荐以 update:myPropName 的模式触发事件取而代之,例:

父组件:
<div id="app">
    <Son :isShow.sync="isShow">
</div>
script:
data () {
    return {
        isShow: true
    }
}


子组件:
<div class="son" v-show="isShow">
    <button @click="close" type="button">关闭</button>
</div>
script:
props: {
    isShow: {
        type: Boolean,
        default: true
    }
},
methods: {
    close () {
        this.$emit('update:isShow', false)
    }
}

8、父子组件的生命周期

加载渲染过程
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted

子组件更新过程
父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated

父组件更新过程
父beforeUpdate -> 父updated

销毁过程
父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed

9、长列表的优化

Vue会通过 object.defineProperty 对数据进行劫持,来实现视图响应数据的变化,然而有些数据可能只需要展示并不需要发生响应式的变化,这个时候可以用 Object.freeze

Object.freeze 可以冻结一个对象,被冻结的对象将无法再修改,例:

data () {
    return {
        users: {}
    }
},
async created() {
    const users = await axios.get("/api/users")
    this.users = Object.freeze(users)
}

需要说明的是,这里只是冻结了 users的值,引用不会被冻结,当我们需要 reactive数据的时候,我们可以重新给 users赋值。

10、$options

vm.$options 用于当前 Vue 实例的初始化选项。可以用来重置 data 对象到初始化的状态

this.$data // 当前的 data对象
this.$options.data() // 获取初始化状态下的 data对象
Object.assign(this.$data, this.$options.data()) // 重置 data 对象到初始化的状态

11、父组件异步传值给子组件

父组件异步获取值再传给子组件,子组件并不能一开始就获取到值,导致有时候会报错。以下为常用的两种方法:

(1)通过 v-if 控制子组件

父组件:
<div id="father">
    <Son :name="name" v-if="showChild></Son>
</div>

script:
data () {
    return {
        name: '',
        showChild: false
    }
},
async created() {
    const name = await axios.get("/api/name")
    this.name = name
    this.showChild = true
}

(2)子组件使用 watch 来监听父组件改变的 props

父组件:
<div id="father">
    <Son :name="name"></Son>
</div>

script:
data () {
    return {
        name: ''
    }
},
async created() {
    const name = await axios.get("/api/name")
    this.name = name
}
子组件:
<div id="son">
    <p class="name">{{ name }}</p>
</div>

script:
props: {
    name: {
        type: String,
        default: ''
    }
},
watch: {
    name (newValue, oldValue) {
        this.name = newValue
    }
}

12、异步加载组件

当一个页面使用了大量组件时,从服务器上同时加载所有组件可能是没有意义的,有些组件可能不需要马上加载。在这种情况下,Vue 允许我们在需要时定义从服务器异步加载的组件。

通过仅加载基本组件并把异步组件的加载推迟到未来的调用时间,可以节省带宽和程序加载时间。

这是一个简单的异步加载组件:

new Vue({
    components: {
        "child": () => import("./components/child")
    }
})

13、自定义组件的 v-model

父组件:

<template>
   <div class="parent">
       父级内容:{{ data }}
        <Child v-model="data"></Child>
   </div>
</template>

<script>
import Child from './components/child'
export default {
  name: 'parent',
  data() {
    return {
      data: 1,
    }
  },
  components: {
    child
  }
}
</script>

子组件:

<template>
    <div>
        <p>子级内容:{{ childnum }}</p>
        <button @click="changeNum">点我</button>
    </div>
</template>

<script>
export default {
    name: 'child',
    model: {
        prop: 'childnum', // 自定义prop属性,默认绑定的是value
        event: 'changeNum' // 自定义触发事件类型,默认触发的事件类型是input
    },
    props: {
        childnum: {
            type: Number
        }
    },
    methods: {
        changeNum() {
            this.$emit('changeNum', this.childnum + 1)
        }
    },
}
</script>

原理:子组件定义 model 属性中的 prop 和 event,父组件通过 v-model 传值给子组件 model 属性中的 prop 对应的变量。然后子组件通过 $emit 发送 event 事件并传递一个结果值,这样外部的 v-model 就收到了传出的值,因此就实现了双向传递。(默认情况下,一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event)

14、选择性的调用 v-if 和 v-show

  • v-if 是对节点进行增加和删除操作,同时它是惰性的,只在条件为 true 时渲染
  • v-show 是对 display 样式进行 block 和 none 的切换,无论条件 true 还是 false 它都会渲染

所以,在需要频繁切换显示隐藏,并且不需要权限的元素上使用 v-show,减少系统的切换开销,否则用 v-if

15、vue 配置跨域

如果前端应用和后端 API 服务器没有运行在同一个主机上,需要在开发环境下将 API 请求代理到 API 服务器。通过配置根目录下的 vue.config.js 文件的 devServer.proxy 选项来实现代理

module.exports = {
    devServer: {
        proxy: {
            '/api': {
                target: 'http://49.234.33.207:80', // 要代理的域名
                changeOrigin: true,
                ws: true,
                pathRewrite: { // 路径重写
                    '^/api': ''
                }
            }
        }
    },
}

使用:

 this.$axios.get("/api/getData")
 // /api/getData 相当于 http://49.234.33.207:80/getData
 // /api 相当于 http://49.234.33.207:80