使用 React 的一些经验

文章有些过期, 更多消息请往导航:
http://nav.react-china.orghtml

在公司用 React 写界面的已经有一段时间了, 有些习惯能够沉淀一下
目前代码当中主要仍是我我的的使用习惯, 后面应该会改善
最近进行的一个页面是参照 Flux 架构作的, 只能说尝试 Flux 还太浅了
后面我会大体罗列一些点, 用 React 的同窗能够对比下或者指点一些问题react

使用 CoffeeScript 写 JSX

我对缩进语法的认同以及对重复写打开关闭符号的反感不解释了
Teambition 先后端代码大部分是 CoffeeScript, 因此我不用有太多的顾虑
JSX 当中的语法, 好比是这样:git

<div class="avatar" data-tip="name">{text}</div>

编译的 JavaScript 是这样:github

React.createElement("div", {class: "avatar", 'data-tip': "name"}, text)

用 CoffeeScript 目前是写成这样:编程

# $ = React.DOM
$.div className: 'avatar', 'data-tip': 'name', text

createElement0.12 出现的, 目前依然兼容 React.DOM 当中的方法调用
长期看也许会修改适应新的语法, 但目前暂时不动:canvas

# $ = React.createElement
$ 'div', className: 'avatar', 'data-tip': 'name', text

0.12 中调整增长了 createFactory, 因此实际项目当中处理并不优雅
目前导出模块时会有不少这样的代码, 将来模块化时是有必要调整的:后端

React.createFactory React.createClass
  displayName: 'app'
  render: ->
    $.div {}

使用 CoffeeScript 写逻辑

由于 JSX 编译结果当中一个标签或者模块就是调用函数返回对象
因此在 CoffeeScript 当中直接把标签看成普通数据来处理就行了
undefined 这样的数值, 在 React 当中自动忽略, 比较方便
因此常常会有相似模版引擎的用法:react-router

$.div className: 'demo',
  if @state.showMenu
    $.div className: 'menu', 'demo of menu'

实践当中, 因为有些位置业务逻辑复杂繁多, 一个 render 当中节点可能不少
经常使用的方案就是建立 renderMenu 这样的方法, 在 render 当中调用
个人理解是复杂的结构中 render 适合写逻辑判断, 其余方法写具体的模块
这样, 之后复制代码或者改逻辑, 在 render 一个方法里就看完了
map 是列表里经常使用的一个办法, 一般我也会拆出 renderItem 这样的方法架构

React 的 addons 里有个 classSet 方法, 对于拼接 className 很是有用
http://facebook.github.io/react/docs/class-name-manipulation.html
我最初是本身写公共函数拼接的, 后来切换到了这个方案并发

CoffeeScript 语法容易出错的地方

CoffeeScript 很简洁是一方面, 不过这个简洁确实会有一些容易出现问题的地方
好比这样两行, 第一行结尾的逗号, 在修改代码时常常容易错误删除:

$.div className: 'a',
  $.span {}

还有 Object 在 CoffeeScript 里的写法, 若是属性较多单行不够也容易出错:

# 这段代码示范的是一个错误
$.input className: 'a',
  onClick: @onClick
# 订正后若是用多行, 好比能够这样
$.input
  className: 'a'
  onClick: @onClick

另外 CoffeeScript 的 if 是只返回最后一个值的, 注意不要写这样的代码:

$.div {},
  if @state.showMenu
    # 这个例子里第一行不能正确返回的..
    $.span {}, "first line"
    $.span {}, "second line"

上边提到的 createFactory API 对于类库也有一些影响
若是从第三方导入 API, 就可能要考虑用上边的 API 进行封装
相对 JSX 来讲, 这里是一个不方便的地方

模块都是本身写的

公司当前项目当中几乎没有用到第三方的 Component
缘由大体理解有这么几个:

  • 一般界面都挺简单的, 不用费不少的事情就写出来了

  • 几乎全部的 UI, 设计界面有较明确的要求, 咱们要本身保持风格一致

  • React 没有看到很是流行的组件库

虽然追求模块化当初对代码重用的热情是很是高的, 但 React 并不自动解决问题
我想说, 使用 React 拆分模块的成本比 Backbone 拆分 View 低很是多
因而在个人应用中有着比 Backbone 稍微细的模块拆分, 也就稍微多了一点重用
可是, 注意重用的模块是不带逻辑的很是小的模块, 大的模块通常很难重用

按照咱们公司设计对界面的不断演进, 我注意到模块当中逻辑会缓缓增长
这不断给模块复用增长难度, 由于我须要加上配置项才能继续复用模块
按我最近的微博上的讨论, 我感到稍微大的模块, 就应该以不进行复用来设想
http://weibo.com/1651843872/BA2tIks5x

Modal

大量使用 Modal 组件我认为是对单页面应用界面的探索不够深刻形成的
当界面上须要更多的交互, 立刻想到不破坏初始的状态, 弹一层出来...
不过确实是干脆利落的办法, 结果是咱们有不少的 Modal 须要处理

以往用 Backbone 和 BootStrap, Modal 是附着在 <body> 之下的
在 React 当中, 涉及到 Modal 打开状态的问题, 我目前的处理是在模块内
也就是说, Modal 是深深嵌套, 而后用 fixed 定位打开的:

$.div {},
  $.div {},
    $.div {},
      $.div {},
        if @state.showModal
          $.div
            className: 'modal-demo', # style: {position: 'fixed'}
            onClose: @onClose
            $.span {}, 'content demo'

一样的方案, 也用在菜单上, 弹出菜单也须要一个状态来控制打开个关闭
不过菜单会涉及到一些定位的问题, 处理起来更加麻烦一些

关于菜单有个全局惟一菜单的问题, 就是一个菜单出现后要关闭其余菜单
首先, 点击菜单内部的元素不能关闭菜单, 须要截断事件冒泡
其次, 点击菜单外部须要失焦关闭菜单, 须要在 window 上进行监听
问题来了, React 假如建立好了菜单, 又监听到冒泡, 关闭了怎么办?
目前我控制了编辑按钮延时触发, 避免监听, 牺牲是打开菜单点击触发依然打开而不是关闭

我询问了下公司 Backbone 里的处理, 用到了对 event.target 父节点探测
可是在我目前的代码当中, 一个模块会被限制只获取尽量少的数据...
并且因为减小了 jQuery 的依赖, 意味着检查父节点不会那么容易
我尚未想到个清晰的方案, 须要继续探索

方法的分类, 命名的习惯

大多的 Component, 定义的结构和命名的习惯大体已经开始固定下来了
我注意到定义模块的分类还跟 Elm 有挺多的类似之处, 好比 Elm 是这样:
https://github.com/evancz/elm-todomvc/blob/master/Todo.elm

  • Model 初始状态

  • Update 对状态能够有哪些更新

  • View 模块组合跟渲染

  • Input 绑定模块和外部的交互

在我这边项目当中一般是这样的:

  • 标记 propTypes, 初始化 state, 绑定模块生命周期的 hook

  • 一些自定义的方法

  • 事件绑定

  • 渲染相关的方法

好吧... 这个其实也不那么像.. 并且一般 View 都是须要完成这几部分功能的
跟 Backbone 区别是, Backbone 极可能由于不方便拆模快, 方法不少甚至变乱
我估计还有监听数据更新操做 DOM 的方法太多, 以及处理其余功能的方法等等
React 里的方法和模块比较紧密, 所以也就明确了几个类别

关于命名, 自定义方法比较随便, 但会使用和模块 API 差异较大的名字
state 当中命名, 我目前统计下来以 show 开头为主, 用来控制界面
至于渲染, 天然是 render 开头的, 好比 renderName, renderActions
在事件当中, 尽可能都是 on 开头, 好比 onNameClick, onTextChange 之类
有一些事件是子模块的事件, 我也用的是 on 作前缀来处理
不过有的模块子模块不少, 方法也就有些乱, 这也须要想点办法改进

列表的节点改变顺序的渐变

界面是这样的, 一个列表, 会不停改变顺序, 过程当中使用 CSS 进行动画
须要注意, DOM 里的顺序改变, 节点删除和建立, CSS 动画就不生效了
因而就须要保持 DOM 顺序不变, 可是经过 absolute 定位控制节点
我采用的手法是使用 id 进行排序, 保证 DOM 的顺序不变:

data
.map (a) ->
  key: a.id
  data: a
.sort (wrap) ->
  wrap.id
.map (a, index) ->
  $.div style: {top: "#{index * 20}px"}

Ajax 代码设计的问题

React 教程给的 Demo, Ajax 没有很明确的介绍, 我也没深刻挖..
Ajax 拿到的数据, 要么设置在 props, 要么设置在 state
固然我考虑的是, 应用当中有 Store, 就尽可能用 Store 了, 方便复用
不过实际的应用当中, 高达 10 层的模块嵌套也可能存在, 并没那么自由

我以前一直操心 Ajax 代码放在哪, 是在 Component 方法内部?
仍是放在 Store 当中? 仍是独立存放在另外一个位置?
麻烦在于, Ajax 加载状态可能跟模块内部紧密相关..
同时, Ajax 代码写在模块当中, 重用的可能性又明显降低了
Ajax 大部分是能够引发 Store 更新的, 那么在 Store 里也许不错
但是, 不在 View 当中, View 就要用 action 通知 Ajax 了, 这很不方便
很差取舍... 当前牺牲的是模块的复用性...

与此相关的一个问题是模块被移除以后, 异步方法调用 setState 会报错
修补的办法是临时加上 isMounted 对模块进行检验...
不过这个方案让我有点困惑, 显然不是一个漂亮的能免于各类问题的方案..
也许异步调用尽可能放到模块之外是更好的办法, 但并不明朗..

强制使用 propTypes

我是写了不少模块后才开始接触到 propTypes 这个属性的:
http://facebook.github.io/react/docs/reusable-components.html
这个能够理解成函数来讲, 是参数的类型, 起到提示(console.warn)的做用
当模块参数多到必定程度, 也不太规则, 就很是须要定义好参数类型
一方面, 这个是文档, 另外一方面, 进行重构时是很是重要的提醒

全局的多语言的处理

咱们的单页面应用须要处理英文两种, 以及设置语言后当即在界面范围内更新
之前咱们模仿的是 Wunderlist, 用特性的属性配合 jQuery 来切换
可是在 React 当中, 全局刷新是很是轻松愉快的事情,
不须要写上属性给选择器调用, 也不会影响 DOM 状态
同时须要注意, 为了达到全局更新的效果, 根节点就须要监听语言的改变
我目前在作的方案是将语言抽象为全局的 Store, 做为数据交给模块渲染
而语言发生改变时, Store 立刻就发送事件, 界面天然就更新了

思惟的转换, 数据流

React 编写应用的方式和 Backbone 差异很大, 甚至和 MVVM 也有不小差异
我认为这种转变跟从过程式编程到函数式编程的转变很是类似
在 Backbone 用进行怎样的操做, 想的就是先作什么, 后作什么
甚至于发送怎样的事件, 让其余的模块来完成这一部分的功能
而在 React 当中, View 之间不是经过事件进行交流的, 而是 propsstate

而 React 的界面, 就像个不断渲染的 canvas, 你只能修改数据来改变界面
而不是界面是不少个有状态的 View, 每一个 View 都须要额外去照顾
因而就须要思考界面由几个状态控制, 用户操做怎样改变那几个状态等等
思考数据以怎样的途径流动, 每一个模块根据这一个数据流如何更新本身
想要简单, 就要尽量用简单的途径, 就是单向的数据流

不过比较可能的是, 咱们很难立刻用单向数据流解决到各类业务问题
而后须要一些手段, 按照 @kejunz 今天微博讲的:
http://weibo.com/1640297597/BBnTtnxWz

组件上位后,组件间通信问题又出来了,以前好像要么pub/sub这种模式,要么回调,数据老是带有业务逻辑色彩的,要和通用组件彻底隔离目前没有特别理想的方案。组件并发工做的构想很诱人,实现和通信都有待解决。CSP没细看过,感受是很酷的东西。期待有实践的人发文章分享。

React 在数据层面的没有给出相近的方案, 这方面有好多要探索的
一个趋势是不少函数式编程的概念会被借鉴过来, 特别是从 Clojure
CSP 最初是 Go 当中的.. 不畏惧艰难的话尝试一下 Clojure 当中的概念:
http://phuu.net/2014/08/31/csp-and-transducers.html

Router 模块不稳定

这是我这两天对付 react-router 遇到了问题, API 更新比较直接
我这会没跟上 API 更新后具体如何使用, 虽然对其功能并不质疑
固然在项目里语法这样的事情挺郁闷的

react-router 和 RequireJS 搭配问题也挺多的, 模块很特殊啊
代码使用 CommonJS 写的, 用 Browserify 打包, 并且须要全局要 React 对象
按我参与 Issue 上的记录, 事情并不乐观, 我只能尽可能考虑 CommonJS 的方案:
https://github.com/rackt/react-router/issues/171
固然将来还有 ES6 须要考虑, 我如今暂时先抛开模块的问题了

Backbone 和 React 混合的架构

在 Backbone View 销毁的时候, 须要调用一下下面的 API

React.unmountComponentAtNode

不然可能出现同时存在两个节点的状况, 在调试工具能够看到

Modal 原先在 Backbone 当中挂在 body, React 中没那么灵活
若是把 Modal 放在外部, 就要考虑额外的, 好比用事件去通知外部了
在 React 当中使用事件是让人不放心事情, 我考虑仍是放在嵌套的 DOM 内部

有些 Component 就是应该从 Store 取数据的

虽然一厢情愿但愿 Component 都是从 props 取数据, 实际上不合适
某些位置, 直接从 Store 的当中获取, 能比较轻易得简化的逻辑
特别明显的好比, 从位置 A 打开的 Modal B, 且 B 是比较复杂的
而 A 的界面, 可能很简单, 或许都不须要 Store 当中的数据
那么这时, B 直接访问 Store, 效果很是好. 我认为大部分的 Modal 都是这样的

这也就是说, 并非全部的 Component 都是适合模块化的
React 的 Component 写界面, 就是普普统统地会遇到各类数据传递的问题
有一部分数据, 特别是嵌入在简单 UI 当中的复杂 UI, 极可能须要很大的权限
以此为代价, B 上层的模块 A 就能够只传入更少的数据, 从而被简化

使用 state 即使数据很容易拿到

React Devtools 的 Chrome 插件, 很容易看到 Component 的 props 和 state
在设计 Component 的 state 和 props 时, 这是很好的一个参考
好比说, 哪些 UI 是很频繁须要确认 props 或者 state, 就拆为模块
这种状况下, 模块拆分就可以带来实际的用处, 就是方便查看状态

相似的状况下, 好比咱们用 Store, 而数据能够很容易从 Store 拿到
一种可能但很差的作法是, 监听更新, 而后调用 .forceUpdate() 更新
把这个数据放在 state 会是一个更好的作法, 能够避免手动调用强制更新
同时, 经过调试工具查看模块当前的 state 就很方便了
方便理解看成一个原则来控制 Component 的粒度应该仍是不错的

相关文章
相关标签/搜索