React 16.3 | Derived State Design
使用 Derived State 的時機
getDerivedStateFromProps的目的:讓元件根據 props 更新 state- 誤用 Derived State 造成問題的設計
- 無條件根據 props 更新 state
- 只要 state 跟 props 不同,則無條件更新 state
使用 Derived State 常見的 Bug
Derived State 同時受控於兩種來源:外部 (props)、內部 (setState)。
- 外部來源為 Controlled
- 內部來源為 Uncontrolled
Anit-pattern:無條件以 props 更新 state
只要父元件重新渲染,componentWillReceiveProps 及 getDerivedStateFromProps 則無條件呼叫。
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
update() {
this.setState(prev => ({counter: ++prev.counter}))
}
render() {
return (
<div data-counter={this.state.counter}>
<Sub value={"This is sub"}/>
</div>
)
}
}
class Sub extends React.Component {
static getDerivedStateFromProps(np, ns) {
//NOTE:
//1. 數據來源-1
//2. 只要 Main 重新渲染,
// 無論傳進 Sub 的 props 有沒有更新,
// 皆會觸發 getDerivedStateFromProps。
return {
value: np.value
}
}
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.state = {
value: null
}
}
onChange(e) {
//NOTE: 數據來源-2
this.setState({value: e.target.value})
}
render() {
return (
<inpu value={this.state.value}
onChange={this.onChange}/>
);
}
}
雖然可以使用 shouldComponentUpdate 來判斷是否需要更新,但是 props 通常不只一種,再加上 Function、Object 很多時候為 literal 宣告,每次重新渲染皆不同,造成判斷條件複雜。
且 shouldComponentUpdate 的本意為優化效能,因此不適合用做 Derived State 的判斷。
Anti-Pattern:消弭預期的 re-render
Imagine a password manager app using the above input component. When navigating between details for two accounts with the same email, the input would fail to reset. This is because the prop value passed to the component would be the same for both accounts! This would be a surprise to the user, as an unsaved change to one account would appear to affect other accounts that happened to share the same email.
class Sub extends React.Component {
static getDerivedStateFromProps(np, ns) {
let result = null;
if (np.value !== this.props.value) {
result = {
value: np:value
}
}
return result
}
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.state = {
value: null
}
}
onChange(e) {
//NOTE: 數據來源-2
this.setState({value: e.target.value})
}
render() {
return (
<inpu value={this.state.value}
onChange={this.onChange}/>
);
}
}
解決方案
Fully Controlled
子元件完全由父元件控制
class Sub extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<inpu value={this.props.value}
onChange={this.props.onChange}/>
);
}
}
Fully Uncontrolled with Key
- 在父元件,針對不同資料(例如:不同人)的子元件給予 key,一旦 key 值修改,則視為新元件。
- 在子元件,state 為自我管理的。
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
<div data-counter={this.state.counter}>
<Sub key={this.state.counter}
defaultValue={"This is sub"}/>
</div>
}
}
class Sub extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.defaultValue
}
}
render() {
return (
<inpu value={this.state.value}
onChange={e => this.setState({value: e.target.event})}/>
);
}
}
getDerivedStateFromProps 增加其他條件作為 props 是否更新的依據
子元件增加更新內容的 API
class Sub extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.defaultValue
}
}
resetValue(val) {
this.setState({value: val});
}
render() {
return (
<inpu
value={this.state.value}
onChange={e => this.setState({value: e.target.event})}
/>
);
}
}