By Michael Thiessen

Translator: Front-end wisdom

Source: dev

Click “like” and then look, wechat search [Big Move the world] pay attention to this person without dACHang background, but with a positive attitude upward. In this paper, making github.com/qq449245884… Has been included, the article has been categorized, also organized a lot of my documentation, and tutorial materials.

Everyone said there was no project on your resume, so I found one and gave it away【 Construction tutorial 】.

Let’s start with a question: Is there a way to fill a parent’s slot from a child?

A colleague asked me this question recently, and the answer is simple: yes. However, my solution is quite different from what you might think, which involves a tricky Vue architecture problem, but also a very interesting one.

Why is this a problem

In our application, we have a top bar that contains different buttons, a search bar, and a few other controls. It might be slightly different depending on which page each person is on, so we need a way to configure it on a per-page basis.

To do this, we want each page to be able to configure the action bar. It seems simple enough, but there’s a problem

The top bar (we call it the ActionBar) is actually part of our main layout and has the following structure:

<template>
  <div>
    <FullPageError />
    <ActionBar />
    <App />
  </div>
</template>
Copy the code

Dynamically inject the location of your App based on the page/route you are on.

We can configure it using some slots on the ActionBar. But how do we control these slots from App components?

Define the problem

First, it is best to be as clear as possible about the problem we are trying to solve.

Let’s look at a component with a child component and a slot:

// Parent.vue
<template>
  <div>
    <Child />
    <slot />
  </div>
</template>
Copy the code

We can fill the Parent slot like this:

// App.vue
<template>
  <Parent>
    <p>This content goes into the slot</p>
  </Parent>
</template>
Copy the code

Nothing special here…

Filling slots for child components is easy, and this is the most common way to use slots.

But is there a way to control what goes from inside the Child component into the Parent component slot?

Put another way: Can we have a child component fill the parent’s slot? Let’s look at the first solution I came up with.

Use props down and Event up

The only way for data to flow through the component tree is to use props. The way to communicate up is to use events. This means that if we want a child component to communicate with its parent, we need to use events to do so.

Therefore, we will use events to pass content into the ActionBars slot

import SlotContent from './SlotContent'; export default { name: 'Application', created() { // As soon as this component is created we'll emit our events this.$emit('slot-content', SlotContent); }};Copy the code

We’ll pack everything we put into the slot into the SlotContent component. Once the application component is created, we issue a slot-Content event and pass the component we want to use.

Our component structure is as follows:

<template>
  <div>
    <FullPageError />
    <ActionBar>
      <Component :is="slotContent" />
    </ActionBar>
    <App @slot-content="component => slotContent = component" />
  </div>
</template>
Copy the code

Listen for this event and set slotContent to whatever our App component sends us. This Component can then be rendered dynamically using the built-in Component.

However, passing an event component feels strange and not mainstream. Fortunately, there is a way to avoid using events altogether.

Using the $options

Since Vue components are just JS objects, we can add any properties we need to them. // app.vue import SlotContent from ‘./SlotContent’; // app.vue import SlotContent from ‘.

export default {
  name: 'Application',
  slotContent: SlotContent,
  props: { /***/ },
  computed: { /***/ },
};
Copy the code

Get the corresponding component from app.slotContent on the home page

<template>
  <div>
    <FullPageError />
    <ActionBar>
      <Component :is="slotContent" />
    </ActionBar>
    <App />
  </div>
</template>

import App from './App';
import FullPageError from './FullPageError';
import ActionBar from './ActionBar';

export default {
  name: 'Scaffold',
  components: {
    App,
    FullPageError,
    ActionBar,
  }
  data() {
    return {
      slotContent: App.slotContent,
    }
  },
};
Copy the code

This is more of a static configuration, which is nicer and simpler, but it’s still not true.

Ideally, we don’t mix paradigms in our code, and everything should be done declaratively.

But here, instead of grouping our components together, we pass them as JS objects. It would be nice if we could write what we want in the slot in the normal Vue way.

Consider Portal

Portal technology in Vue In the Vue project, we used templates to declare the DOM

Nested relationships. However, sometimes components need to be removed from a fixed hierarchy and not subject to a cascading context, such as Modal and Dialog components that want to be removed from the cascading context of the current template.

There are two ways to achieve this effect in Vue. One is to use instructions to manipulate the real DOM and append the element of the instructions to another DOM node using familiar DOM manipulation methods. Another approach is to define a set of components, move vNodes from one component to another, and render them separately.

They work exactly as you would expect. You can teleport anything from one place to another. In our example, we “transport” elements from one location in the DOM to another.

Regardless of how the component tree is displayed, we can control where components are displayed in the DOM.

For example, let’s say we want to fill a Modal. But our modal must be rendered at the root page so we can overwrite it properly. First, we specify in Modal what we want:

<template> <div> <! -- Other components --> <Portal to="modal"> Rendered in the modal. </Portal> </div> </template>Copy the code

Then, in our Modal component, we will have another portal that renders the content:

<template>
  <div>
    <h1>Modal</h1>
    <Portal from="modal" />
  </div>
</template>
Copy the code

This is an improvement because now we’re actually writing HTML, not just passing objects around. It’s more declarative and makes it easier to see what’s happening in your application.

Because Portal does something behind the scenes to render elements in different locations, it completely breaks the model of how DOM rendering works in Vue. It looks like you’re rendering elements normally, but it doesn’t work at all, which can cause a lot of confusion and frustration.

There’s another big problem, which we’ll talk about later.

ascension

“Elevating state” refers to moving state from a child component to a parent or grandparent, moving it up into the component tree.

This can have a significant impact on the architecture of your application. For our purposes, this would be the simpler solution.

The “state” here is what we are trying to pass into the ActionBar component slot. But that state is contained in the Page component, and we can’t really move page-specific logic into the Layout component. Our state must remain within the Page component that we are rendering dynamically.

Therefore, we must promote the entire Page component to improve the state. Currently, our Page component is a child of the Layout component:

<template>
  <div>
    <FullPageError />
    <ActionBar />
    <Page />
  </div>
</template>
Copy the code

Undoing it requires us to flip it over and make the Layout component a child of the Page component. Our Page component looks like this:

<template> <Layout> <! -- Page-specific content --> </Layout> </template>Copy the code

Now our Layout component will look like this, where we can insert the page content using slots:

<template>
  <div>
    <FullPageError />
    <ActionBar />
    <slot />
  </div>
</template>
Copy the code

But that doesn’t allow us to customize anything. We must add some named slots in the Layout component so that we can pass content that should be placed in the ActionBar.

The easiest way to do this is to use a slot to completely replace the ActionBar component:

<template>
  <div>
    <FullPageError />
    <slot name="actionbar">
      <ActionBar />
    </slot>
    <slot />
  </div>
</template>
Copy the code

Thus, if you do not specify an “Actionbar” slot, the actionBar component is used by default. But we can override this slot with our own custom ActionBar configuration:

<template> <Layout> <template #actionbar> <ActionBar> <! -- Custom content that goes into the action bar --> </ActionBar> </template> <! -- Page-specific content --> </Layout> </template>Copy the code

To me, this is an ideal approach, but it does require us to refactor the way the page is laid out. For more complex interfaces, this can be a daunting task.

To simplify the

When we first defined the problem:

Can we have a child component fill the parent’s slot?

But really, the problem has nothing to do with props. More simply, it’s about giving subcomponents control over what is rendered outside of their own subtrees.

We can frame the problem this way

What is the best way for a component to control what is rendered outside of its children?

Examining each solution we propose through this lens gives us an interesting new perspective.

Issues events to the parent component

The only way for data to flow through the component tree is to use props. The way to communicate up is to use events. This means that if we want a child component to communicate with its parent, we need to use events to do so.

Static configuration

Simply provide the necessary information to other components, rather than actively asking another component to do something.

portal

A component has no control over what is outside its child tree. Each of these methods is a different way for another component to execute our commands and control the elements we are really interested in.

In this regard, using Portals is better because they allow us to encapsulate all of this communication logic into a separate component.

ascension

Ascending state is a much simpler and more powerful technique than the three we saw earlier, and our main limitation here is that what we want to control is outside of the child components.

The simplest solution is:

Promoting state and the logic that manipulates that state allows us to have a wider range of components and include target elements in that component. If you can, this is the easiest way to solve this particular problem and all related problems.

Keep in mind that this does not necessarily mean upgrading the entire component. You can also refactor your application to move logic to higher components in the component tree.

Dependency injection

Those of you familiar with software engineering design patterns may have noticed that what we’re doing here is dependency injection, a technique that we’ve been using in software engineering for decades.

One of its uses is to write code that is easy to configure. In our example, we configure the Layout component differently on each Page we use.

When swapping Page and Layout components, we are performing what is called control inversion.

In a component-based framework, the parent controls what the child does, so we chose to let the Page control the Layout component rather than the Layout component controlling the Page.

To do this, we use slots to provide the Layout component with the content it needs to complete the task.

As we’ve seen, using dependency injection makes our code more modular and easy to configure.

conclusion

We discussed four different approaches to this problem, showing the pros and cons of each approach. Then we take it a step further and turn the problem into a more general one of controlling something outside of the component subtree.

, promoted state, and dependency injection are two very useful patterns. They are the best tools in our Arsenal because they can be applied to countless software development problems.

But most of all, I hope you can also learn:

Take a problem with an ugly solution and turn it into a very elegant problem by using some common software patterns. Many other problems can be solved this way, by taking an ugly, complex problem and turning it into a simpler, easier problem to solve.


The bugs that may exist after code deployment cannot be known in real time. In order to solve these bugs, I spent a lot of time on log debugging. Incidentally, I recommend a good BUG monitoring tool for youFundebug.

Original text: dev. To/michaelthie…


communication

This article is updated every week, you can search wechat “big move the world” for the first time to read and urge more (one or two earlier than the blog hey), this article GitHub github.com/qq449245884… It has been included and sorted out a lot of my documents. Welcome Star and perfect. You can refer to the examination points for review in the interview.