# Cyra

## 1. 如何开始

```js
// main.js

import { Cyra } from 'cyra';

import home from './view/home';

Cyra.initApp({
    root: 'body',    // 项目跟容器
    default: 'home'  // 默认视图
    routes: {        // 视图路由设置
        home: home
    }
});


// home.js

import { Page } from 'cyra';

class HomePage extends Page {

    // 定义了该函数后可以在进入视图时候改变页面title
    title () {
        return 'HOME-TITLE';
    }

    // 以下函数如若未做任何事，可以省略不写
    initialize (next) {
        next();
    }

    willAppear (next) {
        this.container.innerHTML = 'Hello World';
        next();
    }

    didAppear (next) {
        next();
    }

    willDisappear (next) {
        next();
    }
}

export default HomePage;
```

## 2. 基础思想

Cyra 设计三个简单的概念 **Page**， **Route** 和 **AppStorage** 来帮助开发者处理复杂的视图切换逻辑、数据传递及视图生命周期管理。

在移动端 SPA 架构中，有以下几个重要的模块：

### 2.2 视图导航

所有导航动作被封装成在 Page.switchRoute 中，你可以在该方法中传递参数。

使用 Cyra 的单页应用可以在任何情况下刷新，Cyra 的框架设计会帮助你处理任何复杂的产品逻辑。


### 2.3 视图管理及内容布局

Cyra 会管理所有视图的生命周期，你可以轻松获悉每一个视图的执行阶段，并针对阶段进行操作。

Cyra 为每一个视图提供一个容器，用户可以在这个容器内自由发挥。

### 2.4 视图之间数据交互

数据传递在使用 Cyra 后会变得无比简单，我们提供 URL 传递及**弱数据**传递, 我们会在后面详细介绍。

### 2.5 服务器数据交互

关于服务器数据交互我们什么都不会做，完全由用户自由发挥。

### 2.6 兼容性

在 Cyra 的设计中，我们考虑了最大的兼容性，您可以通过配置使用 **History API** + **多页降级** 作为路由方式，也可以使用 **Hash** 作为路由方式。


## 3. 视图生命周期

### 3.1 周期图说明

1. 橙色的 entering，leaving 代表视图两大过程， 进入和离开。

2. 最左边的 disappeared，appeared 代表两大状态，显示和隐藏。

3. 其中 initialize, willAppear, didAppear, willDisappear 是用户可以自定义的钩子函数。

4. appearing,disappearing是系统内部的函数过程，主要控制视图的隐藏和展现。如果想要在视图展现和隐藏时添加动画，可改写这两个方法的默认控制，请参见的转场动画。

### 3.2 生命周期

1. 当 url 的 view 部分值（默认路径为 initApp 中传入的 default）匹配到相关的route，其对应的视图开始执行 entering 过程。

2. 视图第一次执行 entering 过程时，依次执行 initialize, willAppear, (appearing), didAppear,若未定义则跳过，若在这其中某一步未调用 next() 也会中断执行。

3. 当视图第二次执行 entering 过程，默认不再执行 initialize, willAppear，如若更改该行为请参见的如何控制重新加载。

5. 当一个页面开始执行 entering 过程，则上一个页面开始执行 leaving 过程，即 willDisappear(disappearing)。



## 4. 使用指导

* 提倡组件层面抽象，减少视图抽象。

* 将视图渲染、数据获取等操作放到视图中最合适的阶段中。

* 强数据传递尽量传递查询参数以保持视图独立，并保证 URL 不会太长。

* 弱数据传递需要做好足够的判断，以保证逻辑的健壮。



# API 文档

## 1. Cyra

#### Cyra.initApp (obj: InitAppObject): void

初始化 Cyra 环境，参数配置如下。

`index: string (required)` 指定默认路由

`root: string (required)` 容器 DOM id

`routes: RouteData (required)` 路由信息

`mode: string (optional)` 路由模式，默认为`'history'`（会自动降级为`'multipage'`），可选`'hash'`

`appRoot: string (optional)` 配合 History API 方式设置的项目虚拟目录，如`'/resource/wa/oneyuan/'`

`paramPrefix: string (optional)` 强数据在 URL 中的前缀，默认为`''`

`alwaysReload: boolean (optional)` 强制每次进入视图刷新，默认为`false`

`dataSplit: (optional)` 'hash' 模式下强数据传参格式

`copyIndex: string (optional)` copyIndex 在 URL 中的前缀，默认为`cpx`

`animation: boolean (optional)` 是否开启视图切换动画，默认为`false`

`animationPrefix: string (optional)` 视图切换动画所加载 CSS class 的前缀，默认为`''`

`animationFunc: string (optional)` 视图切换动画的`timing-function`，默认为`''`

`switchDuration: number (optional)` 视图切换动画持续时间，默认`320`毫秒

#### router: Router

获取 Cyra 上的 router 实例


## 2. Page

和 Page 相关的主要是一个视图的生命周期函数，以及一些简单的 helper 用来设置页面环境。

### 2.1 钩子函数

#### initialize (next: Function): void

视图所属容器显示之前（`display: none`）调用，需要执行`next()`进入下一个生命周期。

#### willAppear (next: Function): void

功能同`initialize`，遗留问题，建议视图展示之前的业务逻辑都写在`willAppear`中，和业务相关的独立逻辑建议拆分成 Page 的实例方法，而不要分散在`initialize`和`willAppear`中。

#### didAppear (next: Function): void

视图所属容器显示之后（`display: block`）调用。

#### beforeEntering (next: Function): void

`initialize`和`willAppear`在 Page 的`shouldReload()`返回`false`时不会再次执行，也就是第二次进入相同视图时不会重复执行，如果需要在页面展示前，无论`shouldReload()`返回结果如何都要执行，可以将代码写在下面的钩子函数中：

### 2.2 helper 函数

#### title (): string

用来设置视图的 title

#### shouldReload (previousPath: string): boolean

用来指定视图第二次之后进入时，是否执行`initialize`和`willAppear`两个钩子函数。

#### switchRoute (path: string, data?: any, copyIndex?: number): void

指向`Cyra.router.switchRoute`，具体用法见对应词条。

#### popAndSwitchRoute (popValue: number, path: string, data?: any, copyIndex?: number): void

指向`Cyra.router.popAndSwitchRoute`，具体用法见对应词条。

#### reload (): void

重新加载当前视图，并且会执行`initialize`和`willAppear`两个钩子函数。

#### prepareForSwitch (next: Function, toPath: string, destinationPagePerform: Function): void

在视图执行`switchRoute()`跳转前执行，弱数据实现方式，可以通过`destinationPagePerform`执行目标视图中的方法，`toPath`指示下一个视图的`path`。执行`next()`来完成跳转，否则停留在当前视图。

#### beforeLeaving (next: Function, cancel: Function, toPath: string): void

离开视图时执行，`toPath`指示即将进入的视图，执行`next()`进入下一视图，执行`cancel()`取消此次跳转。

### 2.3 helper 属性

#### context: ContextType

指向`Cyra.router.context`，详见对应词条。


## 3. Router

Router 主要负责页面间的跳转，初始化 Cyra 后，可以通过 Cyra.router 来完成视图见跳转相关操作。

#### switchRoute (path: string, data?: any, copyIndex?: number, isShadow: boolean = false): void

执行视图跳转，`path`为即将进入的视图的`path`，`data`为传递的强数据，`copyIndex`指示 Mix 页的索引。为了方便，在 Page 中添加同名 helper，这样在视图中，只要执行`this.switchRoute`即可完成跳转，在非视图代码中，还是可以通过`Cyra.router.switchRoute`进行跳转。`isShadow`表示此次跳转是否使用`replace`模式，也就是用新的视图提前当前视图，从而将当前视图从 WebView 中清除（new in v2.1.2）。

**注意：**`isShadow`参数作用和`popAndSwitchRoute`作用类似，都是从 WebView 历史中清除1项或若干项，然后执行跳转；但`popAndSwitchRoute`不适用于项目第1个视图的此类跳转，因为先返回会使得退出当前项目，所以需要使用`replace`（对应 History API 中的`replaceState`和多页/Hash 中的`window.location.replace`）来替换当前历史。但`isShadow`无法清除多条历史记录，而后者可以。

#### popAndSwitchRoute (popValue: number, path: string, data?: any, copyIndex?: number): void

功能类似`switchRoute`，但会在跳转前先后退`popValue`个页面。

#### context: ContextType

当前程序中的环境变量信息。

* `rootContainer: HTMLElement`：Cyra 根容器 DOM id；
* `useSwitch: boolean`：指示当前一次跳转是否为执行`switchRoute`跳转；
* `currentRoute: Route`：当前视图对应的`Route`对象；
* `currentPage: Page`：当前视图所属的`Page`对象；
* `currentContainer: HTMLElement`：当前展示视图的 DOM 容器；
* `previousPage: Page`：上一视图所属的`Page`对象。

## 4. 视图切换动画

视图间切换动画需要按照如下步骤：

1. 在`Cyra.initApp`中设置`animation`为`true`
2. 可添加以下可选参数进行动画配置，动画基于`CSS Animation`：
  * `switchDuration: number`：指示动画切换持续时间，单位`ms`，默认`320`
  * `animationPrefix: string`：自定义动画`keyframes`前缀，默认`''`
  * `animationFunc: string`：自定义动画`timing-function`，默认为`''`
3. 在页面中添加以下4个 CSS keyframes：
  * `left-move-in`：视图从屏幕左侧进入并显示的动画
  * `left-move-out`：视图向屏幕左侧滑出并隐藏的动画
  * `right-move-in`：视图从屏幕右侧进入并显示的动画
  * `right-move-out`：视图向屏幕右侧滑出并隐藏的动画
4. 为了方便，给出一个示例：

```html
<style>
@keyframes left-move-in {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0);
  }
}

@keyframes left-move-out {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-100%);
  }
}

@keyframes right-move-in {
  from {
    transform: translateX(100%);
  }
  to {
    transform: translateX(0);
  }
}

@keyframes right-move-out {
  from {
    transform: translateX(0);
  }
  99% {
    transform: translateX(100%);
  }
  to {
    transform: translateX(-100%);
  }
}
</style>
```

**注意：**`right-move-out`比较重要，视图向右侧滑出时，需要在动画完成之前全部滑出，并在最后将视图重新置在屏幕可视区域左侧隐藏。

## 5. Nginx 配置

当采用`History API`路由模式时，也就是`mode`设置为`'history'`或`''`时，Nginx 需要做相应的配置，以保证直接访问视图的 URL 时可以正常找到 HTML 资源并正确路由。具体配置如下：

```nginx
# match img resource
location ~* ^/resource/wa/([\w-.]+)/([\w-.]+)(/.*)?$ {
 root /resource_static/;
 try_files /resource/$1/$2/$3 /resource/$1/$2 /resource/$1/$3 /resource/$1/$2.html =404;
}
```

其中`/resource/wa/`和`Cyra.initApp`中传入的`appRoot`属性对应。
