填坑之路:Vue中那些容易被忽略的

记录一些在使用Vue开发中常见的坑。

声明本人菜鸟一只,一些描述可能会有偏颇,大佬轻锤,欢迎拍砖。

小葵花课堂开课啦!

插槽相关:slotslot-scope

单个插槽 | 匿名插槽

以上两种叫法也就说明了它的含义:

  • 一个组件中只能有一个该类插槽
  • 该类插槽不设置name属性
<!-- 父组件 -->
<template>
  <div class="father">
    <h3>父组件</h3>
    <child>
      <div class="tmpl">
        <span>test1</span>
        <span>test2</span>
        <span>test3</span>
      </div>
    </child>
  </div>
</template>

<!-- 子组件 -->
<template>
  <div class="child">
    <h3>子组件</h3>
    <slot></slot>
  </div>
</template>

子组件中定义的slot将被父组件中child内包裹的html模板所替代,即渲染后成:

<div class="father">
  <h3>父组件</h3>
  <div class="child">
    <h3>子组件</h3>
    <div class="tmpl">
      <span>test1</span>
      <span>test2</span>
      <span>test3</span>
    </div>
  </div>
</div>
具名插槽

该插槽name属性不为空,且可以在一个组件中出现多次。

<!-- 父组件 -->
<template>
  <div class="father">
    <h3>父组件</h3>
    <child>
      <div class="tmpl" slot="first">
        <span>test1</span>
        <span>test2</span>
        <span>test3</span>
      </div>
      <div class="tmpl" slot="second">
        <span>test4</span>
        <span>test5</span>
        <span>test6</span>
      </div>
      <div class="tmpl">
        <span>test7</span>
        <span>test8</span>
        <span>test9</span>
      </div>
    </child>
  </div>
</template>

<!-- 子组件 -->
<template>
  <div class="child">
    <h3>子组件</h3>
     <!-- 具名插槽 -->
    <slot name="first"></slot>
     <!-- 具名插槽 -->
    <slot name="second"></slot>
     <!-- 匿名插槽 -->
    <slot></slot>
  </div>
</template>

组件中匿名插槽可与具名插槽同时使用,并且:

  • 声明了name的具名插槽将在父组件中找到与其对应名称的slot属性,并将html模版替换到其中
  • 未声明name的将找到对应的未声明slot属性的html模版做替换。
作用域插槽

观察可以发现,以上两种插槽的html模版的内容都是由父组件指定的,那如果我想展示来自子组件的内容该怎么办呢?

所以就有了第三种插槽,我们称之为作用域插槽,坊间也叫携带数据的插槽,顾名思义,这种插槽可以携带数据给到父组件中的html模板。

<!-- 父组件 -->
<template>
  <div class="father">
    <h3>父组件</h3>
    <child>
      <template slot-scope='scope'>
        <div class="tmpl">
          <span v-for='u in scope.data'>test1</span>
        </div>
      </template>
    </child>
  </div>
</template>

<!-- 子组件 -->
<template>
  <div class="child">
    <h3>子组件</h3>
     <!-- 作用域插槽 -->
    <slot :data="arr"></slot>
  </div>
</template>
<script>
export default {
  data () {
    return {
      arr: ['test1','test2','test3']
    }
  }
}
</script>

子组件中定义了一个数组data传给父组件展示,父组件以${slotScopeName}.data的格式去接收这个来自子组件的数组加以展示。

想给第三方UI库Event加自定义参数时

<el-date-picker v-model="editForm[item.name]" 
    placeholder="选择日期"
    :editable="false" style="width:100%"
    @change="(value) => changeHandler(value, item.name)">
</el-date-picker>

如上,使用箭头函数,return一个新的自定义函数,在自定义函数changeHandler中加入你想要的参数。

同路由不同参数时

如果由 app/12345 跳转到 app/23456,两个路由可能都是app/:id,用的同一个组件,所以vue并不会重新走一遍生命周期,导致一些可能的数据没更新的bug

所以,一个比较好的解决方案是监控路由的变化来刷新一个必要的参数。

watch: {
    '$route.params': function (newValue) {
      this.init()
    }
}

watch

如上是我们常用的 watch,但可能你不知道watch 还有两个比较实用的配置:deepimmediate

deep

deep,默认值是 false。顾名思义即表示深入观察,watch会将obj一层层往下遍历,给对象的所有属性都添加了监听。可想而知,这一做法的性能耗费比较大。

watch: {
    obj: {
        handler (newVal, oldVal) {
            console.log('obj的属性变化了')
        },
        deep: true
    },
    
}
// 建议使用
watch: {
    'obj.key': {
        handler (newVal, oldVal) {
            console.log('obj的属性key变化了')
        }
    },
    
}
immediate

immediate,默认值为 false。顾名思义即表示立即监听,在定义了 handler 方法后将立即执行一次。

watch: {
 firstName: {
  handler (newval, oldVal) {
   this.fullName = `${newval} ${this.lastName}`
  },
  immediate: true
 }
}

定时器

很多时候我们可能会有写定时器的需求(比如有个轮询任务),当我们离开这个页面时,你会发现定时任务还在跑,性能炸了。

于是我们可以这么干:

data() {
    return {
        timer: null
    }
}
methods: {
    timing() {
        this.timer = setInterval(() => {
            // dosomething
        }, 100)
    }
}
beforeDestroy() {
    clearInterval(this.timer) // 在组件即将销毁时清掉定时任务
    this.timer = null // 变量释放
}

关键就在当我们离开这个页面(组件销毁前)时清掉定时任务。

路由懒加载

Vue的首屏渲染慢饱受诟病。

究其原因,在于webpack打出来的包太大,导致在进入首页的时候需要加载的文件大且多。

那么我们就可以使用懒加载将页面切分(vue-router支持webpack内置的异步模块加载系统,使用较少的路由组件不再打包到bundles中,只在路由被访问到时按需加载),随用随载,减少首页加载的负担。

常规非懒加载路由配置
import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/components/Login'
import Home from '@/components/Home'
import Profile from '@/components/Profile'

Vue.use(Router)

export default new Router({
    routes: [{
        path: '/login',
        name: 'Login',
        compontent: Login
    }, {
        path: '/home',
        name: 'Home',
        compontent: Home
    }, {
        path: '/profile',
        name: 'Profile',
        compontent: Profile
    }]
})

懒加载路由配置

以下列了常用的三种写法,webpack都支持。

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export default new Router({
    routes: [{
        path: '/login',
        name: 'Login',
        compontent: resolve => require(['@/component/Login'], resolve)
    }, {
        path: '/home',
        name: 'Home',
        compontent: () => import(/* webpackChunkName: "home" */  '@/component/Home')
    }, {
        path: '/profile',
        name: 'Profile',
        compontent: r => require.ensure([], () => r(require('@/component/Profile')), 'profile')
    }]
})

Computed

先来看一段代码:

<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8">
        <title>Computed</title>

    </head>

    <body>
        <div id="app">
            <label for="">FirstName:<input v-model="firstName"/></label><br>
            <label for="">LastName:<input v-model="lastName"/></label><br>
            <label for="">NickName<input v-model="nickName"/></label><br>
            <p>名称:{{name}}</p>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script>
            new Vue({
                el: '#app',
                data: function() {
                    return {
                        firstName: '',
                        lastName: '',
                        nickName: ''
                    }
                },
                computed: {
                    name: function() {
                        console.log('触发computed了')
                        if (this.nickName) {
                            return this.nickName
                        } else {
                            return this.firstName + '.' + this.lastName
                        }
                    }
                }
            })
        </script>
    </body>
</html>

天真时候的我们可能会以为每当nickNamefirstNamelastName三者任一变化都将重新触发name的变更。

其实不然,当nickName有值(不为空)后,重新触发依赖收集,此后将移除对firstNamelastName的依赖;而当nickName再次为空值时,又将会收集到对firstNamelastName等的依赖。

也就是说,依赖收集是动态的。

re-render相关

还是看一段代码:

<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8">
        <title>Re-render</title>

    </head>

    <body>
        <div id="app">
            <div v-for="(item, index) in items" :key="index">
                <div v-if="index === 0">
                    <input v-model="formData[item.key]">
                </div>
                <div v-else>{{item.key}}:{{Date.now()}}</div>
            </div>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
        <script>
            new Vue({
                el: '#app',
                data: function() {
                    return {
                        items: [{
                            key: 'input'
                        }, {
                            key: 'Re-render'
                        }],
                        formData: {
                            input: ''
                        }
                    }
                }
            })
        </script>
    </body>
</html>

以上可能是一种常见的场景。我们会惊奇的发现,当input框中值发生变化,页面re-render了一个新的时间戳。不难看出问题出在Date.now(),这是一个方法(虽然没定义在实例的methods中),故而每当template重新渲染时会再次触发一次method,进而引发意料之外的事故。

<div id="app">
    <div>
        <label>input:<input v-model="model"/></label>
    </div>
    <div>
        <child :items="makeItems()"></child>
    </div>
</div>

以上可能更能说明问题,一些场景下我们可能没深考虑或下意识地直接使用一个method做一些props传给子组件。那么当输入值model变化时触发父组件模板重新渲染,childitems是从method来的所以会被重新做一遍,产生不必要的麻烦。

所以,一些场景下为了避免这种不必要的麻烦,还是推荐使用computed,因为计算属性是基于它们的依赖进行缓存的,而method在触发重新渲染时总会再次执行函数,不做缓存。

...

未完待续。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于...
    云之外阅读 5,040评论 0 29
  • vue概述 在官方文档中,有一句话对Vue的定位说的很明确:Vue.js 的核心是一个允许采用简洁的模板语法来声明...
    li4065阅读 7,176评论 0 25
  • 一:什么是闭包?闭包的用处? (1)闭包就是能够读取其他函数内部变量的函数。在本质上,闭包就 是将函数内部和函数外...
    xuguibin阅读 9,437评论 1 52
  • Vue 实例 属性和方法 每个 Vue 实例都会代理其 data 对象里所有的属性:var data = { a:...
    云之外阅读 2,177评论 0 6
  • 夏至日痕长, 趋车赴定场。 挥杆绿茵处, 入洞喜群芳。 杯举翠微润, 畅怀花径旁。 生辰许心愿, 山水共情长。
    飞雪姐姐阅读 129评论 0 2