首先我们要知道的是,项目性能能主要取决于代码的作用,而不是选择函数式还是类组件。尽管优化策略各有略微不同,但它们之间的性能差异可以忽略不计。

一、函数式组件捕获了渲染所用的值

首先我们来看下面这个组件:

function App(props) {
  const showMessage = () => {
    alert('Hello'   props.user);
  };
  const handleClick = () => {
    setTimeout(showMessage, 3000);
  };
  return (
    <button onClick={handleClick}>Say</button>
  );
}

它渲染了一个利用来模拟网络请求,然后显示一个确认警告的按钮。例如,如果是传递进来的 props.user 是 jie,那么三秒后就会弹出 Hello jie。

那么我们用类应该怎么写这个组件呢?一个简单的重构可能就象这样:

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

我们通常做代码重构的时候都认为他们两个是等效的,但是事实真的如此吗,我们很少注意到它们之间的含义。

下面我们新建一个 react 项目,在 src下新建两个组件,一个 classComponent 组件,一个是 functionComponent 组件。代码就是上面我们写的这两个组件,只不过内容稍有区别:

classComponent:

import React from 'react';
class ProfilePage extends React.Component {
  showMessage = () => {
    alert('你选择了 '   this.props.user);
  };
  handleClick = () => {
    setTimeout(this.showMessage, 3000);
  };
  render() {
    return <button onClick={this.handleClick}>选择</button>;
  }
}
export default ProfilePage;

functionComponent:

import React from 'react';
function ProfilePage(props) {
  const showMessage = () => {
    alert('你选择了 '   props.user);
  };
  const handleClick = () => {
    setTimeout(showMessage, 3000);
  };
  return (
    <button onClick={handleClick}>选择</button>
  );
}
export default ProfilePage;

在 app.js 中我们将这两个组件引入:

import React from "react";
import ReactDOM from "react-dom";
import ProfilePageFunction from './functionComponent';
import ProfilePageClass from './classComponent';
export default class App extends React.Component {
  state = {
    user: '小杰',
  };
  render() {
    return (
      <>
        <label>
          <b>选择你想要拜访的朋友</b>
          <select
            value={this.state.user}
            onChange={e => this.setState({ user: e.target.value })}
          >
            <option value="小杰">小杰</option>
            <option value="小尚">小尚</option>
            <option value="小宁">小宁</option>
          </select>
        </label>
        <h1>欢迎来到 {this.state.user}的 家!</h1>
        <p>
          <ProfilePageFunction user={this.state.user} />
          <b> (这是来自函数式组件的)</b>
        </p>
        <p>
          <ProfilePageClass user={this.state.user} />
          <b> (这是来自类组件的)</b>
        </p>
      </>
    )
  }
}

运行项目,科研看到这样的界面:

当我们单击上面的按钮时,执行的就是函数式组件,点击下面的按钮时,执行的就是类。如果按照我们以往的思路,他们二者都会有相同的结果,但事实真的如此吗?

我们按照下面的顺序执行:

1. 点击函数式组件按钮

2. 在点击后立刻切换想要拜访的朋友

函数式组件的执行结果如下:

页面弹出的还是我们当时选择的值

同样的操作我们再试一下类组件:

现在页面弹出的就是我们实时更改的值了。

在这个例子中,第一个行为是正确的。因为最开始我选择要拜访小杰点击了确定发出了命令,然后我再切换到小尚,但是我并没有点击确定,我的组件不应该混淆我要拜访的人。在这里,类组件的实现很明显是错误的。

所以为什么我们的例子中类组件会有这样的表现?

让我们来仔细看看我们类组件中的方法:showMessage

showMessage = () => {
    alert('你选择了 '   this.props.user);
  };

这个类方法从中读取数据。在 React 中 Props 是不可变的,所以他们永远不会改变。然而,this是,而且永远是,可变的。

事实上,这就是类组件存在的意义。React本身会随着时间的推移而改变,以便你可以在渲染方法以及生命周期方法中得到最新的实例。所以如果在请求已经发出的情况下我们的组件进行了重新渲染,将会改变。

我们的组件属于一个拥有特定 props 和 state 的特定渲染。

然而,调用一个回调函数读取 的 timeout 会打断这种关联。我们的回调并没有与任何一个特定的渲染绑定在一起,所以它失去了正确的 props。

二、闭包让类组件成为拥有特定props和state的渲染

我们想要以某种方式“修复”拥有正确 props 的渲染与读取这些 props 的回调之间的联系。它们在类的某个地方被弄丢了。

一种方法是在调用事件之前读取,然后将他们显式地传递到timeout回调函数中去:

import React from 'react';
class ProfilePage extends React.Component {
  showMessage = (user) => {
    alert('你选择了 '   user);
  };
  handleClick = () => {
    const {user} = this.props;
    setTimeout(() => this.showMessage(user), 3000);
  };
  render() {
    return <button onClick={this.handleClick}>确定</button>;
  }
}
export default ProfilePage;

这种方法会起作用。然而,这种方法使得代码明显变得更加冗长,并且随着时间推移容易出错。如果我们需要的不止是一个props怎么办?如果我们还需要访问state怎么办?

然而,如果我们能利用JavaScript闭包的话问题将迎刃而解。

通常来说我们会避免使用闭包,但是在React中,props和state是不可变的,这就消除了闭包的一个主要缺陷。

这就意味着如果你在一次特定的渲染中捕获那一次渲染所用的props或者state,你会发现他们总是会保持一致,就如同你的预期那样。

class ProfilePage extends React.Component {
  render() {
    const props = this.props;
    const showMessage = () => {
      alert('你选择了 '   props.user);
    };
    const handleClick = () => {
      setTimeout(showMessage, 3000);
    };
    return <button onClick={handleClick}>确定</button>;
  }
}

你在渲染的时候就已经“捕获”了props。这样,在它内部的任何代码(包括)都保证可以得到这一次特定渲染所使用的props。上面的例子是正确的,但是看起来很奇怪。如果你在方法中定义各种函数,而不是使用class的方法,那么使用类的意义在哪里?

所以这个时候我们就明白了函数式组件和类组件的区别:

function ProfilePage({ user }) {
  const showMessage = () => {
    alert('Followed '   user);
  };
  const handleClick = () => {
    setTimeout(showMessage, 3000);
  };
  return (
    <button onClick={handleClick}>Follow</button>
  );
}

当父组件使用不同的props来渲染时,React会再次调用函数。但是我们点击的事件处理函数,"属于"具有自己的值的上一次渲染,并且回调函数也能读取到这个值。它们都保持完好无损。

三、区分useState与useRef的使用

使用Hooks,同样的原则也适用于状态。看这个例子:

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

如果我发送一条特定的消息,组件不应该对实际发送的是哪条消息感到困惑。这个函数组件的变量捕获了我们在浏览器中执行单击处理函数的那一次渲染。所以当我点击“发送”时那一刻输入框中的内容就会被设置为弹出的值。

因此我们知道,在默认情况下React中的函数会捕获props和state。但是如果我们想要读取并不属于这一次特定渲染的,最新的props和state呢?

在函数式组件中,你也可以拥有一个在所有的组件渲染帧中共享的可变变量。它被成为“ref”:

function MyComponent() {
  const ref = useRef(null);
  // 你可以通过 ref.current 来获取保存的值.
  // ...
}

在很多情况下,你并不需要它们,并且分配它们将是一种浪费。但是,如果你愿意,你可以这样手动地来追踪这些值:

function MessageThread() {
  const [message, setMessage] = useState('');
  const latestMessage = useRef('');
  const showMessage = () => {
    alert('You said: '   latestMessage.current);
  };
  const handleSendClick = () => {
    setTimeout(showMessage, 3000);
  };
  const handleMessageChange = (e) => {
    setMessage(e.target.value);
    latestMessage.current = e.target.value;
  };

如果我们在 state 中读取,我们将得到在我们按下发送按钮那一刻的信息。但是当我们通过 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);
  };

正如我们上面看到的,闭包实际上帮我们解决了很难注意到的细微问题。同样,它们也使得在并发模式下能更轻松地编写能够正确运行的代码。这是可行的,因为组件内部的逻辑在渲染它时捕获并包含了正确的props和state。

React函数总是捕获他们的值 —— 现在我们也知道这是为什么了。

到此这篇关于React函数组件与类的区别有哪些的文章就介绍到这了,更多相关React函数组件与类内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

React函数组件与类的区别有哪些的更多相关文章

  1. ios – React native链接到另一个应用程序

    如果是错误的,有人知道如何调用正确的吗?

  2. ios – React Native – 在异步操作后导航

    我正在使用ReactNative和Redux开发移动应用程序,我正面临着软件设计问题.我想调用RESTAPI进行登录,如果该操作成功,则导航到主视图.我正在使用redux和thunk所以我已经实现了异步操作,所以我的主要疑问是:我应该把逻辑导航到主视图?我可以直接从动作访问导航器对象并在那里执行导航吗?.我对组件中的逻辑没有信心.似乎不是一个好习惯.有没有其他方法可以做到这一点?

  3. 在ios中使用带有React Native(0.43.4)的cocoapods的正确方法是什么?

    我已经挖掘了很多帖子试图使用cocoapods为本地ios库设置一个反应原生项目,但我不可避免地在#import中找到了丢失文件的错误.我的AppDelegate.m文件中的语句.什么是使用反应原生的可可豆荚的正确方法?在这篇文章发表时,我目前的RN版本是0.43.4,而我正在使用Xcode8.2.1.这是我的过程,好奇我可能会出错:1)

  4. ios – React Native WebView滚动行为无法按预期工作

    如何确保滚动事件的行为与ReactNative应用程序中的浏览器相同?

  5. ios – React Native – BVLinearGradient – 找不到’React/RCTViewManager.h’文件

    谢谢.解决方法几天前我遇到了完全相同的问题.问题是在构建应用程序时React尚未链接.试试这个:转到Product=>Scheme=>管理方案…=>点击你的应用程序Scheme,然后点击Edit=>转到Build选项卡=>取消选中ParallelizeBuild然后点击标志添加目标=>搜索React,选择第一个名为React的目标,然后单击Add然后在目标列表中选择React并将其向上拖动到该列表中的第一个.然后转到Product=>再次清理并构建项目.这应该有所帮助.

  6. ios – React Native – NSNumber无法转换为NSString

    解决方法在你的fontWeight()函数中也许变成:

  7. ios – React native error – react-native-xcode.sh:line 45:react-native:command not found命令/ bin/sh失败,退出代码127

    尝试构建任何(新的或旧的)项目时出现此错误.我的节点是版本4.2.1,react-native是版本0.1.7.我看过其他有相同问题的人,所以我已经更新了本机的最新版本,但是我仍然无法通过xcode构建任何项目.解决方法要解决此问题,请使用以下步骤:>使用节点版本v4.2.1>cd进入[你的应用]/node_modules/react-native/packager>$sh./packager.s

  8. 反应原生 – 如何通过Xcode构建React Native iOS应用程序到设备?

    我试图将AwesomeProject应用程序构建到设备上.构建成功并启动屏幕显示,但后来我看到一个红色的“无法连接到开发服务器”屏幕.它表示“确保节点服务器正在运行–从Reactroot运行”npmstart“.看起来节点服务器已经运行,因为当我做npm启动时,我收到一个EADDRINUSE消息,表示该端口已经在使用.解决方法从设备访问开发服务器您可以使用开发服务器快速迭代设备.要做到这一点,你的

  9. 静音iOS推送通知与React Native应用程序在后台

    我有一个ReactNative应用程序,我试图获得一个发送到JavaScript处理程序的静默iOS推送通知.我看到的行为是AppDelegate中的didReceiveRemoteNotification函数被调用,但是我的JavaScript中的处理程序不会被调用,除非应用程序在前台,或者最近才被关闭.我很困惑的事情显然是应用程序正在被唤醒,并且它的didReceiveRemoteNotifi

  10. 如何为iOS的React Native设置分析

    所以我已经完成了一个针对iOS的ReactNative项目,但是我想在其中分析.我尝试了react-native-google-analytics软件包,但是问题阻止了它的正常工作.此外,react-native-cordova-plugin软件包只适用于Android,因此插入Cordova插件进行分析的能力现在已成为问题.我也没有Swift/ObjectiveC的经验,所以将完全失去GA的插入.有没有人有任何建议如何连接GoogleAnalytics的ReactNativeforiOS?

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部