# React 中的 state, props
###### tags: `React`
## State
> 一個組件的顯示形態是可以由它數據狀態和配置參數決定的,一個組件可以擁有自己的狀態,就像一個點贊按鈕,可以有“已點贊”和“未點贊”狀態,並且可以在這兩種狀態之間進行切換。React.js的`state`就是用來存儲這種可變化的狀態的。
在物件導向裡面, 若是要更改state, 則每一個component建立的時候都會呼叫一個function, 叫做constructor. 同時因為繼承因此呼叫時會call`super()`
先來理解下程式碼:
```javascript=
class App extends React.Component {
constructor(){
super()
//這個this是在oop中,所以是指instance
this.state = {
title:'hello',
counter: 1
}
}
render(){
return (
<div>
<h1 onClick={()=>{
this.setState({
counter: this.state.counter + 1
})
}}>{this.state.title}//可以把它看成物件要call值</h1>
<div>{this.state.counter}</div>
</div>
)
}
}
```
* 任何要改變`state`的動作都要加`this.setState`, 裡面傳入一個`object` (所以要有key和)
```javascript=
this.setState({
counter: this.state.counter + 1
})
```
而不是
```javascript=
counter: this.state.counter ++
```
### 優化
這一段`onClick`事件, 可以進行優化, 用function包住
```javascript=
()=>
{
this.setState
(
{counter: this.state.counter + 1}
)
}
```
```javascript=
handleClick(){
this.setState({
counter: this.state.counter + 1
})
}
```
在`render()`裡面,也要修改成`this.handleClick`
```javascript=
render(){
return (
<div>
<h1 onClick={this.handleClick}>{this.state.title}</h1>
<div>{this.state.counter}</div>
</div>
)
}
```
### setState補充
這個範例,透過更改`state`實作出簡單的開關,
`isLiked`存放在實例的`state`對象當中,這個對像在構造函數里面初始化。這個組件的render函數內,會根據組件的`state`的中的isLiked不同顯示“取消”或“點贊”內容。並且給`button`加上了點擊的事件監聽。
```javascript=
class Title extends React.Component {
constructor(){
super()
this.state={
isLiked: false
}
this.button = this.button.bind(this)
}
button(){
console.log(this.state.isLiked)
this.setState({
isLiked: !this.state.isLiked
})
console.log(this.state.isLiked)
}
render(){
return(
<h1 onClick={this.button}>
{this.state.isLiked? 'good':'bad'}
</h1>
)
}
}
class App extends React.Component {
render() {
const name = 'yoyoyo'
return (
<div>
<Title />
</div>
)
}
}
```
> 在`Button`事件監聽函數里面,大家可以留意到,我們調用了`setState`函數,每次點擊都會更新`isLiked`屬性為`!isLiked`,這樣就可以做到點贊和取消功能。
> `setState`方法由父類Component所提供。當我們調用這個函數的時候,React.js會更新組件的狀態`state`,並且重新調用`render`方法,然後再把`render`方法所渲染的最新的內容顯示到頁面上。
注意,當我們要改變組件的狀態的時候,不能直接用`this.state = xxx` 這種方式來修改,如果這樣做React.js就沒辦法知道你修改了組件的狀態,它也就沒有辦法更新頁面。所以,一定要使用React.js提供的`setState`方法,它接受一個物件或者函數作為參數。
### setState 接受函數參數
這裡還有要注意的是,當你調用`setState`的時候,React.js並不會馬上修改`state`。而是把這個對象放到一個更新隊列裡面,稍後才會從隊列當中把新的狀態提取出來合併到state當中,然後再觸發組件更新。這一點要好好注意。可以體會一下下面的代碼:
你會發現兩次打印的都是false,即使我們中間已經`setState`過一次了。這並不是什麼bug,只是React.js的`setState`把你的傳進來的狀態緩存起來,稍後才會幫你更新到`state`上,所以你獲取到的還是原來的`isLiked`。
所以如果你想在setState之後使用`新的state`來做後續運算就做不到了,例如:
```javascript=
Button () {
this.setState({ count: 0 }) // => this.state.count 还是 undefined
this.setState({ count: this.state.count + 1}) // => undefined + 1 = NaN
this.setState({ count: this.state.count + 2}) // => NaN + 2 = NaN
}
```
上面的代碼的運行結果並不能達到我們的預期,我們希望`count`運行結果是`3`,可是最後得到的是`NaN`。但是這種後續操作依賴前一個`setState`的結果的情況並不罕見。
這裡就自然地引出了`setState`的第二種使用方式,可以==接受一個函數作為參數==。React.js會把上一個`setState`的結果傳入這個函數,你就可以使用該結果進行運算、操作,然後返回一個對像作為更新`state`的對象:
```javascript=
Button () {
this.setState((prevState) => {
return { count: 0 }
})
this.setState((prevState) => {
return { count: prevState.count + 1 } // 上一个 setState 的返回是 count 为 0,当前返回 1
})
this.setState((prevState) => {
return { count: prevState.count + 2 } // 上一个 setState 的返回是 count 为 1,当前返回 3
})
// 最后的结果是 this.state.count 为 3
}
```
### setState 合併
上面我們進行了三次`setState`,但是實際上組件只會重新渲染一次,而不是三次;這是因為在React.js內部會把JavaScript事件循環中的消息隊列的同一個消息中的`setState`都進行合併以後再重新渲染組件。
## props
看官網學到一課
```javascript=
class HelloMessage extends React.Component{
render(){
return(
<div>
Hello {this.props.name}
</div>
)
}
}
// Hello Taylor
ReactDOM.render(<HelloMessage name='Taylor'/>, document.getElementById('root'))
```
> 就是傳進來給你的, Component之間可以透過props把值傳下去
我預期`this.state.counter`會變得更複雜而不是從1開始.
所以把原本的地方=> 換成`component`型式
但是問題來了, 我原本寫的好好的, `this.state.counter` 怎麼傳進去
很簡單, 在裡面在定義一個物件名稱`number`,把值傳到裡面即可
```javascript=
class Counter extends React.Component{
render(){
return(
<div>{this.props.number}</div>
)
}
}
class App extends React.Component {
constructor(){
super()
this.state = {
title: 'React',
counter : 1
}
this.handleCounter = this.handleCounter.bind(this)
}
handleCounter(){
this.setState({
counter: this.state.counter + 1
})
}
render() {
return (
<div>
<h1 onClick={this.handleCounter}>{this.state.title}</h1>
<Counter number={this.state.counter}/>
</div>
)
}
}
```
### 運作流程
1. 進入`<App />` => render`handleCounter`和`this.state.title`
2. 看到`<Counter />` call `Counter Component` => render`number`
10/4 更新, 我現在是在`App`裡面,但是我又進入了`counter`,在`counter`中
我想取到`App`的`state`, 但是我在不同的`component`裡面, 所以這時候我要先定義一個變數,去接`this.state.number`, 然後在該==新組建上去調用父元素的參數來配置該組件==
### props.children
或是可以改成針對`title`分割, 注意我在`App`裡面是用`<title />`包住,所以在接收時, 要用`this.props.children`
而這樣做的好處是, 可以再包住的`Counter`裡面任意加東西
```javascript=
class Counter extends React.Component{
render(){
return(
<div>{this.props.children}</div>
)
}
}
class Title extends React.Component {
render() {
return (
<h1>{this.props.children}</h1>
)
}
}
class App extends React.Component {
constructor(){
super()
this.state = {
title: 'React',
counter : 1
}
this.handleCounter = this.handleCounter.bind(this)
}
handleCounter(){
this.setState({
counter: this.state.counter + 1
})
}
render() {
return (
<div>
<h1 onClick={this.handleCounter}>{this.state.title}</h1>
<Title>
<a href="https://google.com">{'hello2'}</a>
</Title>
<Counter>
{this.state.counter}
</Counter>
</div>
)
}
}
```
## Class Component vs functional Component
當這個componet不用`state`,不用繼承,就可以用functional Component, 簡單return東西出來.
```javascript=
function Counter(props){
return(
<div>{props.number}</div>
)
}
//等同於
function Counter(props){
const {number} = props
return(
<div>{number}</div>
)
}
//等同於 => 常見做法
function Counter({ number }){
return(
<div>{number}</div>
)
}
// => function裡面的參數已經定義為props,所以這樣寫是指props = {number}(解構語法)
//ES6
const Counter =(props)=>{
return(
<div>{props.number}</div>
)
}
```
## Component 間的溝通
已經在`APP`裡面定義了`state`, `title`和`counter` 兩個屬性.
`return`完後, 因為還想再讓他分割成`component` 所以, 所以又用獨立出`component`
示意:
`App component` => return => `title component` => 透過props接收 =>新`title component` => 想要在裡面增加功能
在children時,沒有辦法去改變parent的state, 所以不能把功能下在`新title component`, 把功能下在上一層`title
component` 比較合理.
### 整個流程
1. render Title text = 1, handleCounter = funcA
2. trigger Title > h1 > onClick => props.handelCounter => funcA
3. App.handleCounter => setState => counter: 2
4. re-render
5. render Title text = 2 , handleCounter = funcA
掌握一個流程, 只要改變`state` 就會re-render
```javascript=
function Counter({ number }){
return(
<div>{number}</div>
)
}
const Title = (props) => {
return (
<h1 onClick={props.handleCounter}>{props.text}</h1>
)
}
class App extends React.Component {
constructor(){
super()
this.state = {
title: 'React',
counter : 1
}
this.handleCounter = this.handleCounter.bind(this)
}
handleCounter(){
this.setState({
counter: this.state.counter + 1
})
}
render() {
console.log('render')
return (
<div>
<Title handleCounter = {this.handleCounter} text={this.state.counter}/>
{/* <h1 onClick={this.handleCounter}>{this.state.title}</h1> */}
<Counter number={this.state.counter}/>
</div>
)
}
}
```
## props透過父元素來傳送參數
已經在changeCounter定義了`number`這個參數,但是沒有給值
所以在子元素的`Text`來定義他的值.
==因為要執行這個function而不是簡單取值,所以寫成執行的語句==
```javascript=
const Text =(props)=>{
return(
<div>
<h1 onClick={()=>{
props.changeCounter(Math.random())
}}>{props.counters}</h1>
</div>
)
}
class Title extends React.Component {
constructor(){
super()
this.state = {
counter: 1
}
this.Button = this.Button.bind(this)
this.changeCounter = this.changeCounter.bind(this)
}
Button(){
this.setState({
counter: this.state.counter + 1
})
}
changeCounter(number){
this.setState({
counter: number
})
}
render(){
return(
<div>
<h1>{this.state.counter}</h1>
<Text changeCounter={this.changeCounter} counters={this.state.counter}/>
</div>
)
}
}
```
==只有`Component`自己可以改變`state`,外部要call只能透過props來取值,真的要改變只能去呼叫父元素提供出的function==