preface
This title is not easy to take.
This article requires the following knowledge:
https://zhuanlan.zhihu.com/p/260811233zhuanlan.zhihu.com
Problem description
One of my recent functional requirements is to obtain the json file stored in COS through axios and render it to the page at the same time.
The problem is that axios can successfully obtain data and render components, but the console will report an error
[Vue warn]: Error in render: "TypeError: Cannot read property 'text' of undefined".
problem analysis
First of all, my situation is more complicated than the problems of bloggers I have found.
Part of my json file is as follows:
{ "0": { "headline": "How to quickly adjust the brightness of the display group?", "url": "zhuanlan.zhihu.com/p/259647484", "dateP": "2020-09-26T13:35:07.000Z", "dateM": "2020-09-26T13:37:24.000Z", "img": "https://pic2.zhimg.com/v2-8b58a57ba0923d23329abc16766da34c_r.jpg", "text": "preface Mac I don't know, but Windows Adjusting the brightness of the display is actually a very annoying thing. My monitor group is a 15.6 A 22 inch secondary screen, a 22 inch display and a 15 inch display.6 Inch laptop screen. Due to hardware reasons, adjust the brightness through the display itself" }, "1": { "headline": "adopt Vue realization vw/vh Adaptive unit", "url": "zhuanlan.zhihu.com/p/257866252", "dateP": "2020-09-22T10:51:08.000Z", "dateM": "2020-09-22T10:51:08.000Z", "img": "https://pic4.zhimg.com/v2-8ac0ec7ef990ca579657ae5b8bea714c_r.jpg", "text": "Preface although there may be more responsive layouts now, I still think adaptive units are easier to use. For some framework UI Components, such as Vuetify,The use of adaptive units may not be supported, which will greatly affect the layout effect. The methods provided in this article can help you achieve this" }, "2": { "headline": "how DIY Portable display?", "url": "zhuanlan.zhihu.com/p/257257464", "dateP": "2020-09-21T13:43:08.000Z", "dateM": "2020-09-21T13:52:32.000Z", "img": "https://pic1.zhimg.com/v2-7f27fabd9fc334d2c7e529561658b82f_r.jpg", "text": "Preface recently, I'm depressed to find a job. I'll write a few articles to relax. This article is long and has many knowledge points. It is recommended to collect or agree to leave a pointer for yourself (mistaken portable display). Portable display can be known by its name. It is a mobile display, so it should meet several major needs: low voltage power supply (relative to transmission" },
If you are curious about how this json file was obtained, you can visit:
LikeDreamwalker/zhihu-articles-apigithub.com
And give me a star!
The problem with this json file is that it is multi-layered. In general, it is a simple one-dimensional array or a layer of objects.
Next, the key question is why axios has successfully obtained data and the component has been rendered, but it will report an error?
undefined means index [0] Text is not defined, but called again, that is, the index object is called when the page is rendered, but index [0] Text has not been defined, that is, axios has not got data yet.
Then the idea is very simple. Scheme 1 is that we try to write the index object in advance, that is, specify it in data
index: {{text=null}}
In scheme 2, we let the corresponding component render the component after axios obtains the data.
Let me first explain that 1 and 1 are a solution, but in my case, I can't control how many objects there are, and each object has many properties. This solution can solve the symptoms but not the root cause. However, when you declare, you should declare according to the real structure of the object as much as possible. If you can't solve this problem like me, there are ways below.
Of course, if your object is simple, it's over here, which can be a solution.
Therefore, the problem becomes simple. The core is when axios obtains the data, and whether it is possible to obtain the data before the page is rendered.
When did axios get the data
There are two general uses of axios: one is placed in created, that is, before page rendering, and the other is placed in mounted, that is, after page rendering.
So we can try to write two cases:
created() { console.log( "start"); axios .get( "https://ldwid-1258491808.cos.ap-beijing.myqcloud.com/json/doNotUseThisJSON.json" ) //For debugging local json //.get("http://localhost:8080/doNotUseThisJSON.json") .then( response => { _this.index = response.data; console.log(response.data); console.log(_this.index); console.log(_this.index[ 0]); }); console.log( "created"); }, mounted() { console.log( "start"); axios .get( "https://ldwid-1258491808.cos.ap-beijing.myqcloud.com/json/doNotUseThisJSON.json" ) //For debugging local json //.get("http://localhost:8080/doNotUseThisJSON.json") .then( response => { _this.index = response.data; console.log(response.data); console.log(_this.index); console.log(_this.index[ 0]); }); console.log( "mounted"); }
Not surprisingly, you will find that these two situations will still throw undefined errors.
But this is contrary to the logic. Since I choose to get the data before page rendering, why do I still report an error?
Guess: network problem
Because axios is asynchronous, maybe the axios statement is actually executed when created, but due to network problems, the loading time is greater than the execution time of the statement, so the data obtained will be after the page rendering.
This guess is logical, but it is actually a problem with the understanding of JS asynchrony and life cycle. Please read the following article:
https://zhuanlan.zhihu.com/p/260811233zhuanlan.zhihu.com
Instead of guessing, let's take a look at the specific execution sequence:
beforeCreate() { console.log( "beforCreate"); }, created() { let _this = this; console.log( "start"); axios // .get( // "https://ldwid-1258491808.cos.ap-beijing.myqcloud.com/json/doNotUseThisJSON.json" // ) .get( "http://localhost:8080/doNotUseThisJSON.json") .then( response => { _this.index = response.data; console.log(response.data); console.log(_this.index); console.log(_this.index[ 0]); }); console.log( "created"); }, beforeMount() { console.log( "beforMount"); }, mounted() { console.log( "mounted"); }
Then we load the page:
The answer may be unexpected. Although the axios statement is placed under created, the actual execution is after mounted.
If you don't believe it, you can also put axios into other life cycles, which will still be the result.
Therefore, axios will get the data after the page is fully rendered. When you get the template string and replacement string on the rendering page, you will naturally report an error if you find that a non-existent attribute is referenced.
Can axios get data before page rendering
This requires knowledge of lifecycle and asynchrony:
https://zhuanlan.zhihu.com/p/260811233zhuanlan.zhihu.com
First, axios is asynchronous. Since it is asynchronous, axios statements will not be accessed until all macro tasks are executed, and continue to be executed in the order of micro task queue. Therefore, axios can no longer get data before page rendering.
If axios is synchronized, when you fill in the wrong API or the API fails, the whole life cycle will be blocked and crash, and finally the page rendering will fail. But obviously, this is not possible in the development process.
If you still have obsession and think it is the above network environment problem, if it is true at this time, axios is actually another thread, parallel to the life cycle, but JavaScript is a single threaded language.
Here, the causal relationship of the whole problem should be very clear:
- If you want index [0] For normal text parsing, we need to fill in the correct text attribute before parsing. At this time, you need to write the index directly or ask axios to fill in the correct text attribute before template parsing
- Due to the life cycle, we know that axios must complete the acquisition before beforeMount
- Due to the asynchronous nature of axios, axios cannot complete the acquisition before page rendering
- If you want to do this, you must make axios execute in another thread, but JavaScript is a single threaded language
Problem solving
After we know the cause of the problem, the way to solve the problem becomes very simple.
We already know in our analysis that there are at least two ways to solve this problem. Let's now look at the second way.
Render the corresponding components after axios obtains the data
Here we use Vue's conditional rendering:
Conditional rendering - Vue jscn.vuejs.org
Since we know that axios will execute after page rendering, we can artificially add a state to identify whether axios has obtained data. For the corresponding template, rendering will be performed only when the state is true.
If you are wondering why this can be achieved, you can take a look at the node reporting an error again. After beforeMount and before Mounted, that is, when the template is parsed into a template string and stored in memory, but has not been rendered on the page (or will be rendered on the page), Vue will not execute the template content that does not meet the rendering conditions:
So is v-if Lazy: if the condition is false at the initial rendering, nothing is done -- the conditional block does not start rendering until the condition first becomes true.We modify the template content:
<v-card-text v-if="status"> {{ index[0].text }} </v-card-text>
Modify data:
data: () => ({ status: false , index: {} }),
Modify axios:
mounted() { let _this = this; axios . get( "https://ldwid-1258491808.cos.ap-beijing.myqcloud.com/json/doNotUseThisJSON.json" ) .then(response => { _this.index = response. data; _this.status = true; }); }
At this time, refresh the page again and no more errors will be reported.
Be sure to put the modification of status in then instead of mounted, otherwise it will still have no effect.
It should be reminded that the judgment condition used by many people to solve this problem is the attribute itself. The premise of using this attribute as the judgment condition is that the attribute exists. Otherwise, undefined will still be converted to false, and null will also be converted to false. The former will still report an error when used, and undefined will not change at this time, The reason why this page can still render data is that two-way binding makes this attribute value exist after the page is rendered, and the template content will be rendered naturally.
If you can declare such content, why not write the complete object directly? If the complete object is not easy to declare manually, why use the attribute value of the object as the judgment condition?
As for performance, I think it has little impact. After all, declaring more than one variable is not a very large amount of computation.
last
I don't know if you understand, but I think I have a new understanding of Vue and JavaScript after I climb out of this pit.