6

I know this is not idiomatic React, but I'm working on a React component (an inverse scrollview) which needs to get notified of downstream virtual DOM changes both before they are rendered and after they are rendered.

This is a little hard to explain so I made a JSFiddle with a starting point: https://jsfiddle.net/7hwtxdap/2/

My goal is to have the log look like:

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

I'm aware that React sometimes batches DOM changes and that's fine (i.e. the log events could be before update; rendered big; rendered big; after update; ...) -- important part is that I am actually notified before and after the DOM changes.


I can manually approximate the behavior by specifying callbacks, as done here: https://jsfiddle.net/7hwtxdap/4/. However, this approach does not scale—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.


Use case:

I'm working on making an "inverted scrolling component" for messages (where, when new elements are added or existing elements change size, the basis for scroll change is from the bottom of the content rather than the top ala every messaging app) which has dynamically modified children (similar to the <MyElement /> class but with variable height, data from ajax, etc). These are not getting data from a Flux-like state store but rather using a pub-sub data sync model (approximated pretty well with the setTimeout()).

To make inverse scrolling work, you need to do something like this:

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

Importantly to this design, I'd like to be able to wrap the element around other elements that are not "inverted scrolling aware" (i.e. do not have to have special code to notify the inverted scrolling handler that they have changed).

4

4 回答 4

3

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

您如何将notifier()回调作为道具传递,然后在子组件的生命周期钩子中调用此componentWillMount函数componentDidUpdate

这是一个带有这个想法的工作 js-fiddle 。让我知道会发生什么。

编辑 1

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

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

所以我建议你使用 redux 来处理状态设置。要开始使用,您可以通过redux 的创建者 Dan Abramov观看这些教程视频。

于 2016-06-19T22:21:15.070 回答
1

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

在您的容器中:

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

装饰器类:

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 {
...
}
于 2016-06-26T17:48:15.833 回答
1

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

例如,我需要让事件从后代而不是孩子冒泡;并且宁愿不必将这些事件处理程序添加到我使用的每个组件中。

为此,我建议对您的解决方案进行两项更改。首先是考虑使用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 。

于 2016-06-20T10:23:58.890 回答
0

也许你可以看看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
    )
  }
}
于 2016-06-25T01:41:55.010 回答