为什么要了解 Function 写法的组件呢?因为它正在变得越来越重要。
那么 React 中 Function Component 与 Class Component 有何不同?
how-are-function-components-different-from-classes 这篇文章带来了一个独特的视角。
顺带一提,以后会用 Function Component 代替 Stateless Component 的说法,原因是:自从 Hooks 出现,函数式组件功能在不断丰富,函数式组件不再需要强调其无状态特性,因此叫 Function Component 更为恰当。
2. 概述原文事先申明:并没有对 Function 与 Classes 进行优劣对比,而仅仅进行特性对比,所以不接受任何吐槽。
这两种写法没有好坏之分,性能差距也几乎可以忽略,而且 React 会长期支持这两种写法。
Capture props对比下面两段代码。
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:
function ProfilePage(props) { const showMessage = () => { alert("Followed " + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return <button onClick={handleClick}>Follow</button>; }(在线 Demo)
这两个组件都描述了同一个逻辑:点击按钮 3 秒后 alert 父级传入的用户名。
如下父级组件的调用方式:
<ProfilePageFunction user={this.state.user} /> <ProfilePageClass user={this.state.user} />那么当点击按钮后的 3 秒内,父级修改了 this.state.user,弹出的用户名是修改前的还是修改后的呢?
Class Component 展示的是修改后的值:
Function Component 展示的是修改前的值:
那么 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 是更好的选择。
Hooks 也具有 capture value 特性看下面的代码:
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 官方也推荐 。
3. 精读原文 how-are-function-components-different-from-classes 从一个侧面讲述了 Function Component 与 Class Component 的不同点,之所以将 Function Component 与 Class Component 相提并论,几乎都要归功于 Hooks API 的出现,有了 Hooks,Function Component 的能力才得以向 Class Component 看齐。
关于 React Hooks,之前的两篇精读分别有过介绍:
精读《React Hooks》
精读《怎么用 React Hooks 造轮子》
但是,虽然 Hook 已经发布了稳定版本,但周边生态跟进还需要时间(比如 useRouter)、最佳实践整理还需要时间,因此不建议重构老代码。
为了更好的使用 Function Component,建议时常与 Class Component 的功能做对比,方便理解和记忆。
下面整理一些常见的 Function Component 问题:
非常建议完整阅读 。
怎么替代 shouldComponentUpdate说实话,Function Component 替代 shouldComponentUpdate 的方案并没有 Class Component 优雅,代码是这样的:
const Button = React.memo(props => { // your component });