关于vue2

283 阅读2分钟

1632787097(1).png

1632788138(1).png

1632923824(1).png

1632925832(1).png

1632925929(1).png

1632959050(1).png 为什么索引用key不行,因为如果比如排序vue发现key为0的dom节点不一样,会直接被干掉,然后重新渲染一个,如果用key为j的话,就会先去找缓存,

1632972102670_CBC8AEA3-784F-4ECD-B81F-B2BB171C4E26.png

directives写在自己的组件里,就是自己组件的自定义方法,这个el就是特殊绑定的的dom,el中有一个方法contains,可以判断点击的dom,在不在绑定的dom范围内,这第二个参数bindings就是绑定的dom上的属性可以拿到,vnode.context就是当前实例的,

截屏2021-09-30 下午2.59.20.png

接下来我们看一下vue-lazy,更应该学习,她的设计模式和方式

/* eslint-disable no-mixed-spaces-and-tabs */
const loading = "http://localhost/images/1.gif";
//@ts-ignore
// eslint-disable-next-line no-undef
Vue.use(VueLazyload, {
	preload: 1.3,
	loading,
});


const getScrollParent = (el) => {
	let parent = el.parentNode;
	while (parent) {
		// eslint-disable-next-line no-undef
		if(/(scroll) | (auto) / .test(getComputedStyle(parent)["overfliow"])) {
			return parent;
		}
		parent = parent.parentNode;
	}
	return parent;
}


const loadImageAsync = (src, resovle, reject) => {
	// eslint-disable-next-line no-undef
	let image  =  new Image();
	image.src = src;
	image.onload = resovle;
	image.onerror = reject;
}
const Lazy = (Vue) => {
	class ReactiveListener{
		constructor({el, src, options, elRender}) {
			this.el = el;
			this.src = src;
			this.options = options;
			this.elRender = elRender;
			this.state = {loading: false}
		}
		checkInView() {
			let { top } = this.el.getBoundingClientRect();

			// eslint-disable-next-line no-undef
			return top < window.innerHeight * this.options.preload || 1.3;
		}
		load() {
			// 用来加载这个图片  先加载loading  如果加载ok的话 显示正常图片 
			loadImageAsync(this.src, ()=>{
				this.state.loading = true;
				this.elRender(this, "finish");
			}, ()=>{
				this.elRender(this, "error");
			});
		}
	}
	return class LazyClass{
	    constructor(options) {
			this.options = options;
			this.bindHandler = false; 
			this.listenerQueue = [];
	    }
    	add(el, bindings) {
			Vue.nextTick(()=> {
				let scrollParent = getScrollParent(el);
				if(scrollParent && !this.bindHandler) {
					this.bindHandler = true;
					scrollParent.addEventListener("scroll", this.handleLazyLoad.bind(this));
				}
				// bindings.value 可以拿到渲染后的属性值, bindings.expression 是拿到渲染前的值
				const listener = new ReactiveListener({
					el,
					src: bindings.value,
					options: this.options,
					elRender: this.elRender.bind(this),
				});
				this.listenerQueue.push(listener);
				this.handleLazyLoad();
			})
	    }
		// eslint-disable-next-line @typescript-eslint/no-empty-function
		elRender(listener, state) {
			let el = listener.el;
			let src = "";
			switch (state) {
				case "loading":
					src = listener.options.loading || "";
					break;
				case "error":
					src = listener.options.error || "";
					break;
				default:
					src = listener.src;
					break;
			}
			el.setAttribute("src", src);
		}
    	handleLazyLoad() {
			// eslint-disable-next-line @typescript-eslint/no-empty-function
			this.listenerQueue.forEach(listener => {
				let catIn = listener.checkInView();
				catIn && listener.load();
			});
		} 
	}
}

const VueLazyload = {
	install(Vue, options) {
		const LazyClass = Lazy(Vue);
		const lazy = new LazyClass(options);
		Vue.directive("lazy", {
			bind: lazy.add.bind(lazy),
		});
	}
};

很多面试官会问,组件有什么作用,比如父组件有两个子组件,一个是a组件,一个是b组件,a组件数据变了的话不会导致父组件和b组件的更新,如果不分组件的话,整个页面都会更新

1633330840(1).png

这样在vue-cli脚手架中其实是不支持template,默认是runtime-only,当然这是在main.js中进行的,如果想要支持template的写法,需要在vue.config.js中写,配置,runtimeComplier:true,就可以

1633334102(1).png 这个$on相当于给Edit绑定一个input事件,下面是事件

1633334214(1).png 这样触发

1633334763(1).png

1633336374(1).png

也可以这样触发,触发的都是@后面的名字

组件中为什么data必须是返回的函数?不能是对象?因为如果用ref进行拿到B实例,改变data的话,在其它组件拿到B实例,data也是改变后的

为什么传值不能用bus因为bus因为bus不好维护,一般不要用

看一看表单怎么封装的

  <div>
      <!-- el-form 表单 modal (数据源),rules (规则 ) vue是单向数据流 -->
      <el-form :modal="ruleForm" :rules="rules" ref="ruleForm">
          <!-- el-form-item 表示div 校验 label + 属性校验 -->
          <!-- v-modal 纯的语法糖 相当于绑定一个input事件 还给了一个value -->
         <el-form-item label="用户名" props="username">
             <el-input v-modal="ruleForm.username"></el-input>
         </el-form-item>
         <el-form-item label="密码" props="password">
             <el-input v-modal="ruleForm.password"></el-input>
         </el-form-item>
         <el-form-item>
             <button @click="submitForm">提交表单</button>
         </el-form-item>
      </el-form>
  </div>
</template>

<script>
import elForm from "./components/el-form";
import elInput from "./components/el-input";
import elFormItem from "./components/el-form-item"

export default {
    components: {
        "el-form": elForm,
        "el-input": elInput,
        "el-form-item": elFormItem,
    },
    data(){
        return{
           ruleForm: {
               username: "",
               password: "",
           },
           rules: {
               username:[
                   {require:true, message:"请输入用户名"},
                   {min:3, max:5, message:"长度在3-5"}
               ],
               password: [{require: true, message: "请输入密码"}],
           }
        }
    },
    methods: {
        submitForm(formName) {
            this.$refs["ruleForm"].validate(valid => {
                if(valid){

                } else {
                    return false;
                }
            });
        }
    }
}
</script>
<template>
    <form>
        <slot></slot>
    </form>
</template>

<script>
export default {
    name:'elForm',
    provide(){ //这个跨层级传值的最好不要用 相当于给自己组件实例上给_provided加上暴露出去的
        return{
            elFormItem:this
        }
    },
    props:{
        rules:{
            type:Object,
            default:()=>({}),
        },
        modal:{
            type:Object,
            default:()=>({}),
        }
    },
    methods:{
        validate(){

        }
    }
}
</script>
<template>
    <div>
        <label v-if="label">{{label}}</label>
        <slot></slot>
        <span v-if="errorMessage">{{errorMessage}}</span>
    </div>
</template>

<script>
export default {
    name:"elFormItem",
    inject:[elForm], // 去找父亲的 ._provided属性
    data(){
        return {errorMessage:null}
    },
    props:{
        label:{
            type:String
        },
        prop:{
            type:String
        }
    },
    mounted(){ //挂载顺序是先子后父
      this.$on("validate",()=>{
         
      })
    },
    methods:{
      validate(){
          let model = this.elForm.model[this.props]
          let ruleValue = this.elForm.rules[this.props];
      }
    }
}
</script>
<template>
    <input type="text" :value="aaa" @input="handleInput">
</template>

<script>
export default {
    modal:{
        // 可以重新定义v-modal解析出来的结果
        prop:'aaa'
    },
    name:"elInput",
    props:{
        aaa:{
            type:String
        }
    },
    methods:{
        handleInput(e){
            this.$emit('input',e.target.value);
            // this.$parent 这个表示是父组件 就算是子组件外面包了一层div,找的不是div这个dom,而是div外面的组件
            // 但是如果想拿到外层那个div的dom的话就需要用 this.$el.parentNode  this._uid是这个组件的唯一标识

            // 这种方式不行,为什么呢,因为万一有的程序员,外面没包div包了一个自己封装的组件,那就找不到对应的el-form-item了 
            // this.$parent.$emit("validate");
            // 怎么办就需要自己实现dispatch
            this.$dispatch('elFormItem','validate');
        }
    }
}
  //这是精确找到表单父组件的
  Vue.prototype.$dispatch = function(componentName,eventName) {
      let parent = this.$parent;
      while(parent){
          let name  = parent.$options.name;
          if(name === componentName){
              break;
          } else {
             parent = parent.$parent;
          }
      }
      if(parent){
          if(eventName){
              parent.$emit(eventName)
          }
          return parent; 
      }
  }
  //这是精确的找到表单子组件
  Vue.prototype.$broadcast = function(componentName,eventName) {
      let children = this.$children;
      let arr = [];

      function findChildren(children){
          children.forEach(child => {
              if(child.$options.name === componentName){
                  if(eventName){
                      child.$emit(eventName);
                  }else{
                      arr.push(child);
                  }
              }
              if(child.$children){
                  findChildren(children.$children);
              }
          });
      }

      findChildren(children);
      return arr;
  }

</script>


1634218646(1).png

这个时候如果直接监控name的话,

 vm.name.a = 1 
 这种改变是监控不了的
 必须要
 vm.name = {b:2}

1634218925(1).png

1634219038(1).png

1634219180(1).png