Prepend content
For Vue two-way binding we must not be unfamiliar, the basic things why to mention? Because this is the foundation of encapsulating components!
Here’s a question to ask ๐ try your basic ๐
pageIndex.vue
<template>
<el-card>
<dynamic-form v-model="testModel" />
</el-card>
</template>
<script>
export default {
/ /... Omit non-critical code
data() {
return {
testModel: 'Init Value'}}}</script>
Copy the code
dynamic-form.vue
<template>
<div>
<el-input v-model="value"></el-input>
</div>
</template>
<script>
export default {
name: 'DynamicForm'.props: {
value: {}}}</script>
Copy the code
Rendering ๐
The above code has two obvious problems:
- We are violating vUE’s single data stream and the child component should not change the parent component’s value
- After the value of the child component v-Model binding changes, the parent component does not achieve the effect of bidirectional binding
There are usually two solutions
Plan a
pageIndex.vue
- <dynamic-form v-model="testModel" />
+ <dynamic-form :value.sync="testModel" />
Copy the code
dynamic-form.vue
- <el-input v-model="value"></el-input>
+ <el-input v-model="newValue"></el-input>
+ export default {
+ computed: {
+ newValue: {
+ get({ value }) {
+ return value
+},
+ set(newVal) {
+ this.$emit('update:value', newVal)
+}
+}
+},
+}
Copy the code
Rendering ๐
This solution is used more often when we encapsulate business components based on open source component libraries. Here is why this is ok
We use the value of the parent component by calculating the property get. Set throws a new value upward, so that the child component does not change the value of the parent component directly. The parent component uses the.sync parent component to change the value of the child component
Sync syntax sugar is equivalent to this
pageIndex.vue
<dynamic-form :value.sync="testModel" />
<dynamic-form :value="testModel" @update:value="testModel = $event" />
Copy the code
This approach is equivalent, and the.sync approach is used a lot, often in the case of “component nesting”, but essentially it does the same thing and works the same way: for example, v-Model can customize the prop/event of the model
V – model
pageIndex.vue
<dynamic-form
v-model="testModel"
@customValue="testModel = $event"
/>
Copy the code
dynamic-form.vue
<template>
<div>
<el-input v-model="newValue"></el-input>
</div>
</template>
<script>
export default {
name: 'DynamicForm'.model: {
prop: 'customValue'.event: 'customEvent'
},
props: {
customValue: {},},computed: {
newValue: {
get({ customValue }) {
return customValue
},
set(newVal) {
this.$emit('customValue', newVal)
}
}
},
}
</script>
Copy the code
Sync way
pageIndex.vue
<dynamic-form
:value="testModel"
:eventName="eventName"
v-on:[eventName] ="testModel = $event"
/>
<script>
export default {
data() {
return {
eventName: 'update:newValue',}}}Copy the code
dynamic-form.vue
<template>
<div>
<el-input v-model="newValue"></el-input>
</div>
</template>
<script>
export default {
name: 'DynamicForm'.model: {
prop: 'customValue'.event: 'customEvent'
},
props: {
customValue: {},},computed: {
newValue: {
get({ customValue }) {
return customValue
},
set(newVal) {
this.$emit('customValue', newVal)
}
}
},
}
</script>
Copy the code
It’s easy to see that the principles are the same, so why design an API in Vue2 that does the same thing as the V-Model?
V-model can’t be used more than once on the same component! Sync is fine, I just throw a different ‘update: XXX ‘in the child component!
In Vue3, the V-model passes arguments to be used multiple times, and you can also customize modifiers, so this syntax is eliminated in Vue3. For details on Vue3, see ๐ถ
Scheme 2
pageIndex.vue
<dynamic-form
v-model="testModel"
/>
Copy the code
dynamic-form.vue
<el-input
v-bind="$attrs"
v-on="$listeners"
>
</el-input>
Copy the code
Rendering ๐
Principle of bidirectional binding: through v-bind=”$attrs”, the value of the parent component is transmitted to el-input to realize the value binding; V-on =”$Listeners “to transmit the @input events implemented internally in the V-Model to el-Input for value responses.
This is the most flexible way to encapsulate components, because in our daily work, the value of the V-Model bound to the custom component is usually not the value of the base type, we are most bound to the object, what will happen to the V-Model bound object?
Teach you how to gracefully encapsulate dynamic forms
The basic function
indexPage.vue
<template>
<el-card>
<dynamic-form
v-model="formModel"
v-bind="formConfig"
/>
</el-card>
</template>
<script>
import DynamicForm from './common/DynamicForm'
export default {
name: 'Model'.components: {
DynamicForm
},
data() {
return {
formModel: {},
formConfig: {
labelWidth: '100px'.formItemList: [{label: 'Username'.type: 'input'.prop: 'userName'}, {label: 'password'.type: 'password'.prop: 'passWord'.'show-password': true
},
{
label: 'note'.type: 'textarea'.prop: 'remark'.maxlength: 400.'show-word-limit': true.'auto-size': { minRows: 3.maxRows: 4}}},}</script>
Copy the code
dynamic-form.vue
<template>
<el-form v-bind="$attrs">
<template v-for="formItem of formItemList">
<dynamic-form-item
v-model="value[formItem.prop]"
:key="formItem.prop"
v-bind="formItem"
/>
</template>
</el-form>
</template>
<script>
import DynamicFormItem from './DynamicFormItem'
export default {
name: 'DynamicForm'.props: {
value: {
type: Object.required: true
},
formItemList: {
type: Array.default: () = >([])}},components: {
DynamicFormItem,
},
created() {
this.initFormItemValue()
},
methods: {
initFormItemValue() {
constformModel = { ... this.value }this.formItemList.forEach((item) = > {
// Set the default value
const { prop, value } = item
if (formModel[prop] === undefined || formModel[prop] === null) {
formModel[prop] = value
}
})
this.$emit('input', { ...formModel })
},
}
}
</script>
Copy the code
dynamic-form-item.vue
<template>
<el-form-item :label="label">
<el-input
v-if="inputType.includes(type)"
:type="type"
v-bind="$attrs"
v-on="$listeners"
>
</el-input>
</el-form-item>
</template>
<script>
/* These configuration items can be extracted into a separate configuration file */
const inputType = [/* Input supported types */]
const selectType = [/* select supported types */]
const customType = [/* Custom form item components */]
export default {
name: 'DynamicFormItem'.props: {
label: {
type: String.required: true
},
type: {
type: String.require: true.validator: (type) = > {
return [...inputType, ...selectType, ...customType]
.includes(type)
}
}
},
data() {
return {
inputType: Object.freeze(inputType),
}
},
}
</script>
Copy the code
Rendering ๐
Tips you can Get:
- through
v-for
Template reduces the creation of unnecessary DOM nodes and can also be implemented this wayv-for/v-if
Used together - No responsive data is required to pass through
Object.freeze
Frozen, Vue does not add this property when initializingget/set
, which is also the point of performance optimization
Support depth attribute
In actual development, the properties of the form object may also be objects. Take a common CRM/ERP system for example, such as **[lease contract, deposit agreement, project setting]**, etc., these references to the master data are basically cascading references, this time the form component needs to support this function
indexPage.vue
<script>
export default {
data() {
return {
formModel: {
depositAgreement: {
depositAmount: 100.businessType: 'traditionWork'.signDate: '2021-7-18'}},formConfig: {
labelWidth: '100px'.formItemList: [{label: 'value'.type: 'number'.prop: 'depositAgreement.depositAmount'}, {label: 'Protocol Type'.type: 'select'.prop: 'depositAgreement.businessType'.options: [{dictKey: 'traditionWork'.dictValue: 'Dedicated office'},}, {label: 'Date of Signature'.type: 'depositAgreement.date'.prop: 'signDate',}]}}},}</script>
Copy the code
Rendering ๐
Supporting the depth attribute requires two methods
_getDeepAttr(model, deepPath)
_setDeepAttr(model, deepPath, val)
_getDeepAttr implementation
let data = {
obj: {
v1: 'v1-val'
}
}
_getDeepAttr(data, 'obj.v1') // => v1-val
Copy the code
/ * * *@description: Depth acquisition property *@param {Object} Model Form object *@param {String} DeepPath Depth property *@return {any} * /
function _getDeepAttr(model, deepPath) {
if(! deepPath)return
if (deepPath.indexOf('. ')! = = -1) {
const paths = deepPath.split('. ')
let current = model
let result = null
for (let i = 0, j = paths.length; i < j; i++) {
const path = paths[i]
if(! current)break
if (i === j - 1) {
result = current[path]
break
}
current = current[path]
}
return result
} else {
return model[deepPath]
}
}
Copy the code
_setDeepAttr implementation
let data = {
obj: {
dep1: {
v1: 'v1'.v2: 'v2'
},
name: 'north song'
}
}
_setDeepAttr(data, 'obj.dep1.v1'.'v1-newVal')
// data.obj.dep1.v1 => v1-newVal
// data.obj.dep1.v2 => v2
Copy the code
/ * * *@description: Sets the depth property *@param {Object} Model Form object *@param {String} DeepPath Depth property *@param {any} Val Specifies the value to be set */
function _setDeepAttr(model, deepPath, val) {
/ / path
let paths = deepPath.split('. ')
// The target value, which will hold all properties that match the path
let targetVal = {}
// Find the prop of each object successively
let pathsNew = [...paths]
let prop
for (let i = paths.length - 1, j = i; i >= 0; i--) {
prop = paths[i]
// The value to be set at the last layer
if (i === j) {
targetVal[prop] = val
} else if (i === 0) {
// Get the root value first
const originalVal = model[prop]
// The root attribute of the first layer needs to be replaced directly
model[prop] = Object.assign(originalVal, targetVal)
} else {
// Update values for each level (excluding stored values)
let curDeppObj = _getDeepAttr(model, pathsNew.join('. '))
// Store the values of the current hierarchy
targetVal[prop] = Object.assign({}, curDeppObj, targetVal)
// Delete the value stored in the previous path
delete targetVal[paths[i + 1]]}// Remove the processed path
pathsNew.pop()
}
}
Copy the code
_getDeepAttr what good talk, here mainly talk about the implementation of _setDeepAttr idea
Implementation approach
- Split path: Get the attribute name under each level, because you need to go down layer by layer to find the array of split levels for backup
- Reverse loop: Our ultimate goal is to assign a value to the attributes of the last level
j
To determine if the current loop is the last layer - A judgment condition in circulatory body
i === j
: The last layer, which is the first loop, we will directlyTemporary objectsSet to the specified valuei === 0
At the first and last level of the loop, we read the model[first level property] and get the root property, which is obviously an object, and replace the root property with a resetTemporary objects- Intermediate level processing: It is necessary to obtain the property object of the current level, merge the objects of the current level and temporary objects, only affecting the target attribute, other attributes of the same level should not be lost, and finally remove the attributes of the previous level
Adjustable to ๐
Strongly recommended to follow the debugging, a learn waste ~๐
dynamic-form.vue
<template>
<el-form v-bind="$attrs">
<template v-for="formItem of formItemList">
<dynamic-form-item
- v-model="value[formItem.prop]"
+ :value="value[formItem.prop] | _formatterItemVal(value, formItem, _getDeepAttr)"
+ @input="bindItemValue(value, formItem, $event)"
:key="formItem.prop"
v-bind="formItem"
/>
</template>
</el-form>
</template>
<script>
import DynamicFormItem from './DynamicFormItem'
export default {
name: 'DynamicForm',
props: {
value: {
type: Object,
required: true
},
formItemList: {
type: Array,
default: () => ([])
}
},
components: {
DynamicFormItem,
},
- created() {
- this.initFormItemValue()
-},
+ filters: {
+ / * *
+ * @description:
+ * @param {any} curVal The value of the current form (null if depth attribute)
+ * @param {Object} value Form value
+ * @param {Object} item Configuration item of the current form
+ * @param {Function} _getDeepAttr Format method of item value
+ * @return {*}
+ * /
+ _formatterItemVal: (curVal, value, item, _getDeepAttr) => {
+ if (curVal) {
+ return curVal
+}
+
+ // Down is the case of the depth attribute, which needs to be formatted to get the value
+
+ // Provides the user with a method to format a value: trim, number, etc
+ const formater = item.formatter
+
+ return typeof formater === 'function'
+? formater(_getDeepAttr(value, item.prop))
+ : _getDeepAttr(value, item.prop)
+}
},
methods: {
- initFormItemValue() {
- const formModel = { ... this.value }
- this.formItemList.forEach((item) => {
- // Set the default value
- const { prop, value } = item
- if (formModel[prop] === undefined || formModel[prop] === null) {
- formModel[prop] = value
-}
-})
- this.$emit('input', { ... formModel })
-},
+ / * *
+ * @description: Implements bidirectional binding
+ * @param {Object} Model Form Object
+ * @param {String} deepPath
+ * @param {any} val Specifies the value to set
+ * /
+ bindItemValue(model, item, val) {
+ // The depth attribute needs to be formatted
+ if (~item.prop.indexOf('.')) {
+ this._setDeepAttr(model, item.prop, val)
+ } else {
+ model[item.prop] = val
+}
+
+ const _model = { ... model }
+ this.$emit('input', _model)
+}
+ _getDeepAttr(){ /*... * /}
+ _setDeepAttr(){/*... * /}
}
}
</script>
Copy the code
dynamic-form-item.vue
<template>
<el-input
v-if="inputType.includes(type)"
:type="type"
v-bind="$attrs"
v-on="$listeners"
/>
<dynamic-select
v-else-if="selectType.includes(type)"
v-bind="$attrs"
v-on="$listeners"
/>
<el-date-picker
v-else-if="dateType.includes(type)"
v-bind="$attrs"
v-on="$listeners"
/>
</template>
Copy the code
The dynamic-select component is a wrapped extension that allows select/treeSelect to dynamically retrieve options via a URL. See this column for more information
Custom content (Slots)
Front knowledge ๐ elaborate slot
After wrapping el-form, the wrapped components also need to support form-item slots. There are three:
The most customized part of our work is the form items. Like this: Using business-encapsulated components as form items
Whatever the component, the form item has to be customizable, and that’s where the slot comes in, right
- Two rendering modes are supported
- Template Indicates the mode of a template
- How to write the render function in a configuration item
Priority: We follow the same rules as vue, the render function is superior to the template template
{
label: 'Custom'.type: 'slot'.prop: 'custom'
}
Copy the code
Template writes slots
<dynamic-form
v-model="formModel"
v-bind="formConfig"
>
<template #custom="{value}">
<el-button>{{value}}</el-button>
</template>
</dynamic-form>
Copy the code
How configuration items write the render function
{
label: 'Custom'.type: 'slot'.prop: 'custom'.render: ({ value, $createElement: h }) = > {
return h('el-button', value)
}
}
Copy the code
dynamic-form.vue
The dynamic-form component changed little, adding this entry to inject the current instance down
export default {
provide() {
return {
formThis: this}}},Copy the code
dynamic-form-item.vue
<template>
<! -- Add support for type: slot -->
<slot-content
v-else-if="isRenderSlot({type, prop})"
v-bind="$attrs"
:render="generateSlotRender()"
/>
</template>
<script>
export default {
/ * * *@description: whether to render custom content *@param {String} type* /
isRenderSlot({ type, prop }) {
if(type ! = ='slot') {
return false
}
/* Support two render modes: 1. How to write the render function in a configuration item */
return [
typeof this.formThis.$scopedSlots[prop],
typeof this.$attrs.render
].includes('function')},// The priority of the render function to render custom content: how to write the render function in the configuration item > how to template
generateSlotRender() {
// normalizeScopedSlot
return ({ value, $createElement }) = > {
// Pass parameters to the slot
constslotScope = { ... this.$attrs, value, $createElement }const renderSlot = this.$attrs.render || this.formThis.$scopedSlots[this.prop]
return renderSlot(slotScope)
}
},
}
</script>
Copy the code
Tips you can Get:
- It takes longer to read than to write
||
It’s a little bit clearer
[
typeof xxx,
typeof xxxx,
].includes('function')
Copy the code
- The method in the $scopeSlots slot is used to return VNode. We can pass parameters to pass data to the slot
Rendering ๐
Label/ERROR built-in slot support
Before implementing these two slots, you need to change the el-form-item to make it easier for users to configure slots using the depth attribute.
Let's say the user sets {prop: 'depositAgreement.depositAmount'. }Copy the code
What do users need to do when they want to customize form items using dynamic-form components
<! - depistiAmountSlot depistiAmountSlot is defined in the data variables: 'depositAgreement. DepositAmount - >
<template# [depistiAmountSlot] ></template>
<! -- This is not the way -->
<template #depositAgreement.depositAmount></template>
Copy the code
When we use a template to write components, we compile everything we write. If there are special characters in the template, we need to do extra logic, so we can enjoy the shortcut of template syntax while losing some flexibility.
dynamic-form-item.vue
<template>
<el-form-item
:rules="rules"
:label="label"
:prop="prop"
>
<template
v-if="formThis.$scopedSlots[realProp + 'Label']"
#label
>
<slot-content
v-bind="_attrs"
:render="formThis.$scopedSlots[realProp + 'Label']"
/>
</template>
<template
v-if="formThis.$scopedSlots[realProp + 'Error']"
#error="{error}"
>
<slot-content
v-bind="{... _attrs, error}"
:render="formThis.$scopedSlots[realProp + 'Error']"
/>
</template>
</el-form-item>
</template>
<script>
export default {
computed: {
// The data passed to the slot
_attrs({ value, label, rules, realProp, $attrs }) {
return{ value, label, rules, realProp, ... $attrs } },Data. val => dataVal
realProp({ prop }) {
return prop.replace(/ \. ([^.] +) +? /g.(. arg) = > {
const [, execProp] = arg
return execProp[0].toUpperCase() + execProp.substr(1)})}}</script>
Copy the code
Rendering ๐
Form validation
Automatic validation support
If it is not a deep attribute, it can be implemented by adding model to e-form and prop/rules to el-form-item, but if it is a deep attribute, it needs to do some extra processing.
{
label: 'value'.type: 'text'.prop: 'depositAgreement.depositAmount'.rules: [{required: true.message: 'The amount cannot be empty'.trigger: 'blur'
},
{
validator: (rule, val, callback) = > {
if (val < 100) {
return callback(new Error('Amount not less than 100'))}return callback()
},
trigger: ['change'.'blur']]}},Copy the code
dynamic-form.vue
{
computed: {
// The item to be checked
validateItem({ formItemList }) {
return formItemList.filter(i= > i.rules)
},
// The data source of the passed form is used to handle the depth attribute verification problem
model({ validateItem, value, isDeepPath, _getDeepAttr }) {
const_model = { ... value }if(! validateItem.length)return _model;
validateItem.forEach(({ prop }) = > {
if (isDeepPath(prop)) {
_model[prop] = _getDeepAttr(_model, prop, _getDeepAttr)
}
})
return _model
}
},
}
Copy the code
Rendering ๐
The model we calculated is only used for the model attribute of El-Form, and only when the user sets the depth attribute and the verification model, the verification attribute will be calculated
Manual verification support
Just set the ref attribute to the el-form. For user customization, the ref is not written to death
{
props: {
// Customize elForm's ref attribute
elFormRef: {
type: String.default: 'elForm'}},methods: {
/ * * *@description: Validates the whole form */
validate(callback) {
return this.$refs[this.elFormRef].validate(callback)
},
/ * * *@description: Single field verification */
validateField(props, callback) {
return this.$refs[this.elFormRef].validateField(props, callback)
},
/ * * *@description: clears checksum */
clearValidate(props) {
return this.$refs[this.elFormRef].clearValidate(props)
},
/ * * *@description: Form reset, clear validation */
resetFields() {
return this.$refs[this.elFormRef].resetFields()
},
}
}
Copy the code
In order to facilitate user operation, do not write the long $refs layer reference, let the user through the reference method
V-on =”$linsters” pit, be careful!
V -on=”$listeners”; v-on=”$listeners”
use
<dynamic-form
ref="dynamicForm"
v-model="formModel"
v-bind="formConfig"
@input="handleInput"
>
Copy the code
dynamic-form.vue
<dynamic-form-item
:key="formItem.prop"
:value="value[formItem.prop] | _formatterItemVal(value, formItem, _getDeepAttr)"
v-bind="formItem"
+ v-on="$listeners"
@input="bindItemValue(value, formItem, $event)"
/>
Copy the code
Take a closer look at this code, really no problem??
$listeners = $listeners = $listeners Is there a problem?
The story begins like this:
The process of interaction between El-Input and dynamic-form-item:
- We change the input field value
- El-input throws an input event and carries the value of the current form item base value of type Sting)
- The dynamic-form-item component writes v-ON =”$listeners” from EL-Input to send all events to El-Input
- We’re using dynamic-form-item and we actually write our own handler for the input event
Here we have implemented a bidirectional binding between dynamic-item and El-input
Dynamic-form-item and dynamic-form interaction
- Dynamic-form we update the value of the form object by listening for @input, which form item is currently changing, and throw an input event
- The user uses the input listening event and event handling method implemented internally by V-Model to help us realize two-way binding
The story here is very complete.
But I wrote an @input event outside myself, and this event was leaked to el-Input! So if the form item changes I have to listen for the v-Model’s internal implementation of the input event handling not to be reassigned?
The result internally reassigns the formModel in v-Model =”formModel”, from an object to the base value (the value thrown by el-Input).
That’s where the story ends. The page has an error
So dynamic-form has two Input events and it’s a program. How do you see that? Dynamic-form print this. Let’s see
dynamic-form.vue
A little bit of a makeover
export default {
// Resolve the issue of double binding for $Listeners
model: {
// Customize the event name of the V-Model listener
event: 'dyInput'}},Copy the code
If we toss the event, we’ll do it this way
this.$emit('dyInput', {... value})Copy the code
Rendering ๐
At this point, the function is complete, but the external listener input event can not be specific to the form item, this event is basically useless! We need to put a flag on the outside to tell the outside world that it was that form item that threw the event.
There is a problem, the form item as projects grow sure type more and more, the event type is more and more, a lot of form item throw event names is the same, the outside is not too good to control these events, all events are written in the book of this component, and want to distinguish between events exactly which component have to throw out the input output inside the event? That’s not good…
The sample code
<dynamic-form
ref="dynamicForm"
v-model="formModel"
v-bind="formConfig"
@input="handleInput"
@change="handleChange"
@visible-change="handleVisibleChange"
@.
/>
Copy the code
We want only the input/change event on the dynamic-form, and all other events to be written in their respective configuration items
{
label: 'Protocol Type'.type: 'select'./ /... Omit the options,props configuration
prop: 'depositAgreement.businessType'.listeners: {
'visible-change': (isShow) = > {
console.log(isShow, 'select'); }}},Copy the code
dynamic-form.vue
<dynamic-form-item
v-on="_listeners"
/>
<script>
export default {
computed: {
// Events that can be listened for in dynamic-form
_listeners({ $listeners }) {
// Support forward transparently transmitted events
let supportEvent = ['input'.'change']
return supportEvent.reduce((_listeners, eventName) = > {
_listeners[eventName] = $listeners[eventName] || (() = >{})return _listeners
}, {})
}
}
}
</script>
Copy the code
dynamic-form-item.vue
<dynamic-form-item
v-on="_listeners"
/>
<script>
export default {
computed: {
// Integrate the listeners in the configuration item, and finally transmit the events transparently downward
onEvent({ $listeners, listeners }) {
// Events in configuration items have a higher priority than events listened for in dynamic-form
return{... $listeners, ... listeners } } } }</script>
Copy the code
Extension: V – Model source analysis
transform component v-model data into props & events
src\core\vdom\create-component.js
// transform component v-model data into props & events
// V-model special processing
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
Copy the code
transformModel
// transform component v-model info (value and callback) into
// prop and event handler respectively.
function transformModel (options, data: any) {
/* model: { prop: 'xx', event: 'xxx, } */
const prop = (options.model && options.model.prop) || 'value'
const event = (options.model && options.model.event) || 'input'
// Assign a value to the attrs prop of the specified model; (data.attrs || (data.attrs = {}))[prop] = data.model.valueconst on = data.on || (data.on = {})
const existing = on[event]
const callback = data.model.callback
if (isDef(existing)) {
if (
Array.isArray(existing)
? existing.indexOf(callback) === -1: existing ! == callback ) { on[event] = [callback].concat(existing) } }else {
// Add a built-in event to the event
on[event] = callback
}
}
Copy the code
This is the end of this article. Most of the form components have been implemented, and the form layout has not been implemented. In fact, you need to use the el-Row /el-col package for the form items, and there is no special logic to post the code
Write in the last
If there is a piece of writing in the article is not very good or there are questions welcome to point out, I will also keep modifying in the following article. I hope I can grow with you as I progress. Those who like my article can also pay attention to it
I’ll be grateful to the first people to pay attention. At this time, you and I, young, packed lightly; And then, rich you and I, full of it.
Practice makes perfect, shortage in one
series
Render function “Component encapsulation-dynamic-select”
Render function “Component encapsulation-dynamic-input”
Render function “Component encapsulation-dynamic-checkbox”
Teach you how to play the render function “Component encapsulation-dynamic-Cascader”
The articles
The idea of modularity is to build the middle and background projects
The first chapter is to develop the background project with modularity
ใ Front-end system ใ Talk about understanding EventLoop from an interview question (updated analysis of four advanced questions)
[Front end system] Build a great oaks from the foundation
[Front-end System] The application scenario of re in development is not just rule verification
“Functional programming practical scenario | nuggets technical essay – double festival special article”
ใ suggested favorites ใ CSS obscure points are all here