Vue 2 Internal Features from the Ground Up

Challenge 7: Solution

Evan You

Evan You

Creator of Vue.js
Vue 2 Internal Features from the Ground Up

Check out a free preview of the full Vue 2 Internal Features from the Ground Up course

The "Challenge 7: Solution" Lesson is part of the full, Vue 2 Internal Features from the Ground Up course featured in this preview video. Here's what you'd learn in this lesson:

Evan walks to the solution to Challenge 7 and takes questions from students. Then Evan adds a class to util.js that will be needed later.


Transcript from the "Challenge 7: Solution" Lesson

>> Evan: The idea is, [COUGH] our smart avatar is a stateful component, so it can hold some state, it can be responsible for counting the API, and will do that in its lifecycle hooks, right?
>> Evan: So the width after your URL should return the smart avatar itself, in this case we use object syntax.

The first thing we do is instead accepting source as a prop, we accept user name as prop, all right? And the other thing is it should be rendering,
>> Evan: Remember to use the proper function syntax. So obviously, it should be just returning the avatar component internally. Because ultimately, this is what we're trying to do.

The interesting thing is, we want to pass additional data. I want to pass props to avatar, I want to pass it to the source props. The question is how do we find out what the source is, right? And let's just say, we have some local states inside this component, which is URL.

And by default, when we first load this component, we only have the username. We don't have the URL yet. And the requirement says, we should, wait, it is here. Where is the requirements? Okay, It's here. It says, before the API returns, the higher order component should be passing placeholder URL to the inner component.

We'll just copy that and use that as the default value for our URL here, ight? We'll just whatever, we'll just pass it in. So now, first, we've created a smart avatar component, not so smart. It always just renders in avatar with a placeholder image. And now, we need to figure out how to find out where the real URL is.

So let's do that in a life cycle hook. So when the component is created, we're gonna call this API. We're gonna fetchURL. And because it's expected user name, we'll just, right? We'll call it with the username. And it accepts the call back which gives you the url that we need.

>> Evan: And inside that call back, we'll just set our url to the correct url.
>> Evan: So does this make sense? So essentially, we're maintaining an intermediate piece of state. And this state is the url, and it starts with a placeholder. And it is changes after we have fetched the desired URL from the API.

All we are doing is passing it down to the avatar component. The good thing is we have extracted a piece of functionality very cleanly out of inner and outer component into this thing. And believe it should work, so it's better to just actually open it in order. So this is the effect, so it shows a placeholder and interns into that.

So imagine, you can do something like a blurry effect or initially showing other things you can do a lot of fancy stuff in here. The good thing is, you can encapsulate all of that inside this higher order component. It doesn't leak anywhere else, so which is very nice.

So one small thing, that we might want to think about, is that this component currently is pretty optimized. In the sense, it always hits the API, whenever it's created. But we can actually create something like a cache here that caches the username and URL pairs. So that, we don't need to actually hit the API all the time.

You can have a cache here with a username and URL pairs. And inside our data here, we can directly start with a cached hit if it exists and we check again here. If it's already a cached hit, we don't need to call the API at all. So you can do a lot of things, authorizations, fancy functionalities, inside here.

But it doesn't affect the usage to the parent component at all. No matter what you do inside that component, this parent component usage stays the same. This inner component, the Avatar component stays the same. They don't need to change, right? You can do all the work inside this higher order component, but you're not affecting your parent, your child.

So this is good, because it shows that it is a good encapsulation mechanism. The only interface we're using to talk between the parent and the child is view props. Because the way we communicate is so constrained. This make ensures that change in the inter implementation of this enhancer doesn't affect other parts in your code.

This is critical, if you have a large team, a large project. And you want to make sure when you are changing things, you're not stepping on other people's toes, not accidentally breaking other things. The trick to that is, make sure this piece of code, finding contracts with other parts of the code base, is minimal.

In this case, the only thing that is connecting to other pieces in the code are the incoming props and the outgoing props. So this is great, because this is a very clear constraint, it's very minimal. It gives you very little chance of breaking other people's things.
>> Speaker 2: In your render function there, you're referencing Avatar.

Shouldn't you be actually referencing the InnerComponent?
>> Evan: Yes that's a mistake here. Yeah, so this makes this with Avatar url more generic. So any InnerComponent that takes a URL prop can be used with this avatar URL thing. And it can be more generic, for example you can take something like, you can even pass the fetchURL function here, right?

So instead of hardcoded to always be fetching avatars, you can on the fly customize what you're actually fetching. It takes the username, you can fetch the avatar, you can fetch other information about the user. But you're using the same interface to enhance inner components. And this increases the reusability for this enhancer as well.

What are the difference between created and mounted hooks for fetching data?
>> Evan: To be honest, the difference is minimal. The main difference is that mounted is called after the rendering happens and the actual dominos are created. So technically, created will be caught earlier, then mounted. If your component takes a long time to render, then created might be called even earlier than mounted.

And it's good to send the request out beforehand, because the request needs to go through the internet and there's latency. So the can overlap with the time it takes to render a component. So that's the main thing. There was an early question about slots. I think, it's actually easier to read the docs about slots.

But slots API was actually derived from the same standard from W3C. And so, in web components, there's a slot mechanism. And views implementation loosely follows that spec, but we kinda enhance it in a few ways. And most importantly, view slot mechanism works in rendered functions too. So if you use a function that uses rendered functions, a user component that uses render functions like this, so you can do, slot = "foo".

>> Evan: And the way to access this slot inside a render function is by doing this.$ This will give you an array of virtual nodes, that falls into this slot. Because you can have multiple things that fall into the same slot. So you gonna have an array. And if you don't give it any slot name, then all unnamed ones will be inside this.$slots.default.

This is also an array, so you can pass them directly as children to this. Essentially, you're passing on the slot content into this inner component. Even better, well, yeah, this is a typical use case. Another trick that we can talk about is inheriting the outer attributes. So we have this higher order components like here.

If you do something like, id="foo", unfortunately it won't be passed down to the inner component, because we didn't explicitly list it. But this id will in fact, be collected inside an attribute card, a property called, this.$attrs. This is a new feature in 2.4. Which means you can pass it down like this.

So this way, you're relaying all the additional attributes down to the intercomponent. So this id were in fact, now, be rendered on the internal Avatar. But the default behavior is only explicitly listed props will be passed down. This also means in created hook, you can not access dominoes, that's correct.

Only mounted hook guarantees the DOM has been created. In created hook, it means the instance is created. On the website, there's a documentation,
>> Evan: Lifecycle Diagram.
>> Evan: As you can see, when the created hook is called we have initialize events, Lifecycle injections and reactivity. These are instance specific, they don't have anything to do with the DOM, or rendering, or anything.

Real rendering, only happens after created hook has been called and there could be on the far compilation and then virtual DOM rendering. And eventually, when the actual DOM has been created, that's when mounted will be called. So now, the component is considered to be in mounted state. I have a question, in view router could import food be change to something like lazy import to get a slightly simpler syntax?

>> Evan: Not really, so the question is really about async components and code splitting together.
>> Evan: I think, this kind of syntax is already simple enough. [LAUGH] So the more important thing is the import syntax, dynamic import is a spec, right? Arrow function is part of JavaScript, dynamic import is part of JavaScript.

We're just using these standard language features for async components and close fitting. Which, in my opinion is better than having to have a custom Lazy import function, which is not part of the standard. Essentially, we are having our own API instead of using standard language features.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now