React函數式組件和類組件[Dan]


一篇對DanHow Are Function Components Different from Classes? 一文的個人閱讀總結,內容來自於此。強烈推薦閱讀 Dan Abramov.的博客

函數式組件和Class組件有什么不同?

Dan很直接的給出了答案:

函數式組件捕獲了渲染所用的值。(Function components capture the rendered values.)

直接看結論可能有點不知所雲。

class組件可能引發的"錯誤"


看一個組件,使用setTimeout模擬網絡請求,點擊button之后警告提示關注某人(user),userprops中讀取。

該組件的function版本:

function ProfilePage(props) {
  const showMessage = () => {
    alert('Followed ' + props.user);
  };

  const handleClick = () => {
    setTimeout(showMessage, 3000);
  };

  return (
    <button onClick={handleClick}>Follow</button>
  );
}

class版本:

 class ProfilePage extends React.Component {
  showMessage = () => {
    alert('Followed ' + this.props.user);
  };

  handleClick = () => {
    setTimeout(this.showMessage, 3000);
  };

  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}

頁面組件代碼:

class App extends React.Component {
  state = {
    user: 'Dan',
  };
  render() {
    return (
      <>
        <label>
          <b>Choose profile to view: </b>
          <select
            value={this.state.user}
            onChange={e => this.setState({ user: e.target.value })}
          >
            <option value="Dan">Dan</option>
            <option value="Sophie">Sophie</option>
            <option value="Sunil">Sunil</option>
          </select>
        </label>
        <h1>Welcome to {this.state.user}’s profile!</h1>
        <p>
          <ProfilePageFunction user={this.state.user} />
          <b> (function)</b>
        </p>
        <p>
          <ProfilePageClass user={this.state.user} />
          <b> (class)</b>
        </p>
        <p>
          Can you spot the difference in the behavior?
        </p>
      </>
    )
  }
}

對於class組件,在選中狀態是userA的時候,點擊follow button之后立馬將select切換其他人(uerB),三秒之后的彈出框是followuserB。(這個動作會在后面多次提及)

在選中userA的時候點擊關注,目的就是關注userA,但是class組件最后彈出框顯示的關注userB,這顯然不符合預期。

為什么、如何解決


如果上面例子使用function組件,彈出框顯示的就會是正確的,雖然切換到了選中的userB,但是彈出框顯示的仍然是點擊的那一刻關注的userA

function組件好像記下了點擊那一刻時候的狀態?

class組件在這個場景中錯誤的原因是class組件每次三秒后從this.props.user中讀取數據,此時的this.props.user已經變了,已經是切換后的新的this.props.user數據。雖然React中props不可變,但是this是可變的

類組件會隨着時間推移改變,在渲染方法和生命周期方法中得到的是最新的實例。而函數式組件的事件處理程序就是渲染結果的一部分,事件處理程序屬於一個擁有特定的propsstate的渲染

也就是說函數式組件保持了事件處理程序與那一次渲染propsstate之間的聯系,本身就是正確的。


來修復類組件中的這個問題:

  1. 可以在點擊的時候就讀取並記錄當下的stateprops,三秒后讀取記錄的數據(而不是讀this.props.xx)再彈出。

    方案可行但是擴展極差,在其他多個變量也這樣做的時候逐層記錄或傳遞非常繁復。

  2. 閉包。

    閉包維持了一個可能隨時間變化的變量,而此處我們要維持的是React的propsstate,React設計中這都是不可變的。讓閉包來維持不變的stateprops,此時再去捕獲這些值,就是一致的。

    render函數中使用閉包:

    class ProfilePage extends React.Component {
      render() {
        // Capture the props!
        const props = this.props;
    
        // Note: we are *inside render*.
        // These aren't class methods.
        const showMessage = () => {
          alert('Followed ' + props.user);
        };
    
        const handleClick = () => {
          setTimeout(showMessage, 3000);
        };
    
        return <button onClick={handleClick}>Follow</button>;
      }
    }
    

    渲染的時候這些需要使用的props已經被捕獲(就像上面方案1的記錄,在render的時候就已經讀取記錄下了)。此時表現彈出內容就會是點擊時候的那個userA了。


class組件的這個問題是修復了,但是在render函數中添加那么多的函數,且並沒有掛載到class上,有點奇怪?

其實去掉class,這就是函數式組件的形式了:

function ProfilePage(props) {
  const showMessage = () => {
    alert('Followed ' + props.user);
  };

  const handleClick = () => {
    setTimeout(showMessage, 3000);
  };

  return (
    <button onClick={handleClick}>Follow</button>
  );
}

React將他們作為參數傳遞,props在渲染時被捕獲了。不同於class組件的this,這里的props不會被改變。

點擊事件處理函數,該函數屬於具有正確user值的一次渲染,事件處理函數和其他回調函數也能讀到這個值。

回頭看這個結論,是不是更好理解一點了:

函數式組件捕獲了渲染所使用的值

函數式組件使用最新的propsstate


函數式組件捕獲了特定渲染的propsstate。但是我們如果又想和class組件一樣讀取最新的propsstate呢?

useRef

Dan 老師:在函數式組件中,你也可以擁有一個在所有的組件渲染幀中共享的可變變量。它被成為“ref”

this.something就像是something.current的一個鏡像。他們代表了同樣的概念。

每一次的渲染結果可以視為一個渲染幀,共享的變量設置為ref,包含DOMRefclass中的實例變量的功能,可以說是非常強大了。

需要最新的propsstate值,可以使用useRef創建的變量來記錄,通過useEffect可以在值變化的時候自動追蹤。

function MessageThread() {
  const [message, setMessage] = useState('');

  // 保持追蹤最新的值。
  const latestMessage = useRef('');
  useEffect(() => {
    latestMessage.current = message;
  });

  const showMessage = () => {
    alert('You said: ' + latestMessage.current);
  };

React函數總是捕獲他們的值


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM