函数式组件VS类组件(二者之间的根本区别)

参考文章:

函数式组件与类组件有何不同

文章在一开始就声明了:这两种写法没有好坏之分,性能差距也几乎可以忽略,而且 React 会长期支持这两种写法。

至于代码的性能问题,这主要取决于代码的作用,而不是选择函数式组件和类组件

React的函数式组件和类组件之间是否有任何根本上的区别?当然有 —— 在心智模型上。

函数式组件捕获了渲染所用的值。(Function components capture the rendered values.)

现在有这么一段代码

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

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

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

它渲染了一个利用setTimeout来模拟网络请求,然后显示一个确认警告的按钮。例如,如果props.userDan,它会在三秒后显示Followed Dan。非常简单。

如果是类组件我们怎么写?一个简单的重构可能就象这样:

class ProfilePage extends React.Component {
  showMessage = () => {
    alert('Followed ' + this.props.user);
  };
  handleClick = () => {
    setTimeout(this.showMessage, 3000);
  };
  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}

这两个组件都描述了同一个逻辑:点击按钮 3 秒后 alert 父级传入的用户名。

那么当点击按钮后的 3 秒内,父级修改了 this.state.user,弹出的用户名是修改前的还是修改后的呢?

你将看到一个非常奇怪的现象:

  • Function Component 展示的是修改前的值:
  • Class 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 是更好的选择。

现在我们明白了React中函数式组件和类组件之间的巨大差别:

函数式组件捕获了渲染所使用的值。

使用Hooks,同样的原则也适用于state。

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>
    </>
  );
}

在点击 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 编写

ref是一种“选择退出”渲染一致性的方法,在某些情况下会十分方便。

通常情况下,你应该避免在渲染期间读取或者设置refs,因为它们是可变得。我们希望保持渲染的可预测性。**然而,如果我们想要特定props或者state的最新值,那么手动更新ref会有些烦人。**我们可以通过使用一个effect来自动化实现它:

function MessageThread() {
  const [message, setMessage] = useState('');
  // 保持追踪最新的值。
  const latestMessage = useRef('');
  useEffect(() => {
    latestMessage.current = message;
  });
  const showMessage = () => {
    alert('You said: ' + latestMessage.current);
  };

我们在一个effect内部执行赋值操作以便让ref的值只会在DOM被更新后才会改变。这确保了我们的变量突变不会破坏依赖于可中断渲染的时间切片和 Suspense等特性。

通常来说使用这样的ref并不是非常地必要捕获props和state通常是更好的默认值。然而,在处理类似于intervals和订阅这样的命令式API时,ref会十分便利。记住,你可以像这样跟踪任何值 —— 一个prop,一个state变量,整个props对象,或者甚至一个函数。

例如当useCallback本身经常改变时。然而,使用一个reducer通常是一个更好的解决方式

使用hooks的最好的心里规则是“写代码时要认为任何值都可以随时更改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码搬运工_田先森

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值