功能性组件和Classes有什么差别?

React函数组件与React类有何差别?

有一段时候,范例的答案是类能够接见更多功用(如状况)。然则自从有了Hook后,这个答案变得不唯一了。

或许你据说个中一个表现更好。哪个?很多此类基准都存在缺点,因而我会小心肠从中得出结论。机能主要取决于代码的作用,而不是您挑选的是函数照样类。在我们的视察中,虽然优化战略有点差别,但机能差别能够忽略不计。

在任何一种状况下,除非您有其他缘由而且不介意成为初期采纳者,不然我们不建议您重写现有组件。Hooks依然是新的。

那末功用性函数和类是不是又基础的区分?

函数组件捕捉rendered的值。

让我们看看这句话是什么意义?

注重:这篇文章不是对类或函数的值推断。我只形貌了React中这两种编程模子之间的区分。有关更广泛地采纳功用的题目,请参阅Hooks罕见题目解答

思索一下这个组件:

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

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

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

它显现一个按钮,运用setTimeout模仿收集要求,然后显现确认警报。比方,假如props.user是’Dan’,它将在三秒后显现’Followed Dan’。很简单。(注重在上面的例子中我是不是运用箭头或函数声明并不主要。函数handleClick()将以完整雷同的体式格局事情。)

我们怎样把它写成一个类?觉得应当是以下所示:

class ProfilePage extends React.Component {
  showMessage = () => {
    alert('Followed ' + this.props.user);
  };

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

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

一般以为这两个代码片断是等效的。人们常常在这些形式之间自由地重构,而不会注重到它们的寄义:

然则,这两个代码片断略有差别。好好看看他们。你看到了差别吗?就个人而言,我花了一段时候才看到这一点。

假如你想自身搞清楚,这里是一个现场演示。本文的其余部分诠释了差别及其主要性。

在我们继承之前,我想强调一点,我所形貌的差别与React Hooks自身无关。以上示例以至不运用Hooks!这就是React中函数和类之间的区分。假如您计划在React运用顺序中更频仍地运用函数,则能够须要相识它。

我们将经由过程React运用顺序中罕见的毛病申明其差别。

翻开此示例

运用两个按钮尝试以下操纵序列:

  • 单击个中一个“关注”按钮。
  • 在3秒之前变动所选的配置文件。
  • 浏览警报笔墨。

你会注重到一个特别的区分:

  • 运用上面的ProfilePage函数,单击Follow on Dan的个人资料,然后导航到Sophie’s依然会提示’Followed Dan’。
  • 运用上面的ProfilePage类,它会正告’Followed Sophie’:

《功能性组件和Classes有什么差别?》

在此示例中,第一个行动是准确的行动。假如我追随一个人然后导航到另一个人的个人资料,我的组件不应当让运用的人觉得疑心。这个类完成显然是毛病的。
那末为何我们的类示例会以这类体式格局运转?
让我们细致看看我们类中的showMessage要领:

class ProfilePage extends React.Component {
  showMessage = () => {
    alert('Followed ' + this.props.user);
  };

此类要领从this.props.user读取。PropsReact中是不可变的,因而它们永久不会转变。但是,这一直是,而且一直是可变的。
React自身会跟着时候的推移而变异,以便您能够在衬着和生命周期要领中浏览新版本。

因而,假如我们的组件在要求处于运转状况时render,则this.props将会变动。showMessage要领从“too new”的props中读取用户。

这暴露了一个关于用户界面性子的风趣视察。假如我们说UI在观点上是当前运用顺序状况的函数,则事宜处置惩罚顺序是衬着结果的一部分 – 就像视觉输出一样。我们的事宜处置惩罚顺序“属于”具有特定propsstate的特定衬着。

假定功用组件不存在。我们怎样处理这个题目?

一种要领是在事宜时期尽早读取this.props,然后将它们显式通报到超时完成处置惩罚顺序:

class ProfilePage extends React.Component {
  showMessage = (user) => {
    alert('Followed ' + user);
  };

  handleClick = () => {
    const {user} = this.props;
    setTimeout(() => this.showMessage(user), 3000);
  };

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

这有用。然则,这类要领会使代码跟着时候的推移变得越发冗杂和轻易失足。假如我们须要不止一个道具怎么办?假如我们还须要接见该州怎么办?假如showMessage挪用另一个要领,而且该要领读取this.props.somethingthis.state.something,我们将再次碰到完整雷同的题目。所以我们必需经由过程showMessage挪用的每一个要领将this.propsthis.state作为参数通报。

一样,在handleClick中内联警报代码并不能处理更大的题目。我们愿望以许可将其拆分为更多要领的体式格局组织代码,同时还要读取与该挪用相干的衬着所对应的propsstate。这个题目以至不是React独占的 – 您能够在任何将数据放入像如许的可变对象的UI库中重现它。
或许,我们能够绑定组织函数中的要领?

class ProfilePage extends React.Component {
  constructor(props) {
    super(props);
    this.showMessage = this.showMessage.bind(this);
    this.handleClick = this.handleClick.bind(this);
  }

  showMessage() {
    alert('Followed ' + this.props.user);
  }

  handleClick() {
    setTimeout(this.showMessage, 3000);
  }

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

不,这不能处理任何题目。请记着,题目是我们从这里读取。支撑太晚了 – 不是我们正在运用的语法!然则,假如我们完整依靠JavaScript闭包,题目就会消逝。

一般会防止闭包,因为很难设想跟着时候的推移能够会发作变异的代价。但在React中,propsstate是不可转变的!(或许最少,这是一个猛烈的引荐。)这消除了一个主要的封闭地区。

这意味着假如你从特定衬着中封闭propsstate,它们的值坚持完整雷同:

class ProfilePage extends React.Component {
  render() {
    // Capture the props!
    const props = this.props;

    // Note: we are *inside render*.
    // These aren't class methods.
    const showMessage = () => {
      alert('Followed ' + props.user);
    };

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

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

你在衬着时“捕捉”了props

如许,它内部的任何代码(包含showMessage)都能够保证看到这个特定衬着的道具。React不再“move our cheese”了。
然后我们能够在里面增加恣意数目的辅佐函数,它们都邑运用捕捉的propsstate

上面的例子是准确的,但看起来很新鲜。假如在render中定义函数而不是运用类要领,那末有一个类是什么意义?
现实上,我们能够经由过程删除它四周的类“shell”来简化代码:

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

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

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

就像上面一样,props依然被捕捉 – React将它们作为参数通报。与此差别,props对象自身永久不会被React变异。假如你在函数定义中组织props,那就更显著了:

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

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

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

当父组件运用差别的props显现ProfilePage时,React将再次挪用ProfilePage函数。然则我们已点击的事宜处置惩罚顺序“属于”具有自身的用户值的前一个衬着和读取它的showMessage回调。他们都完好无损。
《功能性组件和Classes有什么差别?》

如今我们相识React中函数和类之间的庞大差别:

函数组件捕捉显现的值。

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

虽然这不是一个非常好的音讯运用UI,但它申清楚明了一样的看法:假如我发送特定音讯,组件不应当对现实发送的音讯觉得疑心。此函数组件的音讯捕捉“属于”衬着器的状况,该衬着器返回浏览器挪用的单击处置惩罚顺序。因而,当我单击“发送”时,音讯将设置为输入中的内容。

因而,默许状况下,我们晓得React捕捉道具和状况中的函数。然则,假如我们想要浏览不属于这个特定衬着的最新道具或州,该怎么办?假如我们想 “read them from the future”怎么办?

在类中,你能够经由过程浏览this.props或this.state来完成它,因为它自身是可变的。React转变了它。在函数组件中,您还能够具有由一切组件显现同享的可变值。它被称为“ref”:

function MyComponent() {
  const ref = useRef(null);
  // You can read or write `ref.current`.
  // ...
}

然则,您必需自身治理它。

ref与实例字段饰演雷同的角色。它是进入可变敕令天下的逃走舱。您能够熟习“DOM refs”,但观点更加通用。它只是一个盒子,你能够把东西放进去。纵然在视觉上,这个东西看起来像是某种东西的镜子。它们代表了雷同的观点。默许状况下,React不会为函数组件中的最新props或状况建立援用。在很多状况下,您不须要它们,分派它们将是糟蹋的事情。然则,假如您情愿,能够手动跟踪值:

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

假如我们在showMessage中读取音讯,我们会在按下“发送”按钮时看到音讯。然则当我们读取latestMessage.current时,我们取得最新的值 – 纵然我们在按下发送按钮后继承输入。你能够比较两个演示(https://codesandbox.io/s/93m5… https://codesandbox.io/s/ox200vw8k9),看看差别。ref是一种“挑选退出”衬着一致性的要领,在某些状况下能够很轻易。一般,您应当防止在衬着时期读取或设置援用,因为它们是可变的。我们愿望坚持衬着的可展望性。然则,假如我们想取得特定道具或状况的最新值,那末手动更新ref会很烦人。我们能够经由过程运用结果自动化它:

function MessageThread() {
  const [message, setMessage] = useState('');

  // Keep track of the latest value.
  const latestMessage = useRef('');
  useEffect(() => {
    latestMessage.current = message;
  });

  const showMessage = () => {
    alert('You said: ' + latestMessage.current);
  };

demo

结论

在这篇文章中,我们研讨了类中罕见的破裂形式,以及闭包怎样协助我们修复它。然则,您能够已注重到,当您尝试经由过程指定依靠关联数组来优化Hook时,您能够会碰到带有过期闭包的毛病。是不是意味着闭包是题目?我不这么以为。

正如我们上面所看到的,闭包现实上协助我们处理了很难注重到的纤细题目。一样,它们使编写在并发形式下一般事情的代码变得越发轻易。这是能够的,因为组件内部的逻辑封闭了准确的props和衬着state
在我到目前为止看到的一切状况中,因为毛病地假定“功用不会转变”或“props老是雷同”,所以会涌现“陈腐的封闭”题目。现实并非如此,因为我愿望这篇文章有助于廓清。

    原文作者:_小生_
    原文地址: https://segmentfault.com/a/1190000018391208
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞