Skip to content

Instantly share code, notes, and snippets.

@pjchender
Last active April 30, 2017 14:55
Show Gist options
  • Save pjchender/063aff2b057374e3eac4eb4f6ebb89ec to your computer and use it in GitHub Desktop.
Save pjchender/063aff2b057374e3eac4eb4f6ebb89ec to your computer and use it in GitHub Desktop.
[Vue] 生命週期與 AJAX 使用說明
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo Vue</title>
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]/dist/axios.js"></script>
</head>
<body>
<div id="app">
<h1>{{ posts[currentId].title }}</h1>
<h2>{{ posts[currentId].body }}</h2>
<h3>{{ id }}</h3>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
// 這時候因為 posts 沒有資料,但是 render 要顯示,所以會出現錯誤
// TypeError: Cannot read property 'title' of undefined
currentId: 0,
body: '',
title: '',
posts: []
},
computed: {
// computed 的屬性在 vm 初次 create 時不會先被執行一次,
id () {
console.log('id computed')
return `post${this.currentId}`
}
},
methods: {
getCurrentId () {
console.log('getCurrentId', this.currentId)
}
},
beforeCreate () {
// 在 berforeCrate 資料沒 bind 上 vm,所以 this 拿不到資料
console.log('beforeCreate')
},
created () {
// created 之後可以 data bind 上 vm ,可以透過 this 拿到資料
console.log('created')
let self = this
// 這裡面用 AJAX 取得資料
axios({
method: 'get',
url: 'https://jsonplaceholder.typicode.com/posts/'
})
.then(function (response) {
// 把 AJAX 取得的資料代入 Vue 中
self.posts = response.data
console.log('getResponse')
})
.catch(function (error) {
console.log('err', error)
})
},
beforeMount () {
console.log('beforeMount')
},
mounted () {
// vm 初次 create 時,會從 beforeCreate 執行到這裡(mounted)
console.log('mounted')
},
beforeUpdate () {
console.log('beforeUpdate')
},
updated () {
console.log('updated')
}
})
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo Vue</title>
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]/dist/axios.js"></script>
</head>
<body>
<div id="app">
<!-- title 和 body 是透過 computed 取得,
- 使得 computed 會在 beforeMount 和 mounted 被觸發,
- 但是這時候 this.posts 仍然還沒有資料
-->
<h1>{{ title }}</h1>
<h2>{{ body }}</h2>
<h3>{{ id }}</h3>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
// 這時候因為 posts 沒有資料,但是 render 要顯示,所以會出現錯誤
// TypeError: Cannot read property 'title' of undefined
currentId: 0,
posts: []
},
computed: {
// computed 的屬性在 vm 初次 create 時不會先被執行一次,
id () {
console.log('id computed')
return `post${this.currentId}`
},
/**
* 因為在 template 中有 {{ title }}
* 促使 computed 在 beforeMount 和 mounted 之間執行
* 但這時候 this.posts 仍沒有資料,出現錯誤
* Error in render function: "TypeError: Cannot read property 'title' of undefined"
**/
title () {
console.log('title computed')
return this.posts[this.currentId].title
},
body () {
console.log('body computed')
return this.posts[this.currentId].body
}
},
methods: {
getCurrentId () {
console.log('getCurrentId', this.currentId)
}
},
beforeCreate () {
// 在 berforeCrate 資料沒 bind 上 vm,所以 this 拿不到資料
console.log('beforeCreate')
},
created () {
// created 之後可以 data bind 上 vm ,可以透過 this 拿到資料
console.log('created')
let self = this
// 這裡面用 AJAX 取得資料
axios({
method: 'get',
url: 'https://jsonplaceholder.typicode.com/posts/'
})
.then(function (response) {
// 把 AJAX 取得的資料代入 Vue 中
self.posts = response.data
console.log('getResponse')
})
.catch(function (error) {
console.log('err', error)
})
},
beforeMount () {
console.log('beforeMount')
},
mounted () {
// vm 初次 create 時,會從 beforeCreate 執行到這裡(mounted)
console.log('mounted')
},
beforeUpdate () {
console.log('beforeUpdate')
},
updated () {
console.log('updated')
}
})
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo Vue</title>
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]/dist/axios.js"></script>
</head>
<body>
<div id="app">
<h1>{{ title }}</h1>
<h2>{{ body }}</h2>
<h3>{{ id }}</h3>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
currentId: 0,
posts: [],
title: '',
body: ''
},
computed: {
// 除非有需要,否則 computed 的屬性在 vm 初次 create 時不會先被執行一次
// 如果有需要,則 computed 會在 beforeMound 和 mounted 之間執行
id () {
console.log('id computed')
return `post${this.currentId}`
}
},
methods: {
getCurrentId () {
console.log('getCurrentId', this.currentId)
}
},
beforeCreate () {
// 在 berforeCrate 資料沒 bind 上 vm,所以 this 拿不到資料
console.log('beforeCreate')
},
created () {
// created 之後可以 data bind 上 vm ,可以透過 this 拿到資料
console.log('created')
let self = this
// 這裡面用 AJAX 取得資料
axios({
method: 'get',
url: 'https://jsonplaceholder.typicode.com/posts/'
})
.then(function (response) {
// 把 AJAX 取得的資料代入 Vue 中
self.posts = response.data
// 更新需要初始化的資料
self.title = self.posts[self.currentId].title
self.body = self.posts[self.currentId].body
console.log('getResponse')
})
.catch(function (error) {
console.log('err', error)
})
},
beforeMount () {
console.log('beforeMount')
},
mounted () {
// vm 初次 create 時,會從 beforeCreate 執行到這裡(mounted)
console.log('mounted')
},
beforeUpdate () {
console.log('beforeUpdate')
},
updated () {
console.log('updated')
}
})
</script>
</body>
</html>
@pjchender
Copy link
Author

pjchender commented Apr 30, 2017

rendor-error-1.html

如果我們一開始就在 template 中試圖 render 靠 AJAX 取得的資料 <h1>{{ posts[currentId].title }}</h1>,那麼這時候因為 this.posts 還沒有資料,所以會出現錯誤 TypeError: Cannot read property 'title' of undefined;但如果我們接著在 created 的時候把 AJAX 取得的資料給入 this.posts 時,virtual DOM 會 re-render,此時會出現 beforeUpdateupdated,因此資料雖然會顯示,但初次 render 畫面時會出現 Error。

<div id="app">
    <h1>{{ posts[currentId].title }}</h1>
    <h2>{{ posts[currentId].body }}</h2>
    <h3>{{ id }}</h3>
</div>
const vm = new Vue({
      el: '#app',
      data: {
        // 這時候因為 posts 沒有資料,但是 render 要顯示,所以會出現錯誤
        // TypeError: Cannot read property 'title' of undefined
        currentId: 0,
        body: '',
        title: '',
        posts: []
      },
      computed: {
        // computed 的屬性在 vm 初次 create 時不會先被執行一次,
        id () {
          console.log('id computed')
          return `post${this.currentId}`
        }
      }
})

render-error-2.html

另一種情況是,我把需要透過 AJAX 後才能取得的 data 寫在 computed 和 template 中,例如,<h1>{{ title }}</h1>,在 computed 中則包含 title(){ return this.posts[this.currentId].title }。這樣做的話,會使得 vm 在 beforeMountmounted 之前時,因為要 render 出 {{ title }} ,進而使的促發 computed 的執行,但是這時候 AJAX 的資料仍然還沒回來,this.posts 還是沒有內容,因此一樣會出現錯誤 Error in render function: "TypeError: Cannot read property 'title' of undefined"

<div id="app">
    <!-- title 和 body 是透過 computed 取得,
      -  使得 computed 會在 beforeMount 和 mounted 被觸發,
      -  但是這時候 this.posts 仍然還沒有資料
      -->
    <h1>{{ title }}</h1>
    <h2>{{ body }}</h2>
    <h3>{{ id }}</h3>
</div>
const vm = new Vue({
      el: '#app',
      data: {
        // 這時候因為 posts 沒有資料,但是 render 要顯示,所以會出現錯誤
        // TypeError: Cannot read property 'title' of undefined
        currentId: 0,
        posts: []
      },
      computed: {
        // computed 的屬性在 vm 初次 create 時不會先被執行一次,
        id () {
          console.log('id computed')
          return `post${this.currentId}`
        },
        /**
         *  因為在 template 中有 {{ title }}
         *  促使 computed 在 beforeMount 和 mounted 之間執行
         *  但這時候 this.posts 仍沒有資料,出現錯誤
         *  Error in render function: "TypeError: Cannot read property 'title' of undefined"
        **/
        title () {
          console.log('title computed')
          return this.posts[this.currentId].title
        },
        body () {
          console.log('body computed')
          return this.posts[this.currentId].body
        }
      }
})

@pjchender
Copy link
Author

pjchender commented Apr 30, 2017

生命週期基本概念

created: 一般會把 AJAX 取得的資料的過程寫在 created 的時候,在這把取得的資料存到 vm,並且更新相關需要透過 AJAX 資料才能正確初始化的資料
beforeMount: 這時候還沒有 $parent ,組件還沒綁上去
mounted: 的時候則可以開始綁事件,上場,有可能是進到 document 或其他的 component 中
beforeDestroy: 要把自己加上的一些事件清除〈例如,requestAnimatinoFrame〉,還可以拿到 this。
destroyed: 已經拿不到 this
[示範] 程式碼與畫面

相關事件執行時間

  • vm 的 data 要在 created 後才會 bind 上去,因此要透過 this 使用 vm 內的 data 或 vm 內的 methods 時,需要在 created 之後才可以取得 this
  • 一般 vm 初次建立時,生命週期會從 beforeCreatemounted ,不會自動進入 beforeUpdate
  • 如果在 created 之後有更新畫面資料的內容,將會促發進入 beforeUpdateupdated 的 hook
  • 除非相關的資料內容有更新,否則 computed 的屬性在 vm 初次 create 時不會先被執行一次如果有需要,例如畫面
    載入時要 render 被 computed 的屬性 {{ id }},則 computed 會在 beforeMound 和 mounted 之間執行
const vm = new Vue({
      el: '#app',
      data: {
        currentId: 0,
        posts: []
      },
      computed: {
        // 除非有需要,否則 computed 的屬性在 vm 初次 create 時不會先被執行一次
        // 若有需要(例如畫面要 render {{ id }}),那麼會在 beforeMount 和 mounted 之間執行 computed
        id () {
          console.log('id computed')
          return `post${this.currentId}`
        },
        title () {
          console.log('title computed')
          return this.posts[this.currentId].title
        },
        body () {
          console.log('body computed')
          return this.posts[this.currentId].body
        }
      },
      methods: {
        getCurrentId () {
          console.log('getCurrentId', this.currentId)
        }
      },
      beforeCreate () {
        // 在 berforeCrate 資料沒 bind 上 vm,所以 this 拿不到資料
        console.log('beforeCreate')
      },
      created () {
        // created 之後可以 data bind 上 vm ,可以透過 this 拿到資料
        console.log('created')
        let self = this
        // 這裡面用 AJAX 取得資料
        axios({
          method: 'get',
          url: 'https://jsonplaceholder.typicode.com/posts/'
        })
        .then(function (response) {
          // **把 AJAX 取得的資料代入 Vue 中**
          self.posts = response.data
          console.log('getResponse')
        })
        .catch(function (error) {
          console.log('err', error)
        })
      },
      beforeMount () {
        console.log('beforeMount')
      },
      mounted () {
        // **vm 初次 create 時,會從 beforeCreate 執行到這裡(mounted)**
        console.log('mounted')
      },
      beforeUpdate () {
        console.log('beforeUpdate')
      },
      updated () {
        console.log('updated')
      }
})

Vue Life Cycle Map

Vue Life Cycle

@pjchender
Copy link
Author

pjchender commented Apr 30, 2017

搭配 AJAX 較好的使用方法

[示範]程式碼與畫面

在 template 中寫每次 render 時要顯示的資料

<div id="app">
    <h1>{{ title }}</h1>
    <h2>{{ body }}</h2>
    <h3>{{ id }}</h3>
  </div>

把 template 有用到的 data 直接寫到 vm.data 中

  1. 在 created 拿到 request 回來的資料後,若有需要,把 request 取得的資料寫入 vm.data 中
  2. 更新需要透過 AJAX 取的資料後,才能初始化的資料。這時候因為我們有更新資料的關係(原本在 data 已經有給值),因此 vm 在建立時,除了從 beforeCreate 跑到 mounted 之外,會進一步促發 beforeUpdateupdated 的 hook
const vm = new Vue({
      el: '#app',
      data: {
        currentId: 0,
        posts: [],
        title: '',
        body: ''
      },
// ...
      created () {
        // created 之後可以 data bind 上 vm ,可以透過 this 拿到資料
        console.log('created')
        let self = this
        // 這裡面用 AJAX 取得資料
        axios({
          method: 'get',
          url: 'https://jsonplaceholder.typicode.com/posts/'
        })
        .then(function (response) {
          // STEP1: 把 AJAX 取得的資料代入 Vue 中
          self.posts = response.data

          // STEP2: 更新需要透過 AJAX 資料才能初始化的資料,如此將進一步促發 `beforeUpdate` 和 `updated`
          self.title = self.posts[self.currentId].title
          self.body = self.posts[self.currentId].body
          console.log('getResponse')
        })
        .catch(function (error) {
          console.log('err', error)
        })
      }
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment