# @schema-plugin-flow/sifo-react

> A highly extensible JavaScript library, for React. 高扩展性、可二开的插件式前端开发框架

Latest version **1.6.0** (published 2022-05-31) · MIT license · 0 weekly downloads

## Install

```sh
npm install @schema-plugin-flow/sifo-react
pnpm add @schema-plugin-flow/sifo-react
yarn add @schema-plugin-flow/sifo-react
bun add @schema-plugin-flow/sifo-react
```

## Health

**Score 15/100 (F)** — status: abandoned.

Positive: no vulnerabilities.

Warnings: low downloads; no types; no esm support.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 1.6.0 |
| Published | 2022-05-31 |
| First published | 2020-08-30 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 7 |
| Unpacked size | 65.8 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 425 |
| Author | FrominXu |
| Maintainers | fromin |
| Keywords | schema-plugin-flow, sifo, plugin, react |

## Links

- npm: https://www.npmjs.com/package/@schema-plugin-flow/sifo-react
- Repository: https://github.com/alibaba/schema-plugin-flow
- Homepage: https://github.com/alibaba/schema-plugin-flow#readme
- Issues: https://github.com/alibaba/schema-plugin-flow/issues
- npm.io page: https://npm.io/package/@schema-plugin-flow/sifo-react

## Dependencies (7)

- [react](https://npm.io/package/react.md) ^16.10.1
- [react-dom](https://npm.io/package/react-dom.md) ^16.10.1
- [classnames](https://npm.io/package/classnames.md) ^2.2.6
- [prop-types](https://npm.io/package/prop-types.md) ^15.x
- [@babel/runtime](https://npm.io/package/@babel/runtime.md) ^7.12.5
- [@schema-plugin-flow/sifo-model](https://npm.io/package/@schema-plugin-flow/sifo-model.md) ^1.5.0
- [@schema-plugin-flow/sifo-singleton](https://npm.io/package/@schema-plugin-flow/sifo-singleton.md) ^1.2.0

## Alternatives

- [mobx-react](https://npm.io/package/mobx-react.md) — 2.8M weekly downloads
- [rc-tree](https://npm.io/package/rc-tree.md) — 2.6M weekly downloads
- [@react-oauth/google](https://npm.io/package/@react-oauth/google.md) — 1.3M weekly downloads
- [@wagmi/connectors](https://npm.io/package/@wagmi/connectors.md) — 877.0K weekly downloads
- [vee-validate](https://npm.io/package/vee-validate.md) — 836.4K weekly downloads

## Recent versions

- 1.6.0 (latest) — 2022-05-31
- 1.5.1 — 2021-08-02
- 1.5.0 — 2021-07-31
- 1.4.1 — 2021-07-21
- 1.4.0 — 2021-04-26
- 1.3.3 — 2021-03-09
- 1.3.2 — 2021-01-12
- 1.3.1 — 2020-12-16
- 1.3.0 — 2020-11-26
- 1.2.0 — 2020-11-17
- 1.1.5 — 2020-11-05
- 1.1.4 — 2020-10-29
- 1.1.3 — 2020-10-27
- 1.1.2 — 2020-09-15
- 1.1.1 — 2020-09-15
- … 2 more at https://npm.io/package/@schema-plugin-flow/sifo-react/versions

## README

# sifo-react

sifo-react 是组装了[sifo-model](https://www.npmjs.com/package/@schema-plugin-flow/sifo-model)、[sifo-singleton](https://www.npmjs.com/package/@schema-plugin-flow/sifo-singleton) 的一个React组件。      

sifo-react 以 sifo-model 为内核，使用插件式的开发模式，为页面提供了多级定制与扩展能力。包含但不限于：页面结构的修改、渲染组件的替换、组件属性的变更、组件事件的监听与阻断等。结合不同的模型插件，可以实现更加丰富的特定功能。

* ![插件示例](https://img.alicdn.com/tfs/TB1HOQYe6MZ7e4jSZFOXXX7epXa-1264-698.gif)

## codesandbox.io
* sifo-react
  * [sifo-react-quick-start](https://codesandbox.io/s/sifo-react-quick-start-lhmyu)    
  * [sifo-react-decorator](https://codesandbox.io/s/sifo-react-test-decorator-sef79)    
  * [sifo-mplg-form-antd](https://codesandbox.io/s/sifo-react-form-antd-o0hoq)     
  * [sifo-react-mplg-optimize](https://codesandbox.io/s/sifo-react-mplg-optimize-sfmts)  

## SifoApp (sifo-react) Props

参数          | 说明        | 类型 | 是否必传 | 默认值
-------------|-------------|-----|-----|-----
namespace    | 命名空间，这是一个功能集合的主要标识，第三方将根据命名空间来进行功能扩展     | string | 是 | - |
schema       | schema，描述了页面结构      | object | 是 | - |
components | 组件 | object | 是 | {} |
plugins      | 插件，分为模型插件、页面插件和组件插件         | array：[{ componentPlugin, pagePlugin, modelPlugin }, { modelPlugin: otherModelPlugin }] | 否 | [] |
externals | 任意其它信息 | object | 否 | {} |
sifoExtProps | 任意对象，mApi.getSifoExtProps 可以取到即时的值，这点与 externals 相区别 | object | 否 |{}|
modelApiRef | 模型接口外传方法，调用参数为 mApi（接口构建完成时） 或 null（模型销毁时） | function | 否 |
openLogger | 是否在控制台打印出执行日志，不建议在生产环境使用 | bool | 否 | false |
getModelPluginArgs | 获取模型插件实例化时的构造函数参数 | function:(modelPluginId, info) => ([arg1, arg2, ...]) | 否 | | - |
className | 样式类 |string | 否 |  |

## 扩展的 mApi 模型接口

*mApi说明*

| 方法名            | 参数/类型               | 返回值类型             | 描述       |
| ---------------- | -----------------------| --------------------- | ---------------------------------------------------------------------------------------------------|
| getSifoExtProps  | ✘                    |   任意对象            |   获取 SifoApp.sifoExtProps 即时的值   |

## 如何使用
* 项目

  * extend.js
    ```javascript
    import SifoSingleton from '@schema-plugin-flow/sifo-singleton';
    const singleton = new SifoSingleton('quick-start'); // target namespace
    singleton.registerItem('testExtendId', () => {
      return {
        plugins,
        components
      }
    });
    ```

  * app.js
    ```javascript
    import React from 'react';
    import ReactDOM from "react-dom";
    import SifoApp from '@schema-plugin-flow/sifo-react';
    class App extends React.Component {
      render() {
        return (
          <SifoApp
            namespace='quick-start'
            components={components}
            schema={schema}
            plugins={plugins}
          />
        );
      }
    }
    ReactDOM.render(
      <App />
      rootElement
    );
    ```

* runtime
  * load extend js
  * load app js

    你应该在 sifoApp 渲染前加载扩展 js 资源

    ```html
    <script src="extend.js"></script>
    <script src="app.js"></script>
    ```


## QuickStart
下面的例子演示了如何监听一个按钮组件的点击事件，并在点击事件中修改其它组件的属性，同时也演示了多个插件的情形。想了解更多的功能请参考`sifo-model`
```javascript
import React from 'react';
import SifoApp from '@schema-plugin-flow/sifo-react';
// 一些组件
const Container = props => <div {...props} />;
const Slogan = ({ content, ...other }) => <h2 {...other}>{content}</h2>;
const Button = props => <button {...props}>click to change</button>;
// schema 定义了初始的页面结构
const schema = {
  component: "Container",
  id: 'mainId',
  attributes: {},
  children: [
    {
      component: "Slogan",
      id: 'slogan_id',
      attributes: {
        content: 'hello world'
      }
    },
    {
      component: "Button",
      id: 'test_btn_id',
      attributes: {}
    }
  ]
};
// 组件插件可以实现与组件相关的功能
const componentPlugin1 = {
  test_btn_id: {
    onComponentInitial: params => {
      const { event, mApi } = params;
      mApi.addEventListener(event.key, 'onClick', (context, e) => {
        mApi.setAttributes('slogan_id', {
          content: 'hello sifo'
        });
      })
    }
  }
};
// 第二个插件
const componentPlugin2 = {
  test_btn_id: {
    onComponentInitial: params => {
      const { event, mApi } = params;
      mApi.addEventListener(event.key, 'onClick', () => {
        console.log('test_btn_id clicked!')
      })
    }
  }
};
const components = { Container, Slogan, Button };
const plugins = [
  { componentPlugin: componentPlugin1 },
  { componentPlugin: componentPlugin2 }
];
class App extends React.Component {
  render() {
    return (
      <SifoApp
        className='quick-start'
        namespace='quick-start'
        components={components}
        schema={schema}
        plugins={plugins}
        openLogger={false}
      />
    );
  }
}
export default App;
```

## 外部扩展
如果一个页面是用 sifo 开发的，开发者可以在不接触原始代码的情况下，对页面进行扩展。这里用到了 `sifo-singleton` 全局扩展容器，只要在目标页面渲染前载入了扩展插件、组件，扩展功能就会在目标页面上生效。

```javascript
import SifoSingleton from '@schema-plugin-flow/sifo-singleton';
const singleton = new SifoSingleton('test_namespace');// 对目标命名空间进行扩展
// 插件的功能与使用跟前面的示例完全一致
const plugins = [{ pagePlugin, componentPlugin }];
const components = {};
singleton.registerItem('testExtendId', () => {
  return {
    plugins,
    components,
    openLogger: true, 
  }
});
```

## sifoAppDecorator
为一个组件追加扩展能力时，可用修饰器方式。sifoAppDecorator 第一个参数是命名空间，第二个参数与上文的“SifoApp参数”一致（namespace 和 schema 除外）。此外还增加了如下参数：

参数          | 说明        | 类型 | 是否必传 | 默认值
-------------|-------------|-----|-----|-----
fragments    | 片段列表。片段可以只定义一个id，通过 getFragment 方法获取片段来渲染；也可以传一个 schema，以 schema 的第一层 id 来标识  | array | 否 | - |

被 sifoAppDecorator 修饰的组件，props 中将出现 sifoApp 对象，对象包含 addEventListener、 watch、getFragment 等方法和 mApi 接口。

### sifoAppDecorator 示例
下面的示例包含：
* View 组件标注命名空间为 main_namespace；
* View 组件向外暴露 onSubmit、setState、onChange 事件，扩展件就可以监听与干预这些事件；
* View 注册了 getState、setState 观测，扩展件可以发布相应观测消息来与 View 通信；
* View 定义了 $header 片段，以使扩展件可以在页面指定位置渲染内容。
完整示例请参照[这里](https://github.com/alibaba/schema-plugin-flow/blob/master/docs/sifo-react-doc/test-decorator.md)
```javascript
import React from 'react';
import { sifoAppDecorator } from '@schema-plugin-flow/sifo-react';
@sifoAppDecorator('main_namespace', {
  fragments: ['$testFragment', '$fragment2', innerSchema],
  components: {},
  plugins: [],
  openLogger: true
})
class View extends React.Component {
  constructor(props) {
    super(props);
    const { sifoApp } = props;
    this.state = { value: '' };
    // 加入事件监听，这些事件实际上是挂在了以当前命名空间为id的schema节点上
    this.onSubmit = sifoApp.addEventListener('onSubmit', this.onSubmit);
    this.setState = sifoApp.addEventListener('setState', this.setState.bind(this));
    // prepose传入true可使事件先于扩展件注册，在希望外部能够覆盖（扩展）内部方法时可使用
    this.onChange = sifoApp.addEventListener('onChange', this.onChange, true); 
    // 注册观测任务
    sifoApp.watch('getState', (context, getter) => {
      getter(this.state);
    });
    sifoApp.watch('setState', (context, state) => {
      this.setState({
        ...state
      });
    });
  }
  onSubmit = () => {
    //...
  }
  onChange = () => {
    //...
  }
  render() {
    // 将声明的片段放到指定的位置，扩展插件就可以在相应位置渲染自定义的内容了
    const testFragment = this.props.sifoApp.getFragment('$testFragment');
    // 动态传参
    const testFragment2 = this.props.sifoApp.getFragment('$fragment2', {
      stateValue: this.state.value, others: {}
    });
    return (
      <Comp
        onChange={this.onChange}
        onSubmit={this.onSubmit}
      >
        {testFragment}
        {testFragment2}
      </Comp>
    )
  }
}
```

sifoAppDecorator 下的外部扩展示例

```javascript
import React from 'react';
import SifoSingleton from '@schema-plugin-flow/sifo-singleton';
// 
const pagePlugin = {
  onNodePreprocess: (node, info) => {
    const { id, component } = node;
    // sifoAppDecorator 内置了 $sifo-header 和 $sifo-footer 两个节点
    if (id === '$sifo-header') {
      return {
        ...node,
        attributes: {},
        children: ['this is ext-header']
      }
    }
    if (id === '$sifo-footer') {
      return {
        ...node,
        children: ['this is ext-footer']
      }
    }
    if (id === '$fragment2') {
      // 将片段直接换成新的组件，这个组件就可以拿到getFragment的参数
      return {
        ...node,
        component: 'DynamicPropsCom'
      }
    }
    if (id === '$testFragment') {
      return {
        ...node,
        children: [
          {
            component: 'Input',
            id: 'custom'
          }
        ]
      }
    }
    return node;
  },
}
const componentPlugin = {
  // 命名空间认作根节点id
  main_namespace: {
    onComponentInitial: params => {
      const { event, mApi } = params;
      mApi.addEventListener(event.key, 'onSubmit', (context, e) => {
      });
      mApi.addEventListener(event.key, 'onChange', (context, e) => {
        mApi.dispatchWatch('setState', { testState: 'test' })
      });
      mApi.addEventListener(event.key, 'setState', (e, state) => {
        const nextState = {
          ...state,
          customState: new Date().getMilliseconds()
        };
        e.event.next(nextState);
      });
    }
  },
  custom: {
    onComponentInitial: params => { }
  }
};
const singleton = new SifoSingleton('main_namespace');
singleton.registerItem('this_ext_id', () => {
  return {
    plugins: [
      {
        pagePlugin,
        componentPlugin
      }
    ]
  }
});
//
export default {};
```

在函数式组件上使用
```jsx
import React from 'react';
import { sifoAppDecorator } from "@schema-plugin-flow/sifo-react";
const TestFnDecorator = props => {
    const { sifoApp } = props;
    console.log('render ---- mApi instance:', sifoApp.mApi.instanceId);
    const dynamicPanel = sifoApp.getFragment('$temp_panel', { 
      stateValue: '函数式',
      others: { ok: true } 
    });
    return (
      <div>
      <h3>函数式组件</h3>
        { dynamicPanel }
      </div>
    );
}
const App = sifoAppDecorator('test-sifo-decorator', {
  externals: { aa: 1 },
  fragments: ['$temp_panel'],
  className: "decorator-fn-test",
  openLogger: true
})(TestFnDecorator);
export default App;
```

## 非标准的组件接入
```javascript
const TestComponent = {
  name: "TestNonStandard", // 组件名
  useSifoRenderProxy: true, // 标注组件需要使用代理
  /**
   * 渲染方法
   * target: dom对象
   * props: 属性
   * update: 是否是更新
   * preInstance: 前一个render调用返回的对象（更新时）
   */
  render: (target, props, update, preInstance) => {
    // Svelte 组件
    // if (!update) {
    //   return renderTest(props, props.children, target); // 返回instance
    // } else {
    //   preInstance.update(props);
    //   return preInstance;
    // }
    ReactDOM.render(React.createElement(Demo, props), target); // react
  },
  /**
   * 卸载方法，如果不传，默认调用 ReactDOM.unmountComponentAtNode(target);
   */
  unmount: (target, instance) => {
    // instance.unmount(target);
    ReactDOM.unmountComponentAtNode(target); // react
  }
  /**
   * 渲染类型：normal 指在每次react节点渲染时调用render方法，rerenderKey 指在 props.rerenderKey 变化时才调用render方法，默认为 normal，也可以在 props.rerenderType 中指定渲染类型。
   */
  rerenderType: "normal",// "normal"|"rerenderKey"
}
```

## 模型插件推荐列表
* [React标记渲染优化插件](../sifo-mplg-react-optimize)

---
_Source: https://npm.io/package/@schema-plugin-flow/sifo-react · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
