zoukankan      html  css  js  c++  java
  • VUE使用技巧(vue2.x)

    vue使用技巧

    data 数据冻结

    vuedata的数据默认会进行双向数据绑定,若将大量和渲染无关的数据直接放置在data中,将会浪费双向数据绑定时所消耗的性能,将这些和渲染无关的数据进行抽离并配合Object.freeze进行处理。

    tablecolumns数据可单独提取到一个外部js文件作为配置文件,也可以在当前.vue文件中定义一个常量定义columns数据,因为无论如何都是固定且不会修改的数据,应该使用Object.freeze进行包裹,既可以提高性能还可以将固定的数据抽离,一些下拉框前端固定的数据也建议此操作。

    Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。

    • freeze() 返回和传入的参数相同的对象
    • 作为参数传递的对象与返回的对象都被冻结
    • 所以不必保存返回的对象(因为两个对象全等)
    const obj = { a1b2 }
    const obj2 = Object.freeze(obj)
    obj === obj2 // => true

    需要注意的是 Object.freeze() 冻结的是值,这时仍然可以将变量的引用替换掉,还有确保数据不会变才可以使用这个语法,如果要对数据进行修改和交互,就不适合使用冻结了。

    export default {
     data() {
      return {
       // 冻结数据,避免vue进行响应式数据转换
       columns: Objet.freeze([{ title"姓名"key"name" }, { title"性别"key"gender" }])
      }
     }
    }

    debounce 使用

    例如远程搜索时需要通过接口动态的获取数据,若是每次用户输入都接口请求,是浪费带宽和性能的。

    当一个按钮多次点击时会导致多次触发事件,可以结合场景是否立即执行immediate

    <template>
     <select :remote-method="remoteMethod">
      <option v-for="item in temoteList" :value="item.value" :key="item.id">{{item.label}} </option>
     </select>
    </template>
    import {debounce} from 'lodash-es' methods:{ remoteMethod:debounce(function (query) { // to do ... // this 的指向没有问题 },
    200), }

    路由参数解耦

    一般在组件内使用路由参数,都会写如下代码:

    export default {
     computed: {
      id() {
       return this.$route.params.id
      }
     }
    }

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

    如果一个组件既可以是路由组件又可以是普通组件,那该如何实现呢? 有人会说我做个判断就可以了啊

    export default {
        props: {
            userId: {
                typeString,
                default''
            }
        }
        computed: {
            id() {
                return this.userId || this.$route.params.id
            }
        }
    }

    但是这种方式不够优雅,那还有更优雅的处理方式吗? 正确的做法是通过路由props进行参数解耦

    const router = new VueRouter({
     routes: [
      {
       path"/user/:id",
       component: User,
       propstrue
      }
     ]
    })

    将路由的props属性设置为true,就可以在组件内通过props接收$route.prams参数,在模板中就可以直接使用id了,不用关系这个id是从外部传递过来的还是通过路由参数传递过来的。

    export default {
     props: {
      id: {
       typeString,
       default""
      }
     }
    }

    另外你还可以通过函数模式来传递props

    const router = new VueRouter({
     routes: [
      {
       path"/user/:id",
       component: User,
       propsroute => ({
        id: route.query.id
       })
      }
     ]
    })

    函数式组件

    由于函数式组件只是函数,因此渲染性能开销也低很多,适合依赖外部数据变化而变化的组件。

    • 无状态,没有响应式data
    • 无实例,没有this上下文
    • 无生命周期方法,没有钩子函数
    • 不会出现在 Vue devtools 的组件树中
    <template functional>
     <button class="btn btn-primary" v-bind="data.attrs" v-on="listeners"><slot /></button>
    </template>

    watch 高阶使用

    立刻执行

    watch是监听属性改变才触发,有时候我们系统在组件创建时也触发。

    一般的想法是在created生命周期函数里调用一次,以后watch监听变化时触发。

    export default {
     data() {
      return {
       name"Yori"
      }
     },
     watch: {
      name: {
       hander"sayName",
       immediatetrue // 立刻执行
      }
     },
     created() {
      // this.sayName()
     },
     methods: {
      sayName() {
       console.log(this.name)
      }
     }
    }

    深度监听

    watch监听对象时,属性内对象的属性改变无法监听到,这时我们就需要设置深度监听模式。

    export default {
     data() {
      return {
       user: {
        name: {
         firstName"张",
         lastName"三"
        }
       }
      }
     },
     watch: {
      name: {
       handlerfunction({},
       deeptrue
      }
     }
    }

    触发执行多个方法

    watch运行以数组的形式传递的多个方法,方法会一一调用 可以是字符串函数名、匿名函数和对象形式。

    export default {
     watch: {
      name: [
       "sayName",
       function(oldVal, newVal{},
       {
        handlerfunction({},
        immediatetrue
       }
      ]
     }
    }

    监听多个属性

    watch本身不支持多个属性同时监听,我们可以通过计算属性的形式把多个属性合并成一个对象,供watch监听使用

    export default {
     data() {
      return {
       msg1"apple",
       msg2"banana"
      }
     },
     computed: {
      msgObj() {
       const { msg1, msg2 } = this
       return {
        msg1,
        msg2
       }
      }
     },
     watch: {
      msgObj: {
       handler(newVal, oldVal) {
        if (newVal.msg1 != oldVal.msg1) {
         console.log("msg1 is change")
        }
        if (newVal.msg2 != oldVal.msg2) {
         console.log("msg2 is change")
        }
       },
       deeptrue
      }
     }
    }

    组件生命周期钩子

    父子组件的加载顺序是先加载子组件,再加载父组件,但是父组件是无法知道子组件什么时候加载完成的,我们一般的做法是使用$emit进行事件触发,通知父组件。

    • 子组件,在生命周期方法里触发事件
    export default {
     name"ComponentSub",
     mounted() {
      this.$emit("mounted")
     }
    }
    • 父组件,监听事件,接收子组件的事件
    <template>
     <component-sub @mounted="onSubMounted" />
    </template>
    <script>
     export default {
      methods: {
       onSubMounted() {
        //
       }
      }
     }
    </script>

    但是这种方式不够灵活,如果子组件是调用的三方框架组件,有没有更优雅的方式监听组件的生命周期呢。 通过@hook可以监听组件的生命周期方法,组件无需修改,如created,beforeDestroy

    <template>
     <component-a @hook:created="onReady" />
    </template>
    <script>
     export default {
      methods: {
       onReady() {
        // ...
       }
      }
     }
    </script>

    可编程的事件监听器

    我们在写组件时时经常能看到如下代码:

    export default {
     data() {
      return {
       timernull
      }
     },
     methods: {
      start() {
       this.timer = setInterval(() => {
        console.log(Date.now())
       }, 1000)
      }
     },
     mounted() {
      this.start()
     },
     beforeDestroy() {
      if (this.timer) {
       clearInterval(this.timer)
       this.timer = null
      }
     }
    }

    我们使用timer来缓存定时器的引用以便后续使用,只为了在生命周期方法beforeDestroy中释放资源使用。

    • 多了一个timer实例属性
    • 设置和销毁代码分散在各处

    通过可编程的事件监听器,把创建实例和销毁实例放在一起,使代码更加简洁

    export default {
     methods: {
      start() {
       const timer = setInterval(() => {
        console.log(Date.now())
       }, 1000)

       this.$once("hook:beforeDestroy", () => {
        clearInterval(timer)
       })
      }
     }
    }

    手动刷新组件

    是否遇到这种场景,一个组件包含了多个表单和信息展示,出于业务需求,我们需要清空组件里表单的信息还原到刚进来的初始状态 在表单组件少的情况下,我们通过设置初始值的方法来还原组件状态,但是太繁琐,有没有更简单的方式呢

    • 更新key强制渲染组件

    通过更新组件的key来强制重新渲染组件

    <template>
     <component-a :key="key" />
    </template>
    <script>
     export default {
      data() {
       return {
        key0
       }
      },
      methods: {
       forceRerender() {
        this.key++
       }
      }
     }
    </script>

    通过更新组件的key属性,强制使组件不复用缓存,生成一个新的组件,达到还原组件到初始状态的目的

    • v-if强制渲染

    v-if指令,该指令仅在组件为 true 时才渲染。 如果为 false,则该组件在 DOM 中不存在。

    <template>
     <component-a v-if="renderComponent" />
    </template>
    <script>
     export default {
      forceRerender() {
       // 从DOM删除组件
       this.renderComponent = false
       this.$nextTick().then(() => {
        // 在DOM中添加该组件
        this.renderComponent = true
       })
      }
     }
    </script>
    • $forceUpdate()

    迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。

    vue属性本身是响应式的,但是有时候属性改变,页面会出现不刷新情况 如果我们不用$set()方法来更新数据,比如我们改变数组的长度arr.length = 1,页面也要求同步更新,就需要手动调用$forceUpdate强制渲染

    <template>
     <ul>
      <li v-for="(item) in list" :key="item">{{ item }}</li>
     </ul>
    </template>
    <script>
     export default {
      data() {
       return {
        list: ["apple""orange""banana"]
       }
      },
      methods: {
       resetHandler() {
        this.list.length = 1
        this.$forceUpdate()
       }
      }
     }
    </script>

    使用Vue.extend()写命令式 api

    对应一些组件,使用命令式编程的方式更优雅调用组件,如$message,$confirm等弹窗自己使用的比较多,而不是在组件中切换组件的方式来实现效果,下面是简易版的命令式组件的实现。

    • components/Comfirm/index.js
    import Vue from 'vue'
    import Confirm from './Confirm.vue'

    const ConfirmModal = {
      instance: null,
      show (title = '温馨提示', message = '') {
        if (!this.instance) {
          const ConfirmConstructor = Vue.extend(Confirm)
          this.instance = new ConfirmConstructor({
            propsData: {
              title,
              message
            }
          })
        } else {
          this.instance.title = title
          this.message = message
        }
        // $mount("#my") 挂载到指定选择器
        // $mount() 不传入选择器,生成文档之外的元素,类似‘document.creatElement()’在内存中生成DOM,
        this.instance.$mount()
        // this.instance.$el 获取对应的DOM元素
        // 把内存中的DOM挂载到body里
        document.body.appendChild(this.instance.$el)
        Vue.nextTick().then(() => {
          this.instance.show()
        })
      },
      hide () {
        if (this.instance) {
          // 等待动画执行完
          this.instance.hide().then(() => {
            // 删除DOM元素
            document.body.removeChild(this.instance.$el)
          })
        }
      }
    }

    export default {
      install () {
        Vue.prototype.$confirm = ConfirmModal
      }
    }

    export {
      Confirm
    }

    • src/components/Confirm/Confirm.vue
    <template>
     <transition>
      <div class="q-confirm" v-if="visible">
       <div class="q-confirm-overlay" @click="close"></div>
       <div class="q-confirm-content"><h1>Confirm</h1></div>
      </div>
     </transition>
    </template>
    <script>
     export default {
      data() {
       return {
        visiblefalse
       }
      },
      created() {
       console.log("Confirm created")
      },
      mounted() {
       console.log("Confirm mounted")
      },
      methods: {
       show() {
        this.visible = true
       },
       close() {
        this.visible = false
       },
       hide() {
        return new Promise((resolve, reject) => {
         this.visible = false
         setTimeout(() => resolve(), 200)
        })
       }
      }
     }
    </script>
    <style scoped>
     .q-confirm-content {
      width300px;
      height300px;
      position: absolute;
      top50%;
      left50%;
      background: white;
      border1px solid #dddddd;
      transformtranslate(-50%, -50%);
      z-index20;
     }
     .q-confirm-overlay {
      position: absolute;
      width100%;
      height100%;
      top0;
      left0;
      background-colorrgba(0000.2);
      z-index10;
     }
    </style>

    巧用slot-scope数据和 UI 分离组件

    slot-scope语法V2.5添加,V2.6废弃,V2.6+请使用v-slot

    如何写数据和 UI 分离组件,以vue-promised这个库为例。 Promised 组件并不关注你的视图展示成什么样,它只是帮你管理异步流程,并且通过你传入的 slot-scope,在合适的时机把数据回抛给你,并且帮你去展示你传入的视图。

    • 封装前写法
    <template>
     <div>
      <p v-if="error">Error: {{ error.message }}</p>
      <p v-else-if="isLoading && isDelayElapsed">Loading...</p>
      <ul v-else-if="!isLoading">
       <li v-for="user in data">{{ user.name }}</li>
      </ul>
     </div>
    </template>

    <script>
     export default {
      data() => ({
       isLoadingfalse,
       errornull,
       datanull,
       isDelayElapsedfalse
      }),

      methods: {
       fetchUsers() {
        this.error = null
        this.isLoading = true
        this.isDelayElapsed = false
        getUsers()
         .then(users => {
          this.data = users
         })
         .catch(error => {
          this.error = error
         })
         .finally(() => {
          this.isLoading = false
         })
        setTimeout(() => {
         this.isDelayElapsed = true
        }, 200)
       }
      },

      created() {
       this.fetchUsers()
      }
     }
    </script>
    • 封装后写法
    <template>
     <Promised :promise="usersPromise">
      <!-- Use the "pending" slot to display a loading message -->
      <template v-slot:pending>
       <p>Loading...</p>
      </template>
      <!-- The default scoped slot will be used as the result -->
      <template v-slot="data">
       <ul>
        <li v-for="user in data">{{ user.name }}</li>
       </ul>
      </template>
      <!-- The "rejected" scoped slot will be used if there is an error -->
      <template v-slot:rejected="error">
       <p>Error: {{ error.message }}</p>
      </template>
     </Promised>
    </template>

    <script>
     export default {
      data() => ({ usersPromisenull }),

      created() {
       this.usersPromise = this.getUsers()
      }
     }
    </script>

    样式穿透

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

    • less 使用 /deep/
    <style scoped lang="less">
     .content /deep/ .el-button {
      height: 60px;
     }
    </style>
    • scss使用::v-deep
    <style scoped lang="scss">
     .content ::v-deep .el-button {
      height60px;
     }
    </style>
    • stylus使用>>>
    <style scoped ang="stylus">
     外层 >>> .custon-components {
      height60px;
     }
    </style>
  • 相关阅读:
    java学习 接口与继承11 默认方法
    java学习 接口与继承10 内部类
    java学习 接口与继承9 抽象类
    java学习 接口与继承8 final
    理解管理信息系统
    vue中的错误日志
    vue中的ref属性
    2.有24颗外观完全一样的小球,其中有一个是空心的,现在只有一个天平,最少称几次能找出这个特殊的球?
    1.有888瓶编了号码的水及10只健康的小白鼠,其中一瓶水有毒,小白鼠饮用毒水一天后会死,最少需要几天可以找到哪瓶水有毒?
    SQL题1两表联查
  • 原文地址:https://www.cnblogs.com/iPing9/p/14403400.html
Copyright © 2011-2022 走看看