Vue-3 props, $emit, slot, render, JSX and createElement

Props and $emit

When using Vue to develop a project, we divide the content of the project into modules, but sometimes there is data interaction between modules. In real project development, father-son and brother components need to pass values to each other. The most traditional way to pass values is props and $emit.

I. Props

Prop is some custom features that you can register on components. When a value is passed to a prop feature, it becomes an attribute of that component instance. To pass a title to the blog component, we can include it in the list of acceptable props for the component with a props option.

1. Examples of props passing values

<!-- Parent component -->
<template>
    <div id="app">
        <!-- The parent component will Home Components are called three times, and different parameters are passed in each call. -->
        <Home msg="hello world!" />
        <Home msg="tom" />
        <Home msg="lion king" />
    </div>
</template>

<script>
import Home from './components/Home'

export default {
    name: 'App',
    components: { Home },
    data() {
        return { }
    },
}
</script>
<!-- Subcomponents -->
<template>
    <div class="home">
        <!-- props In the data received, just render it directly. -->
        <h3>{{ msg }}</h3>
    </div>
</template>

<script>
    export default {
        name: "Home",
        // Accept the parameters passed in by the outside world when calling the current build through props
        props: ['msg']
    }
</script>

Different parameters can also be passed to subcomponents:

<!-- ... -->
<Home msg="hello world!" />
<Home name="tom" />
<Home ani="lion king" />
<!-- ... -->
<script>
    export default {
        name: "Home",
        // props accepts multiple parameters
        props: ['msg','name','ani']
    }
</script>

2. Pros type validation
If we want each prop to have a specified value type and list prop as an object, the names and values of these properties are prop's respective names and types.

// ...
// Use props to receive data and specify the type of data
props: {
    title: String,
    likes: Number,
    isPublished: Boolean,
    commentIds: Array,
    author: Object,
    callback: Function,
    contactsPromise: Promise // or any other constructor
}
// ...

To make passing values more flexible, vue provides the following props authentication methods:

props: {
    // Basic type checking (`null'and `undefined' will pass any type validation)
    propA: Number,
    // Multiple possible types
    propB: [String, Number],
    // Mandatory strings
    propC: {
      type: String,
      required: true
    },
    // Numbers with default values
    propD: {
      type: Number,
      default: 100
    },
    // Objects with default values
    propE: {
      type: Object,
      // Default values for objects or arrays must be obtained from a factory function
      default: function () {
        return { message: 'hello' }
      }
    },
    // Custom Validation Function
    propF: {
      validator: function (value) {
        // This value must match one of the following strings
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }

If a requirement is not met, Vue will warn you in the browser console. This is especially helpful in developing a component that will be used by others.

Note:

  • All props make a single downlink binding between their father and son props: updates to the parent prop flow downward to the child components, but not vice versa. This prevents accidental changes in the state of the parent component from the child component, resulting in incomprehensible data flow to your application.
  • Note that those props are validated before a component instance is created, so the properties of the instance (such as data, computed, etc.) are not available in default or validator functions.
  • Because components cannot always predict the parameters that other components will pass in when they call the current component, vue also allows components to pass in some non-props features when they are called. These features are automatically added to the root element of the component.

2. $emit

A child component is called by a parent component. When a child component triggers a function, it needs a parent component response, using $emit.

1. $emit simple example

<!-- Subcomponents -->
<template>
  <div class="child">
    <button @click="ev">Buttons in subcomponents</button>
  </div>
</template>
<script>
  export default {
      name: 'Child',
      data() {
          return {
              msg: 'I am a data from child component'
          }
      },
      methods: {
          ev() {
              // When the button of the current component is clicked, an event is thrown out, who calls the current component and who responds to the event when the button of the current component is clicked.
              this.$emit('clickBtn');
          }
      }
  }
</script>
<!-- Parent component -->
<template>
  <div id="app">
    <!-- The parent component binds the responder of events in the child component when calling the child component -->
    <Child v-on:clickBtn="evInFather" />
  </div>
</template>

<script>
  import Child from './components/Child'

  export default {
    name: 'App',
    components: {
        Child
    },
    data() {
      return {
        flag: true
      }
    },
    methods: {
       //evInFather also triggers when the clickBtn event in the Child subcomponent is emit ted    
       evInFather() {
          console.log('father up');
       }
    }
  }
</script>

We can also throw some data to the outside world when we throw an event.

// Child's methods
// ...
methods: {
    ev() {
        // Throw out data
        this.$emit('clickBtn', this.msg);
    }
}
// ...
// methods in parent components
// ...
methods: {
    evInFather(data) {
        console.log(data);
    }
}
// ...

If a component throws more than one data when throwing an event, it can continue to pass a value to $emit in the form of a parameter list, and then call it in the form of a parameter list in the parent component. But when there is a lot of data, it makes the code seem lengthy, so we recommend that all the data to be thrown be integrated into one object and passed in an object to the second parameter of the $emit function.

Slot slot slot

Slot is the second way that components are called, which improves the reusability of components.

When the child component calls the parent component and transfers its value, it tends to transfer in the data layer, but sometimes we hope that the child component can respond better to one of its parent components in the structure rendering layer, and the content of the child component can be rendered through the data control of the parent component.

If we need an operation result prompt box, the title, picture and button bar in the bullet box all render the concrete result according to the operation result. Using props or $emit can also achieve this effect, but if the results of multiple rendering are very different, then the code redundancy goes up again, so Vue provides a more concise way, that is, slots.

A slot is equivalent to encapsulating a component into a large framework. As for what and how to display it, it can transfer data when invoked, control it by data, and display the incoming content when invoked.

1. Small examples of slots

<!-- Alert.vue -->
<template>
    <div class="alert">
        <p>I'm an inherent part of the component.</p>
        <!-- Use in components slot Reserve the content to be distributed, and when the component is called, the content to be distributed will be rendered in slot Location -->
        <slot></slot>
    </div>
</template>

<script>
    export default {
        name: "Alert"
    }
</script>
<template>
  <div id="app">   
     <Alert>
        <! -- Call the alert component and distribute content to it - >
        <p>
            hello, I'm the content that is sent to the subcomponent when father calls
        </p>
     </Alert>
  </div>
</template>
<script>
    // ...
</script>

Note:
If the Alert component does not contain a < slot > element, anything between the start tag and the end tag will be discarded when the component is called.

2. Setting up backup slots for components

If a slot is reserved in a component, but the content is not distributed when the component is called, it may lead to some unpredictable errors in structure and logic, so sometimes it is necessary to set a default content for the slot.

<!-- Alert.vue -->
<div class="alert">
    <slot>
        <! - If you do not distribute content when calling the current component, then show the content in the following p tag - >.
        <p>default message</p>
    </slot>
</div>

3. Named sockets

To encapsulate a component with high reusability, a slot may not be enough. We can reserve multiple slots for each slot at different locations of the component structure, name each slot, and distribute the corresponding content to different slots when calling the component. This is the named slot.

<!-- Alert.vue -->
<div class="alert">
    <div class="title">
        <!-- Named slot title -->
        <slot name="title">Reminder</slot>
    </div>
    <div class="content">
        <!-- slot con -->
        <slot name="con">Are you sure you want to do this?</slot>
    </div>
    <div class="btn">
        <!-- slot btn -->
        <slot name="btn">
            <button>Sure?</button>
        </slot>
    </div>
</div>

Call the Alert component:

<Alert>
    <!-- Distribution of corresponding content to different named slots -->
    <template v-slot:title>
        Tips
    </template>
    <template v-slot:con>
        <p>This operation is dangerous. Do you want to do it?</p>
    </template>
    <template v-slot:btn>
        <button>Sure?</button>
        <button>cancel</button>
    </template>
</Alert>

Abbreviation of Named Slot:

<template #btn></template>

Note:

  • There is no < slot > with the name attribute set, and the default name value is default.
  • The v-slot attribute can only be added to one < template > (except for exclusive default slots).

4. Scope slots

Sometimes we need to access the data inside the component when we call it and distribute content to its socket. The data in the component can be bound to the slot prop, and then the data on the slot prop can be accessed at the same time when the component is called.

<!-- Alert.vue -->
<template>
    <div class="Alert">
        <!-- The data in this slot is related user Yes, the parent component may want to access it when invoked user Other data in the user Binding into slots prop -->
        <slot name="userMsg" v-bind:user="user">{{ user.name }}</slot>
    </div>
</template>

<script>
    export default {
        name: "Alert",
        data() {
          return {
            user: {
                name: 'tom',
                age: 18
            }
          }
        }
    }
</script>

Call the slot and use the value of the slot prop:

<Alert>
    <!-- adopt v-slot Instruction acceptance slot prop -->
    <template v-slot:userMsg="um">
        <!-- Call slot prop Data -->
        <i>{{ um.user.name }}</i>
        //This year
        <mark>{{ um.user.age }}</mark>
        //Year old
    </template>
</Alert>

Multiple slot prop s can be set for slots:

<slot name="userMsg" 
    v-bind:user="user"
    v-bing:num="100000"
>
    {{ user.name }}
</slot>

Receive:

<template v-slot:userMsg="um">
    <i>{{ um.user.name }}</i>
    //This year
    <mark>{{ um.user.age }}</mark>
    //Year old
    <strong>{{ um.num }}</strong>
</template>

Slot prop can also be used for deconstruction:

<template v-slot:userMsg="{user}"></template>

Note:
If there are many slot props, it is recommended to integrate all data into one object and pass one slot prop.

Render function, JSX grammar and CreaeElement function

Render functions are the same as templates in creating html templates, but in some scenarios, the code implemented with templates is tedious and repetitive, so render functions can be used.

If render function is used in component rendering, then you can not use the < template > tag. Only < script > labels and < style > labels are required in component files.

Before we understand the render function, we need to define a concept of Vnode.

1. Vnode (virtual node)

When using Vue to develop a project, the structure rendered on the browser is rendered on the browser by Vue through various conditions, loops, calculations and other operations of the underlying mechanism. We regard the final result rendered on the browser as a real DOM tree.

But Vue responds to data efficiently. Faced with such efficient data response, it is also necessary to update nodes in the page equally efficiently. However, the structure of DOM is very large and complex, and it is particularly difficult to complete all DOM updates.

For example, we render a merchandise management list. When a certain value of a commodity changes, the list rendering of the page also needs to be updated. If we want to use native JS to render, we may need to re-render the entire table, or a row. If you want to locate a cell accurately, the code is very demanding.

Fortunately, when using Vue, we don't need to update the DOM tree manually using JS. Vue provides a virtual DOM tree through which it can track how it wants to change the real DOM.

DOM nodes written in vue file and DOM in render function are virtual DOM. When browser renders, all virtual DOM will be calculated and rendered on browser finally.

When we create a virtual DOM, we include all the information of the virtual DOM, such as child elements, class names, styles, locations, and so on.

Vue instances provide render functions to render virtual DOM, and render function parameters (also a function) to create virtual DOM.

2. Example render function

<script>
    export default {
        name: "Home",
        data() {
          return {
          }
        },
        render(ce) {
            return ce(
                'div'
            )
        }
    }
</script>

The < Home > component can be called normally, and the < div > rendered by the render function is taken when it is called.

render function:

  • The parameter is the ce function (which is written as createElement in many places).
  • The return value is a VNode (node to be rendered).

3. Parameter of render function - createElement function

The parameter of render function is the createElement function, which creates VNode as the return value of render.

The createElement function takes three parameters:

  • Parametric 1:
    String | Object | Function
    An HTML tag name, component option object, or resolve has one of the async functions mentioned above. Required items.
  • Parametric 2:
    Object
    An object that contains all the relevant attributes of this tag (or template). Optional.
  • Parametric 3:
    String | Array
    A child node or list created by createElement.

Following are detailed descriptions and examples of the second and third parameters of createElement:

// Parametric 2
{
    // Same API as `v-bind:class',
    // Accept an array of strings, objects, or strings and objects
    'class': {
        foo: true,
        bar: false
    },
    // Same API as `v-bind:style',
    // Accept an array of strings, objects, or objects
    style: {
        color: 'red',
        fontSize: '14px'
    },
    // Common HTML Characteristics
    attrs: {
        id: 'foo'
    },
    // Component prop
    props: {
        myProp: 'bar'
    },
    // DOM attributes
    domProps: {
        innerHTML: 'baz'
    },
    // The event listener is in the `on'property,
    // But it no longer supports modifiers like `v-on:keyup.enter'.
    // You need to manually check keyCode in the processing function.
    on: {
        click: this.clickHandler
    },
    // Used only for components, to listen for native events, not for internal use of components
    // ` Events triggered by vm.$emit'.
    nativeOn: {
        click: this.nativeClickHandler
    },
    // Custom instructions. Note that you can't `old Value'in `binding'.`
    // Assignment, because Vue has automatically synchronized for you.
    directives: [
        {
        name: 'my-custom-directive',
        value: '2',
        expression: '1 + 1',
        arg: 'foo',
        modifiers: {
            bar: true
        }
        }
    ],
    // The format of scoped slots is
    // { name: props => VNode | Array<VNode> }
    scopedSlots: {
        default: props => createElement('span', props.text)
    },
    // If the component is a subcomponent of another component, you need to specify a name for the slot
    slot: 'name-of-slot',
    // Other special top-level attributes
    key: 'myKey',
    ref: 'myRef',
    // If you apply the same ref name to multiple elements in the rendering function,
    // Then `refs.myRef'becomes an array.
    refInFor: true
}
// Parametric 3:
// If the parameter three is a string, it will be rendered as innerText of the element.
render(ce) {
    return ce(
        'div',
        {},
        '<p>hello world</p>'
    )
}
// If the parameter three is an array, then there is a list of elements'sub-contents, each of which is a VNode created by createElement.
render(ce) {
    return ce(
        'div',
        {},
        [ce('p'),ce('h4')]
    )
}
// But if strings are listed in an array of parameter three, they are spliced together as innerText elements.
render(ce) {
    return ce(
        'div',
        {},
        ['hello','world']
    )
}

4. JSX grammar

If the structure in a template is relatively simple, we can use the createElement function, but once the structure is slightly more complex, the code becomes particularly lengthy.

If you want to continue using render functions, you need to use JSX grammar. Because JSX grammar allows HTML structures to be written in JS code.

render(ce) {
    return (
        <div class="home">
            Here you can write any structure.
        </div>
    );
}

JSX grammar may be more widely used in React.

Logo

更多推荐