Open huangyingzheng opened 4 years ago
// prop 可以是 `${index}.value`这种字符串的形式
// 对应的就是
// model.prop => rules.prop
// model.index.value => rules.index.value
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px">
<el-form-item label="活动名称" prop="name">
<el-input v-model="ruleForm.name"></el-input>
</el-form-item>
<el-form-item label="年龄" prop="age">
<el-input v-model.number="ruleForm.age"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">立即创建</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
<script>
var checkAge = (rule, value, callback) => {
//console.log(rule.max_age)
if (!value) {
return callback(new Error('年龄不能为空'));
}
if (!Number.isInteger(value)) {
callback(new Error('请输入数字值'));
} else {
if (value < rule.max_age) {
callback(new Error('必须年满18岁'));
} else {
callback();
}
}
};
export default {
data() {
return {
ruleForm: {
name: '',
age:''
},
rules: {
name: [
{ required: true, message: '请输入活动名称', trigger: 'blur', validator: function() {} },
{ min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur', validator: function() {} }
],
age: [
{max_age:18, validator: checkAge, trigger: 'blur' }// checkAge自定义规则函数
]
}
};
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
alert('submit!');
} else {
console.log('error submit!!')
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
}
}
</script>
<el-form :model="numberValidateForm" ref="numberValidateForm" label-width="100px" class="demo-ruleForm">
<el-form-item
label="年龄"
prop="age"
:rules="[
{ required: true, message: '年龄不能为空'},
{ type: 'number', message: '年龄必须为数字值'}
]">
<el-input type="age" v-model.number="numberValidateForm.age" autocomplete="off"></el-input>
</el-form-item>
<el-form>
<el-form-item
v-for="(domain, index) in dynamicValidateForm.domains"
:label="'域名' + index"
:key="domain.key"
:prop="`domains.${index}.value`" //绑定的prop
:rules="[
{ required: true, message: '域名不能为空', trigger: 'blur' },
{reg:/^--------$/, validator: checkDomain, trigger: 'blur' }
]"
>
</el-form-item>
// 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
};
};
<!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>
slot 使用
个人觉得slot的使用主要就分成两种:
第二种
v-bind一定要绑在一个变量上
例子