Vue记录

1.双向绑定和单向数据流

vue是单向数据流,父组件通过属性props传值给子组件,子组件也是通过emit修改父组件的属性

双向绑定(单个 v-model  多个 .sync )

双向绑定是一种语法糖,依然是遵循单向数据流,是通过Object.defineProperty来做响应式更新

2. 列表的key值不要用index,用唯一值(例如id)

当你用index作为key时,虚拟dom是以key作为凭证,如果数组出现插入,删除等操作时候,有可能会出现问题,所以要极力避免

3. 响应式更新

getter进行依赖的收集,当data里面的数据页面没有用到的时候,就是没有收集到依赖,当setter的时候,是不会更新的

1. Vue框架对数组的push、pop、shift、unshift、sort、splice、reverse方法进行了改造,
   在调用数组的这些方法时,Vue会Notify Wacher并更新视图

2. Object.defineProperty是不会响应上述的方法的

3. Vue 不能检测对象属性的添加或删除,因为vue只会将data里面存在的key值进行get,set的处理, 
   可以使用vm.$set 实例方法来设置添加或删除的值,确保被监听到

4. vue不能检测索引导致的数组的变动, 解决方案是使用 vm.$set 实例方法,或者用第一步的方法

具体参考vue官方注意事项:响应式注意事项

4. 仿造elementui的form表单的功能,简易功能的demo

4-1. MInput  输入框    实现v-model需要的逻辑,就是value和input事件的结合

<template>
  <input :type="type" :value="value" @input="textChange" />
</template>

<script>
import emitter from "element-ui/src/mixins/emitter"; //引用elementui内部的
export default {
  componentName: "MInput",
  mixins: [emitter], //进行组合到当前组件
  components: {},
  props: {
    value: {
      type: String,
      required: true
    },
    type: {
      type: String,
      default: "text"
    }
  },
  data() {
    return {};
  },
  methods: {
    /**
     * 这里做了个防抖
     */
    textChange(e) {
      this.debounce(() => {
        let txt = e.target.value;
        this.$emit("input", txt);

        //触发父级MFormItem的监听事件m.form.item.validate
        this.dispatch("MFormItem", "m.form.item.validate");
      }, 300)();
    },
    /**
     * 防抖
     */
    debounce(fn, wait) {
      let timeout = null;
      return () => {
        if (timeout) clearTimeout(timeout);
        timeout = setTimeout(fn, wait);
      };
    }
  }
};
</script>
<style lang="scss" scoped></style>

4-2. MFormItem 包住input,实现错误消息的验证和提示的功能,展示label

<template>
  <div class="fm-item-wrapper">
    <div class="fm-item-content">
      <p class="fm-item-label">{{ label }}</p>
      <slot></slot>
    </div>
    <p class="error-txt" v-if="errorStatus">{{ errorText }}</p>
  </div>
</template>

<script>
import emitter from "element-ui/src/mixins/emitter"; //引用elementui内部的
import Schema from "async-validator";
export default {
  componentName: "MFormItem",
  mixins: [emitter], //进行组合到当前组件
  components: {},
  props: {
    prop: String,
    label: String
  },
  inject: ["mForm"], //获取上面传下来的数据mForm
  data() {
    return {
      errorText: "", //错误消息文案
      errorStatus: false //错误消息状态
    };
  },
  mounted() {
    if (this.prop) {
      // 这里是做了一个循环,找到指定的组件componentName名称,进行emit
      //   dispatch(componentName, eventName, params) {
      //   var parent = this.$parent || this.$root;
      //   var name = parent.$options.componentName;

      //   while (parent && (!name || name !== componentName)) {
      //     parent = parent.$parent;

      //     if (parent) {
      //       name = parent.$options.componentName;
      //     }
      //   }
      //   if (parent) {
      //     parent.$emit.apply(parent, [eventName].concat(params));
      //   }
      // },
      this.dispatch("MForm", "m.form.addField", [this]); //触发[增加要验证的属性]的事件,用于总体验证是否成功
    }
    this.$on("m.form.item.validate", this.validate); //监听input数据变化了的发送的要验证的事件
  },
  methods: {
    // 这里用到了async-validator
    validate(callback = () => {}) {
      const rule = this.mForm.rules[this.prop];
      const value = this.mForm.model[this.prop];
      const schema = new Schema({ [this.prop]: rule });

      schema.validate({ [this.prop]: value }, err => {
        if (err) {
          this.errorText = err[0].message;
          this.errorStatus = true;
          callback();
        } else {
          this.errorText = "";
          this.errorStatus = false;
        }
      });
    }
  }
};
</script>
<style lang="scss" scoped>
.fm-item-wrapper {
  padding: 10px;
  border: 1px solid #999;
}
.fm-item-content {
  display: flex;
  align-items: center;
}
.fm-item-label {
  font-size: 14px;
  font-weight: bold;
  padding-right: 15px;
}
.error-txt {
  font-size: 12px;
  color: red;
  margin-top: 5px;
}
</style>

4-3. MForm  表单组件,实现总体的验证,是否成功

<template>
  <form>
    <slot></slot>
  </form>
</template>

<script>
export default {
  componentName: "MForm",
  components: {},
  props: {
    model: Object,
    rules: Object
  },
  /**
   * 向下传入数据,当前MForm,方便子级组件获取数据
   */
  provide() {
    return {
      mForm: this
    };
  },
  data() {
    return {
      fields: [] //需要监听错误的MFormItem的实例数组
    };
  },
  created() {
    // 监听m.form.addField事件,由子级MFormItem触发
    this.$on("m.form.addField", field => {
      if (field) {
        this.fields.push(field);
      }
    });
  },
  methods: {
    //总体验证是否通过
    validate(callback) {
      if (!this.model) {
        return;
      }
      let valid = true;
      //没有验证,直接返回
      if (this.fields.length === 0 && callback) {
        callback(true);
        return;
      }
      this.fields.forEach(field => {
        //调用MFormItem的验证方法
        field.validate(() => {
          valid = false;
        });
      });
      callback(valid);
    }
  }
};
</script>
<style lang="scss" scoped></style>

4-4. 使用组件的页面

<template>
  <div class="home">
    <m-form :model="model" :rules="rules" ref="login">
      <m-form-item prop="userName" label="用户名">
        <m-input v-model="model.userName"></m-input>
      </m-form-item>
      <m-form-item prop="password" label="密码">
        <m-input v-model="model.password"></m-input>
      </m-form-item>
    </m-form>
    <el-button @click="submitMsg">提交数据</el-button>
  </div>
</template>

<script>
// @ is an alias to /src
import MForm from "@/components/form/MForm";
import MInput from "@/components/form/MInput";
import MFormItem from "@/components/form/MFormItem";

export default {
  name: "home",
  components: {
    MForm,
    MInput,
    MFormItem
  },
  data() {
    return {
      model: {
        userName: "",
        password: ""
      },
      rules: {
        userName: [
          { required: true, message: "用户名不能为空" },
          { min: 3, max: 5, message: "长度在 3 到 5 个字符" }
        ],
        password: [{ required: true, message: "密码不能为空" }]
      }
    };
  },
  methods: {
    submitMsg() {
      this.$refs.login.validate(valid => {
        if (valid) {
          alert("开始去请求登录");
        } else {
          console.log("有错误信息");
        }
      });
    }
  }
};
</script>

5. Vue源码(响应式原理)  手动实现 data的简易模式

m.html:  实现如下的功能([插值,指令text,双向绑定model,事件,指令html])

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <p>{{ name }}</p>
        <p m-text="name"></p>
        <p>{{  age }}</p>
        <p>
            {{ doubleAge   }}
        </p>
        <input type="text" m-model="name">
        <p><button @click="changeName">改变名字</button></p>
        <div m-html="html"></div>
    </div>
    <script src="compile.js"></script>
    <script src="mVue.js"></script>
    <script>
        const app=new MVue({
            el:'#app',
            data:{
                name:'张三',
                age: 18,
                html:'<button>这是个html形成的按钮</button>'
            },
            created(){
                console.log("开始");
                setTimeout(()=>{
                    this.name="异步后的名字";
                },500)
            },
            methods:{
                changeName(){
                    this.name="修改后的名字";
                    this.age=100;
                }
            }
        })
    </script>
</body>
</html>

mVue.js

class MVue{
    //构造函数
    constructor(options){

        this.$options=options;

        this.$data=options.data;

        this.observe(this.$data); //监听数据

        new Compile(options.el,this);//编译

        options.created && options.created.call(this); //执行生命周期,要绑定作用域

    }
    // 监听数据
    observe(value){
        if(value && typeof value!=='object'){
            return;
        }
        Object.keys(value).forEach((key)=>{

            this.defineData(value,key,value[key]);

            this.proxyData(key); //代理vm.$data,针对每个key属性
        })
    }

    //做个代理,vm.$data里面属性映射到vm上,可以用vm[key]直接访问
    proxyData(key){
        Object.defineProperty(this,key,{
            get(){
                return this.$data[key]; //访问的时候返回$data的值
            },
            set(newVal){
                this.$data[key]=newVal; //这里面进行赋值,就会触发defineData里面的defineProperty的set
            }
        })
    }

    // 设置值的setter和getter 用 Object.defineProperty
    defineData(obj,key,val){

        this.observe(val); //递归循环遍历,到深层次

        const dep=new Dep();

        Object.defineProperty(obj,key,{
            get(){
                Dep.target && dep.addDep(Dep.target);//访问属性时候,进行依赖收集
                //这就解释了为何只有template里面使用的属性才会有更新机制
                return val;
            },
            set(newVal){
                if(newVal===val){
                    return;
                }
                val=newVal;
                dep.notify();//值变化了,通知更新
            }
        });

    }
}


//依赖 用来管理watcher
class Dep{
    constructor(){
        this.deps=[]; //存放若干watcher,一个属性对应一个watcher,比如foo.bar,foo是一个,bar是一个
    }
    //添加依赖
    addDep(dep){
        this.deps.push(dep);
    }
    //通知进行更新
    notify(){
        this.deps.forEach((dep)=>dep.update()); //这里的dep就是watcher
    }
}

class Watcher{
    constructor(vm,key,cb){

        Dep.target=this; //将watcher的当前实例,指定为Dep的静态属性target

        this.vm=vm; //实例

        this.key=key; //属性

        this.cb=cb; //compile.js更新那边的回调函数

        this.vm[this.key];//访问当前的属性,则调用getter,进行Dep的依赖收集

        Dep.target=null; //释放

    }

    //更新方法,执行回调,在compile.js里面进行node的更新
    update(){
        this.cb && this.cb.call(this.vm,this.vm[this.key]);
    }
}

compile.js: 编译器 [对非html的特殊形式进行解析]

// new Compile(el,vm)

class Compile{
    constructor(el,vm){
        //宿主节点
        this.$el=document.querySelector(el);

        this.$vm=vm;

        if(this.$el){
            this.$fragment=this.nodeToFragment(this.$el); //获取html片段
            this.compile(this.$fragment); //执行后已经是html
            this.$el.appendChild(this.$fragment);

        }

    }
    // 移动dom,进行搬家
    nodeToFragment(node){
        const frag=document.createDocumentFragment();
        let child;
        while(child=node.firstChild){
            frag.appendChild(child);
        }
        return frag;
        
    }

    //编译过程

    compile(el){
        const childNodes=el.childNodes;
        Array.from(childNodes).forEach((node)=>{
            if(this.isElement(node)){
                //元素

                const nodeAttrs=node.attributes;
                Array.from(nodeAttrs).forEach((attr)=>{

                   const name=attr.name; //属性
                   const exp=attr.value; //值

                   //指令
                   if(this.isDirective(name)){
                       //m-text
                       const dir=name.substring(2);
                       this[dir] && this[dir](node,this.$vm,exp);//执行指令
                   }
                   //事件
                   if(this.isEvent(name)){
                    const dir=name.substring(1);
                    this.eventHandler(node,this.$vm,dir,exp);//执行指令
                   }

                })

            }
            if(this.isInterpolation(node)){
                //插值文本

                this.compileText(node);

            }

            node.childNodes && node.childNodes.length>0 && this.compile(node);
        })
    }

    // 事件处理函数
    eventHandler(node,vm,dir,exp){

        const method=vm.$options && vm.$options.methods[exp];

        if(dir && method){
            node.addEventListener(dir,method.bind(vm)); 
            //要绑定this,这里不是立即执行,只是函数的引用,所以用bind
        }

    }
    
    //是否是元素
    isElement(node){
        return node.nodeType===1;
    }

    //是否是指令
    isDirective(attr){
       return attr.indexOf('m-')===0
    }
    
    //是否是事件
    isEvent(attr){
        return attr.indexOf('@')===0
    }

    //指令的m-text的text方法
    text(node,vm,exp){
        this.update(node,vm,exp,'text');
    }

    //指令的m-html的html方法
    html(node,vm,exp){
        this.update(node,vm,exp,'html');
    }

    //指令m-model的model执行方法
    model(node,vm,exp){
        this.update(node,vm,exp,'model');
        node.addEventListener('input',(e)=>{
            vm[exp]=e.target.value; //赋值,触发依赖更新,会执行Dep的notify
        })
    }

    //是否是插值
    isInterpolation(node){
        return node.nodeType===3 && /\{\{(\s*)(.*)(\s*)\}\}/.test(node.textContent);
    }


    //编译文本
    compileText(node){
        const reg=RegExp.$2.trim();
        this.update(node,this.$vm,reg,'text');
    }

    // 更新node,通用方法
    update(node,vm,exp,dir){
        const updateFn= this[dir+'Updater'];
        updateFn(node,vm[exp]); //初始化更新

        //依赖收集
        new Watcher(vm,exp,(val)=>{
            updateFn(node,val);
        })
    }

    //文本的更新
    textUpdater(node,val){
        node.textContent=val;
    }

    //model双向绑定的数据赋值的更新
    modelUpdater(node,val){
        node.value=val;
    }

    //html的更新
    htmlUpdater(node,val){
        node.innerHTML=val;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值