当前位置: 首页 > news >正文

青岛即墨城乡建设局网站搜索引擎优化到底是优化什么

青岛即墨城乡建设局网站,搜索引擎优化到底是优化什么,深圳餐饮网站建设,中国纪检监察报电子版阅读小技巧 1、页面刷新 传统刷新 this.$router.go(0) 、location.reload() 弊端: 刷新整个应用而不是页面如果有应用初始化时调用的接口,方法等也会执行,如根据用户信息获取账号权限,浪费资源,影响体验页面会有一瞬间…

小技巧

1、页面刷新

传统刷新

this.$router.go(0) 、location.reload()

弊端:

  • 刷新整个应用而不是页面
  • 如果有应用初始化时调用的接口,方法等也会执行,如根据用户信息获取账号权限,浪费资源,影响体验
  • 页面会有一瞬间的白屏

新方案1:router-view添加v-if + (provide/inject 或 event-bus)

通过属性控制router-view的重新渲染,达到页面刷新的效果。

// App.vue
<template><router-view v-if="isRouterAlive"/>
</template>export default {provide() {return {AppReload: this.AppReload};},data() {return {isRouterAlive: true   // 控制页面刷新字段}},methods: {// 刷新页面的方法AppReload() {this.isRouterAlive = false;this.$nextTick( () => {this.isRouterAlive = true;});},}
}
复制代码
// 子路由页面
export default {inject: ["AppReload"],methods: {pageReload() {// 调用注入的刷新方法this.AppReload();}}
}
复制代码

也可用event-bus替换provide/inject,方式同理

新方案2:redirect

路由的重定向,适用于菜单类操作,点击菜单可能是跳转到新路由,也可能还是当前路由(用户的习惯如果点击当前路由应该是刷新页面),而在vue中相同路由的跳转并不会刷新。通过重定向的方式可以解决这一问题

// 先注册一个名为 `redirect` 的路由
<script>
export default {beforeCreate() {const { params, query } = this.$routeconst { path } = paramsthis.$router.replace({ path: '/' + path, query })},render: function(h) {return h() // avoid warning message}
}
</script>
复制代码
// 页面跳转时,跳转到 '/redirect' 页面,带上目标页面的参数即可
const { fullPath } = this.$route
this.$router.replace({path: '/redirect' + fullPath
})
复制代码

2、Vue.set使用场景

向响应式对象中添加一个 新的 响应式的 property,且触发视图更新

为什么使用

由于Vue 无法检测到对象属性的添加或删除。而Vue 会在实例初始化时会对data对象属性执行 getter/setter 转化,所以初始化时data对象中存在数据才为响应式数据。那如何在后续为对象添加新的响应式数据,可通过Vue.set。

使用方法:

Vue.set( target, propertyName/index, value )  
// 或 vm.$set
复制代码

其中target为: 响应式对象或数组,且不能是vue实例(不允许动态添加根级响应式 property)

常见场景:

// 初始数据
export default {data () {return {person: {name: 'zs'},list: [{year: '2020',name: 'create'},{year: '2021',name: 'grow'}]}}
}/*
* 对象的操作
*/
// 给对象设置新属性时
this.person.age = 18  // 非响应式,不触发视图更新// 响应式设置
this.$set(this.person, 'age', 18) // 响应式,视图更新
this.person.age = 20 // set之后,数据为响应式,直接赋值也可触发更新/*
* 数组的操作
*/
// 非响应式设置:利用索引直接设置一个数组项时
this.list[0] = {}// 响应式设置
this.$set(this.list, 0, {})
// 或
this.list.splice(0, 1, {})
// 直接给数组的某一项设置新属性时,同理
this.$set(this.list[0], 'time', 1) // 响应式// 响应式数据的直接赋值,也会触发视图更新,并且新值也为响应式
this.person = Object.assign({}, this.person, { a: 1, b: 2 })
// 代替 `Object.assign(this.person, { a: 1, b: 2 })` 
复制代码

set失败场景

set方法基本原理 set方法执行时,内部是会先判断 target 中是否已存在property属性,如果已存在,则是直接对target中property进行简单赋值。

// .set内部执行机制(这里只是简单说明,实际会更复杂)
function set(target, property, value) {if (target[property]) {target[property] = value} else {// 执行新属性的赋值同时getter/setter化,设为响应式数据...}
}
复制代码

基于该机制,部分场景下会出现set失败场景。

如下,person初始化无age属性时,先直接进行了赋值操作,后续再执行set,set则只是赋值,不会将数据 getter/setter 化,导致失败。

// 某些操作中,直接给对象设置新属性
this.person.age = 18// 后续再执行set,则age依旧为非响应式
this.$set(this.person, 'age', 18) 
复制代码

如:v-model 中set失败

v-model绑定对象形式属性时(如a.b),在更改值的回调事件中,会默认调用set方法来对值进行更新;

// html
<input v-model="a.b" />// 输入框内容变更时,默认是调用的set方法进行值的更新
_vm.$set(_vm.a, "b", value)
复制代码

所以我们通常通过 v-model 绑定响应式对象不存在属性时,给我们的感觉就是响应式。通常情况下这是没有问题的。但是如果在v-model更改值的回调前,手动赋值了a.b数据,后续表单值再更改也不会触发视图更新(即上面的set失败场景)。如下:

// data初始化
data {person: {}
}// html
<input v-model="person.age" />// 如果在表单值更改前,已经执行了直接赋值,则表单更改时不会再触发视图更新。
// 比如在页面初始化后,获取了age参数,并进行赋值,此时再去修改input值,并不会触发视图更新
this.person.age = 18
复制代码

3、非响应式数据的“伪视图更新”:

非响应式数据更新时,事件循环中当前组件视图有依赖的响应式数据更新,则视图中非响应式数据也会更新

<template><div>{{c}}{{a.b}}</div>
</template>...
data {a: {},c: 1
}
...
// setValue 方法执行前4次,视图中c、a.b数据都会更新,4次后不再更新视图
setValue() {if (this.c < 5) {this.c += 1;}this.a.b = this.a.b ? this.a.b + 1 : 1;
}
复制代码

原因分析

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖(依赖收集)。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染(派发更新)。

而Vue 在更新 DOM 时是 异步执行 的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。

然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。这也是为什么如果要获取更新后的DOM,通常在Vue.nextTick()回调中处理

而上方的例子中,setValue 方法前4次执行时,都触发了“c”属性的setter,将通知当前组件的watcher,重新渲染组件。而更新DOM是异步的,真正去更新DOM时,a.b值也变更了,则视图中会连同a.b一起更新。而第4次后执行的事件,“c”值不会变更,即使a.b值变更了,也不会触发组件的重新渲染,视图不会再更新。

4、style scoped原理及穿透

CSS模块化,避免组件间样式互相影响。

为什么添加scoped后父子组件间样式不再被继承、覆写(子组件的根节点还是可以被父组件样式影响)。因为添加该属性后每个css选择器语句及html标签都会添加自定义属性hash,通过该hash来控制样式影响指定的元素! 如下:

// 源代码// 父组件
<template><div class="a"><div class="b"><comp /></div></div>
</template><style scoped>
.a{}
.a .b{}
.a .c{}
.a .d{}
.a >>> .c{}
.a >>> .d{}
.a >>> .c .d{}
</style>// comp子组件
<template><p class="c"><span class="d"></span></p>
</template><style scoped>
...
</style>
复制代码
// 编译过后// 如果组件style无scoped属性,则html、css不会添加data-v属性
<template><div data-v-6364f53c class="a"><div data-v-6364f53c class="b"><!--子组件根元素会同时添加父组件、子组件data-v属性--><p data-v-537e2781 data-v-6364f53c class="c"><span data-v-537e2781 class="d"></span></p></div></div>
</template><style><!-- 组件本身元素样式不需穿透 -->.a{}            →  .a[data-v-6364f53c]{}.a .b{}         →  .a .b[data-v-6364f53c] {}<!-- 设置子组件样式 -->.a .c{}         →  .a .c[data-v-6364f53c] {} <!-- 不加穿透也可控制子组件根元素样式-->.a .d{}         →  .a .d[data-v-6364f53c] {} <!-- 无效,无法影响子组件的非根元素--><!-- 设置穿透,可影响子组件所有元素-->.a >>> .c{}     →  .a[data-v-6364f53c] .c {} .a >>> .d{}     →  .a[data-v-6364f53c] .d {}.a >>> .c .d{}  →  .a[data-v-6364f53c] .c .d {}
</style>
复制代码

注意事项:

  • 原生css使用>>>进行样式穿透; scss、less使用/deep/;
  • 不需要嵌套使用穿透语法,在父组件设置穿透可影响所有子孙组件。嵌套使用会导致后续层级穿透语法解析失败,导致出错。
  • 不要在全局样式中使用穿透语法(.vue文件中style不带scope也是全局样式)。全局样式本身就能影响所有组件,不受scoped影响,添加后反而可能导致穿透语法编译失败,样式无法正常生效。

5、组件通信

Vue组件通信中几种主要的场景:父子组件、隔代组件、兄弟组件

  1. props / $emit:父子组件
  2. ref 与 $parent / $children:父子组件
    • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
    • $parent / $children:访问父 / 子实例
  3. EventBus:均可,通过一个空的 Vue 实例作为事件中心,用它来触发事件和监听事件
  4. $attrs / $listeners:隔代组件
  5. provide / inject:隔代组件。祖先组件通过 provider 提供变量,子孙组件通过 inject 注入变量
  6. Vuex:均可。只保存全局数据,不要滥用,否则反而影响性能。如:父子组件通信使用父子组件通信方式就好

如:父组件调用子组件的方法

通过$refs或者$chilren来拿到对应的实例,从而操作

<comp ref="comp"></comp>// 调用子组件方法
this.$refs.comp.eventName()
复制代码

6、.sync && v-model

prop 的“双向绑定”。都是语法糖。单个组件可以将.sync作用于多个属性,而v-model只能使用一个

v-model

作用于自定义组件时,默认会利用名为 value 的 prop 和名为 input 的事件(可在子组件中通过model属性更改属性和事件名)。

// 父组件
<comp v-model="bar.name"></comp>
// 基本等同于 ↓
<comp :value="bar.name" @input="bar.name = $event"></comp>
// 但是不单单只是做了类语法糖的这种处理,还有其他的比如:
// 绑定的如果是对象属性,回调为set的赋值处理等// 子组件:
<div>{{value}}</div>// 接收参数
props: ["value"]
// 更新方式
this.$emit("input", newValue)
复制代码

当作用于表单项时,v-model 在内部为不同的元素使用不同的属性并抛出不同的事件。

  • text、textarea:value 属性、input 事件;
  • checkbox、radio:checked 属性、change 事件;
  • select:value 属性、 change 事件。

.sync

实现机制和v-model是类似的。当有需要在子组件修改父组件值的时候这个方法很好用

// 父组件
<comp :foo.sync="bar.name"></comp>
// 等同于 ↓
<comp :foo="bar.name" @update:foo="bar.name = $event"></comp>// 子组件内更新方式
this.$emit("update:foo", newValue)// 同时设置一个对象的全部属性
<comp v-bind.sync="bar"></comp>
// 把 bar 对象中的每一个 property 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on 监听器
复制代码

7、事件修饰符

.native

用于在某个组件上注册一个原生事件。因为直接通过@click不加该修饰符注册的事件,触发的实际是组件内部通过 $emit传递过来的事件。

// 组件内部没有$emit传递click事件,则事件无法生效。
<vButton @click="clickHandler">按钮</vButton> // 事件作为原生click事件注册生效
<vButton @click.native="clickHandler">按钮</vButton>   
复制代码

而部分UI组件库,在组件上做了处理,如element-ui的 Button组件,点击按钮时,会将点击事情传递出来。

// Button组件内部处理
<buttonclass="el-button"@click="handleClick"
</button>...handleClick: function handleClick(evt) {this.$emit('click', evt);
}
复制代码

所以我们在使用这种组件时,是否添加了.native,点击事件都能生效

.stop

阻止事件冒泡

.prevent

拦截默认事件

.passive

不拦截默认事件。

通俗点说就是每次事件产生,浏览器都会去查询一下是否有preventDefault阻止该次事件的默认动作。我们加上passive就是为了告诉浏览器,不用查询了,我们没用preventDefault阻止默认动作。

常同于滚动监听 @scoll,@touchmove事件,提高效率。因为滚动监听过程中,移动每个像素都会产生一次事件,每次都使用内核线程查询prevent会使滑动卡顿。我们通过passive将内核线程查询跳过,可以大大提升滑动的流畅度。

<div v-on:scroll.passive="onScroll">...</div>
复制代码

注:passive和prevent冲突,不能同时绑定在一个监听器上。

8、生命周期(路由切换,父子组件)

路由切换

路由A->B

B beforeCreate -> B created -> B beforeMount -> A beforeDestroy -> A destroyed -> B mounted
复制代码

可能出现的问题

// A、B组件中绑定了同名的全局事件,A路由切换到B路由时,需要销毁A路由中事件,初始化B路由中事件。
// 如我之前做过的一次,echarts图表点击的自定义事件,A、B页面初始化时都需要注册一个这样的全局事件,页面离开时销毁。// A组件
beforeMount(){window.gotoPageChart = () => {...}
}
destroyed() {window.gotoPageChart = null;
}// B组件
beforeMount(){window.gotoPageChart = () => {...}
}
destroyed() {window.gotoPageChart = null;
}
复制代码

最终切换到B路由后,触发事件时找不到window.gotoPageChart事件。就是因为这里的生命周期顺序,B组件的beforeMount执行赋值后,再执行了A组件的destroyed内方法,将window.gotoPageChart设置了null,导致与预期不一致。

解决方案:将beforeMount生命周期换为mounted即可

父子组件

加载渲染过程

父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
复制代码

子组件更新过程

父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

父组件更新过程

父 beforeUpdate -> 父 updated

销毁过程

父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

9、动态参数

<v-time :[setData]="valueDate"></v-time>...data() {return {valueDate: new Date(),setData: "startDate"     // 动态的属性名,如果这里的值为 endDate,则为endDate属性绑定值。}
}
复制代码

这里会将 setData 作为JavaScript表达式动态求值,结果作为最终的参数来使用。上述绑定将等价于 v-bind:startDate

同样地,你可以使用动态参数作为事件名绑定处理函数:

<button @[eventName]="handler"></button>
复制代码

当 eventName 的值为 focus 时,v-on:[eventName] 将等价于 v-on:focus。

动态参数预期结果为字符串。当值为null时,可以被显性地用于移除绑定

同样可以适用于插槽绑定:

<foo><template #[name]>Default slot</template>
</foo>
复制代码

10、组件库中未暴露的API

除了API中暴露出来的属性、方法,部分组件还有其他支持的属性或方法可以使用。通常这在是API中提供的不满足我们需求时。

如:element ui 下拉菜单:我们希望点击菜单项后不被隐藏,执行自定义操作后再隐藏

// 下拉菜单默认在点击菜单项后会被隐藏,将hide-on-click属性默认为false可以关闭此功能
<el-dropdown ref="dropdown" :hide-on-click="false"></el-dropdown>...
// 而在执行其他操作后,可调用此方法再将下拉菜单隐藏
this.$refs.dropdown.hide();  // 手动隐藏// 也有显示方法
this.$refs.dropdown.show();  // 手动显示
复制代码

查找方式

  • 可以查看组件源码,如 el-dropdown 的 methods
methods: {show() {if (this.triggerElm.disabled) return;clearTimeout(this.timeout);this.timeout = setTimeout(() => {this.visible = true;}, this.trigger === 'click' ? 0 : this.showTimeout);},hide() {if (this.triggerElm.disabled) return;this.removeTabindex();if (this.tabindex >= 0) {this.resetTabindex(this.triggerElm);}clearTimeout(this.timeout);this.timeout = setTimeout(() => {this.visible = false;}, this.trigger === 'click' ? 0 : this.hideTimeout);},...
}
复制代码
  • 查看组件实例对象,如: console.log(this.$refs.dropdown),通过属性或方法名称查看是否有需要的方法。通常普通的方法可以通过方法名判断出基本用途

11、别名的多场景使用(template/js/css)

别名的使用场景,如已经在配置文件中配置好 ./src 目录别名为 @。

// 1、JS文件中,直接使用 @
import chartItem from "@/components/report";// 2、css(scss等预处理)文件中,使用 ~@ 开头
@import '~@/assets/css/global.less';
background-image: url("~@/assets/img/login/user.png");// 3、template模板中,@、~@ 均可
<img src="@/assets/img/login/logo.png">
复制代码

12、computed的 get、set

  • 在处理传入数据和目标数据格式不一致的时候很有用,如时间格式;
// 前端显示时间单位为 秒
<input v-model.number="customTime">...
data() {return {// 后端返回及传递时间为 毫秒form: {time: 3000}}
}computed: {customTime: {get() {return this.form.time / 1000},set(val) {this.form.time = val * 1000}}
}
复制代码
  • 可以在获取数据的同时,监听数据变化,当发生变化时,做一些额外的操作。

最经典的用法就是v-model上绑定一个 vuex 值的时候,input 发生变化时,通过 commit更新存在 vuex 里面的值。

<input v-model="name">...computed: {name: {get() {return this.$store.state.name},set(val) {this.$store.commit('updateName', val)}}
}
复制代码
  • 还有如模态框的二次封装,设置显示、隐藏
<div><el-dialog :visible.sync="dialogVisible">...</el-dialog>
</div>props: {// 模态框是否显示dialogViewVisible: {required: true,type: Boolean,default: false}
}
computed: {dialogVisible: {get() {return this.dialogViewVisible;},set(val) {this.$emit("update:dialogViewVisible", val);}}
}
复制代码

13、watch的常用参数

immediate

当 watch 一个变量的时候,组件初始化时默认并不会执行,需要在created的时候手动调用一次。

// bad
created() {this.fetchUserList();
},
watch: {searchText: 'fetchUserList'
}
复制代码

可以添加immediate属性,这样初始化的时候也会触发

// good
watch: {searchText: {handler: 'fetchUserList',immediate: true,}
}
复制代码

deep

当设置为true时,它会进行深度监听。比如有一个数组或对象,里面任意一项变更都会触发watch。

watch: {searchTextObj: {handler: 'fetchUserList',deep: true}
}
复制代码

14、$attrs && $listeners

组件二次封装的神器

属性的说明

  • 非 prop 的 attribute:传向一个组件,但是该组件并没有定义相应 prop 的 attribute。

显式定义的 prop 适用于向一个子组件传入信息。而其他非 prop的attribute 会被添加到这个组件的根元素上(class 和 style会和子组件本身的合并,而其他的大部分属性会进行替换),子组件设置inheritAttrs: false,可以阻止这一默认行为,根元素不继承 attribute(不影响class和style)

  • $attrs:非 prop 的 attribute( class 和 style 除外 )。可以通过 v-bind="$attrs" 传入内部组件,手动决定这些 attribute 赋予到哪个元素。通常配合子组件 inheritAttrs 选项一起使用。
  • $listeners:向子组件绑定 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="listeners" 传入内部组件 如我们基于第三方组件进行二次封装时,可能会加入一些业务逻辑,但是第三方组件本身可能支持几十个配置参数,不可能所有参数都自定义通过props传递,并且第三方组件后期也可能加入新参数。这时候我们就可以使用v-bind="\attrs"传递所有属性、v-on="$listeners"传递所有方法。

如下:基于Element-UI的el-pagination组件的简单二次封装

// 自定义的pagination组件
// 基于el-pagination组件二次封装,封装公共属性、业务等; 支持父组件传入的el-pagination属性、绑定事件
<div class="custom-pagination"><div>{{custom-prop}}</div><el-paginationbackground:layout="layout"v-bind="$attrs"v-on="$listeners"/>
</div>export default {inheritAttrs: false, // 根元素不继承 非prop的attributeprops: ['custom-prop'],data() {return {layout: 'prev, pager, next, total'}},
}// 父组件
// 引入自定义pagination组件,可直接按照el-pagination规则传递参数,绑定监听事件
<pagination:custom-prop="_customProp":current-page="currentPage":page-size="limit":total="total"@current-change="handleCurrentChange"@size-change="handleSizeChange"
/>
复制代码

如上,我们在自定义的pagination组件中,只显式声明了custom-prop的prop属性,其他定义的属性如current-page、page-size、total将通过pagination组件中定义的v-bind="$attrs"直接透传到el-pagination组件中,而不用在把这些props全部显式定义一遍。事件的传递同理。

我们没有在pagination组件props中声明的属性,给pagination组件绑定的方法,会通过$attrs、$listeners直接传递到el-pagination组件中

组件显式定义接收的 props ,也可以通过this.$props获取,通过v-bind="$props"直接向下传递

15、v-show && v-if

v-if

DOM 区域没有生成,没有插入文档,等条件成立的时候才动态插入到页面!

v-show

DOM 区域在组件渲染的时候同时渲染了,只是单纯用 css 隐藏了

使用

  • 频繁切换显隐的用v-show,条件判断渲染内容不怎么切换的用v-if
  • DOM结构不怎么变化的用v-show, 数据需要改动很大或者布局改动的用v-if

16、自动注册全局组件、指令、过滤器等

传统的注册全局组件方式(指令和过滤器基本也是这样):

// 依次引入全局组件,再依次进行注册
import baseButton from '@/globalComponents/baseButton.vue'
import baseDialog from '@/globalComponents/baseDialog.vue'
Vue.component('baseButton', baseButton)
Vue.component('baseDialog', baseDialog)
复制代码

以上方式,如果组件一旦过多,代码就会非常长,并且后续再添加新的全局组件时,需要再次改写这里的引入、注册代码。不够优雅

我们可以基于 webpack 的require.context()来实现自动引入组件并注册。

例:创建一个globalComponents文件夹,将想要注册到全局的组件都放在这个文件夹里。在入口文件main.js中引入如下:

// require.context:接收3个参数:要搜索的文件夹目录,是否搜索子目录,匹配文件的正则表达式;返回一个根据request获取模块内容的函数。// 找到globalComponents文件跟目录下以.vue命名的文件
const requireComponent = require.context('./globalComponents', false, /\.vue$/
)// 循环找到的文件map对象
requireComponent.keys().forEach(fileName => {// 获取对应文件的模块const componentConfig = requireComponent(fileName)//因为得到的filename格式是: './baseButton.vue', 所以这里我们去掉头和尾,只保留真正的文件名(baseButton)const componentName = fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')Vue.component(componentName, componentConfig.default)
})
复制代码

后续再添加组件到globalComponents目录中,就会自动进行注册了,指令、过滤器等注册方法同理。

并且不单是可以用来进行全局的注册,也可以根据自己的需要,获取指定文件夹下的文件后做其他的处理。

具体关于require.context的使用说明,可以参考另外一篇文章:webpack的require.context详解

17、子组件生命周期监听

hook

监听生命周期。

  • 常用场景1:组件销毁时销毁全局事件或销毁定时器。
// 传统的事件注册、销毁。有2点弊端
// 1.需要在实例中保存要销毁的事件或定时器
// 2.在不同的option中维护,option中内容较多时,维护起来较麻烦,并且容易忘记
mounted() {window.addEventListener('resize', this.debounceHeight)
},
beforeDestroy() {window.removeEventListener('resize', this.debounceHeight)
}
复制代码
// 使用hook销毁,不需要在实例中绑定事件,代码一处维护
mounted() {window.addEventListener('resize', _debounceHeight)// 也可以使用this.$onthis.$once('hook:beforeDestroy', function() {window.removeEventListener('resize', _debounceHeight)})
},
复制代码
  • 常用场景2:监听子组件生命周期。
// 传统方式// 自组件指定生命周期抛出事件
mounted() {this.$emit("mounted")
}
// 父组件监听事件
<comp @mounted="handleEvent"/>
复制代码
// hook方式
<comp @hook:mounted="handleEvent"/>
复制代码

18、动态组件

根据传入的 'is' 参数,动态判断需要渲染哪一个组件

如装修、搭建类页面的JSON数据解析,根据数据中组件类型决定当前使用哪一个组件来渲染

<div v-for="currentComponent in pageList">// :is 属性值需要和组件定义名称对应上<component:is="currentComponent.type":key="currentComponent.id":parmes="currentComponent.data"/>
</div>
复制代码

19、异步组件

组件的懒加载,只有在这个组件需要被渲染的时候才会加载资源。 与路由的懒加载同样的原理,工厂函数可以返回一个对象配置加载时组件、加载失败组件等

通常用来一个页面中,组件内容过多,过大,但是部分组件并不是一进入页面就会被使用。如:点击页面查看按钮后弹出的查看组件,默认进入页面只会加载其他内容的资源文件,点击查看按钮时才会加载查看组件资源。

components: {// 异步引入查看组件ViewPage: () => ({// 需要加载的组件,该属性必填,其他都是非必填component: import("./view"),// 异步组件加载时使用的组件loading: PageLoading// 加载失败时使用的组件error: ErrorComponent,// 展示加载时组件(loading组件)的延时时间。默认值是 200 (毫秒)delay: 200,// 如果提供了超时时间且组件加载也超时了,// 则使用加载失败时使用的组件。默认值是:`Infinity`timeout: 3000})
}
复制代码

20、递归组件

组件在自己的模板中调用自身

  • 需要设置组件的name属性
  • 需有结束的阙值,递归调用是条件性的(使用一个最终会得到 false 的 v-if)
  • 不需要import引入自身
// 组件递归用来开发一些具体有未知层级关系的独立组件。比如:
// 联级选择器和树形控件<template><div v-for="(item,index) in treeArr"><!-- 递归调用自身, 需有一个最终为false的v-if --><tree :item="item.arr" v-if="item.flag"></tree></div>
</template><script>
export default {// 必须定义name,组件内部才能递归调用name: 'tree',data(){return {treeArr: []}}
}
</script>
复制代码

21、Vue.observable()

让一个对象变为响应式数据。Vue 内部就是用它来处理 data 函数返回的对象,让data中数据成为响应式数据。 返回的对象可以直接用于渲染函数和计算属性内,并且会在发生改变时触发相应的更新; 也可以作为最小化的跨组件状态存储器,用于简单的场景。

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

// 文件路径 - /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'export default {name: 'Add',computed: {count () {return store.count}},methods: {setCount: mutations.setCount}
}
</script>
复制代码

22、keep-alive

缓存组件。组件移除时,进行暂存,下次激活时,再直接渲染。

使用场景

  • 保持页面状态(记录当前页面滚动位置、翻页页数等),结合路由一起使用
  • 避免重复渲染(提高性能),在部分组件高频切换的场景使用

注意事项

  • 提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;
  • 被缓存的组件,在生命周期中多2个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。

不能无限制缓存,只缓存需要缓存的,不需要的缓存及时释放。否则占用内存太多可能影响性能

23、router key

常用于相同路由,不同参数的页面跳转。

如/product/1,跳转到/product/2,跳转后页面不会更新,因为vue-router发现这是同一个组件,会复用这个组件。在页面跳转后created、mounted组件初始化生命周期内的方法没有执行。

通常的解决方案是监听$route的变化来初始化数据。虽然可以解决问题但是不够优雅

更好的解决方案:给router-view添加一个路由路径的key值,只要url变化,就会重新创建组件,重新获取数据

<router-view :key="$route.fullpath"></router-view>
复制代码

24、作用域插槽

让插槽的内容能够访问子组件的数据

常用于组件多处使用时,插槽基于子组件数据自定义处理,类似

// 子组件 todo-list
<div>{{ user.firstName }}<slot name="todo" :user="user" :text="text">{{ user.lastName }}</slot>
</div>data() {return {user:{lastName:"Zhang",firstName:"yue",sex:"男",},text: "其他内容"}
}
复制代码
// 父组件// 直接接取数据
<todo-list>//slotProps 可以随意命名//slotProps 接取的是子组件标签slot上属性数据的集合<template #todo="slotProps" >{{slotProps.user.sex}}</template>
</todo-list>// 或解构
<todo-list><template #todo="{ user, text }" >{{text}}</template>
</todo-list>
复制代码

基于这个,我们可以将插槽转换为可复用的模板,这些模板可以基于输入的 prop 渲染出不同的内容。这在设计封装数据逻辑的同时,允许父级组件自定义部分布局的可复用组件时是很有用的。在组件库中也经常会遇到这种用法

25、生产环境的检查、调试

在生产环境如何查看Vue实例,获取、修改实例对象的属性?

  1. 首先,找到我们组件跟元素对应的DOM元素,2种方式
  • 在控制台中通过DOM查找方法(如querySelector或getElementById等)选择元素。
  • 使用Chrome devtools 的 elements面板选中组件的根元素,然后在控制台中输入$0,$0表示最后选择的元素。$1是之前选择的元素,依此类推.它记得最后五个元素$0 – $4.
  1. 通过DOM元素的__vue__属性,获取Vue实例的详细信息
// 方式1
document.querySelector('[data-v-1f10e70e]').__vue__// 方式2
$0.__vue__// 查看组件属性
$0.__vue__.msg// 更改组件属性
$0.__vue__.msg = "获取到组件实例了"
复制代码

性能优化

1、webpack-bundle-analyzer - 构建结果输出分析

可以更简单、直观地分析输出结果,Vue-cli中默认集成了该插件。其他场景需要的话也可以自定义引入:

如在Vue-cli的项目中 执行 $ npm run build --report 后生成分析报告:可以查看我们各个包的大小。引入了哪些资源,从而进一步分析可优化项

2、Object.freeze

大数据优化,数据量特别大的时候,使用容易卡顿,如果这些数据并不需要响应式变化,冻结对象,禁止响应式。

当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter,它们让 Vue 能进行追踪依赖,在属性被访问和修改时通知变化。 使用了 Object.freeze 之后,不仅可以减少 observer 的开销,还能减少不少内存开销

使用方式:this.obj = Object.freeze(Object.assign({}, obj))

注意:冻结只是冻结对象里面的属性,对象本身还是可以更改

new Vue({data: {// 这样vue不会对list里的对象属性做getter、setter绑定list: Object.freeze([{ value: 1 },{ value: 2 }])},mounted () {// 界面不会有响应,因为单个属性被冻结this.list[0].value = 100;// 重新赋值,数据重新变成响应式this.list = [{ value: 100 },{ value: 200 }];}
})
复制代码

3、v-for && v-if

v-for 遍历必须为 item 添加 key。且避免同时使用 v-if,在vue2.x中,同时使用这2个指令时,依旧会把所有数据循环一遍,再控制是否显示(相当于先执行的v-for,再执行的v-if)。在Vue3 中针对这一点做了优化

4、尽量避免全局操作

  • 选择DOM,通过this.refs.xxx.$el的方式,而不是 document.querySelector()等全局选择器。将操作局限在当前组件内
  • 全局事件需要销毁。如某个组件监听窗口变化,window.addEventListener('resize', this.__resizeHandler),一定要在 beforeDestroy或者destroyed生命周期注销。包括定时器
  • 避免过多的全局状态,不是所有的的状态都需要存在Vuex中,需要根据业务进行取舍。只是部分业务的状态处理,可以考虑使用Event Bus或模块化等其他机制。
  • css也尽量避免写过多的全局样式,除了全局公共的样式。其他各个组件的样式都应该使用scoped写法。部分组件内部的差异化可通过样式穿透或使用命名空间。

5、无限列表性能

如果应用存在非常长或者无限滚动的列表,那么需要采用窗口化的技术来优化性能,只需要渲染少部分区域的内容,减少重新渲染组件和创建 dom 节点的时间。 可以参考以下开源项目 vue-virtual-scroll-listvue-virtual-scroller 来优化这种无限列表的场景。

6、防抖节流

涉及到对性能有影响的高频操作,使用防抖节流控制频率,提高性能。

比如我之前做的可视化搭建系统,为了实现配置的所见即所得,每次后台配置组件的展示属性时,都会通过postmessage实时向组件展示区域(另外一个h5系统)发送配置数据,特别是涉及到slider滑块类的配置时,这种高频的传输是比较消耗性能的,此时使用防抖或节流就是很好的选择。

7、组件库的按需加载

对于大型组件库的使用,特别是只用到其中一部分的内容,只引入需要的部分(一般大点的组件库都支持按需引入)

如:echarts

// 方式1: 引入整个包
import echarts from 'echarts'
复制代码
// 方式2: 按需引入// 引入 ECharts 主模块
var echarts = require('echarts/lib/echarts');
// 按需引入用到的模块
require("echarts/lib/chart/line");
require("echarts/lib/chart/pie");
require("echarts/lib/component/grid");
require("echarts/lib/component/legend");
复制代码

可以对比下上面2种引入方式。可以发现,最终打包的代码,按需引入的echarts部分代码包的体积,比全部引入明显小很多。当然如果我们组件库中大部分内容都会被使用,那还是可以整个引入,可以根据实际情况判断。

Vue3中使用注意事项

以上所有内容都是基于vue2.x版本,由于在vue3中对部分api做了改造、移除等处理,包括vue内部处理的改动。所以有些内容并不适用于vue3。 如下:

  • 数据响应式的处理由原来的Object.defineProperty更改为使用Proxy,所以对于数据的响应式不再是getter、setter化处理,Proxy 代理的是对象,性能更好,新增属性也不需要做特殊处理。
  • Vue.observable,改为用 Vue.reactive 替换
  • $on,$off 和 $once 实例方法已被移除
  • vue3中 v-model 合并了原来的v-model & .sync 一个组件也定义多个 v-model,并指定不同的属性名
  • 过滤器(Filters)的移除
  • 异步组件新的定义方法:defineAsyncComponent
  • v-if 与 v-for 作用在同一元素上的优先级修改。2.x中v-for 会优先作用,3.x中v-if 会优先作用
  • 生命周期的重命名。destroyed重命名为unmounted,beforeDestroy重命名为beforeUnmount

包括还有其他更多的改动,这里只列举出了本文档中涉及到的内容。

参考资料

手摸手,带你用vue撸后台

http://www.lbrq.cn/news/2769769.html

相关文章:

  • 新浪做网站开发网站多少钱
  • 网站建设新手如何自己做网站批量查询神马关键词排名
  • mac 本地运行 wordpressseo优化博客
  • 优班图搭建网站品牌营销策略有哪些
  • 小公司做网站还是微博阳西网站seo
  • 免费在线观看电视剧的网站网站优化关键词排名公司
  • 移动端快速排名网站如何提升seo排名
  • 北京营销型网站建设价格上海百度推广公司排名
  • 网站建设平台招商宁波seo推广推荐
  • 东莞做网站服务商制作网站教学
  • 浙江省建设执业资格中心网站广州网站优化外包
  • 网站如何做等级保护他达那非副作用太强了
  • 正规免费网站建设公司营销网
  • 什么网站做兼职可靠黄冈黄页88网黄冈房产估价
  • 极路由4 做网站宁波seo网络推广软件系统
  • 如何提高网站开发效率查询域名网站
  • 哪个网站可以给图片做链接头条广告入口
  • 做网站商城开发什么语言最快软件培训机构排名
  • 做竞价的网站需要做外部链接吗2023疫情最新消息今天
  • 网络服务提供者知道或者应当知道网络用户上优化seo
  • 掀浪云网站建设销售成功案例分享
  • 动态网站制作好了在哪里看wordpress seo教程
  • 韶关seo网站关键词怎么优化到首页
  • 数据库服务器seo优化工作怎么样
  • 网站怎么做网络推广seo广告
  • 小网站推荐一个企业搜索引擎优化
  • 网站开发需呀那些技术seo搜索引擎优化试题及答案
  • 购买网站需要注意什么seo推广关键词公司
  • 金华网站建设方案优化如何建立公司网站网页
  • 长沙优化网站价格百度自动点击器下载
  • 35岁对工作的一些感悟
  • 如何在 uBlock Origin 中忽略指定网站的某一个拦截规则
  • Lucene 8.5.0 的 `.pos` 文件**逻辑结构**
  • 决策树的笔记
  • Linux bash核心介绍及目录命令
  • Java 基础 -- Java 基础知识