New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue高版本中一些新特性的使用 #7

Open
masterkong opened this Issue Aug 31, 2018 · 3 comments

Comments

Projects
None yet
3 participants
@masterkong
Owner

masterkong commented Aug 31, 2018

Vue高版本中一些新特性的使用

一、深度作用选择器( >>> )

严格来说,这个应该是vue-loader的功能。"vue-loader": "^12.2.0"

在项目开发中,如果业务比较复杂,特别像中台或B端功能页面都不可避免的会用到第三方组件库,产品有时会想对这些组件进行一些UI方面的定制。如果这些组件采用的是有作用域的CSS,父组件想要定制第三方组件的样式就比较麻烦了。

深度作用选择器( >>> 操作符)可以助你一臂之力。

<template>
<div>
   <h1 class="child-title">
       如果你希望 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,你可以使用 >>> 操作
   </h1>
</div>
</template>

<script>
export default {
    name: 'child',
    data() {
        return {
        }
    }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.child-title {
    font-size: 12px;
}
</style>

上面的child组件中 .child-title 的作用域CSS设定字体大小为12px,现在想在父组件中定制为大小20px,颜色为红色。

<template>
<div>
   <child class="parent-custom"></child>
</div>
</template>
<script>
import Child from './child';
export default {
    name: 'parent',
    components:{
        Child
    },
    data() {
        return {
        }
    }
}
</script>

<style>
.parent-custom  >>> .child-title {
    font-size:20px;
    color: red;
}
</style>

效果妥妥的。但是别高兴太早,注意到上面的<style>使用的是纯css语法,如果采用less语法,你可能会收到一条webpack的报错信息。

<style lang="less">
.parent-custom {
     >>> .child-title {
        font-size:20px;
        color: red;
    }
}
</style>

ERROR in ./~/css-loader!./~/vue-loader/lib/style-compiler?{"vue":true,"id":"data-v-960c5412","scoped":false,"hasInlineConfig":false}!./~/postcss-loader!./~/less-loader!./~/vue-loader/lib/selector.js?type=styles&index=0!./src/components/parent.vue
Module build failed: Unrecognised input
 @ /src/components/parent.vue (line 22, column 6)
 near lines:
   .parent-custom {
        >>> .child-title {
           font-size:20px;

上面的报错信息其实是less语法不认识 >>>。(less的github issue上有人提议支持>>>操作符,但本文使用的v2.7.3会有这个问题)

解决方案是采用的less的转义(scaping)和变量插值(Variable Interpolation)

<style lang="less">
@deep: ~'>>>';
.parent-custom {
     @{deep} .child-title {
        font-size:20px;
        color: red;
    }
}
</style>

对于其他的css预处理器,因为没怎么用,不妄加评论,照搬一下文档的话。

有些像 Sass 之类的预处理器无法正确解析 >>>。这种情况下你可以使用 /deep/ 操作符取而代之——这是一个 >>> 的别名,同样可以正常工作。

二、组件配置项inheritAttrs、组件实例属性$attrs和$listeners

2.4.0新增

组件配置项 inheritAttrs

我们都知道假如使用子组件时传了a,b,c三个prop,而子组件的props选项只声明了a和b,那么渲染后c将作为html自定义属性显示在子组件的根元素上。

如果不希望这样,可以设置子组件的配置项 inheritAttrs:false,根元素就会干净多了。

<script>
export default {
    name: 'child',
    props:['a','b'],
    inheritAttrs:false
}
</script>

组件实例属性$attrs和$listeners

先看看vm.$attrs文档上是怎么说的

vm.$attrs

类型:{ [key: string]: string }

只读

包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

归纳起来就是两点:

  1. vm.$attrs是组件的内置属性,值是父组件传入的所有prop中未被组件声明的prop(class和style除外)。

还是以前面的child组件举例

//parent.vue
<template>
    <div>
        <child class="parent-custom" a="a" b="b" c="c"></child>
    </div>
</template>

//child.vue
<script>
export default {
    name: 'child',
    props:['a','b'],
    inheritAttrs:false,
    mounted(){
        //控制台输出:
        //child:$attrs: {c: "c"}
        console.log('child:$attrs:',this.$attrs);
    }
}
</script>

  1. 组件可以通过在自己的子组件上使用v-bind="$attrs",进一步把值传给自己的子组件。也就是说子组件会把$attrs的值当作传入的prop处理,同时还要遵守第一点的规则。
//parent.vue
<template>
    <div>
        <child a="a" b="b" c="c"></child>
    </div>
</template>

//child.vue
<template>
    <div>
        <grand-child v-bind="$attrs" d="d"></grand-child>
    </div>
</template>
<script>
export default {
    name: 'child',
    props:['a','b'],
    inheritAttrs:false
}
</script>

//grandchild.vue
<script>
export default {
    name: 'grandchild',
    props:[],
    //props:['c'],
    inheritAttrs:false,
    mounted(){
        //控制台输出:
        //grandchild:$attrs: {d: "d", c: "c"}
        console.log('grandchild:$attrs:',this.$attrs);

        //如果props:['c']
        //控制台输出:
        //grandchild:$attrs: {d: "d"}
    },
}
</script>

vm.$listeners

类型:{ [key: string]: Function | Array }

只读

包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

归纳起来也是两点:

  1. vm.$listeners是组件的内置属性,它的值是父组件(不含 .native 修饰器的) v-on 事件监听器。
  2. 组件可以通过在自己的子组件上使用v-on="$listeners",进一步把值传给自己的子组件。如果子组件已经绑定$listener中同名的监听器,则两个监听器函数会以冒泡的方式先后执行。
//parent.vue
<template>
    <div>
        <child @update="onParentUpdate"></child>
    </div>
</template>
<script>
export default {
    name: 'parent',
    components:{
        Child
    },
    methods:{
        onParentUpdate(){
            console.log('parent.vue:onParentUpdate')
        }
    }
}
</script>

//child.vue
<template>
    <div>
        <grand-child @update="onChildUpdate" v-on="$listeners"></grand-child>
    </div>
</template>
<script>
export default {
    name: 'child',
    components:{
        GrandChild
    },
    methods:{
        onChildUpdate(){
            console.log('child.vue:onChildUpdate')
        }
    }
}
</script>

//grandchild.vue
<script>
export default {
    name: 'grandchild',
    mounted(){
        //控制台输出:
        //grandchild:$listeners: {update: ƒ}
        console.log('grandchild:$listeners:',this.$listeners);

        //控制台输出:
        //child.vue:onChildUpdate
        //parent.vue:onParentUpdate
        this.$listeners.update();
    }
}
</script>

三、组件选项 provide/inject

2.2.0 新增

如果列举Vue组件之间的通信方法,一般都会说通过prop,自定义事件,事件总线,还有Vuex。
provide/inject提供了另一种方法。

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

如果你熟悉 React,这与 React 的上下文特性(context)很相似。

不过需要注意的是,在文档中并不建议直接用于应用程序中。

provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。

//parent.vue
<template>
    <div>
        <child></child>
    </div>
</template>
<script>
export default {
    name: 'parent',
    provide: {
        data: 'I am parent.vue'
    },
    components:{
        Child
    }
}
</script>

//child.vue
<template>
    <div>
        <grand-child></grand-child>
    </div>
</template>
<script>
export default {
    name: 'child',
    components:{
        GrandChild
    }
}
</script>

//grandchild.vue
<script>
export default {
    name: 'grandchild',
    inject: ['data'],
    mounted(){
        //控制台输出:
        //grandchild:inject: I am parent.vue
        console.log('grandchild:inject:',this.data);
    }
}
</script>

provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。

inject 选项应该是一个字符串数组或一个对象,该对象的 key 代表了本地绑定的名称,value 就为provide中要取值的key。

在2.5.0+时对于inject选项为对象时,还可以指定from来表示源属性,default指定默认值(如果是非原始值要使用一个工厂方法)。

const Child = {
  inject: {
    foo: {
      from: 'bar',
      default: 'foo'
      //default: () => [1, 2, 3]
    }
  }
}

四、作用域插槽 slot-scope

2.1.0 新增

2.5.0+,slot-scope 不再限制在 <template> 元素上使用,而可以用在插槽内的任何元素或组件上。

作用域插槽的文档说明很详细。下面举个例子来展示下应用场景。

1

2

可以看出列表页和编辑页对于数据的展示是一样的,唯一的区别是在不同页面对于数据有不同的处理逻辑。相同的数据展示这块就可抽取成一个组件,不同的地方则可以借助作用域插槽实现。

//data-show.vue
<template>
<div>
   <ul>
        <li v-for="item in list">
            <span>{{item.title}}</span>
            <slot v-bind:item="item">
            </slot>
        </li>
    </ul>
</div>
</template>

//list.vue
<template>
<p>列表页</p>
    <data-show :list="list">
        <template slot-scope="slotProps">
            <span v-if="slotProps.item.complete"></span>
            <span v-else>x</span>
        </template>
    </data-show>
</template>

//edit.vue
<template>
<p>编辑页</p>
   <data-show :list="list">
       <template slot-scope="slotProps">
           <a v-if="slotProps.item.complete">查看</a>
           <a v-else>修改</a>
       </template>
   </data-show>
</template>

五、Vue的错误捕获

  1. 全局配置errorHandler

从2.2.0起,这个钩子也会捕获组件生命周期钩子里的错误。

从 2.4.0 起这个钩子也会捕获 Vue 自定义事件处理函数内部的错误了。

更详细的说明可以查看文档errorHandler

  1. 生命周期钩子errorCaptured

2.5.0+新增

更详细的说明可以查看文档errorCaptured

如果熟悉React的话,会发现它跟错误边界(Error Boundaries)的概念很像,实际上也确实是这么用的。

在文档Error Handling with errorCaptured Hook就举了一个典型的例子

Vue.component('ErrorBoundary', {
  data: () => ({ error: null }),
  errorCaptured (err, vm, info) {
    this.error = `${err.stack}\n\nfound in ${info} of component`
    return false
  },
  render (h) {
    if (this.error) {
      return h('pre', { style: { color: 'red' }}, this.error)
    }
    // ignoring edge cases for the sake of demonstration
    return this.$slots.default[0]
  }
})
<error-boundary>
  <another-component/>
</error-boundary>

需要强调的是errorCaptured并不能捕获自身错误和异步错误(比如网络请求,鼠标事件等产生的错误)。

In 2.5 we introduce the new errorCaptured hook. A component with this hook captures all errors (excluding those fired in async callbacks) from its child component tree (excluding itself).

参考

@masterkong masterkong changed the title from Vue高版本中的一些新特性的使用 to Vue高版本中一些新特性的使用 Aug 31, 2018

@WenTao-Love

This comment has been minimized.

Show comment
Hide comment
@WenTao-Love

WenTao-Love Sep 6, 2018

谢谢楼主干货分享,请教一下,对于Vue项目中怎么接入vConsole.js ( https://github.com/Tencent/vConsole )进行准生产环境调试呢?谢谢!

WenTao-Love commented Sep 6, 2018

谢谢楼主干货分享,请教一下,对于Vue项目中怎么接入vConsole.js ( https://github.com/Tencent/vConsole )进行准生产环境调试呢?谢谢!

@masterkong

This comment has been minimized.

Show comment
Hide comment
@masterkong

masterkong Sep 6, 2018

Owner

@WenTao-Love
webpack项目安装 vconsole npm包:
npm install vconsole --save-dev;

index.js:
import Vconsole from 'vconsole';
const vConsole = new Vconsole();

Owner

masterkong commented Sep 6, 2018

@WenTao-Love
webpack项目安装 vconsole npm包:
npm install vconsole --save-dev;

index.js:
import Vconsole from 'vconsole';
const vConsole = new Vconsole();

@hellomrbigshot

This comment has been minimized.

Show comment
Hide comment
@hellomrbigshot

hellomrbigshot Sep 9, 2018

学习了[抱拳]

hellomrbigshot commented Sep 9, 2018

学习了[抱拳]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment