20230627----重返学习-全局处理vue-Vue.use()-element-ui说明-vue2中原型链-如何设置样式

45 篇文章 0 订阅
37 篇文章 0 订阅

day-100-one hundred-20230627-全局处理vue-Vue.use()-element-ui说明-vue2中原型链-如何设置样式

全局处理vue

  1. 创建/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;
      
  2. /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

  1. vuejs-devtools 的配置说明
  2. vuejs-devtools插件

Vue.use()

  • Vue.use() 是Vue2中使用/注册插件的方式。
    1. 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说明

  1. fang/f20230627/day0627/node_modules/element-ui/src/index.js中可以看到element-ui的源码入口。

  2. 全局导入:在全局配置中使用通过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
      };
      
    • 关于全局引入:

      1. 如果项目比较大,比如80个组件用到了40多个,就全局导入。
      2. 如果项目比较少,80个组件,才使用10多个,就按需导入。
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tZy3y1dg-1687881808375)(./Vue.use(ElementUI)]后执行的事.jpg)

  3. 按需导入:

    • 实现组件库的按需导入:

      1. 安装插件

        $ yarn add babel-plugin-component -D
        babel-plugin-component 是 element 对 babel-plugin-import 的重写
        
        • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jwAtmvLW-1687881808378)(./element-ui的按需导入.jpg)]
      2. 按需导入需要的组件等,并且需要把组件注册为全局组件

        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这个目录中。
  4. 局部组件中调用全局注册的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的源码发现:
          1. 源码在:
            • 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

<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)]
          1. 组件内部的元素的事件可以通过事件传播传播到组件的根节点上。所以可以在组件根节点对事件进行事件委托,监听到具体的元素所触发的事件。
    • 而之前的项目中,尤其是调用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注册接收的属性,可以在$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>
            
      • 各应用场景:

        1. 如果需要用到父组件中对应方法执行的返回结果,一般用this.$listeners.['自定义事件名'](要传的参数)
        2. 其它场景,一般都统一使用this.$emit('自定义事件名',要传的参数)

vue2中原型链

  • 在Vue2中,我们创建的 单文件组件,默认都是 ”类组件“「每个组件都相当于一个类」,调用此组件,相当于创建这个组件类的一个实例

    • 实例.proto —> VueComponent.prototype —> Vue.prtototype
  • 总结:每一次调用组件,都相当于创建 Vue 类的一个实例,都可以基于原型链,找到Vue.prototype「组件内部的this就是实例,可以访问到Vue.prototype上的信息」

  • Vue2中组件的分类:

    • 主流分类模式:
      • 全局组件: 基于 Vue.component 注册,一但注册为全局组件,可以在其它任意组件中,直接调用!「真实项目中,通用组件(比如UI组件库中的组件),一般会被注册为全局组件」
        1. 全局组件不能太多,否则容易引发命名冲突。
        2. 太少的话,常引用的地方都要写一遍导入与注册与流程的流程,导致代码变多。如果为全局组件,则只需要调用即可。
      • 局部/私有组件:在其它组件中,调用它的时候,需要 1导入、2注册、3调用。
    • 还有一种分类模式:
      • 类组件:每创建一个组件,都相当于创建一个类(内部是基于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>
      
      1. <template>标签上使用v-for,是不能设置key的。
      2. key可以不设置,也可以设置。在一些语法检测工具中,会标红。不过不设置也没什么关系。如果要设置key,key要在<template>标签内部的根节点上加。

v-html的安全问题

  • 面试题:v-html的安全问题。

    1. 有时从服务器拿到的内容是后端生成的,有结构和样式。如:<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>
            
      • 但是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中的要渲染的内容,不能是用户的代码。
          1. 用户基于富文本编辑器,编辑好内容后,提交给服务器的内容:包含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>
      
  1. scoped样式私有化实现的原理

    • 在没有设置scoped属性之前:

      • 渲染的标签没有什么特殊处理。
      • 编写的样式都是全局样式。
    • 这样在组件合并渲染的时候,如果两个组件设置了相同的样式类名,就很可能发生样式冲突!

      • 在不使用任何技术方案的情况下,我们完全可以依托于命名规范,来解决样式冲突问题:

        1. 让每个组件最外层样式类名是唯一的。

          • 比如以路径+组件名+后缀,作为样式类的命名规范。

            .home-demo-box{...}
            
        2. 其内部元素的样式,都设置在这个唯一的样式类名下。

          .home-demo-box{
            .link{
              ...
            }
          }
          
        • 但是此类方案,需要所有开发者,严格遵照此规范进行处理!
    • 但vue中提供了专门的样式私有化处理方案,即style-scoped。一旦给<style>标签设置scoped属性

      1. 每创建一个单文件组件,该组件都有一个唯一的id值,比如:data-v-fecc192e

      2. 当设置scoped属性之后,组件视图中出现的所有元素(包括调用的子组件的根元素-但不包括子组件内部元素),都会设置一个属性:组件id。

        <div data-v-fecc192e class="demo-box">
        

        实际的值:

        <div data-v-fecc192e="" class="demo-box">
        
      3. 并且我们在<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>
      
  2. 但是此类方案也存在弊端:调用子组件的内部元素(除根元素),是没有设置组件id的这个属性的,如果在style中,给这些元素写样式,编写的样式是不生效的!

    • 原因:编写的样式有属性选择器,但是结构上没有相关的属性。

    • 解决方案:把编写样式中的属性选择器去掉即可,此时需要用到:deep()样式穿透!

      :deep(.el-icon-message) {...}
      
      /deep/ .el-icon-message {...}
      
      • 所谓样式穿透,就是把我们写的样式中的属性选择器干掉!
        • 我们一般都把这样的样式,写在某个私有化样式的底下。
样式私有化后的问题
  • 面试题:在 Vue 组件 <style lang='less' scoped> 中编写的样式没有生效,都可能存在哪些原因?以及该如何解决?
    • 从我之前的开发经验来讲,可能有两种情况:
      • 编写的样式权重不够,此时为了图简单,可以很暴力地使用!important,但是建议还是用其它方式调节权重即可!
      • 最常见的情况,就是给子组件内部元素编写样式,因为元素不会设置组件唯一id这个属性,但是我们写的样式会设置上这样的属性选择器,所以样式不生效;此时我们基于:deep穿透一下即可!
      • 当然有时候也是因为项目太急,加班时间有点多,眼花写错了!
css模块私有化方案
  • Vue中还提供了一种样式私有化方案:CSS Modules;
  • 具体步骤:
    1. 创建一个文件名.module.css的文件。
      1. 这个也可以不用.module.css为后缀,需要到在/vue.config.js中进行配置。
      2. 好像也可以不用css,而是使用less文件。
    2. 在vue文件中以模块方式导入该css文件。
    3. 将该css文件模块存储为变量。
    4. <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;
      }
      

进阶参考

  1. vue-全局配置
  2. 取消生产环境下的Vue提示信息
  3. vuejs-devtools 的配置说明
  4. vuejs-devtools插件
  5. Vue-use
  6. 富文本编辑器
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值