React Native教學 Part 4 - Props & States

2018/06/12 posted in  React Native comments

PropsStates是React的重要概念,在Component化的架構內,所有data都以這兩種形式存在,它們見證著一個Component的起承轉合。

Overview

React Props States

Props

Props全寫是Properties,可以解作資產,一般來自Parent component,例如:

// Parent
render(){
  return (
    <ChildComponent text={'123'} />
  );
}

這樣ChildComponent就得到了一個props,可以在各種lifecycle methods裡使用(例如render()):

// Child
render(){
  return (
    <Text>{this.props.text}</Text>
  );
}
  • {}是JSX裡的syntax,裡面可以寫任何js,例如可以{getSomeString()}等等
  • this是指「現在這個component instance」,this.props就是它收到的所有props

States

States是Component內儲存的data,一般會在constructor()建立:

class SomeComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      text: 'Hello World!',
    };
  }
}
render(){
  return (
    <Text>{this.state.text}</Text>
  );
}

setState()

1*IugEwe6Lkm5iFB-Q9zvc5

要改變state,不能直接this.state.text = 'something',必須通過setState()

setState(stateChange[, callback])
// 或
setState(updater[, callback])

例子

可以直接pass一個object進去:

// states: { text: 'abc', counter: 0 }

this.setState({ text: 'hello' });

// states: { text: 'hello', counter: 0 }

留意stateChange並不直接是新的state,stateChange會和舊state融合。

也可以詳細define一個updater:

// states: { text: 'abc', counter: 0 }
// props: { step: 2 }

this.setState((prevState, props) => {
  return {counter: prevState.counter + props.step};
});

// states: { text: 'abc', counter: 2 }
// props: { step: 2 }

setState()是異步執行的(asynchronous)

這是很多bug的來源,請謹記setState()是異步的。

例如:

// state: { quantity: 0 }
const updateState = () => {
  this.setState({ quantity: this.state.quantity + 1 })
}

updateState();
updateState();
updateState();

React會嘗試把多個setState融合成一次update,所以可能this.state.quantity一直沒有變,一直都是0+1。這樣我們expect quantity是3,但結果會變成1。

這情況需要用updater,確保每次update都基於上一個update:

this.setState((prevState) => {
  return {quantity: prevState.quantity + 1};
});

更新次序

雖然是異步,但update的次序還是按照原本的,例如:

this.setState({ quantity: 1 });
this.setState({ quantity: 2 });
this.setState({ quantity: 3 });
// 最後必定等於3

Callback

由於setState()的異步特性,所以提供了callback param,執行setState()完成後的工作。

// 錯誤示範
this.setState({quantity: 1});
console.log(this.state.quantity);

// 正確示範
this.setState({quantity: 1}, () => {
  console.log(this.state.quantity);
})

為甚麼會是異步?

因為每次state改動都會觸發re-render頁面,基於效能(performance)的考慮,React會盡可能減少需要重新render的次數。

setState() Doc

Props是不可變的(Immutable)

Child是不可以改動Parent給你的props的,如果真的要改動,可以先變成自己的states:

class SomeComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      text: props.text,
    };
  }
}

然後再用setState()去改動它。

為甚麼Immutable?

React跟從Unidirectional Data Flow的設計理念,所有data由上而下、由Parent到Child,一層一層把props傳下去,而且只會向單一方向傳,而不會傳回頭。

目的在於令所有data的改變都十分清晰和可以預見,各Component之間不會有副作用(side-effect),也方便debug。

那Child可以如何改變Parent?

Parent可以給Child一個callback function,這種做法最常見於button,要處理button按下去後的工作,必須給它一個callback,例如:

class SomeComponent extends PureComponent {
  onPressButton = () => {
    console.log('Button pressed!');
  }
  render() {
    return (
      <Button onPress={this.onPressButton} />
    );
  }
}

注意是this.onPressButton,而不是this.onPressButton()(這個在render的時候就會直接被call了)。

再來一個例子:

// Parent
class ParentComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      text: 'Hello World!',
    };
  }
  changeText = (text) => {
    this.setState({ text });  // 這是ES6的語法,等於{ text: text }
  }
  render(){
    return (
      <ChildComponent text={this.state.text} onChangeText={this.changeText} />
    );
  }
}

// Child
class ChildComponent extends PureComponent {
  render(){
    const { text, onChangeText } = this.props;  // 也是ES6的語法
    return (
      <TextInput value={text} onChangeText={onChangeText} />
    );
  }
}
  1. User在child的input box輸入文字
  2. 改動parent的state
  3. 然後parent re-render
  4. 又傳新的text給child
  5. 最後input box的value就會更新

Reference

React - State and Lifecycle
React.Component API Reference


下一篇:React Native教學 Part 4.5 - This Binding和Arrow Function(箭頭函式)