集成第三方库

React 或许是构建 UI 的最佳选择之一。良好的设计与强大的支持,还有庞大的社区。但是,有些情况下,我们想要使用外部服务或想要集成一些完全不同的东西。众所周知,React 在底层与实际 DOM 有大量的交互并控制页面上渲染什么,基本上它是开发者与实际 DOM 间的桥梁。这也正是为什么 React 集成第三方组件有些麻烦的地方。在本节中,我们将来介绍如何安全地混用 React 和 jQuery 的 UI 插件。

示例

我为这个示例挑选了 tag-it 这个 jQuery 插件。它将无序列表转换成可以管理标签的输入框:

  1. <ul>
  2. <li>JavaScript</li>
  3. <li>CSS</li>
  4. </ul>

转换成:

tag-it

要运行起来,我们需要引入 jQueyr、jQuery UI 和 tag-it 插件。这是运行的代码:

  1. $('<dom element selector>').tagit();

选择 DOM 元素,然后调用 tagit()

现在,我们来创建一个简单的 React 应用,它将使用 jQuery 插件:

  1. // Tags.jsx
  2. class Tags extends React.Component {
  3. render() {
  4. return (
  5. <ul>
  6. {
  7. this.props.tags.map(
  8. (tag, i) => <li key={ i }>{ tag } </li>
  9. )
  10. }
  11. </ul>
  12. );
  13. }
  14. };
  15. // App.jsx
  16. class App extends React.Component {
  17. constructor(props) {
  18. super(props);
  19. this.state = { tags: ['JavaScript', 'CSS' ] };
  20. }
  21. render() {
  22. return (
  23. <div>
  24. <Tags tags={ this.state.tags } />
  25. </div>
  26. );
  27. }
  28. }
  29. ReactDOM.render(<App />, document.querySelector('#container'));

App 类是入口。它使用了 Tags 组件,Tags 组件会根据传入的 tags 属性来展示无序列表。当 React 在页面上渲染列表时就有了 <ul> 标签,这样就可以和 jQuery 插件连接起来。

强制单通道渲染

首先,我们要做的就是强制 Tags 组件进行单通道渲染。这是因为当 React 在实际 DOM 中添加完容器元素后,我们想将控制权交给 jQuery 。如果不做控制的话,那么 React 和 jQuery 将会操纵同一个 DOM 元素而彼此之间不知情。要实现单通道渲染,我们需要使用生命周期方法 shouldComponentUpdate,像这样:

  1. class Tags extends React.Component {
  2. shouldComponentUpdate() {
  3. return false;
  4. }
  5. ...

这里永远都返回 false ,我们想让组件知道永远不进行重新渲染。定义 shouldComponentUpdate 对于 React 组件来说,是让其知道是否触发 render 方法。这适用于我们的场景,因为我们想使用 React 来添加 HTML 标记,添加完后就不想再依靠 React 。

初始化插件

React 提供了 API 来访问实际 DOM 节点。我们需要在相应的节点上使用 ref 属性,稍后可以通过 this.refs 来访问 DOM 。componentDidMount 是最适合初始化 tag-it 插件的生命周期方法。这是因为当 React 将 render 方法返回的结果挂载到 DOM 时才调用此方法。

  1. class Tags extends React.Component {
  2. ...
  3. componentDidMount() {
  4. this.list = $(this.refs.list);
  5. this.list.tagit();
  6. }
  7. render() {
  8. return (
  9. <ul ref='list'>
  10. {
  11. this.props.tags.map(
  12. (tag, i) => <li key={ i }>{ tag } </li>
  13. )
  14. }
  15. </ul>
  16. );
  17. }
  18. ...

上面的代码和 shouldComponentUpdate 一起使用就会使 React 渲染出有两项的 <ul> ,然后 tag-it 会其转换成标签可编辑的插件。

使用 React 控制插件

假如说我们想要通过代码来为已经运行的 tag-it 插件添加新标签。这种操作将由 React 组件触发,并需要使用 jQuery API 。我们需要找到一种方式将数据传递给 Tags 组件,但同时还要保持单通道渲染。

为了说明整个过程,我们需要在 App 类中添加一个输入框和按钮,点击按钮时将输入框的值传给 Tags 组件。

  1. class App extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this._addNewTag = this._addNewTag.bind(this);
  5. this.state = {
  6. tags: ['JavaScript', 'CSS' ],
  7. newTag: null
  8. };
  9. }
  10. _addNewTag() {
  11. this.setState({ newTag: this.refs.field.value });
  12. }
  13. render() {
  14. return (
  15. <div>
  16. <p>Add new tag:</p>
  17. <div>
  18. <input type='text' ref='field' />
  19. <button onClick={ this._addNewTag }>Add</button>
  20. </div>
  21. <Tags
  22. tags={ this.state.tags }
  23. newTag={ this.state.newTag } />
  24. </div>
  25. );
  26. }
  27. }

我们使用内部状态来存储新添加的标签名称。每次点击按钮时,我就更新状态并触发 Tags 组件的重新渲染。但由于 shouldComponentUpdate 的存在,页面上不会有任何的更新。唯一的变化就是得到 newTag 属性的新值,另一个生命周期方法 componentWillReceiveProps 会捕获到属性的新值:

  1. class Tags extends React.Component {
  2. ...
  3. componentWillReceiveProps(newProps) {
  4. this.list.tagit('createTag', newProps.newTag);
  5. }
  6. ...

.tagit('createTag', newProps.newTag) 是纯粹的 jQuery 代码。如果想调用第三方库的方法,componentWillReceiveProps 是个不错的选择。

下面是 Tags 组件的完整代码:

  1. class Tags extends React.Component {
  2. componentDidMount() {
  3. this.list = $(this.refs.list);
  4. this.list.tagit();
  5. }
  6. shouldComponentUpdate() {
  7. return false;
  8. }
  9. componentWillReceiveProps(newProps) {
  10. this.list.tagit('createTag', newProps.newTag);
  11. }
  12. render() {
  13. return (
  14. <ul ref='list'>
  15. {
  16. this.props.tags.map(
  17. (tag, i) => <li key={ i }>{ tag } </li>
  18. )
  19. }
  20. </ul>
  21. );
  22. }
  23. };

结语

尽管 React 承担了操纵 DOM 树的工作,但我们仍可以集成第三方的库和服务。生命周期方法让我们在渲染过程中可以进行足够的控制,这样才能够完美地连接 React 和非 React 世界。