2

我有一个使用 React/Redux 的应用程序。我使用 Electron 将其打包为一个独立的应用程序。我目前正在构建基础以允许用户将自己的插件或小部件添加到应用程序中。由于我使用的是 Redux,我想继续使用 Redux 来跟踪安装的内容以及它为应用程序带来的更改/新功能。

在我深入探讨我的问题之前,让我们举个例子。我的核心应用程序有一个带有“控制”按钮的侧边栏。核心有两个按钮,但我想允许插件在那里添加自己的按钮。当然,我可以手动继续,在按钮侧边栏组件的 js 文件中导入插件并手动添加按钮,但这会破坏可插入性的目的。所以我能想到的最好和最简单的解决方案是从插件代码中分派一个动作,例如 ADD_SIDEBAR_BUTTON 它基本上将一个带有按钮元数据的对象添加到按钮数组(包括那些是核心的)。这个:

store.dispatch({type: "ADD_SIDEBAR_BUTTON", payload: {type: "button", path: "goSomewhere", label: "A label"}});

虽然这可以工作,但它极大地限制了按钮的功能,因为按钮的实际逻辑和 jsx 将来自核心应用程序。在实践中,我只是渴望让插件传递一个 React 组件,这将使插件在事件、生命周期的完全控制等方面有很大的自由度……但是传递除了普通对象之外的任何东西都是 Redux不,不。

另一种解决方案是让插件开发人员创建一个带有导出类的文件,并使用 redux 提供它的路径,如下所示:

class MyPluginButton extends Component {
   handleClick() => evt => {
     // do my special things
   }
   render() {
     return <button onClick={this.handleClick}>My Special Button</button>
   }
}

export default MyPluginButton;

然后插件的一个 initialize() 函数将调度操作并传递核心导入按钮所需的信息(如果没有正确完成,我也可能会想到安全问题):

store.dispatch({type: "ADD_SIDEBAR_BUTTON", payload: {path: "myplugin/src/components/MyPluginButton"}});

现在我的问题是,有没有人有好的想法可以/应该如何实施?我想确保没有其他我错过的模式或实现。

4

1 回答 1

1

我最终做的是一个 pluginRegistration 模块,简而言之,其基本思想是在我无法再次找到的博客上找到的:

const _registeredComponents = {};

export const registerComponent = (pluginName, component, injectAction) => {

  _registeredComponents[`plugin_${pluginName}_${component.PLUGIN_COMPONENT_NAME}`] = {pluginName: pluginName, component: component, action: injectAction};

};

export const getRegisteredComponents = () => {
   return {..._registeredComponents};
};

export const getRegisteredComponent = fullPluginComponentName => {
   return _registeredComponents[fullPluginComponentName].component;
}

想要添加的组件使用上面的函数注册自己并传递一个动作。然后,我从 ComponentDidMount 中的主 App 组件循环遍历 _registeredComponents 中的所有条目,调度沿组件类 ref 传递的操作以将我的按钮(在商店中,键字符串)添加到现有的 ControlButtons 组件。之后,父控件按钮组件从状态中获取 fullPluginComponentName 并从引用中实例化组件。瞧,一个使用 Redux 的完全动态的 React 组件实例化/注入,但在 store 中只保留普通对象。

我的按钮组件很普通,除了添加了静态getter(使用constructor.name很诱人,但是在缩小JS时,名称会丢失,可能会导致问题。)

export class MyButtonComponent extends Component {
  static get PLUGIN_COMPONENT_NAME() {
    return "MyButtonComponent";
  }

  render() {
   return (<button>My Useless Button</button>);
  }
}

在我创建要注入 React 类的按钮的同一文件中,我只需注册组件,如下所示:

registerComponent("MyPluginName", MyButtonComponent, actions.addButtonToControls);

这将插件名称、实际组件类引用和我想要触发的操作联系在一起。

为了获取可用的组件条目的键值,我的主 App 组件在挂载后循环访问已注册的组件条目:

class App extends Component {
   componentDidMount() {
       let pluginComponents = getRegisteredComponents();
       for (let fullPluginComponentName in pluginComponents) {
           let entry = pluginComponents[fullPluginComponentName];
           this.props.dispatch({type: entry.action, payload: {pluginName: entry.pluginName, fullPluginNameComponent: fullPluginComponentName}}); 
       }
   }
   render() { ... }
}

这将具有将组件键添加到存储区的效果,该存储区将用于稍后在 ControlButtons 中获取类引用,这是一个动态加载按钮数组的容器。这是后者如何从商店加载它们的简要版本:

class ControlButtons extends Component {
    getDynamicButtons() {
        // this.props.dynamicButtons is an array stored in redux and populated from the looped dispatch-es calls for each registered component.
        return this.props.dynamicButtons.map(componentEntry => {
            let MyDynamicButton = getRegisteredComponent(componentEntry.fullPluginComponentName);
            return <MyDynamicButton />;
        });
    }
    render() {
      <div>
         {this.getDynamicButtons()}
      </div>
    }
}

当然,不知何故,必须导入包含按钮类定义的文件。这是我旅程的下一步,我可能还会保存一组模块/文件名并使用 require 来加载它们。但那是另一回事了。

于 2018-02-17T04:28:19.097 回答