<Page>
  <h1>5 minutes through Roof</h1>
  **版本: 0.4.x**

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

  <h2>1. 名词</h2>
  <h3>1.1 数据</h3>

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

  <h3>1.2 Node</h3>

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

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

var Will = new User({name:'Will',familyName:'Smith'})
`}</Code>

  <h3>1.3 Nodes</h3>

  <p>功能与 Node 相同，区别是 Nodes 是用来定义集合数据的。  </p>

  <h3>1.4 事件</h3>

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

  <h3>1.5 监听器</h3>

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

  <h3>1.6 Roof rootContainer</h3>

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

  <h3>1.7 Roof container</h3>

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

  <h2>2. 演示代码功能</h2>

  <p>roof-react-router-demo 中演示的主要功能有:
</p>

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

  <h2>3. Here We Go</h2>
  <h3>3.1 目录结构</h3>
<p>项目是通过 antd-bin 生成的，目录结构与生成时相同，除了不要改动 `/src` 外和 `src/entry/index.jsx` 的位置以外，其他都可以随意改。
</p>

<Code type='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
  `}</Code>

  <h3>3.2 路由设置</h3>

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


<Code type='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>
`}</Code>


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

  <h3>3.3 创建 rootContainer</h3>

  <p>
    rootContainer 可以隔离两个页面的数据，并且在页面切换时自动销毁掉被切换走的页面上的数据，保障不会内存泄露。

    跟着 import 里所写来打开 `/src/component/UsersAndBlogs.jsx` 这个组件，这个组件对应着 路由 `/users_and_blogs`。

    它的写法和 react 组件完全一致，所有 react 的 api 和 callback 都可以使用。Roof 只是读取其中的 `data` 和 `events` 对象来初始化数据和事件。注意，`render` 方法中的 `{this.props.children}` 是 react-router 要求的。
  </p>

  <Code type='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>
  }

});
`}</Code>

  <h3>3.4 定义数据</h3>

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




  <Code type='javascript'>{`
const User = Node.createClass({
  getFullName : function(){
    return
  }
})

export const Users = Nodes.createClass({$factory:User})
`}</Code>

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


  <Code type='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)
  })
}
`}</Code>

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

  <h3>3.5 使用数据</h3>

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



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

  <p>

    `cursors` 字段用来指定要从 rootContainer 中获取哪个数据，它的键值含义分别是

    数据用什么名字传给当前组件 : 数据在 rootContainer 中叫什么名字


    获取到的数据就是上节中 Promise 里返回的 `new Users`。具体的数据 api 请参考 [roof-node文档](http://site.alipay.im/roof/roof-book/node.html)
  </p>

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

    在跟着 rootContainer 中的配置找到定义监听器的文件 `/src/events/user.js`：</p>





  <Code type='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'
      }])
    }
  }
}
`}</Code>

  <p>
    Roof 要求它返回一个函数，返回的执行结果是一个键值对，键名就是需要监听的事件名。这里的代码是 `user.random`，与我们刚才触发的事件一致。值就是监听器，监听器可以是函数也可以是一个对象(参考`/src/events/guide.js`文件)，当我们要使用例如**指定执行顺序**等高级功能时就使用对象。

    代码中的监听器里，通过 `galaxy.get` 方法可以拿到当前 rootContainer 下的所有数据。直接操作相应数据，订阅了该数据的组件就会自动刷新。

    事件及监听器的其他用法请参考 [roof-bus文档](http://site.alipay.im/roof/roof-book/yingyongshijian/README.html`)。</p>

</Page>









