问题:像 AngularJS 中的作用域注入,用于更现代的库

我有一个用旧的 AngularJS 编写的整个平台,并希望随着时间的推移更改为更现代的框架。但是,让我对所有这些 ES6、Webpack 和 TypeScript 糖感到担忧的是动态范围注入。不确定我使用的词是否正确,但我们的用例是我们构建了一个在部署时编译的 AngularJS 应用程序。然后在非常特殊的场景中,客户需要为页面提供一些特殊的东西,我们现在可以在 AngularJS 中动态地注入页面上已经存在的范围并修改范围属性。例如。如果我们有一个点击后会跳转到页面 Y 的按钮。然后我们可以在他们的页面上使用 JavaScript 动态地注入按钮指令并覆盖范围属性以更改功能。

这对 VueJS、React 或 Angular 是否可行?在已编译的包之外使用良好的旧 JavaScript 来注入组件并改变它们的行为?这样做对我们来说非常重要,所以我们真的不想在选择错误的框架时犯任何错误。

用例/示例:

假设我们的应用程序实现了一个按钮元素的指令。该指令然后在单击时重定向用户。然后在编译后的源代码之外,我们在一个非常具体的场景中希望这个按钮做其他事情。

例如:

/**
 * @ngdoc overview
 * @name el-button
 *
 * @description
 */

angular.module('el-button', [])
.directive(‘elButton’, function() {
  return {
    restrict: 'E',
    replace: true,
    scope: true,
    template: '<button class="el-button" ng-click="click()">Button</button>',
    link: function(scope, element, attrs) {
        scope.click = function () { window.location.href = '/some-url'; }
    }
  };
});
angular.module('someapp').requires.push('el-button');

然后我们可以在编译后的源代码之外直接在页面上改变行为来做其他事情:

/**
 * @ngdoc overview
 * @name custom.el-button
 *
 * @description
 */

angular.module('custom.el-button', [])
.directive(‘elButton’, function() {
  return {
    restrict: 'C',
    replace: false,
    scope: false,
    link: function(scope, element, attrs) {
        scope.click = function () { alert('click'); }
    }
  };
});
angular.module('someapp').requires.push('custom.el-button');

在这种情况下,我们将行为从重定向用户更改为显示警报。

解答

这对 VueJS、React 或 Angular 是否可行?在已编译的包之外使用良好的旧 JavaScript 来注入组件并改变它们的行为?

不,至少没有在它们的共同点之外使用这些框架

分层范围的概念(scope设置为true)是 AngularJS 特有的。由于设计原因,它在大多数用途中已被隔离范围所取代。

组件是在 AngularJS 1.5 中引入的,应该可以平滑过渡到 Angular 2。

在所有上述框架中编写el-button指令的正确方法是将其表示为组件并在必要时对其进行扩展。它们当前的主要用途涉及构建步骤(Webpack + Babel/TypeScript 工具链),因此组件应该在编译时而不是运行时扩展(内联脚本)。

AngularJS(现代)

ES 模块和细粒度的 AngularJS 模块一起使用,这以样板为代价提供了额外的可扩展性。

el-button.js

export class Controller {
  onClick = () => {/* general implementation */};
}

export const component = {
  template: '<button ng-click="$ctrl.onClick()">Button</button>',                                    
  controller: Controller
};

export default angular.module('namespace.el-button', []).component('elButton', component).name;

general-app.js

import elButtonComponentModule from './el-button';

export default angular.module('namespace.app', [elButtonComponentModule, ...]).name;

在 AngularJS 中比在其他框架中更容易做到这一点,因为它允许使用同名模块 (namespace.el-button) 覆盖原始组件模块:

我的-l-button.js

import { Controller as OriginalController, component as originalComponent } from './el-button';

export class Controller extends OriginalController {
  onClick = () => {/* my implementation */};
}

export const component = {
  ...originalComponent,
  controller: Controller
};

export default angular.module('namespace.el-button', []).component('elButton', component).name;

我的应用程序.js

import generalAppModule from './general-app';
import myElButtonComponentModule from './my-el-button';

export default angular.module('my.app', [generalAppModule, myElButtonComponentModule]).name;

Angular 模块用于注册组件。不允许使用重复的选择器,这要求不要在模块声明中列出原始组件。

el-button.html

<button (click)="onClick()">Button</button>

el-button.js

@Component({
  selector: 'el-button',
  templateUrl: require('./el-button.html')
})
export default class ElButton {
  onClick() {/* general implementation */}
}

general-app.js

import ElButton from './el-button';

@NgModule({
  declarations: [ElButton, ...], ...
})
...

我的-l-button.js

import OriginalComponent from './el-button';

@Component({
  selector: 'el-button',
  templateUrl: require('./el-button.html')
})
export class ElButton {
  onClick() {/* my implementation */}
}

我的应用程序.js

import ElButton from './my-el-button';

@NgModule({
  declarations: [ElButton, ...], ...
})
...

这可能需要在多个级别用自定义模块替换模块,以从嵌套模块重新定义组件并导致样板。

查看

Vue 没有框架模块,但提供了开箱即用的可扩展性。

el-button.js

export default Vue.component('el-button', {
  template: `<button @click="onClick">Button</button>`,
  methods: {
    onClick() {/* general implementation */}
  }
};

我的-l-button.js

import OriginalComponent from './el-button';

export default Vue.component('el-button', OriginalComponent.extends({
  methods: {
    onClick() {/* my implementation */}
  }
};

定义具有相同选择器的组件将覆盖原始组件。如果组件在本地注册,这可能会变得更加复杂。

反应

由于它是视图库而不是成熟的框架,因此它不提供任何东西来扩展已经在视图中使用的组件。这需要显式编写组件以使用依赖注入来扩展它们,例如使用 React 上下文从外部接收onClick:

el-button.jsx

const ElButtonContext = React.createContext({
   onClick() {/* general implementation */}
});

const ElButton = () => {
   const { onClick } = React.useContext(ElButtonContext);
   return <button onClick={onClick}>Button</button>;
}

我的应用程序.jsx

<ElButtonContext.Provider value={ onClick() {/* my implementation */} }>
  <ComponentThatUsesElButton/>
</ElButtonContext.Provider>

独立于框架的模块替换

所有列出的框架都可以在构建时与捆绑器交换依赖关系; Webpack 通过resolve.alias选项和NormalModuleReplacementPlugin允许这样做。

此选项特定于环境,可能不可用(默认情况下在create-react-app项目中不可用),但这允许扩展组件,即使在应用程序级别不支持,例如对于反应:

我的-l-button.jsx

import OriginalComponent from '@aliased-el-button-module';

function onClick() {/* my implementation */}

const ElButton = props => {
   const el = OriginalComponent(props); // a hack that heavily depends on the implementation
   el.props.onClick = onClick;
   return el;
}

如果已经构建了需要替换的模块,例如它属于第三方库,这可能会变得更加复杂。

这对 VueJS、React 或 Angular 是否可行?在已编译的包之外使用良好的旧 JavaScript 来注入组件并改变它们的行为?

不,至少如果应用程序是根据为这些框架建议的最佳实践开发的。这样做需要打破 JS 模块提供的封装,通过将所有重要的应用程序内部分配给window来污染全局范围:

const ElButton = () => {...};

const ComponentThatUsesElButton = () => (
  <window.myNamespace.ElButton /> // ugh!
);

window.myNamespace = {
  React,
  ElButton,
  ComponentThatUsesElButton,
  ...
};

所有这些框架都应该与额外的工具链一起使用,并且不能很好地与_good old JavaScript_一起使用。 Angular 依赖于 ES.next 装饰器和提前编译,纯 JS 的功能有限。 Vue 使用自定义 .vue 格式,并且对纯 JS 的功能有限。 React 在纯 JS 中功能齐全,但在没有 JSX 语法的情况下更加冗长和缺乏表达力。

Logo

Vue社区为您提供最前沿的新闻资讯和知识内容

更多推荐