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
- vuejs-devtools 的配置说明
- vuejs-devtools插件
Vue.use()
Vue.use()
是Vue2中使用/注册插件的方式。- Vue-use
- 可以传递对象或者函数。
-
传递对象:要求对象必须具备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><buttonclass="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>
- 如果传递的属性值,不符合校验规则,控制台会抛出警告错误,但是不影响属性的获取和视图的渲染。
- vue2-props校验官方文档
- 如果传递的属性值,不符合校验规则,控制台会抛出警告错误,但是不影响属性的获取和视图的渲染。
-
- 基于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; }
-
进阶参考
- vue-全局配置
- 取消生产环境下的Vue提示信息。
- vuejs-devtools 的配置说明
- vuejs-devtools插件
- Vue-use
- 富文本编辑器