爲何要了解 Function 寫法的組件呢?由於它正在變得愈來愈重要。html
那麼 React 中 Function Component 與 Class Component 有何不一樣?前端
how-are-function-components-different-from-classes 這篇文章帶來了一個獨特的視角。react
順帶一提,之後會用 Function Component 代替 Stateless Component 的說法,緣由是:自從 Hooks 出現,函數式組件功能在不斷豐富,函數式組件再也不須要強調其無狀態特性,所以叫 Function Component 更爲恰當。
原文事先申明:並無對 Function 與 Classes 進行優劣對比,而僅僅進行特性對比,因此不接受任何吐槽。git
這兩種寫法沒有好壞之分,性能差距也幾乎能夠忽略,並且 React 會長期支持這兩種寫法。
對比下面兩段代碼。github
Class Component:微信
class ProfilePage extends React.Component { showMessage = () => { alert("Followed " + this.props.user); }; handleClick = () => { setTimeout(this.showMessage, 3000); }; render() { return <button onClick={this.handleClick}>Follow</button>; } }
Function Component:less
function ProfilePage(props) { const showMessage = () => { alert("Followed " + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return <button onClick={handleClick}>Follow</button>; }
(在線 Demo>))函數
這兩個組件都描述了同一個邏輯:點擊按鈕 3 秒後 alert
父級傳入的用戶名。性能
以下父級組件的調用方式:this
<ProfilePageFunction user={this.state.user} /> <ProfilePageClass user={this.state.user} />
那麼當點擊按鈕後的 3 秒內,父級修改了 this.state.user
,彈出的用戶名是修改前的仍是修改後的呢?
Class Component 展現的是修改後的值:
<img width=500 src="https://img.alicdn.com/tfs/TB...;>
Function Component 展現的是修改前的值:
<img width=500 src="https://img.alicdn.com/tfs/TB...;>
那麼 React 文檔中描述的 props
不是不可變(Immutable) 數據嗎?爲啥在運行時還會發生變化呢?
緣由在於,雖然 props
不可變,是 this
在 Class Component 中是可變的,所以 this.props
的調用會致使每次都訪問最新的 props
。
而 Function Component 不存在 this.props
的語法,所以 props
老是不可變的。
爲了便於理解,筆者補充一些代碼註解:
Function Component:
function ProfilePage(props) { setTimeout(() => { // 就算父組件 reRender,這裏拿到的 props 也是初始的 console.log(props); }, 3000); }
Class Component:
class ProfilePage extends React.Component { render() { setTimeout(() => { // 若是父組件 reRender,this.props 拿到的永遠是最新的。 // 並非 props 變了,而是 this.props 指向了新的 props,舊的 props 找不到了 console.log(this.props); }, 3000); } }
若是但願在 Class Component 捕獲瞬時 Props,能夠: const props = this.props;
,但這樣的代碼很蹩腳,因此若是但願拿到穩定的 props
,使用 Function Component 是更好的選擇。
看下面的代碼:
function MessageThread() { const [message, setMessage] = useState(""); const showMessage = () => { alert("You said: " + message); }; const handleSendClick = () => { setTimeout(showMessage, 3000); }; const handleMessageChange = e => { setMessage(e.target.value); }; return ( <> <input value={message} onChange={handleMessageChange} /> <button onClick={handleSendClick}>Send</button> </> ); }
(在線 Demo)
在點擊 Send
按鈕後,再次修改輸入框的值,3 秒後的輸出依然是 點擊前輸入框的值。這說明 Hooks 一樣具備 capture value 的特性。
利用 useRef
能夠規避 capture value 特性:
function MessageThread() { const latestMessage = useRef(""); const showMessage = () => { alert("You said: " + latestMessage.current); }; const handleSendClick = () => { setTimeout(showMessage, 3000); }; const handleMessageChange = e => { latestMessage.current = e.target.value; }; }
只要將賦值與取值的對象變成 useRef
,而不是 useState
,就能夠躲過 capture value 特性,在 3 秒後獲得最新的值。
這說明了利用 Function Component + Hooks 能夠實現 Class Component 作不到的 capture props、capture value,並且 React 官方也推薦 新的代碼使用 Hooks 編寫。
原文 how-are-function-components-different-from-classes 從一個側面講述了 Function Component 與 Class Component 的不一樣點,之因此將 Function Component 與 Class Component 相提並論,幾乎都要歸功於 Hooks API 的出現,有了 Hooks,Function Component 的能力才得以向 Class Component 看齊。
關於 React Hooks,以前的兩篇精讀分別有過介紹:
可是,雖然 Hook 已經發布了穩定版本,但周邊生態跟進還須要時間(好比 useRouter
)、最佳實踐整理還須要時間,所以不建議重構老代碼。
爲了更好的使用 Function Component,建議時常與 Class Component 的功能作對比,方便理解和記憶。
下面整理一些常見的 Function Component 問題:
很是建議完整閱讀 React Hooks FAQ。
說實話,Function Component 替代 shouldComponentUpdate
的方案並無 Class Component 優雅,代碼是這樣的:
const Button = React.memo(props => { // your component });
或者在父級就直接生成一個自帶 memo
的子元素:
function Parent({ a, b }) { // Only re-rendered if `a` changes: const child1 = useMemo(() => <Child1 a={a} />, [a]); // Only re-rendered if `b` changes: const child2 = useMemo(() => <Child2 b={b} />, [b]); return ( <> {child1} {child2} </> ); }
相比之下,Class Component 的寫法一般是:
class Button extends React.PureComponent {}
這樣就自帶了 shallowEqual
的 shouldComponentUpdate
。
因爲 useEffect
每次 Render 都會執行,所以須要模擬一個 useUpdate
函數:
const mounting = useRef(true); useEffect(() => { if (mounting.current) { mounting.current = false; } else { fn(); } });
更多能夠查看 精讀《怎麼用 React Hooks 造輪子》
React 官方文檔提供了一種方案:
const [ignored, forceUpdate] = useReducer(x => x + 1, 0); function handleClick() { forceUpdate(); }
每次執行 dispatch
時,只要 state
變化就會觸發組件更新。固然 useState
也一樣能夠模擬:
const useUpdate = () => useState(0)[1];
咱們知道 useState
下標爲 1 的項是用來更新數據的,並且就算數據沒有變化,調用了也會刷新組件,因此咱們能夠把返回一個沒有修改數值的 setValue
,這樣它的功能就僅剩下刷新組件了。
更多能夠查看 精讀《怎麼用 React Hooks 造輪子》
useState
目前的一種實踐,是將變量名打平,而非像 Class Component 同樣寫在一個 State 對象裏:
class ClassComponent extends React.PureComponent { state = { left: 0, top: 0, width: 100, height: 100 }; } // VS function FunctionComponent { const [left,setLeft] = useState(0) const [top,setTop] = useState(0) const [width,setWidth] = useState(100) const [height,setHeight] = useState(100) }
實際上在 Function Component 中也能夠聚合管理 State:
function FunctionComponent() { const [state, setState] = useState({ left: 0, top: 0, width: 100, height: 100 }); }
只是更新的時候,再也不會自動 merge,而須要使用 ...state
語法:
setState(state => ({ ...state, left: e.pageX, top: e.pageY }));
能夠看到,更少的黑魔法,更可預期的結果。
雖然不怎麼經常使用,可是畢竟 Class Component 能夠經過 componentWillReceiveProps
拿到 previousProps
與 nextProps
,對於 Function Component,最好經過自定義 Hooks 方式拿到上一個狀態:
function Counter() { const [count, setCount] = useState(0); const prevCount = usePrevious(count); return ( <h1> Now: {count}, before: {prevCount} </h1> ); } function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }); return ref.current; }
經過 useEffect
在組件渲染完畢後再執行的特性,再利用 useRef
的可變特性,讓 usePrevious
的返回值是 「上一次」 Render 時的。
可見,合理運用 useEffect
useRef
,能夠作許多事情,並且封裝成 CustomHook 後使用起來仍然很方便。
將來
usePrevious
可能成爲官方 Hooks 之一。
useState
函數的參數雖然是初始值,但因爲整個函數都是 Render,所以每次初始化都會被調用,若是初始值計算很是消耗時間,建議使用函數傳入,這樣只會執行一次:
function FunctionComponent(props) { const [rows, setRows] = useState(() => createRows(props.count)); }
useRef
不支持這種特性,須要
寫一些冗餘的函斷定是否進行過初始化。
掌握了這些,Function Component 使用起來與 Class Component 就幾乎沒有差異了!
Function Component 功能已經能夠與 Class Component 媲美了,但目前最佳實踐比較零散,官方文檔推薦的一些解決思路甚至不比社區第三方庫的更好,能夠預料到,Class Component 的功能會被五花八門的實現出來,那些沒有被收納進官方的 Hooks 乍看上去可能會眼花繚亂。
總之選擇了 Function Component 就同時選擇了函數式的好與壞。好處是功能強大,幾乎能夠模擬出任何想要的功能,壞處是因爲能夠靈活組合,若是自定義 Hooks 命名和實現不夠標準,函數與函數之間對接的溝通成本會更大。
討論地址是: 精讀《Stateless VS Class 組件》 · Issue #137 · dt-fe/weekly
若是你想參與討論,請 點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。
關注 前端精讀微信公衆號
special Sponsors
版權聲明:自由轉載-非商用-非衍生-保持署名( 創意共享 3.0 許可證)