5 minutes through Roof

**版本: 0.4.x**

这篇文档主要通过讲解 [roof-react-router-demo](http://gitlab.alipay-inc.com/roof/roof-react-router-demo) 的代码来介绍 **0.4.x** 版 Roof 的用法。建议先 clone 代码仓库。

1. 名词

1.1 数据

一般指的是页面上的整个应用共享的数据,区别于组件内部的 state 。这些数据都是通过 Node 或者 Nodes 的子类实例化出来的。

1.2 Node

Roof 提供的用来包装单个数据的基类。一般先使用 `Node.createClass` 来创建自己的子类,在使用 `new` 将关键字实例化子类,例如:

javascript
//创建子类一般都是为了定制自己的 api var User = Node.createClass({ getFullName : function(){ return this.get('name') + this.get('familyName') } }) var Will = new User({name:'Will',familyName:'Smith'})

1.3 Nodes

功能与 Node 相同,区别是 Nodes 是用来定义集合数据的。

1.4 事件

这里指的事件一般不是页面上的交互事件,例如 `click`。而是 Roof 提供的一个事件类,专门用来触发和处理应用逻辑的,如 `user.login`。它有基本的 `fire`(触发) 和 `on`(函数)。还有一些高级功能,例如 `指定监听同一事件的监听的触发顺序`。

1.5 监听器

即 Roof 提供的事件类的“监听函数”,因为它本质上实际上是一个对象,除了带有监听函数外,还有顺序等信息,所以这里统一称之为监听器。

1.6 Roof rootContainer

通过调用 `Roof.createRootContainer` 创建出来的组件,本质上是一个 react 组件。rootContainer 用来定义当前这个组件内部使用的所有数据和事件。不同的 rootContainer 之间的数据和事件不共享。

1.7 Roof container

通过调用 `Roof.createContainer` 创建出来的组件,本质上也是一个 react 组件。它只能存在 rootContainer 的内部,它最主要的功能是通过 `cursors` 字段来获取 rootContainer 中声明的数据。

2. 演示代码功能

roof-react-router-demo 中演示的主要功能有:

  • 多个 Roof rootContainer 共存,通过 react-router 切换。rootContainer 都有各自的数据和事件定义。
  • 数据的定义和使用
  • 数据初始化时如何异步取数据
  • 事件及监听器的定义和使用
  • 如何在监听器中操作当前 rootContainer 下的所有数据

3. Here We Go

3.1 目录结构

项目是通过 antd-bin 生成的,目录结构与生成时相同,除了不要改动 `/src` 外和 `src/entry/index.jsx` 的位置以外,其他都可以随意改。

javascript
├── index.html ├── package.json └── src ├── common │   └── lib.js ├── component //所有用到的组件 │   ├── About.jsx │   ├── App.jsx │   ├── Blogs.jsx │   ├── IntroAndAbout.jsx │   ├── NoMatch.jsx │   ├── User.jsx │   ├── Users.jsx │   └── UsersAndBlogs.jsx ├── data //所有用到数据 │   ├── About.js │   ├── Blogs.js │   ├── Intro.js │   └── Users.js ├── entry │   └── index.jsx └── events //所有用到的事件 ├── guide.js └── user.js

3.2 路由设置

打开 `/src/entry/index.jsx` 里面可以看到,`react-router` 的使用方法与官方一致。请参阅[官方文档](https://github.com/rackt/react-router)

javascript
<Router> <Route path="/" component={App}> <Route path="intro_and_about" component={IntroAndAbout}/> <Route path="users_and_blogs" component={UsersAndBlogs}> <Route path="/users_and_blogs/:id" component={User}/> </Route> <Route path="*" component={NoMatch}/> </Route> </Router>

这个项目中我们使用了是三个路由,可以看做是两个大页面,`/users_and_blogs` 和 `intro_and_about`。其中`/users_and_blogs`页面上还有个子路由`/users_and_blogs/:id`。

3.3 创建 rootContainer

rootContainer 可以隔离两个页面的数据,并且在页面切换时自动销毁掉被切换走的页面上的数据,保障不会内存泄露。 跟着 import 里所写来打开 `/src/component/UsersAndBlogs.jsx` 这个组件,这个组件对应着 路由 `/users_and_blogs`。 它的写法和 react 组件完全一致,所有 react 的 api 和 callback 都可以使用。Roof 只是读取其中的 `data` 和 `events` 对象来初始化数据和事件。注意,`render` 方法中的 `` 是 react-router 要求的。

javascript
const UsersAndBlogs = Roof.createRootContainer({ data : { users : UsersData, blogs : BlogsData, }, events : { user : UserEvents, guard: UserGuard }, render(){ return <div> <h1>Users:</h1> <Users /> <h1>Blogs:</h1> <Blogs /> {this.props.children} </div> } });

3.4 定义数据

循着 `/src/component/UsersAndBlogs.jsx` 中数据的定义打开 `/src/data/Users.js` 文件来看数据怎么定义。

javascript
const User = Node.createClass({ getFullName : function(){ return } }) export const Users = Nodes.createClass({$factory:User})

首先注意到代码中分别用 `Nodes` 和 `Node` 创建了两个子类 `User` 和 `Users`。其中 `Users` 是个集合,它通过声明 `$factory:User` 可以使它其中的每一个数据都是一个 `User` 的实例。

javascript
export default function mimicUsers(){ return new Promise(( resolve, reject )=>{ setTimeout(()=>{ var success = Math.random() > 0.5 if( success ){ resolve(new Users([{ name:'Jason', familyName:'Williams', id:1 },{ name:'Fill', familyName : 'Mill', id:2 }] )) }else{ reject('manual failure') } },500) }) }

然后看到整个文件最后往外 export 除了一个函数。Roof 需要开发者在这个函数中返回实例化的 Node 或 Nodes 子类,及 `new Users` 或者 `new User` 对象。如果你的数据需要在初始化时就异步填充数据,那么返回一个 resolve `new Users` 或者 `new User` 的 Promise 也可以。如果有错误要处理,那么 reject,页面上组件可以通过 Roof 收到错误消息。

3.5 使用数据

刚才已经提到 rootContainer 中只负责定义数据,使用数据要用 Roof container。我们打开 `/src/component/Users.jsx`。这个组件就是通过 `Roof.createContainer` 创建的。 先看 `cursors`,`renderBeforeReady`,`renderWhenError`这三个和数据有关的字段:

javascript
cursors : { users : 'users' }, renderBeforeReady(){ return <div>loading users...</div> }, renderWhenError(){ return <div>initialize data failed</div> },

`cursors` 字段用来指定要从 rootContainer 中获取哪个数据,它的键值含义分别是 数据用什么名字传给当前组件 : 数据在 rootContainer 中叫什么名字 获取到的数据就是上节中 Promise 里返回的 `new Users`。具体的数据 api 请参考 [roof-node文档](http://site.alipay.im/roof/roof-book/node.html)

3.6 使用事件及监听器

在同一个而文件里面可以看到,`render` 函数中 button 上绑定了一个 `randomUsers` 方法,在方法中使用 `this.bus.fire('user.random')` 触发了一个事件。其中 `this.bus` 就是当前 rootContainer 下所有子 container 共享的事件总线。注意,它是不会默认传到使用 `React.createClass` 创建的普通组件中的,所以如果子组件中出现了**普通组件嵌套container**的情况,里层的container就会收不到 `bus` 实例。创建组件时与业务相关的组件都应使用 `Roof.createContainer` 创建,这样一般就容易出现这种情况。 在跟着 rootContainer 中的配置找到定义监听器的文件 `/src/events/user.js`:

javascript
export default function( galaxy ){ return { 'user.random' : function(){ var users = galaxy.get('users') users.replace([{ id:3, name:'Steve', familyName:'Nash' },{ id : 4, name : 'Mike', familyName : 'Jordon' }]) } } }

Roof 要求它返回一个函数,返回的执行结果是一个键值对,键名就是需要监听的事件名。这里的代码是 `user.random`,与我们刚才触发的事件一致。值就是监听器,监听器可以是函数也可以是一个对象(参考`/src/events/guide.js`文件),当我们要使用例如**指定执行顺序**等高级功能时就使用对象。 代码中的监听器里,通过 `galaxy.get` 方法可以拿到当前 rootContainer 下的所有数据。直接操作相应数据,订阅了该数据的组件就会自动刷新。 事件及监听器的其他用法请参考 [roof-bus文档](http://site.alipay.im/roof/roof-book/yingyongshijian/README.html`)。