inject 和 provider 是vue中的组合选项,需要一起使用。目的是允许一个祖先组件向其所有子孙后代注入依赖(简单地说就是祖先组件向子孙后代传值的一种方法,祖先组件通过provider提供变量,子孙后代通过inject注入接收变量)
provider: Object || () => Object
inject: Array || Object
Eg.
button.vue:
1 <template>
2 <button
3 class="el-button"
4 @click="handleClick"
5 :disabled="buttonDisabled || loading"
6 :autofocus="autofocus"
7 :type="nativeType"
8 :class="[
9 type ? 'el-button--' + type : '',
10 buttonSize ? 'el-button--' + buttonSize : '',
11 {
12 'is-disabled': buttonDisabled,
13 'is-loading': loading,
14 'is-plain': plain,
15 'is-round': round,
16 'is-circle': circle
17 }
18 ]"
19 >
20 <i class="el-icon-loading" v-if="loading"></i>
21 <i :class="icon" v-if="icon && !loading"></i>
22 <span v-if="$slots.default"><slot></slot></span>
23 </button>
24 </template>
25 <script>
26 export default {
27 name: 'ElButton',
28
29 // 通过inject向button中注入变量
30 inject: {
31 elForm: {
32 default: ''
33 },
34 elFormItem: {
35 default: ''
36 }
37 },
38
39 props: {
40 type: {
41 type: String,
42 default: 'default'
43 },
44 size: String,
45 icon: {
46 type: String,
47 default: ''
48 },
49 nativeType: {
50 type: String,
51 default: 'button'
52 },
53 loading: Boolean,
54 disabled: Boolean,
55 plain: Boolean,
56 autofocus: Boolean,
57 round: Boolean,
58 circle: Boolean
59 },
60
61 computed: {
62 _elFormItemSize() {
63 return (this.elFormItem || {}).elFormItemSize;
64 },
65 buttonSize() {
66 return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
67 },
68 buttonDisabled() {
69 return this.disabled || (this.elForm || {}).disabled; // 通过inject注入的form变量,获得祖先节点form的disabled属性
70 }
71 },
72
73 methods: {
74 handleClick(evt) {
75 this.$emit('click', evt);
76 }
77 }
78 };
79 </script>
form.vue:
1 <template>
2 <form class="el-form" :class="[
3 labelPosition ? 'el-form--label-' + labelPosition : '',
4 { 'el-form--inline': inline }
5 ]">
6 <slot></slot>
7 </form>
8 </template>
9 <script>
10 import objectAssign from 'element-ui/src/utils/merge';
11
12 export default {
13 name: 'ElForm',
14
15 componentName: 'ElForm',
16
17 // 通过provider向子孙后代注入变量elform,讲this(即form)注入给子孙后代,后代通过获取此变量获取form中的各种配置,如disabled属性等
18 provide() {
19 return {
20 elForm: this
21 };
22 },
23
24 props: {
25 model: Object,
26 rules: Object,
27 labelPosition: String,
28 labelWidth: String,
29 labelSuffix: {
30 type: String,
31 default: ''
32 },
33 inline: Boolean,
34 inlineMessage: Boolean,
35 statusIcon: Boolean,
36 showMessage: {
37 type: Boolean,
38 default: true
39 },
40 size: String,
41 disabled: Boolean,
42 validateOnRuleChange: {
43 type: Boolean,
44 default: true
45 },
46 hideRequiredAsterisk: {
47 type: Boolean,
48 default: false
49 }
50 },
51 watch: {
52 rules() {
53 if (this.validateOnRuleChange) {
54 this.validate(() => {});
55 }
56 }
57 },
58 data() {
59 return {
60 fields: []
61 };
62 },
63 created() {
64 this.$on('el.form.addField', (field) => {
65 if (field) {
66 this.fields.push(field);
67 }
68 });
69 /* istanbul ignore next */
70 this.$on('el.form.removeField', (field) => {
71 if (field.prop) {
72 this.fields.splice(this.fields.indexOf(field), 1);
73 }
74 });
75 },
76 methods: {
77 resetFields() {
78 if (!this.model) {
79 process.env.NODE_ENV !== 'production' &&
80 console.warn('[Element Warn][Form]model is required for resetFields to work.');
81 return;
82 }
83 this.fields.forEach(field => {
84 field.resetField();
85 });
86 },
87 clearValidate(props = []) {
88 const fields = props.length
89 ? this.fields.filter(field => props.indexOf(field.prop) > -1)
90 : this.fields;
91 fields.forEach(field => {
92 field.clearValidate();
93 });
94 },
95 validate(callback) {
96 if (!this.model) {
97 console.warn('[Element Warn][Form]model is required for validate to work!');
98 return;
99 }
100
101 let promise;
102 // if no callback, return promise
103 if (typeof callback !== 'function' && window.Promise) {
104 promise = new window.Promise((resolve, reject) => {
105 callback = function(valid) {
106 valid ? resolve(valid) : reject(valid);
107 };
108 });
109 }
110
111 let valid = true;
112 let count = 0;
113 // 如果需要验证的fields为空,调用验证时立刻返回callback
114 if (this.fields.length === 0 && callback) {
115 callback(true);
116 }
117 let invalidFields = {};
118 this.fields.forEach(field => {
119 field.validate('', (message, field) => {
120 if (message) {
121 valid = false;
122 }
123 invalidFields = objectAssign({}, invalidFields, field);
124 if (typeof callback === 'function' && ++count === this.fields.length) {
125 callback(valid, invalidFields);
126 }
127 });
128 });
129
130 if (promise) {
131 return promise;
132 }
133 },
134 validateField(prop, cb) {
135 let field = this.fields.filter(field => field.prop === prop)[0];
136 if (!field) { throw new Error('must call validateField with valid prop string!'); }
137
138 field.validate('', cb);
139 }
140 }
141 };
142 </script>
form-item.vue
1 <template>
2 <div class="el-form-item" :class="[{
3 'el-form-item--feedback': elForm && elForm.statusIcon,
4 'is-error': validateState === 'error',
5 'is-validating': validateState === 'validating',
6 'is-success': validateState === 'success',
7 'is-required': isRequired || required,
8 'is-no-asterisk': elForm && elForm.hideRequiredAsterisk
9 },
10 sizeClass ? 'el-form-item--' + sizeClass : ''
11 ]">
12 <label :for="labelFor" class="el-form-item__label" :style="labelStyle" v-if="label || $slots.label">
13 <slot name="label">{{label + form.labelSuffix}}</slot>
14 </label>
15 <div class="el-form-item__content" :style="contentStyle">
16 <slot></slot>
17 <transition name="el-zoom-in-top">
18 <slot
19 v-if="validateState === 'error' && showMessage && form.showMessage"
20 name="error"
21 :error="validateMessage">
22 <div
23 class="el-form-item__error"
24 :class="{
25 'el-form-item__error--inline': typeof inlineMessage === 'boolean'
26 ? inlineMessage
27 : (elForm && elForm.inlineMessage || false)
28 }"
29 >
30 {{validateMessage}}
31 </div>
32 </slot>
33 </transition>
34 </div>
35 </div>
36 </template>
37 <script>
38 import AsyncValidator from 'async-validator';
39 import emitter from 'element-ui/src/mixins/emitter';
40 import objectAssign from 'element-ui/src/utils/merge';
41 import { noop, getPropByPath } from 'element-ui/src/utils/util';
42
43 export default {
44 name: 'ElFormItem',
45
46 componentName: 'ElFormItem',
47
48 mixins: [emitter],
49
50 // 通过provider向子孙后代注入变量elform,讲this(即form-item)注入给子孙后代,后代通过获取此变量获取form中的各种配置,如size属性等
51 provide() {
52 return {
53 elFormItem: this
54 };
55 },
56
57 inject: ['elForm'],
58
59 props: {
60 label: String,
61 labelWidth: String,
62 prop: String,
63 required: {
64 type: Boolean,
65 default: undefined
66 },
67 rules: [Object, Array],
68 error: String,
69 validateStatus: String,
70 for: String,
71 inlineMessage: {
72 type: [String, Boolean],
73 default: ''
74 },
75 showMessage: {
76 type: Boolean,
77 default: true
78 },
79 size: String
80 },
81 watch: {
82 error: {
83 immediate: true,
84 handler(value) {
85 this.validateMessage = value;
86 this.validateState = value ? 'error' : '';
87 }
88 },
89 validateStatus(value) {
90 this.validateState = value;
91 }
92 },
93 computed: {
94 labelFor() {
95 return this.for || this.prop;
96 },
97 labelStyle() {
98 const ret = {};
99 if (this.form.labelPosition === 'top') return ret;
100 const labelWidth = this.labelWidth || this.form.labelWidth;
101 if (labelWidth) {
102 ret.width = labelWidth;
103 }
104 return ret;
105 },
106 contentStyle() {
107 const ret = {};
108 const label = this.label;
109 if (this.form.labelPosition === 'top' || this.form.inline) return ret;
110 if (!label && !this.labelWidth && this.isNested) return ret;
111 const labelWidth = this.labelWidth || this.form.labelWidth;
112 if (labelWidth) {
113 ret.marginLeft = labelWidth;
114 }
115 return ret;
116 },
117 form() {
118 let parent = this.$parent;
119 let parentName = parent.$options.componentName;
120 while (parentName !== 'ElForm') {
121 if (parentName === 'ElFormItem') {
122 this.isNested = true;
123 }
124 parent = parent.$parent;
125 parentName = parent.$options.componentName;
126 }
127 return parent;
128 },
129 fieldValue() {
130 const model = this.form.model;
131 if (!model || !this.prop) { return; }
132
133 let path = this.prop;
134 if (path.indexOf(':') !== -1) {
135 path = path.replace(/:/, '.');
136 }
137
138 return getPropByPath(model, path, true).v;
139 },
140 isRequired() {
141 let rules = this.getRules();
142 let isRequired = false;
143
144 if (rules && rules.length) {
145 rules.every(rule => {
146 if (rule.required) {
147 isRequired = true;
148 return false;
149 }
150 return true;
151 });
152 }
153 return isRequired;
154 },
155 _formSize() {
156 return this.elForm.size;
157 },
158 elFormItemSize() {
159 return this.size || this._formSize;
160 },
161 sizeClass() {
162 return this.elFormItemSize || (this.$ELEMENT || {}).size;
163 }
164 },
165 data() {
166 return {
167 validateState: '',
168 validateMessage: '',
169 validateDisabled: false,
170 validator: {},
171 isNested: false
172 };
173 },
174 methods: {
175 validate(trigger, callback = noop) {
176 this.validateDisabled = false;
177 const rules = this.getFilteredRule(trigger);
178 if ((!rules || rules.length === 0) && this.required === undefined) {
179 callback();
180 return true;
181 }
182
183 this.validateState = 'validating';
184
185 const descriptor = {};
186 if (rules && rules.length > 0) {
187 rules.forEach(rule => {
188 delete rule.trigger;
189 });
190 }
191 descriptor[this.prop] = rules;
192
193 const validator = new AsyncValidator(descriptor);
194 const model = {};
195
196 model[this.prop] = this.fieldValue;
197
198 validator.validate(model, { firstFields: true }, (errors, invalidFields) => {
199 this.validateState = !errors ? 'success' : 'error';
200 this.validateMessage = errors ? errors[0].message : '';
201
202 callback(this.validateMessage, invalidFields);
203 this.elForm && this.elForm.$emit('validate', this.prop, !errors, this.validateMessage || null);
204 });
205 },
206 clearValidate() {
207 this.validateState = '';
208 this.validateMessage = '';
209 this.validateDisabled = false;
210 },
211 resetField() {
212 this.validateState = '';
213 this.validateMessage = '';
214
215 let model = this.form.model;
216 let value = this.fieldValue;
217 let path = this.prop;
218 if (path.indexOf(':') !== -1) {
219 path = path.replace(/:/, '.');
220 }
221
222 let prop = getPropByPath(model, path, true);
223
224 this.validateDisabled = true;
225 if (Array.isArray(value)) {
226 prop.o[prop.k] = [].concat(this.initialValue);
227 } else {
228 prop.o[prop.k] = this.initialValue;
229 }
230
231 this.broadcast('ElTimeSelect', 'fieldReset', this.initialValue);
232 },
233 getRules() {
234 let formRules = this.form.rules;
235 const selfRules = this.rules;
236 const requiredRule = this.required !== undefined ? { required: !!this.required } : [];
237
238 const prop = getPropByPath(formRules, this.prop || '');
239 formRules = formRules ? (prop.o[this.prop || ''] || prop.v) : [];
240
241 return [].concat(selfRules || formRules || []).concat(requiredRule);
242 },
243 getFilteredRule(trigger) {
244 const rules = this.getRules();
245
246 return rules.filter(rule => {
247 if (!rule.trigger || trigger === '') return true;
248 if (Array.isArray(rule.trigger)) {
249 return rule.trigger.indexOf(trigger) > -1;
250 } else {
251 return rule.trigger === trigger;
252 }
253 }).map(rule => objectAssign({}, rule));
254 },
255 onFieldBlur() {
256 this.validate('blur');
257 },
258 onFieldChange() {
259 if (this.validateDisabled) {
260 this.validateDisabled = false;
261 return;
262 }
263
264 this.validate('change');
265 }
266 },
267 mounted() {
268 if (this.prop) {
269 this.dispatch('ElForm', 'el.form.addField', [this]);
270
271 let initialValue = this.fieldValue;
272 if (Array.isArray(initialValue)) {
273 initialValue = [].concat(initialValue);
274 }
275 Object.defineProperty(this, 'initialValue', {
276 value: initialValue
277 });
278
279 let rules = this.getRules();
280
281 if (rules.length || this.required !== undefined) {
282 this.$on('el.form.blur', this.onFieldBlur);
283 this.$on('el.form.change', this.onFieldChange);
284 }
285 }
286 },
287 beforeDestroy() {
288 this.dispatch('ElForm', 'el.form.removeField', [this]);
289 }
290 };
291 </script>