冒泡 componentWillUpdate 和 componentDidUpdate

Bubbling componentWillUpdate and componentDidUpdate

我知道这不是惯用的 React,但我正在开发一个 React 组件(反向滚动视图),它需要在渲染之前和渲染之后得到下游虚拟 DOM 更改的通知渲染。

这有点难以解释,所以我制作了一个 JSFiddle,起点是:https://jsfiddle.net/7hwtxdap/2/

我的目标是让日志看起来像:

Begin log

render

rendered small

rendered small

rendered small

before update

rendered big

after update

before update

rendered big

after update

before update

rendered big

after update

我知道 React 有时会批量 DOM 更改,这很好(即日志事件可能是 before update; rendered big; rendered big; after update; ...)——重要的是我实际上在 DOM 之前和之后收到通知=46=] 变化。


我可以通过指定回调来手动近似行为,如此处完成:https://jsfiddle.net/7hwtxdap/4/。但是,这种方法无法扩展——例如,我需要让事件从后代冒泡而不是 children;并且宁愿不必将这些类型的事件处理程序添加到我使用的每个组件中。


用例:

我正在为消息制作一个 "inverted scrolling component"(其中,当添加新元素或现有元素更改大小时,滚动更改的基础是从内容的底部而不是顶部 ala每个消息传递应用程序)已动态修改 children(类似于 <MyElement /> class,但高度可变,数据来自 ajax,等等)。这些不是从 Flux-like 状态存储中获取数据,而是使用 pub-sub 数据同步模型(与 setTimeout() 非常接近)。

要使反向滚动起作用,您需要执行以下操作:

anyChildrenOfComponentWillUpdate() {
  let m = this.refs.x;
  this.scrollBottom = m.scrollHeight - m.scrollTop - m.offsetHeight;
}
anyChildrenOfComponentDidUpdate() {
  let m = this.refs.x;
  m.scrollTop = m.scrollHeight - m.offsetHeight - this.scrollBottom;
}

对于这个设计很重要,我希望能够将元素包裹在其他不是 "inverted scrolling aware" 的元素周围(即不必有特殊代码来通知反向滚动处理程序它们有改变了)。

根据设计,React 尊重组件上的 setState 调用应该触发 that 组件的重新渲染。我所知道的挂钩组件生命周期的最可靠方法是组件本身。

你如何传递一个 notifier() 回调作为 prop,然后在子组件的 componentWillMountcomponentDidUpdate 生命周期钩子中调用这个函数?

Here's a working js-fiddle有了这个想法。让我知道发生了什么。

编辑 1

如果您不想通过每个子组件来回传递回调,那么我想说您刚刚遇到了 ReduxFlux 的特定用例。

使用 Redux,组件可以通过 reducer 函数更改存储的状态。然后重新渲染每个监视该存储中变化的组件。 Flux 使用相同的想法。

所以我建议你使用 redux 来处理状态设置。要开始,您可以阅读 redux 的创建者 Dan Abramov 的 these tutorial videos

如果不知道您为什么需要此功能,就很难知道什么是解决问题的最佳方法。但是,我将解决这个具体问题:

I need, for example, to have events bubble from descendents rather than children; and would rather not have to add these kind of event handlers to every component I use.

为此,我建议对您的解决方案进行两处更改。第一种是考虑使用 context,但在决定这样做之前一定要阅读警告。这将允许您在单个组件中定义回调,并让它们在所有后代中立即可用。

第二个更改是将所有日志记录逻辑移动到一个单独的组件中,并让其他组件根据需要继承它。该组件看起来像:

class LoggingComponent extends React.Component {
  componentWillUpdate() {
    if (this.context.onWillUpdate) {
        this.context.onWillUpdate();
    }
  }
  componentDidUpdate() {
    if (this.context.onDidUpdate) {
        this.context.onDidUpdate();
    }
  }
}

LoggingComponent.contextTypes = {
    onWillUpdate: React.PropTypes.func,
    onDidUpdate: React.PropTypes.func
};

这样,您就可以轻松地将此功能添加到新组件中,而无需重复逻辑。我已经用工作演示更新了您的 jsfiddle

也许你可以看看 react-virtualized,它有一个反向滚动列表的例子。

export default class Example extends Component {
  constructor (props) {
    super(props)

    this.state = {
      list: []
    }
  }

  componentDidMount () {
    this._interval = setInterval(::this._updateFeed, 500)
  }

  componentWillUnmount () {
    clearInterval(this._interval)
  }

  render () {
    const { list } = this.state

    return (
      <div className={styles.VirtualScrollExample}>
        <VirtualScroll
          ref='VirtualScroll'
          className={styles.VirtualScroll}
          width={300}
          height={200}
          rowHeight={60}
          rowCount={list.length}
          rowRenderer={::this._rowRenderer}
        />
      </div>
    )
  }

  _updateFeed () {
    const list = [ ...this.state.list ]

    list.unshift(
      // Add new item here
    )

    this.setState({ list })

    // If you want to scroll to the top you can do it like this
    this.refs.VirtualScroll.scrollToRow(0)
  }

  _rowRenderer (index) {
    return (
      // Your markup goes here
    )
  }
}

正如 James 所说,您可以使用上下文来定义一个回调函数,您可以将其传递给所有后代组件,如果您不想扩展您的组件,您可以使用装饰器来包装高阶组件他们。像这样:

在您的容器中:

class Container extends React.Component {
    static childContextTypes =  {
        log: React.PropTypes.func.isRequired
    };

   getChildContext() {
      return {
            log: (msg) => {
                console.log(msg)
            }
        }
    }

  render() {
    console.log("Begin Log")
    return (
        <div>
          <MyElement />
          <MyElement />
          <MyElement />
        </div>
    );
  }
}

装饰者class:

export const Logger = ComposedComponent => class extends React.Component {
    static contextTypes = {
        log: React.PropTypes.func.isRequired
    };

    constructor(props) {
        super(props)
    }

    componentWillReceiveProps(nextProps) {
        this.context.log('will receive props');
    }

    componentWillUpdate() {
        this.context.log('before update');
    }

    componentDidUpdate() {
        this.context.log('after update');
    }

  render() {
      return <ComposedComponent {...this.props}/>
  }
} 

然后装饰你想要的组件

@Logger
class MyElement extends React.Component {
...
}