手记

Using React components in your Ember app

声明: 我是学习Ember.js团队的一员,本篇文章不是要比较react和ember.这两个框架都非常棒!

如果一个使用Ember的团队想要重用React团队的组件该怎么做呢?或者您可能知道并喜欢多个前端工具集。本篇文章正适合这些人,当然还有思想开放的开发者!

这些都是基于我在企业工作时做的改变,截止目前为止在生产环境上已经使用了6个月的经验之谈。要注意的唯一因素是通过确保应用程序不包含React库的重复项来压缩大小。

接下来首先要让Ember项目能够识别JSX的语法,给予它编译JSX代码的能力。在Ember项目中运行下面的命令:

npm install --save-dev babel-plugin-transform-class-properties babel-plugin-transform-react-jsx

在ember-cli-build.js文件中中, 做如下的修改:

ember-cli-build.js.diff

'use strict'; const EmberApp = require('ember-cli/lib/broccoli/ember-app'); module.exports = function(defaults) {   let app = new EmberApp(defaults, {
-    // Add options here+    babel: {
+      plugins: [
+        'transform-class-properties',
+        'transform-react-jsx',
+      ]
+    }
   });

接着,我们要确保有 eslint 来识别JSX代码. 在Ember项目中运行下面的代码:

npm install --save-dev eslint-plugin-babel eslint-plugin-react   babel-eslint;

将如下修改添加到.eslintrc.js文件中:

diff --git a/.eslintrc.js b/.eslintrc.jsindex 99f9d25..b2970eb 100644--- a/.eslintrc.js+++ b/.eslintrc.js@@ -1,11 +1,17 @@
 module.exports = {
   root: true,
+  parser: 'babel-eslint',
   parserOptions: {
     ecmaVersion: 2017,
-    sourceType: 'module'+    sourceType: 'module',
+    ecmaFeatures: {
+      jsx: true
+    }
   },
   plugins: [
-    'ember'+    'babel',
+    'ember',
+    'react',
   ],
   extends: [     'eslint:recommended',
@@ -15,6 +21,8 @@ module.exports = {
     browser: true
   },
   rules: {
+    'react/jsx-uses-react': 'error',
+    'react/jsx-uses-vars': 'error',
   },
   overrides: [     // node files

运行如下命令在项目中添加React和React DOM

npm install --save react react-dom

接着在ember-cli-build.js文件中做如下修改:

ember-cli-build.js.diff

'use strict';const EmberApp = require('ember-cli/lib/broccoli/ember-app');const glob = require('glob');module.exports = function(defaults) {  let app = new EmberApp(defaults, {    // Add options here
    babel: {      plugins: [        'transform-class-properties',        'transform-react-jsx',
      ]
    }
  });  // Use `app.import` to add additional libraries to the generated
  // output files.
  //
  // If you need to use different assets in different
  // environments, specify an object as the first parameter. That
  // object's keys should be the environment name and the values
  // should be the asset to use in that environment.
  //
  // If the library that you are including contains AMD or ES6
  // modules that you would like to import into your application
  // please specify an object with the list of modules as keys
  // along with the exports of each module as its value.+  app.import({
+    development: 'node_modules/react/umd/react.development.js',
+    production: 'node_modules/react/umd/react.production.min.js'+  });
+
+  app.import({
+    development: 'node_modules/react-dom/umd/react-dom.development.js',
+    production: 'node_modules/react-dom/umd/react-dom.production.min.js'+  });   return app.toTree();
 };

添加这些imports会在app中引入全局React和ReactDOM对象。这非常重要, 因为任何我们要引入的React库要正常工作都需要全局调用这些对象。

让我们创建vendor shims,以便我们可以让这些库使用es6导入语法。我们不在这些imports上使用amd transformation的原因是在使用transformation时不会创建全局对象。

运行以下命令,并使用下面所示的要点替换生成的文件的内容。接着在ember-cli-build.js文件中引入它们。

ember generate vendor-shim react
ember generate vendor-shim react-dom

ember-cli-build.js.diff

   // please specify an object with the list of modules as keys
   // along with the exports of each module as its value.

   app.import('node_modules/react/umd/react.production.min.js');
   app.import('node_modules/react-dom/umd/react-dom.production.min.js');

+  app.import('vendor/shims/react.js');
+  app.import('vendor/shims/react-dom.js');   return app.toTree();
 };

react-dom.js

(function() {  function vendorModule() {    'use strict';    return {      'default': self['ReactDOM'],      __esModule: true,
    };
  }

  define('react-dom', [], vendorModule);
})();

react.js

(function() {  function vendorModule() {    'use strict';    return {      'default': self['React'],      __esModule: true,
    };
  }

  define('react', [], vendorModule);
})();

创建一个可以创建React组件容器的基类. 这个想法的背后原理是将React的组件包含在Ember的组件内。这样做有助于简化 simple这些组件。接下来创建一个包含以下内容的app/react-component.js文件。

react-component.js

import Component from '@ember/component';import ReactDOM from 'react-dom';export default Component.extend({  /**
   * We don't need a template since we're only creating a
   * wrapper for our React component
   **/
  layout: '',  /**
   * Renders a react component as the current ember element
   * @param {React.Component} reactComponent. e.g., <HelloWorld />
   */
  reactRender(reactComponent) {
    ReactDOM.render(reactComponent, this.element);
  },  /**
   * Removes a mounted React component from the DOM and
   * cleans up its event handlers and state.
   */
  unmountReactElement() {
    ReactDOM.unmountComponentAtNode(this.element);
  },  /**
   * Cleans up the rendered react component as the ember
   * component gets destroyed
   */
  willDestroyComponent() {    this._super();    this.unmountReactElement();
  }

})

首先我们运行ember g component hell-world 创建必修的‘hello world’ 组件, 并将如下内容添加到hello-world.js文件:

import ReactComponent from '../../react-component';let Greeter = ({name}) => <h2>Hello from {name}!!!</h2>;export default ReactComponent.extend({
  didInsertElement() {    this._super(...arguments);    this.reactRender(<Greeter name="React"/>);
  }
});

这太简单了 。 注意在第8行我们将值'React'传入到React组件中,这个属性可以是Ember组件的属性。现在来做一个更加复杂的示例。

在app中添加react-aria-modal 。并运行npm install --save @sivakumar-kailasam/react-aria-modal接着在ember-cli-build.js中做如下修改:

ember-cli-build.js.diff

+  app.import('node_modules/@sivakumar-kailasam/react-aria-modal/dist/react-aria-modal.js', {
+    using: [{
+      transformation: 'amd',
+      as: 'react-aria-modal'+    }]
+  });

现在可以在app中使用它了,先创建一个component的容器。

ember g component aria-modal

dialog-modal.js

import React from 'react';import AriaModal from 'react-aria-modal';export default class DemoModal extends React.Component {

  state = {    modalActive: false
  };

  activateModal = () => {    this.setState({ modalActive: true });
  };

  deactivateModal = () => {    this.setState({ modalActive: false });
  };

  getApplicationNode = () => {    return document.getElementById('ember-application');
  };

  render() {    const modal = this.state.modalActive
      ? <AriaModal
          titleText={this.props.title}
          onExit={this.deactivateModal}
          initialFocus="#demo-one-deactivate"
          getApplicationNode={this.getApplicationNode}
          underlayStyle={{ paddingTop: '2em' }}
        >
          <div id="demo-two-modal" className="modal">
            <header className="modal-header">
              <h2 id="demo-two-title" className="modal-title">
                {this.props.title}              </h2>
            </header>
            <div className="modal-body">
              <p>
                Here is a modal
                {' '}                <a href="#">with</a>
                {' '}                <a href="#">some</a>
                {' '}                <a href="#">focusable</a>
                {' '}
                parts.              </p>
              <input onChange={(e) => this.props.onTextChange(e.target.value)} value={this.props.title}/>            </div>
            <footer className="modal-footer">
              <button id="demo-one-deactivate" onClick={this.deactivateModal}>
                deactivate modal              </button>
            </footer>
          </div>
        </AriaModal>
      : false;

    return (      <div>
        <button onClick={this.activateModal}>
          activate modal        </button>
        {modal}      </div>
    );
  }
}

modal.js

import ReactComponent from '../../react-component';import DemoModal from './demo-modal';import { get, set } from '@ember/object';

export default ReactComponent.extend({

  title: 'An awesome demo',

  onTextChange(text) {    set(this, 'title', text);    this.renderModal();
  },

  didInsertElement() {    this._super(...arguments);    this.renderModal();
  },

  renderModal() {    this.reactRender(
      <DemoModal
        title={get(this, 'title')}
        onTextChange={(text) => this.onTextChange(text)}
        />
    );
  }

});

这个例子演示了在React和Ember组件间绑定方法的一种方式。通过绑有Ember组件的方法的React组件传值来更新标题,并重新渲染react组件。

注意以下的动图记录了如何立即重新渲染更新的内容。这是因为增加的更新应用到了已经渲染的React组件中。可以在文章末点击demo网站链接体验。

上面这些,你可能自己很轻松的做出来了。但是直到现在我还有个重要的因素没提到。

你要引入的React组件需要接受UMD模块加载规范。可以阅读学习 https://medium.freecodecamp.org/javascript-modules-a-beginner-s-guide-783f7d7a5fcc 了解UMD和其他模块化加载格式,

必须在react-aria-modal的fork上设置[rollup.js](https://rollupjs.org/guide/en) 才能运行这个演示应用程序。在这里 https://github.com/davidtheclark/react-aria-modal/compare/master...sivakumar-kailasam:master 查阅rollup的功能。

如果你的React组件项目是用了webpack,你可以查阅 https://github.com/bvaughn/react-virtualized 找到需要生成多种模块格式输出的webpack的设置。

https://sivakumar-kailasam.github.io/react-integration-sample/ 可以看到开发好的app,在repo查看出现在这篇博文的代码。试试用Ember和React的开发者工具查看这个app玩玩!

编辑: Alex LaFroscia 在这篇文章的基础上发布了一个实验性的addon(插件)https://github.com/alexlafroscia/ember-cli-react .这是我为什么热爱emer社区!

如果你喜欢这篇文章, 在twitter @sivakumar_k上关注我。

译文出处

1人推荐
随时随地看视频
慕课网APP