【问题标题】:How to pass arguments to callback in React + Typescript without new function creation?如何在不创建新函数的情况下将参数传递给 React + Typescript 中的回调?
【发布时间】:2017-12-25 02:28:29
【问题描述】:

假设您有以下 TSX 代码:

render() {
    const someParameter = 5;

    return (
        <div onClick={() => this.doSomething(someParameter)}></div>
    );
}

现在,我们知道我们不应该将这样的粗箭头函数传递给 onClick 处理程序,因为它每次都会创建一个新函数,从而强制重新渲染(当我们'将此回调传递给深层组件,而不仅仅是 div)。

那么解决方案是,如果没有参数,将doSomething 绑定到我们类的构造函数中的this,如this.doSomething = this.doSomething.bind(this),然后将绑定的函数作为回调传递。但是,这在我们的例子中并没有削减它,因为我们想将一个参数传递给函数 - someParameter。想象一下, someParameter 不仅仅是一个像上面那样的愚蠢的常量,而是一个数组中的元素,我们在我们的渲染方法中的一个数组上的 map() 中得到它。我们如何处理这种情况?也就是说,我们如何传递一个不是每次都从头开始创建的函数,从而破坏我们仅在必要时才智能地重新渲染的能力?

谢谢!

【问题讨论】:

  • 在这种情况下确定"that we shouldn't pass fat arrow functions" 之前,我们确实应该对我们的应用程序进行基准测试。当然,如果 render 被调用 很多 次,那是次优的,但否则它可能没问题。它读起来也很清楚,并且会进行类型检查。
  • 我同意 Aluan 的观点。如果您没有性能问题,则可能无需担心这一点。除非您引起大量级联的重新渲染,否则在计算成本方面可能没问题,您可以担心清晰度和简洁性。
  • 不过,它破坏了使用 Redux 的很大一部分好处,尤其是当您在高级别传递回调时。另外,我的问题不是“讨论这种性能优化的好处”,而是“我们如何做到这一点?”

标签: javascript reactjs typescript


【解决方案1】:

bind 不仅可以绑定上下文,还可以绑定参数。所以你可以在构造函数中做这样的事情:

this.doSomething = this.doSomething.bind(this, someArgument)

仅当someArgument 在组件生命周期内不发生更改时,上述解决方案才有效。
如果此参数将是动态的,则唯一的解决方案是使用示例中的箭头函数 - 但请注意,仅当此函数作为道具传递给子组件(而不是 HTML 元素)时,可能会导致额外的重新渲染。根据反应文档:

在大多数情况下,这很好。但是,如果此回调作为 支撑较低的组件,这些组件可能会做额外的 重新渲染。

如果你不将它传递给 React 子组件,它应该不是问题,也不会导致任何额外的重新渲染。请注意,React 组件和 HTML 标签之间存在差异。如果您使用箭头函数作为 HTML 元素上的事件回调(例如,在地图中生成的 div 上),它不会触发此 div 的额外渲染,因为它不是组件并且没有渲染方法。 React Virtual DOM 算法将决定是否应该更新与此 div 对应的实际 DOM 元素,在我们的例子中,现有的 DOM 元素将不会被更新(您可以使用开发工具检查它)。

class Hello extends React.Component{
  constructor() {
    super();
    this.state = {test: 0};
  }
  componentDidMount() {
    var i =0;
    this.timer = setInterval(() => {
      i++;
    	this.setState({test: i})
    }, 1000)
  }
  componentWillUnmount() {
    clearInterval(this.timer)
  }
  render() {
    return (<div>{this.state.test}
      {
        [1, 4, 6].map((v) => {
          return (<div key={v} onClick={() => { console.log(this.state.test)}}>test</div>)
        })
      }
    </div>);
  }
};

ReactDOM.render(
  <Hello name="World" />,
  document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
    <!-- This element's contents will be replaced with your component. -->
</div>

您可以检查上述代码 sn-p 生成的输出,以查看 div 元素未在 DOM 中更新,尽管它们具有定义为事件回调的箭头函数并且父组件正在重新渲染。

如果您需要将此函数传递给子组件,您可以分别传递带有函数的 prop 和带有参数值的 prop 以避免过度重新渲染(当然您还必须实现 shouldComponentUpdate):

<SomeComponent myClick={this.doSomething} myArgument={someArgument} />

在 SomeComponent 中的某处:

//JSX in SomeComponent render method
<div onClick={() => this.props.myClick(this.props.myArgument)} >...</div>

您也可以在 SomeComponent 中创建额外的方法,而不是使用箭头函数:

handleMyClick() {
   this.props.myClick(this.props.myArgument);
}

然后使用 JSX:

<div onClick={this.handleMyClick} >...</div>

总结一下:如果你在 HTML 标签上使用箭头函数作为事件回调,它不会导致性能问题,也不会触发任何额外的重新渲染——它不会破坏仅在必要时智能重新渲染的能力。仅当箭头函数作为道具传递给子组件时,它可能会导致额外的重新渲染,在这种情况下,您可以使用我建议的解决方案。

【讨论】:

  • 是的,但是 bind 在构造函数中只调用一次,而不是在每次重新渲染时调用。正如我提到的 bind 应该在构造函数中使用
  • 啊,我明白了,好点子。在这种情况下,我仍然更喜欢this.doSomething = something =&gt; {...}; 以提高可读性,但我认为这是暗示性的。根据您的背景,Bind 可能更具可读性
  • @MichaelTontchev 我想你误解了我的意思。您不需要将其包装在自己的组件中。这只是一个示例,展示了如何将回调传递给已经存在的子组件以避免触发该子组件的额外重新渲染。如果你想为 HTML 元素(不是 React 组件)添加事件回调,你可以使用 bind 或箭头函数,它不应该引起任何问题,你不必将它包装在组件中。在 HTML 元素上使用箭头函数作为道具不会导致任何额外的重新渲染 - 只有将它传递给子 React 组件时才会出现问题
  • @Michael Tontchev 如果您想在map 内将事件回调传递给div(因此HTML 元素而不是React 组件),您可以使用箭头函数,因为正如我所提到的,它不会导致这些 div 的额外重新渲染。当新函数作为 prop 传递时,只有 React 组件(div 不是 React 组件)可以重新渲染。此外,在每个父组件渲染上创建新的箭头函数不应导致任何性能问题。所以总结一下:你可以将箭头函数传递给map中生成的div,如果你将它传递给React组件,你应该避免它。
  • @MichaelTontchev 您是否使用开发工具检查了我的代码 sn-p?开发工具会突出显示每个已更新的 DOM 元素,但正如您所见,代码 sn-p 中的“div”元素并未更新,尽管它们每秒都会收到新函数作为事件回调。这要归功于虚拟 DOM 优化 - DOM 元素仅在“可见”更改时更新。更改事件回调不需要更新 DOM 树,因为它不会在 DOM 中进行视觉更改。当然,在“后台”中添加了新的事件处理程序,但它可以在不重新渲染实际 DOM 元素的情况下完成
【解决方案2】:

您能否检查一下“Protips”部分中提出的解决方案? https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md

看起来这可能有效:

    var List = createReactClass({
              render() {
        return (
          <ul>
            {this.props.items.map(item =>
              <ListItem key={item.id} item={item} onItemClick={this.props.onItemClick} />
            )}
          </ul>
        );
      }
    });

    var ListItem = createReactClass({
      render() {
        return (
          <li onClick={this._onClick}>
            ...
          </li>
        );
      },
      _onClick() {
        this.props.onItemClick(this.props.item.id);
      }
    });

【讨论】:

    猜你喜欢
    • 2020-09-10
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多