huangyingzheng / daily_progress

每天进步一点点
1 stars 0 forks source link

Vue #2

Open huangyingzheng opened 4 years ago

huangyingzheng commented 4 years ago

slot 使用

个人觉得slot的使用主要就分成两种:

例子

例如,我们要实现一个 组件,它是一个列表且包含布局和过滤逻辑:

<ul>
<li
v-for="todo in filteredTodos"
v-bind:key="todo.id"

{{ todo.text }}
</li>
</ul>

我们可以将每个 todo作为父级组件的插槽,以此通过父级组件对其进行控制,然后将 todo作为一个插槽 prop 进行绑定:

<ul>
<li
v-for="todo in filteredTodos"
v-bind:key="todo.id"

<slot name="todoItem" v-bind:todo="todo">
{{ todo.text }}
</slot>
</li>
</ul>

使用

<todo_list v-bind: "todo_list">
<template #todoItem={ todo}>
<span v-if="todo.isComplete">✔</span>
{{ todo.text }}
</template>
</todo_list>
huangyingzheng commented 4 years ago

关于 el-form validate的使用以及源码分析

v-model, rules 和 ref

// form.vue
//#76 form-item会emit一个事件,接收就好
created() {
    this.$on('el.form.addField', (field) => {
      if (field) {
        this.fields.push(field);
      }
    });
    /* istanbul ignore next */
    this.$on('el.form.removeField', (field) => {
      if (field.prop) {
        this.fields.splice(this.fields.indexOf(field), 1);
      }
    });
  },

// # 109 
// 我们使用的this.$refs['formname'].validate 里面的validate 就是这个validate
validate(callback) {
    if (!this.model) { // 如果没有模板直接报错
      console.warn('[Element Warn][Form]model is required for validate to work!');
      return;
    }
    let promise;
    // if no callback, return promise
    if (typeof callback !== 'function' && window.Promise) {
      promise = new window.Promise((resolve, reject) => {
        callback = function(valid) { // 这个valid是从form-item 里面返回的,下面会讲
          valid ? resolve(valid) : reject(valid);
        };
      });
    }
    let valid = true;
    let count = 0;
    // 如果需要验证的fields为空,调用验证时立刻返回callback
    if (this.fields.length === 0 && callback) {
      callback(true);
    }
    let invalidFields = {};
    this.fields.forEach(field => { // 猜测这个field应该是一个form-item 的示例
      field.validate('', (message, field) => { // 这个validate也是form-item里面的
        if (message) {
          valid = false; // 存在校验没通过
        }
        invalidFields = objectAssign({}, invalidFields, field); // 应该是重新了Object.assign
        if (typeof callback === 'function' && ++count === this.fields.length) { // 最后一个的处理
          callback(valid, invalidFields); // 如果cb是函数,正常执行,参数是校验结果和校验失败的field
        }
      });
    });
    if (promise) {
      return promise;// 如果没有cb,那么返回一个promise,如果有promise返回一个promise, 这样写提高兼容性
    }
  },
// form-item.vue ,这里主要讲几个关键的方法
// # 54
  provide() {
    return {
      elFormItem: this
    };
  }, 
  inject: ['elForm'],
// 对内注入elForm, 对外抛出elFormItem

//#189 每个form-item 单独校验
import AsyncValidator from 'async-validator';
  validate(trigger, callback = noop) { // 这个就是我上文提到的form-item 里面的validate
      this.validateDisabled = false;
      const rules = this.getFilteredRule(trigger); //获取rules
      if ((!rules || rules.length === 0) && this.required === undefined) { // 没有rules, 直接通过
        callback();
        return true; 
      }
      this.validateState = 'validating';
      const descriptor = {};
      if (rules && rules.length > 0) {
        rules.forEach(rule => {
          delete rule.trigger;
        });
      }
      descriptor[this.prop] = rules; //每个form-item 单独校验
      const validator = new AsyncValidator(descriptor);
      const model = {};
      model[this.prop] = this.fieldValue; // 这里就是为什么一定要有model, 而且props必须可以直接访问
      validator.validate(model, { firstFields: true }, (errors, invalidFields) => { // 参考asyn-validator 不展开
      // validation failed, errors is an array of all errors
      // fields is an object keyed by field name with an array of
      // errors per field
      // https://github.com/yiminghe/async-validator
      //- firstFields: Boolean|String[], Invoke callback when the first validation rule of the specified sofield generates an error, no more validation rules of the same field are processed. true means all fields. 所以项,只要有规则一个产生error, 该项后面规则都不执行。
      //说到底就是对async-validator一个封装
        this.validateState = !errors ? 'success' : 'error';
        this.validateMessage = errors ? errors[0].message : '';
        callback(this.validateMessage, invalidFields);
        this.elForm && this.elForm.$emit('validate', this.prop, !errors, this.validateMessage || null);
      });
    },

//# 252
 getRules() {
    let formRules = this.form.rules; // parent 组件的rules
    const selfRules = this.rules; // prop 传入的rules
    const requiredRule = this.required !== undefined ? { required: !!this.required } : []; // prop 传入
    const prop = getPropByPath(formRules, this.prop || ''); // 判断parent 传的rules是否和prop传入冲突
    formRules = formRules ? (prop.o[this.prop || ''] || prop.v) : []; // 以prop 为准
    return [].concat(selfRules || formRules || []).concat(requiredRule); // 整合
  },
  getFilteredRule(trigger) {
    const rules = this.getRules();
    return rules.filter(rule => {
      if (!rule.trigger || trigger === '') return true; // 全触发
      if (Array.isArray(rule.trigger)) {
        return rule.trigger.indexOf(trigger) > -1; //按需触发
      } else {
        return rule.trigger === trigger;
      }
    }).map(rule => objectAssign({}, rule));
  },

// #130
  form() { // 找到parent为el-form的组件
    let parent = this.$parent;
    let parentName = parent.$options.componentName;
    while (parentName !== 'ElForm') {
      if (parentName === 'ElFormItem') {
        this.isNested = true;
      }
      parent = parent.$parent;
      parentName = parent.$options.componentName;
    }
    return parent;
  },

// util #47
export function getPropByPath(obj, path, strict) {
  let tempObj = obj;
  path = path.replace(/\[(\w+)\]/g, '.$1'); // enleve []
  path = path.replace(/^\./, ''); // enleve first.

  let keyArr = path.split('.');
  let i = 0;
  for (let len = keyArr.length; i < len - 1; ++i) {
    if (!tempObj && !strict) break;
    let key = keyArr[i];
    if (key in tempObj) {
      tempObj = tempObj[key]; // neste address
    } else {
      if (strict) {
        throw new Error('please transfer a valid prop path to form item!');
      }
      break;
    }
  }
  return {
    o: tempObj,
    k: keyArr[i],
    v: tempObj ? tempObj[keyArr[i]] : null
  };
};
huangyingzheng commented 4 years ago

一百行代码实现双向绑定(Proxy, Reflect版)

<!DOCTYPE html>
<html>
  <head>
    <title>Parcel Sandbox</title>
    <meta charset="UTF-8" />
  </head>

  <body>
    <div id="app"></div>
  </body>
  <script>
    // 可以打印出变量名和变量值
    // 使用方式console.log_({obj}), 如果输入是对象,则输入需要想整形。 node.value = nodeValue, 输入nodeValue
    Object.prototype.log_ = function(input) {
      return this.log(Object.keys(input)[0], '=>', input)
    }

    const observeMap = {}

    function Mi(opts) {
      console.log_({opts});
      this.data = observer(opts.data())
      this.node = getNodes(opts.template);
      this.compileElement(this.node);
    }

    function getNodes(str) {
      const div = document.createElement("div");
      div.innerHTML = str;
      return div.children[0];
    }

    Mi.prototype.compileElement = function (node) {
      // 递归处理dom节点
      Array.from(node.childNodes).forEach((node) => { // 不能是children, 不然取不了文字节点
        let text = node.textContent;
        console.log_({text});
        let reg = /\{\{(.*)\}\}/; //括号用来分组

        if (node.nodeType === 1) {
          this.compile(node);
        } else if (node.nodeType === 3 && reg.test(text)) { // 3是Element 或者 Attr 中实际的  文字
          this.compileText(node, reg.exec(text)[1]);// 第一组的value,对应括号里面的value
        }

        if (node.childNodes && node.childNodes.length > 0) {
          this.compileElement(node)
        }
      });
    };

    Mi.prototype.compileText = function (node, expression) {
      let reg = /\{\{.*\}\}/;
      expression = expression.trim()
      let value = this.data[expression]
      const oldText = node.textContent
      value = typeof value === 'undefined' ? '' : value
      node.textContent = oldText.replace(reg, value) // 初始化部分,从placeholder to real value

      add(expression, (value, oldValue) => { // once the value corresponded changed, this function will be called
        value = typeof value === 'undefined' ? '' : value
        node.textContent = oldText.replace(reg, value)
      })
    };

    // delet addtional attributes
    Mi.prototype.compile = function (node) {
      console.log_({nodeValue: node.value})
      console.log(node.value);
      Array.from(node.attributes).forEach((attr) => {
        if (attr.name === "v-model") {
          node.value = this.data[attr.value];
          node.addEventListener("input", (e) => {
            this.data[attr.value] = e.target.value;
          });
          node.removeAttribute(attr.name);// 避免二次找到
        }
      });
      return node;
    };

    // insert cb in Map
    function add(expression, cb) {
      observeMap[expression] = cb
    }

    function observer(target) {
      const handler = {
        get: function(target, property, recevier) {
          return Reflect.get(target, property)
        },
        set: function(target, property, value, recevier) {
          const oldValue = Reflect.get(target, property)
          const setResult = Reflect.set(target, property, value)
          if(observeMap[property]) {
            Reflect.apply(observeMap[property], recevier, [value, oldValue])
          }
          return setResult // 必须返回一个boolean值
        }
      }
      return new Proxy(target, handler)
    }

    Mi.prototype.mount = function (selector) {
      document.querySelector(selector).appendChild(this.node) // insertAdjcntHTML ???
    };
  </script>
  <script>
    new Mi({
      template: `<div><p>输入框的值:{{ name }}</p><input type="text" v-model="name"></div>`,
      data() {
        return {
          name: "huang"
        };
      }
    }).mount("#app");
  </script>
</html>