分类
javascript vue 性能 算法 解决方案

做虚拟列表时所遇到的坑

页面跳动

出现这种情况的原因是虚拟列表所渲染出的Dom是有限的,向下滚动时需要将上方的Dom移除,向上滚动时需要添加上方的Dom

解决方案

降低Dom渲染的频率

  1. 我有一个当前所处位置的检测值命名为topIndex,以3的倍数进行检测,当我所处位置为1值为0,所处位置为4值为3
  2. 我有一个判断当前滚动方向的检测值,命名为slideArrow,当向下滑动屏幕是置为down,向上滑动屏幕时值为up
  3. 我有一个由topIndex和slideArrow组合而成的计算值,命名为topAndArrowChange
  4. 监听topAndArrowChange,当发生改变时判断滑动方向,改变虚拟列表的值
  5. 当快要到底部时会触发查找列表,数据会放进源列表recommendList中
  6. 当滑动方向为up向上滑动,看下面的内容。或者滑动方向为down向下滑动,看上面的内容。那么虚拟列表为源数据的截取值。
topAndArrowChange(newVal) {
      const circle = 30
      const circleNum = parseInt(this.topIndex / circle, 10)
      console.log(newVal)
      if (this.slideArrow === 'up') {
        this.virtualRecommendList = this.recommendList.slice(circleNum * circle, circleNum * circle + 60)
      } else {
        const start = Math.max((Math.round(this.topIndex / circle) - 1) * circle, 0)
        const end = Math.round(this.topIndex / circle) * circle + circle
        this.virtualRecommendList = this.recommendList.slice(start, end)
      }
    },

看下面的内容,截取的部分

如果当前的topIndex为40,则截取30至90,最大截取60条

看上面的内容,截取的部分

如果当前的topIndex是0,则截取0-30

如果当前的topIndex是36,则截取0-60

如果当前的topIndex是119,则截取90-150

如果当前的topIndex是129,则截取90-150

无限触发查找数据

出现原因是初始时虚拟列表没有赋值,滚动过快触底,虚拟列表来不急赋值

解决方案

  1. 添加源数据列表监听,发现源数据列表更新的同时修改虚拟列表的值
  2. 只有在查找数据时才会触发源列表数据更新,因此虚拟列表的截取值为
 // 防止无限触发查找列表
    recommendList() {
      const circle = 30
      const circleNum = parseInt(this.topIndex / circle, 10)
      this.virtualRecommendList = this.recommendList.slice(circleNum * circle)
    },

当还是以30为一个循环,最多截取不超过60个Dom

总结

综上,此方案虚拟列表最多渲染60条数据

分类
vue

vue 自定义modal框

先写一个正常的vue组件

写一个创建组件的方法

import Vue from 'vue'

/**
 * 创建一个根组件
 * @param Component 组件
 * @param props 组件属性
 */
const createComponent = (Component, props) => {
  // vue.extend()获取创建实例
  const Ctor = Vue.extend(Component)
  // 创建组件实例,这时得到的是虚拟dom,如何转化为真实dom
  const comp = new Ctor({
    // propsData: props
  })
  // 前面说到$mount就是这个作用
  comp.$mount()
  // 挂在在body上,这时是comp的dom
  document.body.appendChild(comp.$el)
  comp.remove = () => {
    // 移除本身
    document.body.removeChild(comp.$el)
    // 释放自己所占资源
    comp.$destroy()
  }
  return comp
}

export default createComponent

定义打开弹窗方法

import CandyBox from './CandyBox'
import createComponent from './createComponent'

const Modal = {}
// 弹窗组件实例
const modalInstance = {}
const openModal = (name, Component, options) => {
  if (!modalInstance[name]) {
    modalInstance[name] = createComponent(Component)
  }
  // 在渲染完成打开,否则首次动画播放有问题
  setTimeout(() => {
    modalInstance[name].open(options)
  })
}
Modal.candyBox = (options = {}) => {
  openModal('candyBox', CandyBox, options)
}
export default Modal

如果需要预加载定义预加载方法

// 弹窗预加载 (可在项目入口调用该函数,实现弹窗预加载)
Modal.prefetch = () => {
  modalInstance.candyBox = createComponent(CandyBox)
}
/**

在项目入口执行预加载

import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import Modal from './components/Modal'

new Vue({
  el: '#app',
  store,
  router,
  render: h => h(App)
})
Modal.prefetch()
分类
vue

vue 数据更新页面不更新

使用Object.assign

Object.assign(this.somedetail, detail);

应该使用

this.somedetail = Object.assign({}, this.somedetail,detail);

使用Vue.set

this.$set(this.somedetail,detail);

Vue.set(this.somedetail,detail);

如果是数组不更新

使用数组自定义方法push,pop,shift,unshift,splice

分类
vue

vue组件shadow dom化

当我的npm组件rsa-verify在uni-app样式不起作用,在调查原因后发现uni-app将我的input转为uni-input,因此为input所写的样式失效了。于是需要shandow dom来隔离npm 插件和原生内容。

代码

<template>
  <div class="shadow-dom">
    // 插件内容
    </div>
  </div>
</template>
 shadowDomInit() {
      // 创建shadow dom
      const parentElement = document.querySelector('.shadow-dom');
      const fragment = document.createDocumentFragment();
      fragment.appendChild(parentElement.children[0]);
      const shadowRoot = parentElement.attachShadow({ mode: 'open' });
      shadowRoot.appendChild(fragment);
      const sheet = new CSSStyleSheet();
      // 拷贝样式
      document.styleSheets.forEach(stylesheet => {
        stylesheet.cssRules.forEach(rule => {
          if (rule.selectorText) {
            sheet.insertRule(rule.cssText)
          }
        })
      });
      shadowRoot.adoptedStyleSheets = [sheet];
    },
mounted() {
  this.shadowDomInit();
}

第二种初始化方式

    shadowDomInit() {
                // 创建shadow dom
                const parentElement = document.querySelector('.shadow-protocol-frame');
                const fragment = document.createDocumentFragment();
                fragment.appendChild(parentElement.children[0]);
                const shadowRoot = parentElement.attachShadow({mode: 'open'});
                shadowRoot.appendChild(fragment);
                const style = document.createElement('style');
                style.textContent = `.frame-wrap{flex:1;border:none;overflow:hidden;position:absolute;left:0;top:0;width:110%;height:130%;}#outerContainer{overflow:hidden;}.hide-scroll{position:relative;display:flex;overflow:hidden;flex:1;}.hide-scroll::after{content:'';display:block;}.sign-modal{opacity:0;visibility:hidden;transition:opacity .3s;position:fixed;left:0;right:0;top:0;bottom:0;margin:auto;display:flex;flex-flow:column nowrap;max-height:500px;height:80%;max-width:300px;padding:20px;width:90%;background:white;border-radius:10px;box-shadow:rgba(0,0,0,0.1) 0 2px 15px;}.sign-modal.active{opacity:1;visibility:visible;}.sign-modal-title{text-align:center;}.sign-footer{height:76px;border-top:1px solid #F4F5F6;display:flex;padding:0 5px;justify-content:space-evenly;align-items:center;margin-top:10px;}.reject-button{border-radius:6px;background:rgba(217,220,220,0.30);font-size:16px;color:rgba(0,0,0,0.65);display:flex;justify-content:center;align-items:center;width:115px;height:40px;cursor:pointer;}.agree-button{border-radius:6px;background:#4277BD;font-size:16px;color:white;display:flex;justify-content:center;align-items:center;width:115px;height:40px;cursor:pointer;}`;
                shadowRoot.appendChild(style);
                shadowRoot.adoptedStyleSheets = [style];
            },

注意要点

  • attachShadow的主体不能为template的母元素,因为这会使母元素的非shandow dom子元素无效化。
  • 插件经过shandow dom化后,内部的document.querySelector和document.getElementByid需要转换成document.querySelector(‘.shadowRoot’).shadowRoot.querySelector。因为经过shandow dom化以后,javascript只能使用特殊方法访问节点。
分类
vue

vue 函数式组件

使用函数式编程可以提高渲染效率,因为它没有状态,没有实例,并且它是一个组件可复用。下面用例展示了如何使用函数式组件。

给函数式组件传递事件

// 子组件
<template functional>
    <div v-example v-on="listeners">{{props.msg}}</div>
</template>
// 父组件 
<list @click="handleClick" :msg="msg"></list>
 handleClick() {
      // eslint-disable-next-line no-debugger
      debugger;
      this.msg = 'aaa';
    }

函数式组件可以自己定义类,也可接收外部传入的类。样式同理。

<template functional>
    <div class="x" :class="[data.class, data.staticClass]" v-example v-on="listeners" :style="data.staticStyle">{{props.msg}}</div>
</template>

在函数式组件中加入别的组件,且别的组件可以不是函数式组件。

<template functional>
    <div class="x" :class="[data.class, data.staticClass]" v-example>
        {{props.msg}}
        <component :is="injections.components.Pane"/>
        <button  v-on="listeners">action</button>
    </div>
</template>

<script>
    import Pane from './Pane';
    export default {
        inject: {
            components: {
                default: {
                    Pane,
                }
            }
        }
    }
</script>

在函数式组件中更改数据源内容

<template functional>
    <div>
        {{$options.setContent(props.msg)}}
    </div>
</template>

<script>
    export default {
        setContent(val) {
            return val + 'sldkflkjdf';
        }
    }
</script>

插槽在函数式组件中能正常使用

成为函数式组件中,不能在script中写props