Props和States是React的重要概念,在Component化的架構內,所有data都以這兩種形式存在,它們見證著一個Component的起承轉合。
Overview
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()
要改變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的次數。
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} />
);
}
}
- User在child的input box輸入文字
- 改動parent的state
- 然後parent re-render
- 又傳新的text給child
- 最後input box的value就會更新
Reference
React - State and Lifecycle
React.Component API Reference
下一篇:React Native教學 Part 4.5 - This Binding和Arrow Function(箭頭函式)