# Connect

采用 HOC，使一个组件具备更多功能

## 案例演示

当前案例中所有请求数据，采用 [randomuser](https://randomuser.me) 进行模拟。

### Connect 中 eventConnect用法

通过 `eventConnect` 实现两个组件之间数据互传。点击按钮发送数据。

> 如果使用 `mf` 引入模块，外部模块无法用 `eventConnect` 进行数据传递时，此时需要使用 `import { PubSub } from 'amos-framework';` 处理;
> 如果使用 `import { PubSub } from 'ray-eventpool';` 中的模块，因为 `instance` 不一致，从而导致数据传递失败。

---demo
```js
import { Connect, Button } from 'amos-framework';

const eventConnect = Connect.eventConnect;
const topic = 'EVENT_CONNECT';

// page1

// 触发消息
@eventConnect
class Page1 extends Component {
  render() {
    return (
      <Button onClick={() => this.props.trigger(topic, '我是来自 Page1 组件的数据')}>page1</Button>
    );
  }
}

// 触发消息
@eventConnect
class Page2 extends Component {
  render() {
    return (
      <Button style={{ marginLeft: 6 }} onClick={() => this.props.trigger(topic, '我是来自 Page2 组件的数据')}>page2</Button>
    );
  }
}

// 接收消息
@eventConnect
class Demo extends Component {

  constructor(props) {
    super(props);
    this.state = {
      content: ''
    };
  }

  componentDidMount() {
    this.props.subscribe(topic, (topic, content) => {
      this.setState({
        content
      });
    });
  }

  render() {
    return (
      <div style={{ width: '40em', display: 'inline-block' }}>
        <div style={{ marginBottom: 20, borderBottom: '1px solid #ccc' }}>
          数据接收区:
          <p style={{ color: 'red' }}>{this.state.content}</p>
        </div>
        <Page1 />
        <Page2 />
      </div>
    );
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### Connect 中 loadingConnect 用法

通过 `loadingConnect` 实现数据加载过渡提示。

---demo
```js
import { Connect, Button, message, JsonView, Spin, Row, Col } from 'amos-framework';

const loadingConnect = Connect.loadingConnect;
const standardLoading = Connect.standardLoading;

const mockAPI = 'https://randomuser.me/api';

const getData = params => {
  const url = params ? `${mockAPI}?${params}` : `${mockAPI}?results=5&inc=name,email,nat&noinfo`;
  return window.fetch(url).then(res => res.json());
};

const getDataErr = params => {
  const url = params ? `${mockAPI}?${params}` : `${mockAPI}?results=5&inc=name,email,nat&noinfo`;
  return window.fetch(url).then(res => res.json()).then(d => {
    throw Error('我是主动触发的错误！');
  });
};

const style = {
  height: '15em',
  overflow: 'auto',
  fontSize: '10px'
};

class Loader extends Component {

  constructor(props) {
    super(props);
    this.state = {
      dataSource: null
    };
  }

  reload = () => {
    this.props.fetch(getData, 'results=2&inc=name,phone,nat&noinfo', data => {
      this.setState({
        dataSource: data
      });
    }, err => {
      message.danger('失败！');
    });
  }

  errorAction = () => {
    this.props.fetch(getDataErr, 'results=2&inc=name,phone,nat&noinfo', data => {
      this.setState({
        dataSource: data
      });
    }, err => {
      message.danger(err.message);
    });
  }

  render() {
    return (
      <div className="btn-demo">
        <Button onClick={this.reload}>获取数据</Button>
        <Button onClick={this.errorAction}>失败示例</Button>
        <JsonView tag="pre" style={style} value={this.state.dataSource} />
      </div>
    );
  }
}

// 默认 spinner
const DefaultLoader = loadingConnect({
  spinnerProps: {
    size: 'lg'
  }
})(Loader);

// 标准 spinner
const StdLoader = standardLoading(Loader);

//  自定义 spinner
const CusLoader = loadingConnect({
  spinner: Spin,
  spinnerProps: {
    type: 'scale',
    color: '#163cff'
  }
})(Loader);

// 自定义 indicator
const CusIndicatorLoader = loadingConnect({
  spinnerProps: {
    type: 'scale',
    color: '#163cff'
  },
  renderIndicator(loading){
    return <Spin type="scale" color="#345fa6" loading={loading} />;
  }
})(Loader);

ReactDOM.render((
  <div>
    Connect 中 loadingConnect 用法： 默认 spinner
    <Row><DefaultLoader /></Row>
    Connect 中 standardLoading 用法： 标准 spinner
    <Row><StdLoader /></Row>
    Connect 中 loadingConnect 用法: 自定义 spinner
    <Row><CusLoader /></Row>
    Connect 中 loadingConnect 用法: 自定义 indicator
    <Row><CusIndicatorLoader /></Row>
  </div>
), _react_runner_);
```
---demoend

### Connect 中 withAsyncData 用法

通过 `withAsyncData` 实现数据加载过渡提示。

---demo
```js
import { Connect, JsonView, Spin, Row } from 'amos-framework';

const withAsyncData = Connect.withAsyncData;

const mockAPI = 'https://randomuser.me/api';

const style = {
  height: '10em',
  minHeight: '8em',
  overflow: 'auto',
  fontSize: '10px'
};

const initAction = () => {
  return fetch(`${mockAPI}?results=2&inc=name,phone,nat&noinfo`).then(res => res.json());
};

class SyncData extends Component {

  constructor(props) {
    super(props);
  }

  render() {
    const { dataSource } = this.props;
    return (
      <div>
        <h5>withAsyncData 结果：</h5>
        <JsonView tag="pre" style={style} value={dataSource} />
      </div>
    );
  }
}

// 默认 spinner
const DefaultAsyncData = withAsyncData({
  initAction,
  spinnerProps: {
    size: 'lg'
  }
})(SyncData);

//  自定义 spinner
const CusAsyncData = withAsyncData({
  initAction,
  spinner: Spin,
  spinnerProps: {
    type: 'scale',
    color: '#163cff'
  }
})(SyncData);

// 自定义 indicator
const CusIndicatorAsyncData = withAsyncData({
  initAction,
  spinnerProps: {
    type: 'scale',
    color: '#163cff'
  },
  renderIndicator(loading){
    return <Spin type="scale" color="#345fa6" loading={loading} />;
  }
})(SyncData);

ReactDOM.render((
  <div>
    Connect 中 withAsyncData 用法： 默认 spinner
    <Row><DefaultAsyncData /></Row>
    Connect 中 withAsyncData 用法: 自定义 spinner
    <Row><CusAsyncData /></Row>
    Connect 中 withAsyncData 用法: 自定义 indicator
    <Row><CusIndicatorAsyncData /></Row>
  </div>
), _react_runner_);
```
---demoend

### Connect 中 enhanceRender 用法

---demo
```js
import { Connect, Button } from 'amos-framework';

const enhanceRender = Connect.enhanceRender;
const delay = 1000;

/**
 * 仅当 visible 为true 时，才执行 asyncLoad 方法，仅执行一次
 * @class CRender
 * @extends {Component}
 */
@enhanceRender
class CRender extends Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 0
    };
  }

  componentWillUnmount() {
    clearInterval(this.intervalID);
  }

  asyncLoad = () => {
    this.intervalID = setInterval(() => {
      this.setState(preState => ({ value: preState.value + 1 }));
    }, delay);
  }

  render() {
    const { visible } = this.props;
    if (visible){
      return (
        <div>当前值:{this.state.value}</div>
      );
    }
    return null;
  }
}

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      visible: false
    };
  }

  render() {
    const { dataSource } = this.props;
    return (
      <div>
        <Button onClick={() => this.setState({ visible: true })}>启动</Button>
        <CRender visible={this.state.visible} />
      </div>
    );
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### Connect 中 dbclickConnect 用法

---demo
```js
import { Connect, Button } from 'amos-framework';

const dbclickConnect = Connect.dbclickConnect;

@dbclickConnect()
class EventDemo extends Component {
  render() {
    const { onClick, onDoubleClick } = this.props;
    return (
      <div onClick={onClick} onDoubleClick={onDoubleClick}>
        我是目标，单击双击试试
      </div>
    );
  }
}

class Demo extends Component {
  onClick = () => console.log('click!');
  onDoubleClick = () => console.log('dbclick!');

  render() {
    return (
      <div>
        <EventDemo onClick={this.onClick} onDoubleClick={this.onDoubleClick} />
      </div>
    );
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

## 详细类说明

### eventConncet

  使一个组件具有 trigger/subscribe 功能，组件卸载时，无需关心取消事件订阅

  组件增加 的props:

* subscribe(topic, (topic, data) => {});
* trigger(topic, data)

```js
import React, { Component } from 'react';
import { Connect } from 'amos-framework';

const eventConnect = Connect.eventConnect;

class Com extends Component {

  constructor(props) {
    super(props);
    this.state = {
      content: ''
    };
  }

  componentDidMount() {
    this.props.subscribe('E_CONNECT', (topic, content) => {
      this.setState({
        content
      });
    });
  }

  render() {
    const { content } = this.state;
    return <div>{content}</div>;
  }
}

export default eventConnect(Com);
```

### portalConnect

使用 Portal

### enhanceStorage

采用 storage 进行跨页面通信，传参等。同时，支持通用操作 storage `read、write、remove`。

使用监听模式时，组件需要实现 `onStorageChange` 方法，或者在父组件中给当前监听组件传递 props时，需要传递 `onStorageChange`。参数为 `{ data, event }`，如果不设置 `storageItems`，则 data 为 `{}`

新增的 props 有 `read/write/remove/toStr/toJSON`

* 接收监听 storage 变化

```js
 @enhanceStorage({
   storageItems: ['test', 'test1']
 })
class Receive extends Component {
  constructor(props) {
    super(props);
    this.state = {

    };
  }

  /*
   * data 数据格式， 将 `storageItems` 中的每一项数据获取到，同时转化为一个 object。
   * 如： { test: 'val1', test1: 'val2' }, 其中 value 是 string类型
   */
  onStorageChange = (data, e) => {
    console.log(data, e);
    console.log(this.props.read('test'));
    console.log(this.props.read('test1'));
  }
}
```

* 普通使用 storage

```js
 @enhanceStorage({
   noListening: true
 })
class Normal extends Component {
  handleAdd = () => {
    this.props.write('test', 'my value');
  }

  handleRead = () => {
    const val = this.props.read('test');
  }

  handleDel = () => {
    this.props.remove('test');
  }
}
```

### withRouter

直接使用 `withRouter` 给组件添加具体的路由操作。

```js
const routerShape = PropTypes.shape({
  push: PropTypes.func.isRequired,
  replace: PropTypes.func.isRequired,
  go: PropTypes.func.isRequired,
  goBack: PropTypes.func.isRequired,
  goForward: PropTypes.func.isRequired,
  setRouteLeaveHook: PropTypes.func.isRequired,
  isActive: PropTypes.func.isRequired
});

const contextTypes = {
  router: routerShape,
  history: PropTypes.object.isRequired,
  // Nested children receive the route as context, either
  // set by the route component using the RouteContext mixin
  // or by some other ancestor.
  route: PropTypes.object
}
```

> 常见问题说明

如果被包裹的组件，props 中未获取到 `router、route` 等属性，此时检查父组件，查看是否未将prop 传递。

```js
@withRouter
class MyRouterCom extends Component {
  componentDidMount() {
    // 此处 route 如果未定义，请检查父组件是否将 props 传递
    const { router, route } = this.props;
    router.setRouteLeaveHook(route, this.routerWillLeave);
  }

  routerWillLeave = () => {
    window.confirm('要离开此页面吗？\n\n 系统可能不会保存您所做的更改。');
  }

  render(){
    return <div></div>;
  }
}

class ParentCom extends Component {
  render(){
    return <MyRouterCom {...this.props} />
  }
}
```

### loadingConnect

通用过渡

```js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Connect, Button, message, JsonView, Spin } from 'amos-framework';

const loadingConnect = Connect.loadingConnect;
const mockAPI = 'https://randomuser.me/api';

const getData = params => {
  const url = params ? `${mockAPI}?${params}` : `${mockAPI}?results=5&inc=name,email,nat&noinfo`;
  return fetch(url).then(res => res.json());
};

const getDataErr = params => {
  const url = params ? `${mockAPI}?${params}` : `${mockAPI}?results=5&inc=name,email,nat&noinfo`;
  return fetch(url).then(res => res.json()).then(d => {
    throw Error('我是主动触发的错误！');
  });
};

const style = {
  height: '20em'
};

class Loader extends Component {

  constructor(props) {
    super(props);
    this.state = {
      dataSource: null
    };
  }

  reload = () => {
    this.props.fetch(getData, 'results=2&inc=name,phone,nat&noinfo', data => {
      this.setState({
        dataSource: data
      });
    }, err => {
      message.danger('失败！');
    });
  }

  errorAction = () => {
    this.props.fetch(getDataErr, 'results=2&inc=name,phone,nat&noinfo', data => {
      this.setState({
        dataSource: data
      });
    }, err => {
      message.danger(err.message);
    });
  }

  render() {
    return (
      <div>
        <Button onClick={this.reload}>获取数据</Button>
        <Button onClick={this.errorAction}>失败示例</Button>
        <JsonView tag="pre" style={style} value={this.state.dataSource} />
      </div>
    );
  }
}

Loader.propTypes = {
  fetch: PropTypes.func
};

export const DefaultLoader = loadingConnect({
  spinnerProps: {
    size: 'lg'
  }
})(Loader);

export const CusLoader = loadingConnect({
  spinner: Spin,
  spinnerProps: {
    type: 'scale',
    color: '#163cff'
  }
})(Loader);
```

#### loadingConnect HOC 参数说明

```js
const options = {
  spinner: PropTypes.element, // 用于设置自定义的加载符, ReactElement
  spinnerProps: PropTypes.object, // 加载符组件的属性
  renderIndicator: `(loading) => ReactNode` // NestedSpin 自定义的 indicator
};
```

当 未设置 spinner 时，采用默认的 `NestedSpin` 作为加载符，具体的参数，可到 [spinner](/framework/spinner#NestedSpin) 查看, 可以通过 `renderIndicator` 设置自定义的 `indicator`。

`indicator` 参数也也可以通过 `spinnerProps` 进行传递，但是，不进行 `loading` 控制。

#### loadingConnect 增强的 props 说明

`this.props.fetch` 参数说明

`fetch(api, args, onSuccess, onError)`:

* `api`: api 返回值必须是一个 Promise
* `args`: api 请求参数
* `onSuccess`: 请求成功回调，此处可以用于设置数据
* `onError`: 失败回调，此处用于提示失败信息

### enhanceClickOutside

点击 outer 区域

### dbclickConnect

双击事件hoc，当单击与双击同时存在时，只触发双击

```js
@dbclickConnect({
  delay: 300 // 双击时间间隔，默认 300ms
})
```

### enhanceRender

高级 `render`，主要用于 `visible` 控制组件显隐时，可自由控制，是否执行初始化加载。

内置 `asyncLoad`，用于初始化加载数据，被包裹组件，需要实现该方法

```js
@enhanceRender
class MyComponent extends Component {
  constructor(props){
    super(props);
    this.state = {
      dataSource: null
    };
  }

  /**
   * 初始化加载数据
   * 当 props.visible 为false时，不会加载数据，当为 true 时，自动加载数据
   */
  asyncLoad(){
    loadInfoAction().then(data => {
      this.setState({
        dataSource: data
      });
    })
  }
}

MyComponent.propTypes = {
  // 控制组件的显影
  visible: PropTypes.bool
};
```
