# 1. 引言
为什么要了解 Function 写法的组件呢?因为它正在变得越来越重要。
那么 React 中 **Function Component 与 Class Component 有何不同?**
[how-are-function-components-different-from-classes](https://overreacted.io/how-are-function-components-different-from-classes/) 这篇文章带来了一个独特的视角。
> 顺带一提,以后会用 Function Component 代替 Stateless Component 的说法,原因是:自从 Hooks 出现,函数式组件功能在不断丰富,函数式组件不再需要强调其无状态特性,因此叫 Function Component 更为恰当。
# 2. 概述
原文事先申明:并没有对 Function 与 Classes 进行优劣对比,而仅仅进行特性对比,所以不接受任何吐槽。
> 这两种写法没有好坏之分,性能差距也几乎可以忽略,而且 React 会长期支持这两种写法。
## Capture props
对比下面两段代码。
**Class Component:**
```jsx
class ProfilePage extends React.Component {
showMessage = () => {
alert("Followed " + this.props.user);
};
handleClick = () => {
setTimeout(this.showMessage, 3000);
};
render() {
return ;
}
}
```
**Function Component:**
```jsx
function ProfilePage(props) {
const showMessage = () => {
alert("Followed " + props.user);
};
const handleClick = () => {
setTimeout(showMessage, 3000);
};
return ;
}
```
([在线 Demo](https://codesandbox.io/s/pjqnl16lm7))
这两个组件都描述了同一个逻辑:点击按钮 3 秒后 `alert` 父级传入的用户名。
如下父级组件的调用方式:
```jsx
**Function Component 展示的是修改前的值:**
那么 React 文档中描述的 `props` 不是不可变(Immutable) 数据吗?为啥在运行时还会发生变化呢?
原因在于,虽然 `props` 不可变,是 `this` 在 Class Component 中是可变的,因此 `this.props` 的调用会导致每次都访问最新的 `props`。
而 Function Component 不存在 `this.props` 的语法,因此 `props` 总是不可变的。
为了便于理解,笔者补充一些代码注解:
**Function Component:**
```jsx
function ProfilePage(props) {
setTimeout(() => {
// 就算父组件 reRender,这里拿到的 props 也是初始的
console.log(props);
}, 3000);
}
```
**Class Component:**
```jsx
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 特性
看下面的代码:
```jsx
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 (
<>
>
);
}
```
([在线 Demo](https://codesandbox.io/s/93m5mz9w24))
在点击 `Send` 按钮后,再次修改输入框的值,3 秒后的输出依然是 **点击前输入框的值**。这说明 Hooks 同样具有 capture value 的特性。
利用 `useRef` 可以规避 capture value 特性:
```jsx
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 编写](https://reactjs.org/docs/hooks-faq.html#do-i-need-to-rewrite-all-my-class-components)。
# 3. 精读
原文 [how-are-function-components-different-from-classes](https://overreacted.io/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》](https://github.com/dt-fe/weekly/blob/master/79.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20Hooks%E3%80%8B.md)
- [精读《怎么用 React Hooks 造轮子》](https://github.com/dt-fe/weekly/blob/master/80.%E7%B2%BE%E8%AF%BB%E3%80%8A%E6%80%8E%E4%B9%88%E7%94%A8%20React%20Hooks%20%E9%80%A0%E8%BD%AE%E5%AD%90%E3%80%8B.md)
但是,虽然 Hook 已经发布了稳定版本,但周边生态跟进还需要时间(比如 `useRouter`)、最佳实践整理还需要时间,因此不建议重构老代码。
为了更好的使用 Function Component,建议时常与 Class Component 的功能做对比,方便理解和记忆。
下面整理一些常见的 Function Component 问题:
> 非常建议完整阅读 [React Hooks FAQ](https://reactjs.org/docs/hooks-faq.html#do-i-need-to-rewrite-all-my-class-components)。
## 怎么替代 shouldComponentUpdate
说实话,Function Component 替代 `shouldComponentUpdate` 的方案并没有 Class Component 优雅,代码是这样的:
```jsx
const Button = React.memo(props => {
// your component
});
```
或者在父级就直接生成一个自带 `memo` 的子元素:
```jsx
function Parent({ a, b }) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() =>
**special Sponsors**
- [DevOps 全流程平台](https://e.coding.net/?utm_source=weekly)
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))