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`)。