1.require.context()

1.场景:如页面需要导入多个组件,原始写法:

1
2
3
4
import titleCom from '@/components/home/titleCom'
import bannerCom from '@/components/home/bannerCom'
import cellCom from '@/components/home/cellCom'
components:{titleCom,bannerCom,cellCom}

2.这样就写了大量重复的代码,利用 require.context 可以写成

1
2
3
4
5
6
7
8
const path = require('path')
const files = require.context('@/components/home', false, /\.vue$/)
const modules = {}
files.keys().forEach(key => {
 const name = path.basename(key, '.vue')
 modules[name] = files(key).default || files(key)
})
components:modules

这样不管页面引入多少组件,都可以使用这个方法

3.API 方法

1
2
3
4
5
6
实际上是 webpack 的方法,vue 工程一般基于 webpack,所以可以使用
require.context(directory,useSubdirectories,regExp)
接收三个参数:
directory:说明需要检索的目录
useSubdirectories:是否检索子目录
regExp: 匹配文件的正则表达式,一般是文件名

2.优雅更新props

更新 prop 在业务中是很常见的需求,但在子组件中不允许直接修改 prop,因为这种做法不符合单向数据流的原则,在开发模式下还会报出警告。因此大多数人会通过 $emit 触发自定义事件,在父组件中接收该事件的传值来更新 prop

child.vue:

1
2
3
4
5
6
7
8
9
10
export defalut {
    props: {
        title: String 
    },
    methods: {
        changeTitle(){
            this.$emit('change-title', 'hello')
        }
    }
}

parent.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
<child :title="title" @change-title="changeTitle"></child>
export default {
    data(){
        return {
            title: 'title'
        
    },
    methods: {
        changeTitle(title){
            this.title = title
        }
    }
}

  

这种做法没有问题,我也常用这种手段来更新 prop。但如果你只是想单纯的更新 prop,没有其他的操作。那么 sync 修饰符能够让这一切都变得特别简单。

parent.vue:

1
<child :title.sync="title"></child>
 

child.vue:

1
2
3
4
5
6
7
8
9
10
export defalut {
    props: {
        title: String 
    },
    methods: {
        changeTitle(){
            this.$emit('update:title', 'hello')
        }
    }
}

只需要在绑定属性上添加 .sync,在子组件内部就可以触发 update:属性名 来更新 prop。可以看到这种手段确实简洁且优雅,这让父组件的代码中减少一个“没必要的函数”。

自定义组件双向绑定

默认情况下,v-model 是 @input 事件侦听器和 :value 属性上的语法糖。但是,你可以在你的Vue组件中指定一个模型属性来定义使用什么事件和value属性——非常棒!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
组件 model 选项:
 
允许一个自定义组件在使用 v-model 时定制 prop 和 event。默认情况下,一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event,但是一些输入类型比如单选框和复选框按钮可能想使用 value prop 来达到不同的目的。使用 model 选项可以回避这些情况产生的冲突。
 
input 默认作为双向绑定的更新事件,通过 $emit 可以更新绑定的值
 
<my-switch v-model="val"></my-switch>
 
export default {
    props: {
        value: {
            type: Boolean,
            default: false
        }
    },
    methods: {
        switchChange(val) {
            this.$emit('input', val)
        }
    }
}
复制代码
修改组件的 model 选项,自定义绑定的变量和事件
 
<my-switch v-model="num" value="some value"></my-switch>
 
export default {
    model: {
        prop: 'num',
        event: 'update'
    },
    props: {
        value: {
            type: String,
            default: ''
        },
        num: {
            type: Number,
            default: 0
        }
    },
    methods: {
        numChange() {
            this.$emit('update', num++)
        }
    }
}

  

 3.provide/inject

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

简单来说,一个组件将自己的属性通过 provide 暴露出去,其下面的子孙组件 inject 即可接收到暴露的属性。

App.vue:

1
2
3
4
5
6
7
export default {
    provide() {
        return {
            app: this
        }
    }
}

child.vue:

1
2
3
4
5
6
export default {
    inject: ['app'],
    created() {
        console.log(this.app) // App.vue实例
    }
}

在 2.5.0+ 版本可以通过设置默认值使其变成可选项:

1
2
3
4
5
6
7
8
9
10
export default {
    inject: {
        app: {
            default: () => ({})
        }
    },
    created() {
        console.log(this.app)
    }
}

如果你想为 inject 的属性变更名称,可以使用 from 来表示其来源:

1
2
3
4
5
6
7
8
9
10
11
12
export default {
    inject: {
        myApp: {
            // from的值和provide的属性名保持一致
            from: 'app',
            default: () => ({})
        }
    },
    created() {
        console.log(this.myApp)
    }
}

需要注意的是 provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。但是某些时候,或许它能帮助到我们。

4.巧用template

相信 v-if 在开发中是用得最多的指令,那么你一定遇到过这样的场景,多个元素需要切换,而且切换条件都一样,一般都会使用一个元素包裹起来,在这个元素上做切换。

1
2
3
4
5
<div v-if="status==='ok'">
    <h1>Title</h1>
    <p>Paragraph 1</p>
    <p>Paragraph 2</p>
</div>

如果像上面的 div 只是为了切换条件而存在,还导致元素层级嵌套多一层,那么它没有“存在的意义”。

我们都知道在声明页面模板时,所有元素需要放在 <template> 元素内。除此之外,它还能在模板内使用,<template> 元素作为不可见的包裹元素,只是在运行时做处理,最终的渲染结果并不包含它。

1
2
3
4
5
6
7
8
9
<template>
    <div>
        <template v-if="status==='ok'">
          <h1>Title</h1>
          <p>Paragraph 1</p>
          <p>Paragraph 2</p>
        </template>
    </div>
</template>

同样的,我们也可以在 <template> 上使用 v-for 指令,这种方式还能解决 v-for 和 v-if 同时使用报出的警告问题。

1
2
3
<template v-for="item in 10">
    <div v-if="item % 2 == 0" :key="item">{{item}}</div>
</template>

template使用v-if,
template使用v-for

5.小型状态管理器 Vue.observable

 2.6.0 新增
用法:让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象;

返回的对象可以直接用于渲染函数和计算属性内,并且会在发生改变时触发相应的更新;
也可以作为最小化的跨组件状态存储器,用于简单的场景。

通讯原理实质上是利用Vue.observable实现一个简易的 vuex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 文件路径 - /store/store.js
import Vue from 'vue'
 
export const store = Vue.observable({ count: 0 })
export const mutations = {
 setCount (count) {
 store.count = count
 }
}
 
//使用
<template>
 <div>
  <label for="bookNum">数 量</label>
   <button @click="setCount(count+1)">+</button>
   <span>{{count}}</span>
   <button @click="setCount(count-1)">-</button>
 </div>
</template>
 
<script>
import { store, mutations } from '../store/store' // Vue2.6新增API Observable
 
export default {
 name: 'Add',
 computed: {
 count () {
  return store.count
 }
 },
 methods: {
 setCount: mutations.setCount
 }
}
</script>

6.卸载watch监听 

通常定义数据观察,会使用选项的方式在 watch 中配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export default {
    data() {
        return {
            count: 1     
        }
    },
    watch: {
        count(newVal) {
            console.log('count 新值:'+newVal)
        }
    }
}
 
// 或则
 
@Watch("visible")
  clearSearch(visible: boolean) {
    if (visible) {
      this.searchValue = "";
    }
  }

  

 

除此之外,数据观察还有另一种函数式定义的方式:

1
2
3
4
5
6
7
8
9
10
11
12
export default {
    data() {
        return {
            count: 1     
        }
    },
    created() {
        this.$watch('count', function(){
            console.log('count 新值:'+newVal)
        })
    }
}

 

它和前者的作用一样,但这种方式使定义数据观察更灵活,而且 $watch 会返回一个取消观察函数,用来停止触发回调:

1
2
3
4
5
6
7
8
9
10
11
12
13
let unwatchFn = this.$watch('count', function(){
    console.log('count 新值:'+newVal)
})
this.count = 2 // log: count 新值:2
unwatchFn()
this.count = 3 // 什么都没有发生...
$watch 第三个参数接收一个配置选项:
 
this.$watch('count', function(){
    console.log('count 新值:'+newVal)
}, {
    immediate: true // 立即执行watch
})

7.$on(‘hook:’)删除事件监听器

删除事件监听器是一种常见的最佳实践,因为它有助于避免内存泄露并防止事件冲突。

如果你想在 created 或 mounted 的钩子中定义自定义事件监听器或第三方插件,并且需要在 beforeDestroy 钩子中删除它以避免引起任何内存泄漏,那么这是一个很好的特性。下面是一个典型的设置:

1
2
3
4
5
6
mounted () {
    window.addEventListener('resize', this.resizeHandler);
},
beforeDestroy () {
    window.removeEventListener('resize', this.resizeHandler);
}

使用 $on('hook:')方法,你可以仅使用一种生命周期方法(而不是两种)来定义/删除事件。

1
2
3
4
5
6
mounted () {
  window.addEventListener('resize', this.resizeHandler);
  this.$on("hook:beforeDestroy", () => {
    window.removeEventListener('resize', this.resizeHandler);
  })
}

8.@hook:【event】父组件监听子组件的生命周期钩子函数

 

比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:

 

1 // Parent.vue
2 <Child @mounted="doSomething"/>
3 
4 // Child.vue
5 mounted() {
6   this.$emit("mounted");
7 }

 

 以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:

复制代码
 1 //  Parent.vue
 2 <Child @hook:mounted="doSomething" ></Child>
 3 
 4 doSomething() {
 5    console.log('父组件监听到 mounted 钩子函数 ...');
 6 },
 7 
 8 //  Child.vue
 9 mounted(){
10    console.log('子组件触发 mounted 钩子函数 ...');
11 },    
12 
13 // 以上输出顺序为:
14 // 子组件触发 mounted 钩子函数 ...
15 // 父组件监听到 mounted 钩子函数 ...
复制代码

 

 当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。

 9.动态指令参数

  Vue 2.6的最酷功能之一是可以将指令参数动态传递给组件。假设你有一个按钮组件,并且在某些情况下想监听单击事件,而在其他情况下想监听双击事件。这就是这些指令派上用场的地方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
    ...
    <aButton @[someEvent]="handleSomeEvent()" />...
</template>
<script>
  ...
  data(){
    return{
      ...
      someEvent: someCondition ? "click" : "dbclick"
    }
  },
  methods: {
    handleSomeEvent(){
      // handle some event
    }
  
</script>

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
场景
1.为组件添加loading效果
2.按钮级别权限控制 v-permission
3.代码埋点,根据操作类型定义指令
4.input 输入框自动获取焦点
 
注意事项
注意:
    1.自定义指令名称,不能使用驼峰规则,而应该使用"my-dir" 或 “my_dir” 或 “mydir”
    2.使用时,必须加v-    如:<p v-my-dir="xxxx"></p>
 
指令的生命周期
1.bind
 只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
 
2.inserted
 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。
 
3update
 所在组件的 VNode 更新时调用,但是可能发生在其孩子的 VNode 更新之前。指令的值可能发生了改变也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新。
 
3.componentUpdated
 所在组件的 VNode 及其孩子的 VNode 全部更新时调用。
 
4.unbind
    只调用一次, 指令与元素解绑时调用。
 
钩子函数的参数
1.el
    指令所绑定的元素,可以用来直接操作 DOM。
2.binding一个对象,包含以下属性:
    name: 指令名,不包括 v- 前缀。
    value: 指令的绑定值, 例如: v-my-directive="1 + 1", value 的值是 2。
    oldValue: 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
    expression: 绑定值的字符串形式。 例如 v-my-directive="1 + 1" , expression 的值是 "1 + 1"
    arg: 传给指令的参数。例如 v-my-directive:foo, arg 的值是 "foo"
    modifiers: 一个包含修饰符的对象。 例如: v-my-directive.foo.bar, 修饰符对象 modifiers 的值是 { foo: true, bar: true }。
 
3.vnode
    编译生成的虚拟节点。
4.oldVnode】
    上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
 
示例:
    bind: function (el, binding, vnode) {}
  
[注意]除了 el 之外,其它参数都是只读的,尽量不要修改他们。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
 
声明局部指令
<template>
  <div class="hello">
      <div v-test='name'></div>
  </div>
</template>
<script>
export default {
  data () {
    return {
     name:'我是名字',
    }
  },
  directives:{
      test:{
        inserted: function (el,binding) {// 指令的定义
           el.style.position = 'fixed'
               el.style.top = binding.value + 'px'
        },
        bind: function (el, binding, vnode) {
 
            }
      }
  }
}
 
全局声明指令
main.js
示例:
    import Vue from 'vue';
    Vue.directive('focus',{
       bind:function(e,v){
           console.log('bind指令')
       },
       inserted:function(e,v){
           console.log('inserted指令')
           console.log(this)        //  指令内部this指向window
           e.focus();
       },
       update:function(){
           console.log('update指令')
       }
    })
 
 
动态指令参数
指令的传参类型有两种:
    1. v-xxxx="参数"
        通过binding.value接收
    2. v-xxx:参数1="参数2"  
        通过binding.arg
场景:我们需要把元素需要动态的固定在左或者顶部?
 
使用:
<div v-for="(item, index) in list" :key="index">
    <div v-zoom:{direction:item.direcition}="{width: item.width, height: item,height}"></div>
</div>
<script>
    data () {
        return {
            list: [
                {width: 100, height: 200, direction: 'left'},
                {width: 140, height: 240, direction: 'top'}
            ]
        }
    }
</script>
 
声明指令
directive('pin', {
    bind: function (el, binding, vnode) {
        el.style.position = 'fixed'
        var s = (binding.arg.direction == 'left' ? 'left' : 'top')
        el.style[s] = binding.value + 'px'
    }
})

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Vue.directive('role', {
    inserted: function (el, binding, vnode) {
      let role = binding.value
      if(role){
        const applist = sessionStorage.getItem("applist")
        const hasPermission = role.some(item => applist.includes(item))
        // 是否拥有权限
        if(!hasPermission){
          el.remove() //没有权限则删除模块节点
        }
      }
    }
})
 
 
Vue.directive('role', {
    inserted: function (el, binding, vnode) {
      let role = binding.value
      if(role){
        // vnode.context 为当前实例
        const applist = vnode.context.$store.state.applist
        const hasPermission = role.some(item => applist.includes(item))
        if(!hasPermission){
          el.remove()
        }
      }
    }
})

10.解耦  

一般在组件内使用路由参数,大多数人会这样做:

1
2
3
4
5
6
7
export default {
    methods: {
        getParamsId() {
            return this.$route.params.id
        }
    }
}

在组件中使用 $route 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。

正确的做法是通过 props 解耦

1
2
3
4
5
6
7
const router = new VueRouter({
    routes: [{
        path: '/user/:id',
        component: User,
        props: true
    }]
})

将路由的 props 属性设置为 true 后,组件内可通过 props 接收到 params 参数

1
2
3
4
5
6
7
8
export default {
    props: ['id'],
    methods: {
        getParamsId() {
            return this.id
        }
    }
}

另外你还可以通过函数模式来返回 props

复制代码
const router = new VueRouter({
    routes: [{
        path: '/user/:id',
        component: User,
        props: (route) => ({
            id: route.query.id
        })
    }]
})
复制代码

 

重用相同路由的组件

 

开发人员经常遇到的情况是,多个路由解析为同一个Vue组件。问题是,Vue出于性能原因,默认情况下共享组件将不会重新渲染,如果你尝试在使用相同组件的路由之间进行切换,则不会发生任何变化。

1
2
3
4
5
6
7
8
9
10
const routes = [
  {
    path: "/a",
    component: MyComponent
  },
  {
    path: "/b",
    component: MyComponent
  },
];

如果你仍然希望重新渲染这些组件,则可以通过在 router-view 组件中提供 :key 属性来实现。

1
2
3
<template>
    <router-view :key="$route.path"></router-view>
</template>

把所有Props传到子组件很容易

这是一个非常酷的功能,可让你将所有 props 从父组件传递到子组件。如果你有另一个组件的包装组件,这将特别方便。所以,与其把所有的 props 一个一个传下去,你可以利用这个,把所有的 props 一次传下去:

1
2
3
<template>
  <childComponent v-bind="$props" />
</template>

代替:

1
2
3
<template>
  <childComponent :prop1="prop1" :prop2="prop2" :prop="prop3" :prop4="prop4" ... />
</template>

把所有事件监听传到子组件很容易

如果子组件不在父组件的根目录下,则可以将所有事件侦听器从父组件传递到子组件,如下所示:

1
2
3
4
5
6
<template>
    <div>
    ...
        <childComponentv-on="$listeners" />...   
  <div>
</template>

如果子组件位于其父组件的根目录,则默认情况下它将获得这些组件,因此不需要使用这个小技巧。

11.样式穿透

在开发中修改第三方组件样式是很常见,但由于 scoped 属性的样式隔离,可能需要去除 scoped 或是另起一个 style 。这些做法都会带来副作用(组件样式污染、不够优雅),样式穿透在css预处理器中使用才生效。

我们可以使用 >>> 或 /deep/ 解决这一问题:

1
2
3
4
5
6
7
8
9
10
<style scoped>
外层 >>> .el-checkbox {
  display: block;
  font-size: 26px;
  
  .el-checkbox__label {
    font-size: 16px;
  }
}
</style>
1
2
3
4
5
6
7
8
9
10
<style scoped>
/deep/ .el-checkbox {
  display: block;
  font-size: 26px;
  
  .el-checkbox__label {
    font-size: 16px;
  }
}
</style>

12.watch高阶使用

立即执行:immediate:true

watch 是在监听属性改变时才会触发,有些时候,我们希望在组件创建后 watch 能够立即执行

可能想到的的方法就是在 create 生命周期中调用一次,但这样的写法不优雅,或许我们可以使用这样的方法

深度监听 deep:true

在监听对象时,对象内部的属性被改变时无法触发 watch ,我们可以为其设置深度监听

触发监听执行多个方法

使用数组可以设置多项,形式包括字符串、函数、对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
export default {
    data: {
        name: 'Joe'
    },
    watch: {
        name: [
            'sayName1',
            function(newVal, oldVal) {
                this.sayName2()
            },
            {
                handler: 'sayName3',
                immaediate: true
            }
        ]
    },
    methods: {
        sayName1() {
            console.log('sayName1==>', this.name)
        },
        sayName2() {
            console.log('sayName2==>', this.name)
        },
        sayName3() {
            console.log('sayName3==>', this.name)
        }
    }
}

  

watch监听多个变量

watch本身无法监听多个变量。但我们可以将需要监听的多个变量通过计算属性返回对象,再监听这个对象来实现“监听多个变量”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
export default {
    data() {
        return {
            msg1: 'apple',
            msg2: 'banana'
        }
    },
    compouted: {
        msgObj() {
            const { msg1, msg2 } = this
            return {
                msg1,
                msg2
            }
        }
    },
    watch: {
        msgObj: {
            handler(newVal, oldVal) {
                if (newVal.msg1 != oldVal.msg1) {
                    console.log('msg1 is change')
                }
                if (newVal.msg2 != oldVal.msg2) {
                    console.log('msg2 is change')
                }
            },
            deep: true
        }
    }
}

13.自定义验证 Props

你可能已经知道可以将props验证为原始类型,例如字符串,数字甚至对象。你也可以使用自定义验证器——例如,如果你想验证一个字符串列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
props: {
  status: {
    type: String,
    required: true,
    validator: function (value) {
      return [
        'syncing',
        'synced',
        'version-conflict',
        'error'
      ].indexOf(value) !== -1
    }
  }
}

  

 

 

posted on   ygunoil  阅读(259)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示