day-100-one hundred-20230627-全局处理vue-Vue.use()-element-ui说明-vue2中原型链-如何设置样式
全局处理vue
-
创建
/src/global.js
,用于设置全局相关的配置。-
fang/f20230627/day0627/src/global.js
import Vue from "vue"; // [vue-全局配置](https://v2.cn.vuejs.org/v2/api/#全局配置) //[取消生产环境下的Vue提示信息](https://v2.cn.vuejs.org/v2/api/#productionTip)。 Vue.config.productionTip = false; // [vuejs-devtools 的配置说明](https://v2.cn.vuejs.org/v2/api/#devtools) //[vuejs-devtools插件](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) // [Vue-use](https://v2.cn.vuejs.org/v2/api/#Vue-use) // Vue.use(function install(_Vue){ // console.log(`_Vue===Vue-->`, _Vue===Vue); // //然后在该函数内部想怎么处理,就是自己的事了! // }) // // [Vue-use](https://v2.cn.vuejs.org/v2/api/#Vue-use) // Vue.use({ // install(_Vue) { // console.log(`对象:_Vue===Vue-->`, _Vue === Vue); // //然后在该函数内部想怎么处理,就是自己的事了! // }, // }); /* // 全局配置element-ui。 // 导入element-ui import "element-ui/lib/theme-chalk/index.css"; import ElementUI from "element-ui"; Vue.use(ElementUI); */ import "element-ui/lib/theme-chalk/index.css"; import { Button, Tag, Loading, Message } from "element-ui"; Vue.use(Button).use(Tag).use(Loading.directive); Vue.prototype.$message = Message;
-
-
在
/src/main.js
中,在根视图/src/App.vue
导入之前先导入。-
fang/f20230627/day0627/src/main.js
import Vue from 'vue' import './global' import App from './App.vue'
-
vuejs-devtools
Vue.use()
Vue.use()
是Vue2中使用/注册插件的方式。- 可以传递对象或者函数。
-
传递对象:要求对象必须具备install的成员,成员值是一个函数,当Vue.use处理的时候,会自动执行install方法,并且把Vue传递进去。
-
返回的依旧是Vue对象,所以可以实现链式写法。
import Vue from "vue"; // [Vue-use](https://v2.cn.vuejs.org/v2/api/#Vue-use) Vue.use({ install(_Vue) { console.log(`对象:_Vue===Vue-->`, _Vue === Vue); //然后在该函数内部想怎么处理,就是自己的事了! }, });
-
传递函数:函数本身就是install方法,会被触发执行,传递Vue!
import Vue from "vue"; // [Vue-use](https://v2.cn.vuejs.org/v2/api/#Vue-use) Vue.use(function install(_Vue){ console.log(`_Vue===Vue-->`, _Vue===Vue); //然后在该函数内部想怎么处理,就是自己的事了! })
- 然后在该函数内部想怎么处理,就是自己的事了!
- 也就是说,可以对Vue原型对象设置一些原型方法,也可以设置一些自定义指令或自定义组件。
- 然后在该函数内部想怎么处理,就是自己的事了!
-
- 可以传递对象或者函数。
element-ui说明
-
在
fang/f20230627/day0627/node_modules/element-ui/src/index.js
中可以看到element-ui的源码入口。 -
全局导入:在全局配置中使用通过Vue.use()后,会调用它的element-ui中的install方法,而install方法中主要执行了:
-
fang/f20230627/day0627/src/global.js
import ElementUI from "element-ui" import 'element-ui/lib/theme-chalk/index.css' Vue.use(ElementUI)
- 这个实际上,是执行了
/node_modules/element-ui/lib/index.js
。而/node_modules/element-ui/lib/index.js
,这个在网上查和问人,得知是由/node_modules/element-ui/src/index.js
打包而来。
- 这个实际上,是执行了
-
fang/f20230627/day0627/node_modules/element-ui/src/index.js
// Vue.use后调用该方法。 const install = function(Vue, opts = {}) { // 设置element-ui的国际化处理。 locale.use(opts.locale); locale.i18n(opts.i18n); // 注册85个全局组件。 components.forEach(component => { Vue.component(component.name, component); }); Vue.use(InfiniteScroll);// 注册一个v-InfiniteScroll自定义指令,用于虚拟滚动。fang/f20230627/day0627/node_modules/element-ui/packages/infinite-scroll/index.js。v-loading自定义指令。 Vue.use(Loading.directive);// 注册 // 向Vue的原理上挂载一个$ELEMENT配置项,这个配置项是element-ui的全局配置。 Vue.prototype.$ELEMENT = { size: opts.size || '', zIndex: opts.zIndex || 2000 }; // 还在Vue的原型上挂载很多消息提示的方法,组件中基于this.$xxx直接调用就可以了。 Vue.prototype.$loading = Loading.service; Vue.prototype.$msgbox = MessageBox; Vue.prototype.$alert = MessageBox.alert; Vue.prototype.$confirm = MessageBox.confirm; Vue.prototype.$prompt = MessageBox.prompt; Vue.prototype.$notify = Notification; Vue.prototype.$message = Message; };
/* Automatically generated by './build/bin/build-entry.js' */ import Pagination from '../packages/pagination/index.js'; import Dialog from '../packages/dialog/index.js'; import Autocomplete from '../packages/autocomplete/index.js'; import Dropdown from '../packages/dropdown/index.js'; import DropdownMenu from '../packages/dropdown-menu/index.js'; import DropdownItem from '../packages/dropdown-item/index.js'; import Menu from '../packages/menu/index.js'; import Submenu from '../packages/submenu/index.js'; import MenuItem from '../packages/menu-item/index.js'; import MenuItemGroup from '../packages/menu-item-group/index.js'; import Input from '../packages/input/index.js'; import InputNumber from '../packages/input-number/index.js'; import Radio from '../packages/radio/index.js'; import RadioGroup from '../packages/radio-group/index.js'; import RadioButton from '../packages/radio-button/index.js'; import Checkbox from '../packages/checkbox/index.js'; import CheckboxButton from '../packages/checkbox-button/index.js'; import CheckboxGroup from '../packages/checkbox-group/index.js'; import Switch from '../packages/switch/index.js'; import Select from '../packages/select/index.js'; import Option from '../packages/option/index.js'; import OptionGroup from '../packages/option-group/index.js'; import Button from '../packages/button/index.js'; import ButtonGroup from '../packages/button-group/index.js'; import Table from '../packages/table/index.js'; import TableColumn from '../packages/table-column/index.js'; import DatePicker from '../packages/date-picker/index.js'; import TimeSelect from '../packages/time-select/index.js'; import TimePicker from '../packages/time-picker/index.js'; import Popover from '../packages/popover/index.js'; import Tooltip from '../packages/tooltip/index.js'; import MessageBox from '../packages/message-box/index.js'; import Breadcrumb from '../packages/breadcrumb/index.js'; import BreadcrumbItem from '../packages/breadcrumb-item/index.js'; import Form from '../packages/form/index.js'; import FormItem from '../packages/form-item/index.js'; import Tabs from '../packages/tabs/index.js'; import TabPane from '../packages/tab-pane/index.js'; import Tag from '../packages/tag/index.js'; import Tree from '../packages/tree/index.js'; import Alert from '../packages/alert/index.js'; import Notification from '../packages/notification/index.js'; import Slider from '../packages/slider/index.js'; import Loading from '../packages/loading/index.js'; import Icon from '../packages/icon/index.js'; import Row from '../packages/row/index.js'; import Col from '../packages/col/index.js'; import Upload from '../packages/upload/index.js'; import Progress from '../packages/progress/index.js'; import Spinner from '../packages/spinner/index.js'; import Message from '../packages/message/index.js'; import Badge from '../packages/badge/index.js'; import Card from '../packages/card/index.js'; import Rate from '../packages/rate/index.js'; import Steps from '../packages/steps/index.js'; import Step from '../packages/step/index.js'; import Carousel from '../packages/carousel/index.js'; import Scrollbar from '../packages/scrollbar/index.js'; import CarouselItem from '../packages/carousel-item/index.js'; import Collapse from '../packages/collapse/index.js'; import CollapseItem from '../packages/collapse-item/index.js'; import Cascader from '../packages/cascader/index.js'; import ColorPicker from '../packages/color-picker/index.js'; import Transfer from '../packages/transfer/index.js'; import Container from '../packages/container/index.js'; import Header from '../packages/header/index.js'; import Aside from '../packages/aside/index.js'; import Main from '../packages/main/index.js'; import Footer from '../packages/footer/index.js'; import Timeline from '../packages/timeline/index.js'; import TimelineItem from '../packages/timeline-item/index.js'; import Link from '../packages/link/index.js'; import Divider from '../packages/divider/index.js'; import Image from '../packages/image/index.js'; import Calendar from '../packages/calendar/index.js'; import Backtop from '../packages/backtop/index.js'; import InfiniteScroll from '../packages/infinite-scroll/index.js'; import PageHeader from '../packages/page-header/index.js'; import CascaderPanel from '../packages/cascader-panel/index.js'; import Avatar from '../packages/avatar/index.js'; import Drawer from '../packages/drawer/index.js'; import Statistic from '../packages/statistic/index.js'; import Popconfirm from '../packages/popconfirm/index.js'; import Skeleton from '../packages/skeleton/index.js'; import SkeletonItem from '../packages/skeleton-item/index.js'; import Empty from '../packages/empty/index.js'; import Descriptions from '../packages/descriptions/index.js'; import DescriptionsItem from '../packages/descriptions-item/index.js'; import Result from '../packages/result/index.js'; import locale from 'element-ui/src/locale'; import CollapseTransition from 'element-ui/src/transitions/collapse-transition'; const components = [ Pagination, Dialog, Autocomplete, Dropdown, DropdownMenu, DropdownItem, Menu, Submenu, MenuItem, MenuItemGroup, Input, InputNumber, Radio, RadioGroup, RadioButton, Checkbox, CheckboxButton, CheckboxGroup, Switch, Select, Option, OptionGroup, Button, ButtonGroup, Table, TableColumn, DatePicker, TimeSelect, TimePicker, Popover, Tooltip, Breadcrumb, BreadcrumbItem, Form, FormItem, Tabs, TabPane, Tag, Tree, Alert, Slider, Icon, Row, Col, Upload, Progress, Spinner, Badge, Card, Rate, Steps, Step, Carousel, Scrollbar, CarouselItem, Collapse, CollapseItem, Cascader, ColorPicker, Transfer, Container, Header, Aside, Main, Footer, Timeline, TimelineItem, Link, Divider, Image, Calendar, Backtop, PageHeader, CascaderPanel, Avatar, Drawer, Statistic, Popconfirm, Skeleton, SkeletonItem, Empty, Descriptions, DescriptionsItem, Result, CollapseTransition ]; // Vue.use后调用该方法。 const install = function(Vue, opts = {}) { // 设置element-ui的国际化处理。 locale.use(opts.locale); locale.i18n(opts.i18n); // 注册85个全局组件。 components.forEach(component => { Vue.component(component.name, component); }); Vue.use(InfiniteScroll);// 注册一个v-InfiniteScroll自定义指令,用于虚拟滚动。fang/f20230627/day0627/node_modules/element-ui/packages/infinite-scroll/index.js。v-loading自定义指令。 Vue.use(Loading.directive);// 注册 // 向Vue的原理上挂载一个$ELEMENT配置项,这个配置项是element-ui的全局配置。 Vue.prototype.$ELEMENT = { size: opts.size || '', zIndex: opts.zIndex || 2000 }; // 还在Vue的原型上挂载很多消息提示的方法,组件中基于this.$xxx直接调用就可以了。 Vue.prototype.$loading = Loading.service; Vue.prototype.$msgbox = MessageBox; Vue.prototype.$alert = MessageBox.alert; Vue.prototype.$confirm = MessageBox.confirm; Vue.prototype.$prompt = MessageBox.prompt; Vue.prototype.$notify = Notification; Vue.prototype.$message = Message; }; /* istanbul ignore if */ if (typeof window !== 'undefined' && window.Vue) { install(window.Vue); } export default { version: '2.15.13', locale: locale.use, i18n: locale.i18n, install, CollapseTransition, Loading, Pagination, Dialog, Autocomplete, Dropdown, DropdownMenu, DropdownItem, Menu, Submenu, MenuItem, MenuItemGroup, Input, InputNumber, Radio, RadioGroup, RadioButton, Checkbox, CheckboxButton, CheckboxGroup, Switch, Select, Option, OptionGroup, Button, ButtonGroup, Table, TableColumn, DatePicker, TimeSelect, TimePicker, Popover, Tooltip, MessageBox, Breadcrumb, BreadcrumbItem, Form, FormItem, Tabs, TabPane, Tag, Tree, Alert, Notification, Slider, Icon, Row, Col, Upload, Progress, Spinner, Message, Badge, Card, Rate, Steps, Step, Carousel, Scrollbar, CarouselItem, Collapse, CollapseItem, Cascader, ColorPicker, Transfer, Container, Header, Aside, Main, Footer, Timeline, TimelineItem, Link, Divider, Image, Calendar, Backtop, InfiniteScroll, PageHeader, CascaderPanel, Avatar, Drawer, Statistic, Popconfirm, Skeleton, SkeletonItem, Empty, Descriptions, DescriptionsItem, Result };
-
关于全局引入:
- 如果项目比较大,比如80个组件用到了40多个,就全局导入。
- 如果项目比较少,80个组件,才使用10多个,就按需导入。
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tZy3y1dg-1687881808375)(./Vue.use(ElementUI)]后执行的事.jpg)
-
-
按需导入:
-
实现组件库的按需导入:
-
安装插件
$ yarn add babel-plugin-component -D babel-plugin-component 是 element 对 babel-plugin-import 的重写
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jwAtmvLW-1687881808378)(./element-ui的按需导入.jpg)]
-
按需导入需要的组件等,并且需要把组件注册为全局组件
import { Button, Tag } from 'element-ui' Vue.component(Button.name, Button) Vue.component(Tag.name, Tag)
-
但是这样做有点麻烦;
-
通过看element-ui具体组件的源码,可以用Vue.use()给注册。
import "element-ui/lib/theme-chalk/index.css";//element-ui的样式表,好像不能按需导入。所以要全部导入。 import { Button, Tag, Loading, Message } from "element-ui"; Vue.use(Button).use(Tag).use(Loading.directive); Vue.prototype.$message = Message;
-
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ta1Q26kS-1687881808379)(./element-ui组件的按需导入.jpg)]
-
element-ui的样式表,好像不能按需导入。所以要全部导入。
-
- 看element-ui的样式,可以看到一些具体的UI组件是在
/node_modules/element-ui/packages
这个目录中。
-
-
-
局部组件中调用全局注册的el-button时。
<template> <div class="demo-box"> <!-- 因为el-button是一个组件,所以@click="buttonHandle"并不是基于addEventListener给元素做事件绑定,而是给el-button组件的事件池中,注入一个click的自定义事件,方法是buttonHandle。 - buttonHandle方法是否可以执行,需要在el-button组件内部,基于this.$emit()处理。 - 分析el-button的源码发现: - 调用这个组件,最后组件渲染出来的是一个button标签。 - 默认给button标签做了click事件绑定,点击按钮执行组件内部的handleClick方法。 - 在handleClick方法中,把刚才注册的click自定义事件,以及绑定的buttonHandle方法,基于$emit()通知执行。 - @mouseenter="buttonHandle"操作 - 这个操作也是给组件的事件池中,注入一个mouseenter的自定义事件,方法是buttonHandle。 - 但可惜的是,通过对el-button源码的分析,我们发现,其内部没有对mouseenter自定义事件做处理,所以即便鼠标进入按钮区域,也不会有任何的效果。 --> <el-button type="primary" @click="buttonHandle" @mouseenter="buttonHandle">element按钮</el-button> </div> </template> <script> export default { methods: { buttonHandle(){ this.$message.success('哇哇叫') } } }; </script>
- 因为el-button是一个组件,所以@click="buttonHandle"并不是基于addEventListener给元素做事件绑定,而是给el-button组件的事件池中,注入一个click的自定义事件,方法是buttonHandle。
- buttonHandle方法是否可以执行,需要在el-button组件内部,基于this.$emit()处理。
- 分析el-button的源码发现:
- 源码在:
-
fang/f20230627/day0627/node_modules/element-ui/packages/button/index.js
import ElButton from './src/button'; /* istanbul ignore next */ ElButton.install = function(Vue) { Vue.component(ElButton.name, ElButton); }; export default ElButton;
-
fang/f20230627/day0627/node_modules/element-ui/packages/button/src/button.vue
-
- 源码在:
- 分析el-button的源码发现:
- buttonHandle方法是否可以执行,需要在el-button组件内部,基于this.$emit()处理。
- 因为el-button是一个组件,所以@click="buttonHandle"并不是基于addEventListener给元素做事件绑定,而是给el-button组件的事件池中,注入一个click的自定义事件,方法是buttonHandle。
<template>
<button
class="el-button"
@click="handleClick"
:disabled="buttonDisabled || loading"
:autofocus="autofocus"
:type="nativeType"
:class="[
type ? 'el-button--' + type : '',
buttonSize ? 'el-button--' + buttonSize : '',
{
'is-disabled': buttonDisabled,
'is-loading': loading,
'is-plain': plain,
'is-round': round,
'is-circle': circle
}
]"
>
<i class="el-icon-loading" v-if="loading"></i>
<i :class="icon" v-if="icon && !loading"></i>
<span v-if="$slots.default"><slot></slot></span>
</button>
</template>
<script>
export default {
name: 'ElButton',
inject: {
elForm: {
default: ''
},
elFormItem: {
default: ''
}
},
props: {
type: {
type: String,
default: 'default'
},
size: String,
icon: {
type: String,
default: ''
},
nativeType: {
type: String,
default: 'button'
},
loading: Boolean,
disabled: Boolean,
plain: Boolean,
autofocus: Boolean,
round: Boolean,
circle: Boolean
},
computed: {
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
buttonSize() {
return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
},
buttonDisabled() {
return this.$options.propsData.hasOwnProperty('disabled') ? this.disabled : (this.elForm || {}).disabled;
}
},
methods: {
handleClick(evt) {
this.$emit('click', evt);
}
}
};
</script>
- 调用这个组件,最后组件渲染出来的是一个button标签。
- 默认给button标签做了click事件绑定,点击按钮执行组件内部的handleClick方法。
- 在handleClick方法中,把刚才注册的click自定义事件,以及绑定的buttonHandle方法,基于$emit()通知执行。
- @mouseenter="buttonHandle"操作
- 这个操作也是给组件的事件池中,注入一个mouseenter的自定义事件,方法是buttonHandle。
- 但可惜的是,通过对el-button源码的分析,我们发现,其内部没有对mouseenter自定义事件做处理,所以即便鼠标进入按钮区域,也不会有任何的效果。
- 但可以通过`@vue原生支持事件.native`来让组件的根视图节点中强制注入一个对应的原生事件。
```vue
<template>
<div class="demo-box">
<el-button type="primary" @click="buttonHandle" @mouseenter.native="buttonHandle">element按钮</el-button>
<el-tag>标签</el-tag>
</div>
</template>
<script>
export default {
methods: {
buttonHandle(){
this.$message.success('哇哇叫')
}
}
};
</script>
<style>
</style>
```
- 可以看到,移动到el-button后,可以触发绑定的事件了。即便el-button内部并没有触发mouseenter自定义事件。这个是因为,buttonHandle实际上是绑定在el-button根节点上的mouseenter事件中的。
修饰符.native的作用
- 面试题:修饰符 .native 的作用
- 在我之前的项目中,遇到过这样的需求:当我调用其它组件(比如UI组件库中的组件),基于v-on(@符)绑定事件和方法,此操作并不是给元素做事件绑定,而是往组件的事件池中注入自定义事件和方法,此时就需要在组件内部,对这个自定义事件,基于$emit()做处理!
- 如果在组件中没有相关的处理,则我们注入的自定义事件是没有用的;而UI组件库中的源码我们是无法更改的,我需要的一些事件需求,组件库并不支持。
- 比如:el-tag等组件中并不支持mouseenter/mouseover。
- 此时我们就需要使用.native修饰符,设置此修饰符后:
- 会强制给组件的根元素,完成对应的事件绑定。当事件触发,会把我们绑定的方法执行。
- 只不过这些事件必须是浏览器标准事件。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bDiV7G07-1687882579600)(./组件内部事件通过事件传播传播到组件根节点.jpg)]
- 组件内部的元素的事件可以通过事件传播传播到组件的根节点上。所以可以在组件根节点对事件进行事件委托,监听到具体的元素所触发的事件。
- 会强制给组件的根元素,完成对应的事件绑定。当事件触发,会把我们绑定的方法执行。
- 而之前的项目中,尤其是调用UI组件库中的组件,.native修饰符用的还是很多的!
父组件传递给子组件的属性
- 代码示例:
- 父组件
- fang/f20230627/day0627/src/views/Demo2.vue
- 子组件
- fang/f20230627/day0627/src/views/Child.vue
- 父组件
- 组件说明
-
针对父组件传递进来的属性:
-
class/style: 不能被props注册接收,也不会在$attrs中,只会直接设置在子组件的根元素上。
<template> <div class="child-box">子组件</div> </template> <script> export default { props: ["class", "style"], created() { console.log(`this-->`, this); }, }; </script>
-
可以基于props注册接收(细节如下面所示。)
- 基于props注册接收的属性
-
都会直接挂载到实例上,而且进行了响应式劫持。
-
被注册的属性,不会出现在根元素上,也不会出现在$attrs中。
-
注册接收的时候,还可以对属性进行规则校验。
<template> <div class="child-box">子组件</div> </template> <script> export default { props: { x: { type: [Number, String] }, title: { type: String, required: true, }, bool: { type: Boolean, default: false, }, info: Object, }, created() { console.log(`this-->`, this); }, }; </script>
- 如果传递的属性值,不符合校验规则,控制台会抛出警告错误,但是不影响属性的获取和视图的渲染。
-
- 基于props注册接收的属性
-
没有被props注册接收的属性,可以在$attrs中获取,也可以在子组件的根元素上看到。
-
-
对于传递的属性,如果不想让其出现在根元素上(排除class/style)?
-
基于props注册接收即可。
-
也可以设置
inheritAttrs: false
<template> <div class="child-box">子组件</div> </template> <script> export default { inheritAttrs: false, created() { console.log(`this-->`, this); }, }; </script>
-
-
针对于父组件传递的自定义事件
-
基于$listeners获取子组件事件池中,所有的自定义事件和绑定的方法。
<template> <div class="child-box">子组件</div> </template> <script> export default { created() { console.log(`this.$listeners-->`, this.$listeners); }, }; </script>
-
基于$emit()通知自定义事件执行,传递实参信息。
-
this.$emit(‘自定义事件名’,要传的参数) => 返回值是子组件的实例
-
如:this.$emit(‘close’,100)
<script> export default { created() { console.log(`this-->`, this); let res = this.$emit("close", 10); console.log(`子组件中,接收父组件handle执行的返回结果。res-->`, res); //组件实例。 }, }; </script>
-
-
this.$listeners.‘自定义事件名’ => 返回值是父组件对应方法执行的返回结果
-
如:this.$listeners.close(10)
<script> export default { created() { console.log(`this-->`, this); let res = this.$listeners.close(10) console.log(`子组件中,接收父组件handle执行的返回结果。res-->`, res);//父组件方法的返回值-'return-fang'。 }, }; </script>
-
-
-
各应用场景:
- 如果需要用到父组件中对应方法执行的返回结果,一般用
this.$listeners.['自定义事件名'](要传的参数)
- 其它场景,一般都统一使用
this.$emit('自定义事件名',要传的参数)
。
- 如果需要用到父组件中对应方法执行的返回结果,一般用
-
-
vue2中原型链
-
在Vue2中,我们创建的 单文件组件,默认都是 ”类组件“「每个组件都相当于一个类」,调用此组件,相当于创建这个组件类的一个实例
- 实例.proto —> VueComponent.prototype —> Vue.prtototype
-
总结:每一次调用组件,都相当于创建 Vue 类的一个实例,都可以基于原型链,找到Vue.prototype「组件内部的this就是实例,可以访问到Vue.prototype上的信息」
-
Vue2中组件的分类:
- 主流分类模式:
- 全局组件: 基于 Vue.component 注册,一但注册为全局组件,可以在其它任意组件中,直接调用!「真实项目中,通用组件(比如UI组件库中的组件),一般会被注册为全局组件」
- 全局组件不能太多,否则容易引发命名冲突。
- 太少的话,常引用的地方都要写一遍导入与注册与流程的流程,导致代码变多。如果为全局组件,则只需要调用即可。
- 局部/私有组件:在其它组件中,调用它的时候,需要 1导入、2注册、3调用。
- 全局组件: 基于 Vue.component 注册,一但注册为全局组件,可以在其它任意组件中,直接调用!「真实项目中,通用组件(比如UI组件库中的组件),一般会被注册为全局组件」
- 还有一种分类模式:
- 类组件:每创建一个组件,都相当于创建一个类(内部是基于Vue.extend构建的),调用组件相当于创建类的一个实例(this),而且实例可以基于原型链找到 Vue.prototype!
- 函数组件:创建组件就相当于创建一个函数,调用组件仅仅是把函数执行,这样没有实例(this)这些东西了!! ===> functional
- 主流分类模式:
v-for和v-if的优先级问题
-
面试题:v-for和v-if的优先级问题
<template> <div class="demo-box"> <div v-for="(item, index) in arr" :key="item" v-if="index % 2 === 0"> {{ item }} </div> </div> </template> <script> export default { data() { return { arr: [10, 20, 30, 40], }; }, }; </script>
-
在vue2中,v-for的优先级要高于v-if,这样在循环的时候,v-for会先创建元素,再经过v-if的条件判断,如果结果是false,再把创建的元素销毁。DOM元素的一创一销之间,会造成性能的浪费!
-
在vue3中,v-if的优先级要高于v-for,这样在v-if中,是无法使用v-for循环的内容的。
- 如item及index的,会报错。
-
总之:不论是vue2还是vue3中,都不要把v-for和v-if作用在相同的元素上。如果有类似的需求,可以基于
<template>标签
,把v-for和v-if分开处理!<template> <div class="demo-box"> <!-- `<template>标签`上使用v-for,是不能设置key的。 --> <template v-for="(item, index) in arr"> <div :key="item" v-if="index % 2 === 0"> {{ item }} </div> </template> </div> </template> <script> export default { data() { return { arr: [10, 20, 30, 40], }; }, }; </script>
<template>标签
上使用v-for,是不能设置key的。- key可以不设置,也可以设置。在一些语法检测工具中,会标红。不过不设置也没什么关系。如果要设置key,key要在
<template>标签
内部的根节点上加。
-
v-html的安全问题
-
面试题:v-html的安全问题。
- 有时从服务器拿到的内容是后端生成的,有结构和样式。如:
<a href="#">方一</a><button>呵</button>一
。-
直接渲染,样式不太好
<template> <div class="demo-box" > {{ body }} </div> </template> <script> export default { data() { return { body: `<a href="#">方一</a><button>呵</button>一`, }; }, }; </script>
<template> <div class="demo-box" v-text="body"> </div> </template> <script> export default { data() { return { body: `<a href="#">方一</a><button>呵</button>一`, }; }, }; </script>
-
-
vue-dompurify-html。
-
vue-html-sanitizer。
-
回答:
-
在我之前的项目开发中,内容的渲染我基本上都是基于小胡子语法来处理的。
- 但是此语法也有一个不足:如果渲染的内容包含html标签字符串,最后渲染的结果全部会作为普通字符串,无法把其识别为真正的html标签(类似于innerText)。
-
此时我们就需要基于v-html指令来渲染,这个指令可以把渲染内容中的html标签字符串,变为真正的html标签(类似于innerHTML)。
<template> <div class="demo-box" v-html="body"> </div> </template> <script> export default { data() { return { body: `<a href="#">方一</a><button>呵</button>一`, }; }, }; </script>
-
- 但是此语法也有一个不足:如果渲染的内容包含html标签字符串,最后渲染的结果全部会作为普通字符串,无法把其识别为真正的html标签(类似于innerText)。
-
但是v-html的使用上需要慎重,官方有这样的提示:在网站上动态渲染任意 HTML 是非常危险的,因为容易导致 XSS 攻击(XSS:跨站脚本攻击,有网站上注入恶意的客户端代码)。
<template> <div class="demo-box" v-html="body"> </div> </template> <script> export default { data() { return { body: ` <a href="#">方一</a> <button onClick="alert('我是button中的病毒,将清空页面!');document.body.innerHTML=''">呵</button> 一 <script>alert('我是script中的病毒');console.log('5555')<\/script>`, }; }, }; </script>
- 只在可信内容上使用 v-html,永远不要用在用户提交的内容上。即v-html中的要渲染的内容,不能是用户的代码。
- 用户基于富文本编辑器,编辑好内容后,提交给服务器的内容:包含html、样式、内容的字符串。
- 如果需要渲染的信息,是用户提交的内容,则要慎重再慎重!
- 只在可信内容上使用 v-html,永远不要用在用户提交的内容上。即v-html中的要渲染的内容,不能是用户的代码。
-
如果必须要渲染这样的内容,则我们在渲染之前,需要对内容中,容易导致XSS攻击的标签,进行过滤处理(比如过滤掉
<script>
、<iframe>
、一些js脚本等等)。- 如果自己处理,则使用正则匹配解析…处理起来具备一定的难度,而此时我们会使用一些插件来解决。
- 推荐:vue-dompurify-html。
- 推荐:vue-html-sanitizer。
-
还可以在用户提交信息的时候,对提交的内容直接做安全校验,但凡包含一些有安全隐患的内容,就不允许提交了!其实后端一般也会做校验,防止用户直接绕开浏览器根据接口把有问题内容给提交。
-
- 有时从服务器拿到的内容是后端生成的,有结构和样式。如:
如何设置样式
- 面试题:如何设置样式「面试题:Class 与 Style 如何动态绑定?」
-
class动态绑定
-
对象方式:
<template> <!-- 基于对象的方式来管理 --> <div :class="{ 'demo-box': true, active: bool, }" @click="bool = !bool" ></div> </template> <script> export default { data() { return { bool: true, }; }, }; </script> <style lang="less" scoped> .demo-box { width: 100px; height: 100px; background: pink; &.active { border: 5px solid red; } } </style>
-
数组方式:
<template> <!-- 基于数组的方式来管理 --> <div :class="['demo-box', bool ? 'active' : '']" @click="bool = !bool"></div> </template> <script> export default { data() { return { bool: true, act: "active", }; }, }; </script> <style lang="less" scoped> .demo-box { width: 100px; height: 100px; background: pink; &.active { border: 5px solid red; } } </style>
-
数组样式的场景:
<template> <div :class="['demo-box', act]" @click="bool = !bool"></div> </template> <script> export default { data() { return { act: "active",//通过控制一个变量的值来控制一个样式类名,适用于数组。 }; }, }; </script>
-
-
-
style动态绑定:
-
对象方式:
<template> <div @click="bool = !bool" :style="{ width: '100px', height: '100px', background: 'pink', border: bool ? '5px solid red' : '', }" ></div> </template> <script> export default { data() { return { bool: true, }; }, }; </script>
-
数组方式:
<template> <div :style="[aa, bb]">哈哈</div> </template> <script> export default { data() { return { aa: { color: "red" }, bb: { fontSize: "20px", }, }; }, }; </script>
-
-
样式私有化方案
- 面试题:样式私有化方案「面试题:在 Vue 组件
<style lang='less' scoped>
中编写的样式没有生效,都可能存在哪些原因?以及该如何解决?」 - 样式私有化方案:
-
无样式私有化:给
<style>标签
设置scoped属性
;<template> <div class="demo-box"> <h2 class="title">哈哈哈哈哈哈哈哈</h2> <div class="con"> <el-button type="primary" icon="el-icon-message">el-button按钮</el-button> </div> </div> </template> <script> export default { data() { return {}; }, }; </script> <style lang="less"> .demo-box{ } </style>
-
有了样式私有化:
<template> <div class="demo-box"> <h2 class="title">哈哈哈哈哈哈哈哈</h2> <div class="con"> <el-button type="primary" icon="el-icon-message">el-button按钮</el-button> </div> </div> </template> <script> export default { data() { return {}; }, }; </script> <style lang="less" scoped> .demo-box{ } </style>
-
-
scoped样式私有化实现的原理
-
在没有设置scoped属性之前:
- 渲染的标签没有什么特殊处理。
- 编写的样式都是全局样式。
-
这样在组件合并渲染的时候,如果两个组件设置了相同的样式类名,就很可能发生样式冲突!
-
在不使用任何技术方案的情况下,我们完全可以依托于命名规范,来解决样式冲突问题:
-
让每个组件最外层样式类名是唯一的。
-
比如以路径+组件名+后缀,作为样式类的命名规范。
.home-demo-box{...}
-
-
其内部元素的样式,都设置在这个唯一的样式类名下。
.home-demo-box{ .link{ ... } }
- 但是此类方案,需要所有开发者,严格遵照此规范进行处理!
-
-
-
但vue中提供了专门的样式私有化处理方案,即
style-scoped
。一旦给<style>标签
设置scoped属性
:-
每创建一个单文件组件,该组件都有一个唯一的id值,比如:
data-v-fecc192e
。 -
当设置
scoped属性
之后,组件视图中出现的所有元素(包括调用的子组件的根元素-但不包括子组件内部元素),都会设置一个属性:组件id。<div data-v-fecc192e class="demo-box">
实际的值:
<div data-v-fecc192e="" class="demo-box">
-
并且我们在
<style>标签
中编写的样式,都被加上一个属性选择器。.demo-box[data-v-fecc192e]{...}
- 这样即便编写的样式还是全局样式,但是因为设置了属性选择器,只有拥有相同属性的元素,此样式才会对其生效!
- 这就是Vue样式私有化的原理!
-
-
代码示例:
<template> <div class="demo-box"> <h2 class="title">哈哈哈哈哈哈哈哈</h2> <div class="con"> <el-button type="primary" icon="el-icon-message">el-button按钮</el-button> </div> </div> </template> <script> export default { data() { return {}; }, }; </script> <style lang="less" scoped> .demo-box { box-sizing: border-box; background: pink; .title{ font-size: 20px; } .el-button{ border-radius: 0; } } </style>
-
-
但是此类方案也存在弊端:调用子组件的内部元素(除根元素),是没有设置组件id的这个属性的,如果在style中,给这些元素写样式,编写的样式是不生效的!
-
原因:编写的样式有属性选择器,但是结构上没有相关的属性。
-
解决方案:把编写样式中的属性选择器去掉即可,此时需要用到
:deep()
样式穿透!:deep(.el-icon-message) {...}
/deep/ .el-icon-message {...}
- 所谓样式穿透,就是把我们写的样式中的属性选择器干掉!
- 我们一般都把这样的样式,写在某个私有化样式的底下。
- 所谓样式穿透,就是把我们写的样式中的属性选择器干掉!
-
样式私有化后的问题
- 面试题:在 Vue 组件
<style lang='less' scoped>
中编写的样式没有生效,都可能存在哪些原因?以及该如何解决?- 从我之前的开发经验来讲,可能有两种情况:
- 编写的样式权重不够,此时为了图简单,可以很暴力地使用
!important
,但是建议还是用其它方式调节权重即可! - 最常见的情况,就是给子组件内部元素编写样式,因为元素不会设置组件唯一id这个属性,但是我们写的样式会设置上这样的属性选择器,所以样式不生效;此时我们基于
:deep
穿透一下即可! - 当然有时候也是因为项目太急,加班时间有点多,眼花写错了!
- 编写的样式权重不够,此时为了图简单,可以很暴力地使用
- 从我之前的开发经验来讲,可能有两种情况:
css模块私有化方案
- Vue中还提供了一种样式私有化方案:CSS Modules;
- 具体步骤:
- 创建一个
文件名.module.css
的文件。- 这个也可以不用
.module.css
为后缀,需要到在/vue.config.js
中进行配置。 - 好像也可以不用css,而是使用less文件。
- 这个也可以不用
- 在vue文件中以模块方式导入该css文件。
- 将该css文件模块存储为变量。
- 在
<template>标签
中,使用:class="css文件模块名['类名']"
的方式来设置对应的DOM元素。
- 创建一个
- 代码:
-
fang/f20230627/day0627/src/views/Demo7.vue
<template> <div :class="sty['demo-box']"> <h2 :class="sty.title">哈哈</h2> <el-button type="danger" :class="sty.button">呵呵</el-button> </div> </template> <script> import sty from "./demo.module.css"; console.log(`sty-->`, sty); export default { data() { return { sty }; }, }; </script>
-
fang/f20230627/day0627/src/views/demo.module.css
.demo-box{ background: skyblue; } .title{ font-size: 20px; } .button{ border-radius: 0; }
-