• 传递函数给组件
    • 如何将事件处理器(比如 onClick)传递给组件?
    • 如何为函数绑定组件实例?
      • 在构造函数中绑定(ES2015)
      • class 属性(第三阶段提案)
      • 在 Render 中的绑定
      • 在 Render 中使用箭头函数
    • 可以在 render 方法中使用箭头函数吗?
    • 为什么绑定是必要的?
    • 为什么我的函数每次组件渲染时都会被调用?
    • 如何传递参数给事件处理器或回调?
      • 示例:通过箭头函数传递参数
      • 示例:通过 data-attributes 传递参数
    • 怎样阻止函数被调用太快或者太多次?
      • 节流
      • 防抖
      • requestAnimationFrame 节流
      • 测试速率限制

    传递函数给组件

    如何将事件处理器(比如 onClick)传递给组件?

    可以将事件处理器和其他函数作为 props 传递给子组件:

    1. <button onClick={this.handleClick}>

    如果需要在事件处理器中访问父组件,还需要为该函数绑定组件实例(参见下文)。

    如何为函数绑定组件实例?

    有以下几种方式可以确保函数可以访问组件属性,比如 this.propsthis.state,这取决于使用的语法和构建步骤。

    在构造函数中绑定(ES2015)

    1. class Foo extends Component {
    2. constructor(props) {
    3. super(props);
    4. this.handleClick = this.handleClick.bind(this);
    5. }
    6. handleClick() {
    7. console.log('Click happened');
    8. }
    9. render() {
    10. return <button onClick={this.handleClick}>Click Me</button>;
    11. }
    12. }

    class 属性(第三阶段提案)

    1. class Foo extends Component {
    2. // Note: this syntax is experimental and not standardized yet.
    3. handleClick = () => {
    4. console.log('Click happened');
    5. }
    6. render() {
    7. return <button onClick={this.handleClick}>Click Me</button>;
    8. }
    9. }

    在 Render 中的绑定

    1. class Foo extends Component {
    2. handleClick() {
    3. console.log('Click happened');
    4. }
    5. render() {
    6. return <button onClick={this.handleClick.bind(this)}>Click Me</button>;
    7. }
    8. }

    注意:

    在 render 方法中使用 Function.prototype.bind 会在每次组件渲染时创建一个新的函数,可能会影响性能(参见下文)。

    在 Render 中使用箭头函数

    1. class Foo extends Component {
    2. handleClick() {
    3. console.log('Click happened');
    4. }
    5. render() {
    6. return <button onClick={() => this.handleClick()}>Click Me</button>;
    7. }
    8. }

    注意:

    在 render 方法中使用箭头函数也会在每次组件渲染时创建一个新的函数,这会破坏基于恒等比较的性能优化。

    可以在 render 方法中使用箭头函数吗?

    一般来说是可以的,并且使用箭头函数是向回调函数传递参数的最简单的办法。

    但是如果遇到了性能问题,一定要进行优化!

    为什么绑定是必要的?

    在JavaScript中,以下两种写法是等价的:

    1. obj.method();
    1. var method = obj.method;
    2. method();

    bind 方法确保了第二种写法与第一种写法相同。

    使用 React,通常只需要绑定传递给其他组件的方法。例如,<button onClick={this.handleClick}> 是在传递 this.handleClick ,所以需要绑定它。但是,没有必要绑定 render 方法或生命周期方法:我们并没有将它们传递给其他的组件。

    Yehuda Katz 的文章详细解释了什么是绑定,以及函数在 JavaScript 中怎么起作用。

    为什么我的函数每次组件渲染时都会被调用?

    确保你在传递一个函数给组件时,没有调用这个函数

    1. render() {
    2. // Wrong: handleClick is called instead of passed as a reference!
    3. return <button onClick={this.handleClick()}>Click Me</button>
    4. }

    正确做法是,传递函数本身(不带括号):

    1. render() {
    2. // Correct: handleClick is passed as a reference!
    3. return <button onClick={this.handleClick}>Click Me</button>
    4. }

    如何传递参数给事件处理器或回调?

    可以使用箭头函数包裹事件处理器,并传递参数:

    1. <button onClick={() => this.handleClick(id)} />

    以上代码和调用 .bind 是等价的:

    1. <button onClick={this.handleClick.bind(this, id)} />

    示例:通过箭头函数传递参数

    1. const A = 65 // ASCII character code
    2. class Alphabet extends React.Component {
    3. constructor(props) {
    4. super(props);
    5. this.handleClick = this.handleClick.bind(this);
    6. this.state = {
    7. justClicked: null,
    8. letters: Array.from({length: 26}, (_, i) => String.fromCharCode(A + i))
    9. };
    10. }
    11. handleClick(letter) {
    12. this.setState({ justClicked: letter });
    13. }
    14. render() {
    15. return (
    16. <div>
    17. Just clicked: {this.state.justClicked}
    18. <ul>
    19. {this.state.letters.map(letter =>
    20. <li key={letter} onClick={() => this.handleClick(letter)}>
    21. {letter}
    22. </li>
    23. )}
    24. </ul>
    25. </div>
    26. )
    27. }
    28. }

    示例:通过 data-attributes 传递参数

    同样的,也可以使用 DOM API 来存储事件处理器需要的数据。如果需要优化大量元素或使用依赖于 React.PureComponent 相等性检查的渲染树,请考虑使用此方法。

    1. const A = 65 // ASCII character code
    2. class Alphabet extends React.Component {
    3. constructor(props) {
    4. super(props);
    5. this.handleClick = this.handleClick.bind(this);
    6. this.state = {
    7. justClicked: null,
    8. letters: Array.from({length: 26}, (_, i) => String.fromCharCode(A + i))
    9. };
    10. }
    11. handleClick(e) {
    12. this.setState({
    13. justClicked: e.target.dataset.letter
    14. });
    15. }
    16. render() {
    17. return (
    18. <div>
    19. Just clicked: {this.state.justClicked}
    20. <ul>
    21. {this.state.letters.map(letter =>
    22. <li key={letter} data-letter={letter} onClick={this.handleClick}>
    23. {letter}
    24. </li>
    25. )}
    26. </ul>
    27. </div>
    28. )
    29. }
    30. }

    怎样阻止函数被调用太快或者太多次?

    如果你有一个 onClick 或者 onScroll 这样的事件处理器,想要阻止回调被触发的太快,那么可以限制执行回调的速度,可以通过以下几种方式做到这点:

    • 节流:基于时间的频率来进行抽样更改 (例如 _.throttle)
    • 防抖:一段时间的不活动之后发布更改 (例如 _.debounce)
    • requestAnimationFrame 节流:基于 requestAnimationFrame 的抽样更改 (例如 raf-schd))可以看这个比较 throttle 和 debounce 的可视化页面

    注意:

    .debounce.throttleraf-schd 都提供了一个 cancel 方法来取消延迟回调。你需要在 componentWillUnmount 中调用该方法,或者对代码进行检查来保证在延迟函数有效期间内组件始终挂载。

    节流

    节流阻止函数在给定时间窗口内被调不能超过一次。下面这个例子会节流 “click” 事件处理器,使其每秒钟的只能调用一次。

    1. import throttle from 'lodash.throttle';
    2. class LoadMoreButton extends React.Component {
    3. constructor(props) {
    4. super(props);
    5. this.handleClick = this.handleClick.bind(this);
    6. this.handleClickThrottled = throttle(this.handleClick, 1000);
    7. }
    8. componentWillUnmount() {
    9. this.handleClickThrottled.cancel();
    10. }
    11. render() {
    12. return <button onClick={this.handleClickThrottled}>Load More</button>;
    13. }
    14. handleClick() {
    15. this.props.loadMore();
    16. }
    17. }

    防抖

    防抖确保函数不会在上一次被调用之后一定量的时间内被执行。当必须进行一些费时的计算来响应快速派发的事件时(比如鼠标滚动或键盘事件时),防抖是非常有用的。下面这个例子以 250ms 的延迟来改变文本输入。

    1. import debounce from 'lodash.debounce';
    2. class Searchbox extends React.Component {
    3. constructor(props) {
    4. super(props);
    5. this.handleChange = this.handleChange.bind(this);
    6. this.emitChangeDebounced = debounce(this.emitChange, 250);
    7. }
    8. componentWillUnmount() {
    9. this.emitChangeDebounced.cancel();
    10. }
    11. render() {
    12. return (
    13. <input
    14. type="text"
    15. onChange={this.handleChange}
    16. placeholder="Search..."
    17. defaultValue={this.props.value}
    18. />
    19. );
    20. }
    21. handleChange(e) {
    22. // React pools events, so we read the value before debounce.
    23. // Alternately we could call `event.persist()` and pass the entire event.
    24. // For more info see reactjs.org/docs/events.html#event-pooling
    25. this.emitChangeDebounced(e.target.value);
    26. }
    27. emitChange(value) {
    28. this.props.onChange(value);
    29. }
    30. }

    requestAnimationFrame 节流

    requestAnimationFrame 是在浏览器中排队等待执行的一种方法,它可以在呈现性能的最佳时间执行。一个函数被 requestAnimationFrame 放入队列后将会在下一帧触发。浏览器会努力确保每秒 60 帧(60fps)。然而,如果浏览器无法确保,那么自然会限制每秒的帧数。例如,某个设备可能只能处理每秒 30 帧,所以每秒只能得到 30 帧。使用 requestAnimationFrame 来节流是一种有用的技术,它可以防止在一秒中进行 60 帧以上的更新。如果一秒钟内完成 100 次更新,则会为浏览器带来额外的负担,而用却户无法感知到这些工作。

    注意:

    使用这个方法时只能获取某一帧中最后发布的值。也可以在 MDN 中看优化的示例。

    1. import rafSchedule from 'raf-schd';
    2. class ScrollListener extends React.Component {
    3. constructor(props) {
    4. super(props);
    5. this.handleScroll = this.handleScroll.bind(this);
    6. // Create a new function to schedule updates.
    7. this.scheduleUpdate = rafSchedule(
    8. point => this.props.onScroll(point)
    9. );
    10. }
    11. handleScroll(e) {
    12. // When we receive a scroll event, schedule an update.
    13. // If we receive many updates within a frame, we'll only publish the latest value.
    14. this.scheduleUpdate({ x: e.clientX, y: e.clientY });
    15. }
    16. componentWillUnmount() {
    17. // Cancel any pending updates since we're unmounting.
    18. this.scheduleUpdate.cancel();
    19. }
    20. render() {
    21. return (
    22. <div
    23. style={{ overflow: 'scroll' }}
    24. onScroll={this.handleScroll}
    25. >
    26. <img src="/my-huge-image.jpg" />
    27. </div>
    28. );
    29. }
    30. }

    测试速率限制

    在测试速率限制的代码是否正确工作的时候,如果可以(对动画或操作)进行快进将会很有帮助。如果正在使用 jest ,那么可以使用 mock timers 来快进。如果正在使用 requestAnimationFrame 节流,那么就会发现 raf-stub 是一个控制动画帧的十分有用的工具。