# React 的生命週期
###### tags: `React`
#### `constructor`要放`props`,務必做好做滿

> [官方指南](http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/)
> [正確掌握React生命週期](https://zhuanlan.zhihu.com/p/24926575)
## 小書內容補充:
我們把**React.js將組件渲染,並且構造DOM元素然後塞入頁面的過程稱為組件的掛載**(這個定義請好好記住)。其實React.js內部對待每個組件都有這麼一個過程,也就是初始化組件->掛載到頁面上的過程。所以你可以理解一個組件的方法調用是這麼一個過程:
```javascript=
-> constructor()
-> render()
// 然后构造 DOM 元素插入页面, 其實就是我return裡面寫的內容
```
這當然是很好理解的。React.js 為了讓我們能夠更好的掌控組件的掛載過程,往上面插入了兩個方法:
```javascript=
-> constructor()
-> componentWillMount()
-> render()
// 然后构造 DOM 元素插入页面
-> componentDidMount()
```
掛載的時候,React.js會在組件的render之前調用`componentWillMount`,在DOM元素塞入頁面以後調用`componentDidMount`。
## Constructor的作用
### 示範1:
不讓子元素,產生出`Dom`, 所以在父元素就已經定義好,若是等於true就render出
`<Title />`, 若不是就不render.
```
`{isShow && <Title />}`
```
```javascript=
class Title extends React.Component{
constructor(props) {
super(props)
console.log('Title created')
}
render() {
return (
<h1>Hello React</h1>
)
}
}
class App extends React.Component {
constructor(props){
super(props)
this.state={
isShow: true
}
}
render() {
console.log('App created')
const {isShow} = this.state
return (
<div>
{isShow && <Title />}
<button onClick={()=>{
this.setState({
isShow: !this.state.isShow
})
}}>toggle</button>
</div>
)
}
}
```
## 示範二:(透過CSS)
```javascript=
class Title extends React.Component{
constructor(props) {
super(props)
console.log('Title created')
}
render() {
const {isShow} = this.props
return (
<h1 style={{display: isShow ? 'block': 'none'}}>Hello React</h1>
)
}
}
class App extends React.Component {
constructor(props){
super(props)
this.state={
isShow: true
}
}
render() {
console.log('App created')
const {isShow} = this.state
return (
<div>
{/* {isShow && <Title />} */}
<Title isShow={isShow}/>
<button onClick={()=>{
this.setState({
isShow: !this.state.isShow
})
}}>toggle</button>
</div>
)
}
}
```
## shouldComponentUpdate 示範範例
> 決定要不要call render function, 實際上會用到檢查機制上. 加了不一定效能會變好, 要衡量這個結果, 做優化沒有那麼容易.
從底下程式碼可以看出, 我的render裡面有`<Title>`這個`component`
但是問題是我點選`button`,數字改變,但是因為render裡面有`<Title>`這個`component`,所以他又會被render一次, 因此加上一個機制, 如果下一個傳進來的值,和現在的不相等, 我就不render了.
---
immutable Data => 是為了shouldComponetUpdate而存在
=> 這樣才能夠比較前後資料是否相同.
---
==注意==
比較有分成兩種, shallow compare vs deep compare
```javascript=
var a = {
a: 1
}
var b = {
a: 1
}
shallow compare => false,
deep compare => true
```
```javascript=
class Title extends React.Component{
// 關鍵程式碼
// shouldComponentUpdate(nextProps){
// if(nextProps !== this.props.title){
// return true
// }
// return false
// }
render() {
console.log('Title render')
return (
<h1>{this.props.title}</h1>
)
}
}
class App extends React.Component {
constructor(props){
super(props)
this.state={
number: 1
}
}
// 決定要不要call render function, 實際上會用到檢查機制上.
// shouldComponentUpdate(nextProps, nextState){
// console.log(nextState)
// if(nextState.number % 2 === 0){
// return false
// }
// return true
// }
render() {
const {number} = this.state
return (
<div>
<h1>{this.state.number}</h1>
<Title title={'hello world'} />
<button onClick={()=>{
this.setState({
number: this.state.number + 1
})
}}>click me</button>
</div>
)
}
}
```
加上changeTitle這個button,讓大夥更容易理解
```javascript=
class Title extends React.Component{
shouldComponentUpdate(nextProps){
if(nextProps.title !== this.props.title){
return true
}
return false
}
render() {
console.log('Title render')
return (
<h1>{this.props.title}</h1>
)
}
}
class App extends React.Component {
constructor(props){
super(props)
this.state={
title: 'hello world',
number: 1
}
}
render() {
const {number, title} = this.state
return (
<div>
<h1>{this.state.number}</h1>
<Title title={title} />
<button onClick={()=>{
this.setState({
number: this.state.number + 1
})
}}>click me</button>
<button onClick={() => {
this.setState({
title: Math.random()
})
}}>change title</button>
</div>
)
}
}
```
## componentDidMount && componentWillMount && componentWillUnmount
### 小書
這邊先來補充下小書的`componentWillMount` && `componentWillUnmount`
其實例子和老師舉的差不多, 只是這邊是用`timer`來做顯示和隱藏
奇怪要我自己做開關, 我還有點忘記, 明天再來複習一下.
我們一般會把組件的`state` 的初始化工作放在`constructor`裡面去做;
在`componentWillMount`進行組件的啟動工作,例如Ajax數據拉取、定時器的啟動;
==(注意, 在16.3版本後`componentWillMount`已經被`componentDidMount`取代)==
組件從頁面上銷毀的時候,有時候需要一些數據的清理,例如定時器的清理,就會放在`componentWillUnmount`裡面去做。
```javascript=
class Clock extends React.Component{
constructor() {
super()
this.state = {
date: new Date()
}
}
componentWillMount() {
this.timer = setInterval(() => {
this.setState({date: new Date()})
}, 1000);
}
componentWillUnMount() {
clearInterval(this.timer)
}
render(){
return(
<div>
<p>現在時間是</p>
{this.state.date.toLocaleTimeString()}
</div>
)
}
}
class App2 extends React.Component{
constructor(){
super()
this.state ={
isShow: true
}
this.handleShow = this.handleShow.bind(this)
}
handleShow(){
this.setState({
isShow: !this.state.isShow
})
}
render(){
const {isShow} = this.state
return(
<div>
{isShow ? <Clock />: null}
<button onClick={this.handleShow}>按鈕</button>
</div>
)
}
}
```
---
底下範例是在說明按了button後, title會被隱藏.
並且同時在call了一個cb, 在2秒後會在更改值為`ya`
而在隱藏時, 因為在`DOM`找不到`title`這個`component`
所以會報錯, 這個時候`componentWillMount` 就出現了, 來清除這個這個在
`componentDidMount`的兩秒假設.
> 通常這兩個function是會成對出現的.
```javascript=
class Title extends React.Component{
constructor(props){
super(props)
this.state = {
title: 'hello'
}
}
//在render完後才call這個function.
//在剛才建立的時候會被建立, 這時候還沒有render.
componentDidMount(){
const timer = setTimeout(() => {
this.setState({
title: 'ya!!'
})
}, 2000);
}
//做一些清除垃圾的事情
componentWillMount(){
clearTimeout(this.timer)
}
//通常這兩個都是成對出現的
render() {
const {title} = this.state
return (
<h1>{title}</h1>
)
}
}
class App extends React.Component {
constructor(props){
super(props)
this.state={
showTitle: true
}
}
render() {
const {showTitle} = this.state
return (
<div>
<button onClick={()=>{
this.setState({
showTitle: !this.state.showTitle
})
}}>toggle</button>
{showTitle && < Title/>}
</div>
)
}
}
```
## componentDidUpdate
簡單來講, 就是用來判斷state是否改變了?
```javascript=
//底下是我原本的寫法,其實蠻笨的, 因為重複很多component.
class Apple extends React.Component{
constructor(){
super()
this.state={
counter:1
}
}
render(){
const {counter} = this.state
return(
<div>apple:
{counter}
<button onClick={()=>{
this.setState({
counter: this.state.counter + 1
})
}}>+1</button>
</div>
)
}
}
class Orange extends React.Component {
constructor() {
super()
this.state = {
counter: 1
}
}
render() {
const { counter } = this.state
return (
<div>orange:
{counter}
<button onClick={() => {
this.setState({
counter: this.state.counter + 1
})
}}>+1</button>
</div>
)
}
}
class Banana extends React.Component {
constructor() {
super()
this.state = {
counter: 1
}
}
render() {
const { counter } = this.state
return (
<div>banana:
{counter}
<button onClick={() => {
this.setState({
counter: this.state.counter + 1
})
}}>+1</button>
</div>
)
}
}
class App extends React.Component {
render() {
return (
<div>
<Apple />
<Orange />
<Banana />
</div>
)
}
}
```
```javascript=
//這邊是老師的寫法, 直接都放在同一個component去做渲染.
class App extends React.Component {
constructor(props){
super(props)
this.state ={
apple: 1,
orange: 1,
banana: 1
}
this.addApple = this.addApple.bind(this)
this.addOrange = this.addOrange.bind(this)
this.addBanana = this.addBanana.bind(this)
this.log = this.log.bind(this)
}
// react 最核心是在state改變,需求是判斷水果數量有沒有變動,所以直接監測之前的state和現在的state是否有變動是更好的作法
componentDidUpdate(prevProps, prevState){
if (prevState.apple !== this.state.apple
|| prevState.orange !== this.state.orange
|| prevState.banana !== this.state.banana
){
this.log()
}
}
log(){
console.log(this.state.apple, this.state.orange, this.state.banana)
}
//這邊是就有的方法,我變動了state,我來看他log出來的東西
addApple(){
this.setState({
apple: this.state.apple + 1
}, //this.log)
//裡面原本有一個cb當作參數, 這邊直接簡化成this.log.
}
addOrange() {
this.setState({
orange: this.state.orange + 1
}//this.log)
}
addBanana() {
this.setState({
banana: this.state.banana + 1
}//this.log)
}
render() {
const {apple, orange, banana} = this.state
return (
<div>
<div>
apple: {apple}
<button onClick={this.addApple}>+1</button>
</div>
<div>
orange: {orange}
<button onClick={this.addOrange}>+1</button>
</div>
<div>
banana: {banana}
<button onClick={this.addBanana}>+1</button>
</div>
</div>
)
}
}
```