react組件---徹底可控組件、非可控的組件與派生state

1、派生state常見使用問題

大部分使用派生 state 致使的問題,不外乎兩個緣由:1,直接複製 props 到 state 上;2,若是 props 和 state 不一致就更新 state安全

直接複製props到state

最多見的誤解就是 getDerivedStateFromPropscomponentWillReceiveProps 只會在 props 「改變」時纔會調用。實際上只要父級從新渲染時,這兩個生命週期函數就會從新調用,無論 props 有沒有「變化」。因此,在這兩個方法內直接複製(_unconditionally_)props 到 state 是不安全的。這樣作會致使 state 後沒有正確渲染函數

class EmailInput extends Component {
  state = {
    email: this.props.email
  };

  render() {
    return <input onChange={this.handleChange} value={this.state.email} />;
  }

  handleChange = event => {
    this.setState({ email: event.target.value });
  };
  
  componentWillReceiveProps(nextProps) {
    // Do not do this!
    if (nextProps.email !== this.state.email) {
        this.setState({ email: nextProps.email });
    }
  }
}

class Timer extends Component {
  state = {
    count: 0
  };

  componentDidMount() {
    this.interval = setInterval(
      () =>
        this.setState(prevState => ({
          count: prevState.count + 1
        })),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.interval);
  }

  render() {
    return (
      <Fragment>
        <blockquote>請輸入郵箱:</blockquote>
        <EmailInput email="example@google.com" />
        <p>
          此組件每秒會從新渲染一次
        </p>
      </Fragment>
    );
  }
}
render(<Timer />, document.getElementById("root"));

state 的初始值是 props 傳來的,當在 <input> 裏輸入時,修改 state。可是若是父組件從新渲染,咱們輸入的全部東西都會丟失,即便在重置 state 前比較 nextProps.email !== this.state.email 仍然會致使更新。
這個小例子中,使用 shouldComponentUpdate ,比較 props 的 email 是否是修改再決定要不要從新渲染。可是在實踐中,一個組件會接收多個 prop,任何一個 prop 的改變都會致使從新渲染和不正確的狀態重置。加上行內函數和對象 prop,建立一個徹底可靠的 shouldComponentUpdate 會變得愈來愈難。並且 shouldComponentUpdate 的最佳實踐是用於性能提高,而不是改正不合適的派生 state性能

在 props 變化後修改 state

繼續上面的示例,咱們能夠只使用 props.email 來更新組件,這樣能防止修改 state 致使的 bugthis

class EmailInput extends Component {
  state = {
    email: this.props.email
  };

  render() {
    return <input onChange={this.handleChange} value={this.state.email} />;
  }

  handleChange = event => {
    this.setState({ email: event.target.value });
  };
  
  componentWillReceiveProps(nextProps) {
    // Do not do this!
    if (nextProps.email !== this.props.email) {
        this.setState({ email: nextProps.email });
    }
  }
}

如今組件只會在 prop 改變時纔會改變,可是仍然有個問題。想象一下,若是這是一個密碼輸入組件,擁有一樣 email 的兩個帳戶進行切換時,這個輸入框不會重置(用來讓用戶從新登陸)。由於父組件傳來的 prop 值沒有變化!google

幸運的是,有兩個方案能解決這些問題。這二者的關鍵在於,任何數據,都要保證只有一個數據來源,並且避免直接複製它。咱們來看看這兩個方案。設計

1、徹底可控組件

咱們都知道React表單中有受控組件,那麼什麼是徹底可控組件呢,看下列示例你就明白了code

function ControlledEmailInput(props) {
  return (
    <label>
      Email: <input value={props.email} onChange={props.handleChange} />
    </label>
  );
}

class App extends Component {
  state = {
    draftEmail: 'some.email@test.com'
  };

  handleEmailChange = event => {
    this.setState({ draftEmail: event.target.value });
  };

  resetForm = () => {
    this.setState({
      draftEmail: 'some.email@test.com'
    });
  };

  render() {
    return (
      <Fragment>
        <h1>此示例展現了什麼是徹底可控組件</h1>
        <ControlledEmailInput
          email={this.state.draftEmail}
          handleChange={this.handleEmailChange}
        />
        <button onClick={this.resetForm}>重置</button>
      </Fragment>
    );
  }
}
render(<App />, document.getElementById("root"));

從上示例咱們就知道了,這不正是咱們所見過的組件間通訊嘛,子組件中的email徹底受父組件數據控制就像提線木偶同樣component

2、有key的徹底不受控組件

讓子組件本身存儲臨時的state數據,子組件仍然能夠從 prop 接收「初始值」,可是更改以後的值就和 prop 不要緊了orm

class EmailInput extends Component {
  state = { email: this.props.defaultEmail };

  handleChange = event => {
    this.setState({ email: event.target.value });
  };
  
  resetForm = () => {
    this.setState({ email: 'some.email@test.com' });
  };

  render() {
    return <input onChange={this.handleChange} value={this.state.email} />;
  }
}

class App extends Component {
  inputRef = React.createRef();
  
  state = {
    draftEmail: 'some.email@test.com',
  };
  
  resetForm = () => {
    this.inputRef.current.resetForm()
  };

  render() {
    return (
      <Fragment>
        <h1>此示例展現了什麼是有Key的非可控組件</h1>
        <EmailInput
          defaultEmail={this.state.draftEmail}
          ref={this.inputRef}
        />
        <button onClick={this.resetForm}>重置</button>
      </Fragment>
    );
  }
}
render(<App />, document.getElementById("root"));

總結

設計組件時,重要的是肯定組件是受控組件仍是非受控組件。
不要直接複製 props 的值到 state 中,而是去實現一個受控的組件,而後在父組件裏合併兩個值。
對於不受控的組件,當你想在 prop 變化時重置 state 的話,能夠選擇一下幾種方式:對象

  • 建議: 重置內部全部的初始 state,使用 key 屬性
  • 選項一:僅更改某些字段,觀察特殊屬性的變化(好比 props.userID)。
  • 選項二:使用 ref 調用實例方法。
相關文章
相關標籤/搜索