React纯组件(纯函数)

275 阅读4分钟

有些JavaScript函数是纯函数。纯函数只执行计算。通过严格地只将组件编写为纯函数,可以避免随着代码库的增长而出现令人困惑的bug和不可预测的行为。然而,为了获得这些好处,你必须遵守一些规则。

纯函数

在计算机科学(尤其是函数式编程领域)中,纯函数是具有以下特征的函数:

  • 它处理与自己有关的任务。它不会改变函数之外的任何对象或变量。
  • 给定同样的输入,输出总是一样的。给定相同的输入,纯函数总是返回相同的结果。

纯函数的一个例子:数学公式。

看这个数学公式:y = 2x。

如果x = 2,那么总是y = 4。

如果x = 3,那么总是y = 6。

如果我们把它变成一个JavaScript函数,它看起来像这样:

function double(number) {
  return 2 * number;
}

在上面的例子中,double是一个纯函数。如果你传递3,它总是返回6。

React就是围绕这个概念设计的。React编写的每个组件都是纯函数。你编写的React组件必须总是在给定相同输入的情况下返回相同的JSX:

function Recipe({ drinkers }) {
  return (
    <ol>    
      <li>Boil {drinkers} cups of water.</li>
      <li>Add {drinkers} spoons of tea and {0.5 * drinkers} spoons of spice.</li>
      <li>Add {0.5 * drinkers} cups of milk to boil and sugar to taste.</li>
    </ol>
  );
}

export default function App() {
  return (
    <section>
      <h1>Spiced Chai Recipe</h1>
      <h2>For two</h2>
      <Recipe drinkers={2} />
      <h2>For a gathering</h2>
      <Recipe drinkers={4} />
    </section>
  );
}

7.png

将drinks ={2}传递给Recipe时,总是返回包含2杯水的JSX。

将drinks ={4}传递给Recipe时,总是返回包含4杯水的JSX。

副作用:导致非预期的结果

React的渲染过程必须始终是纯的,组件只返回它们的JSX。不能改变函数之外的任何对象或变量。

看下面打破这一规则的例子:

let guest = 0;

function Cup() {
  // Bad: changing a preexisting variable!
  guest = guest + 1;
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup />
      <Cup />
      <Cup />
    </>
  );
}

1.png

该组件读取和写在外部声明的变量。多次调用该组件将产生不同的JSX!如果其他组件读取guest,它们也会生成不同的JSX!这是不可预测的。

通过把guest作为prop来修复这个组件:

function Cup({ guest }) {
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup guest={1} />
      <Cup guest={2} />
      <Cup guest={3} />
    </>
  );
}

2.png

现在组件是纯的,因为它返回的JSX只依赖prop guest。

组件能以任何顺序呈现,在y = 5x之前或之后调用y = 2x并不重要,两个公式都是相互独立的。每个组件应该只“为自己考虑”,在渲染过程中不与其他组件协作或依赖,每个组件都应该自己计算JSX !

局部变量

在渲染时更改刚刚创建的变量和对象完全没有问题。在这个例子中,你创建了一个[]数组,将它赋值给一个cups变量,然后调用push()方法向cups添加项:

function Cup({ guest }) {
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaGathering() {
  let cups = [];
  for (let i = 1; i <= 12; i++) {
    cups.push(<Cup key={i} guest={i} />);
  }
  return cups;
}

3.png

如果cups变量或[]数组是在teaggathering函数之外创建的,将会有一个很大的问题!

在渲染函数teaggathering中创建的cups变量。函数teaggathering之外的代码不知道发这个cups变量。这个cups局部变量不会产生副作用。

在哪里能使用副作用

虽然函数式编程在很大程度上依赖于纯粹性,但在某些时候,某些地方必须做出改变。这就是编程的意义所在!这些变化(更新屏幕、开始动画、改变数据)被称为副作用。它们是“在旁边”发生的事情,而不是在渲染过程中。

在React中,副作用通常在事件处理函数中。事件处理函数是在执行某些操作(例如单击按钮)时React运行的函数。即使在组件内部定义了事件处理程序,它们也不会在渲染期间运行!所以事件处理函数不需要是纯的。

如果你想了很多方法也不能找到副作用存放的地方,你可以尝试放到组件的useEffect中。渲染完以后,就能执行useEffect里面的副作用。然而,这种方法应该是你最后的选择。

参考文献: Keeping Components Pure