前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >不一样的React组件化

不一样的React组件化

作者头像
腾讯IVWEB团队
发布2020-06-29 11:58:18
8220
发布2020-06-29 11:58:18
举报

不一样的React组件化

我们做了什么?

  • 反向依赖
  • 无props化
  • 无actionType化
  • 禁止依赖检查

说到React的组件化,可能许多人第一印象就是写一个React.Component,再简单不过。我也问过一部分同学,说:

我正在做React组件化,你知道React怎么组件化么?

他们很惊讶:React天生不就是为组件化的么?组件可以定义propsstate,状态改变了引发组件的重绘,组件之间并不影响。

我说好,那现在如果有一个组件,我从这个工程拷出来,粘贴到另一个工程,然后代码跑不起来了。原因是这个组件需要一个list属性,它包含一个某种数据结构的列表,那种数据结构也未知,总之这个组件迁移过后各种报错和undefined!怎么解决?

“组件只是视图层,至于数据层面,需要自顶向下下发,这个list数据,应该是要发一个ajax去获取吧?”

“嗯对,你提到一个概念,自顶向下,为什么要自顶向下呢?如果底层任意一个组件有改动,最顶层的组件也要改动罗?”

“差不多是这样的。”

“嗯,那我要做的就是解决这个问题。”

回到“我们做了什么”的问题上来,我们所有的探索,都是为了减少组件迁移带来的额外工作量,进而让页面由组件拼接这种开发模式成为可能,再进而,我们会做一个平台,拖拖拽拽出一个React工程(注意我没有说“一个页面”)成为可能。

围绕这个目标,借鉴已有的前端GUI开发经验(多数并不是来自于React方面的实践),我们做的工作包括:反向依赖、状态隔离、无actionType化、禁止依赖检查。

反向依赖

在一般的React实践中,视图层和数据层的依赖都是正向的。视图层的正向依赖可以举例为:组件B是组件A的子元素,那么需要再组件A中显示声明组件B的存在。

代码语言:javascript
复制
import B from './B'
//...
render(){
    return <div>
        <B />
    </div>
}

数据层的正向依赖可以举例为:子组件B的数据需要父组件A自顶向下数据发放。

代码语言:javascript
复制
import B from './B'
//...
render(){
    return <div>
        <B list={this.props.list}/>
    </div>
}

在我们的探索中,发现视图层的正向依赖很浅,依赖变动带来的工作量小,是可以接受的。例如,组件B被删除了,页面不需要显示组件B的内容,如果组件A不做改动,代码肯定是报错的,因为找不到B。我们直接将import B from './B'等删除即可,并不会带来太多的工作量。

但是数据层的正向依赖往往很深。例如,组件B被拷贝到了其他工程里,迁移过后各种报错、各种undefined。我们只能去找这个组件B需要哪些props字段,然后从顶层下发给它,如果没有所需的数据,还得单独拉一个ajax请求去获取组件B需要的数据。有时候我们会用React的connect方法直接注入,但组件多了,会偶现connect注入的属性重名的情况,一片凌乱。

所以,一定得将数据层的正向依赖关系拆开。

解决办法是将正向依赖反过来。由父组件A声明某种服务接口,然后子组件B按需依赖父组件B的服务接口。这种设计模式有些地方也叫做“依赖注入(dependence inject)”。

代码语言:javascript
复制
// 伪代码高能注意!!!
B.require('A', 'data_list', (list)=>{
    // .....
})

无props化

光有反向依赖还不够,对于组件B,它可能依赖于一个ajax接口,但是整个页面可能只有组件B会用到这个ajax接口。如果组件B从工程中移走,就一定势必剩下一部分和B相关的代码保留在工程中。且如果B组件仍然需要上级的某些属性传入,组件迁移后还是不能直接使用。

我们希望一个组件能够向插件一样被拔下来,同时能够在另一个工程里即插即用。要达到这一点,诸如以下的写法是不提倡的:

代码语言:javascript
复制
return <div>
    <B name={this.props.name}>
</div>

因为如果组件B迁移了,开发者很可能不知道this.props.name怎么取值。所以,我们希望将一定粒度的组件无props化,去除组件与上层,同级组件的任何依赖,在render函数中只有添加一个tag标签就可以使用:

代码语言:javascript
复制
return <div>
    <B />
</div>

我们只希望用三个标签来完成,不带任何props:

代码语言:javascript
复制
return <div>
    <Logo />
    <Tabs />
    <Content />
</div>

有些人可能会说,这还不简单么?只要用redux的connect方法封装一下,就不用给这个jsx标签添加props了。其实这样做表面上是没有props了,实际上,组件的数据仍然来自于顶层,依赖同样存在,组件迁移后仍然不可用。我们要做到的是:即没有自顶向下的数据依赖,又没有标签上的props传入。

我们的做法也很简单,既然不带任何props,上面例子中,Content中明显是一个列表,如果顶层没有将列表传入,就必须保证Content组件自己持有这个列表。对于列表的所有操作都封装在这个Content组件中,保持数据的独立性。这就是我们达到的“无props化”。这个做法最初也叫做状态隔离,后来我觉得“没有props”这样的表述一听就懂,就换成了“无props化”

无actionType化

这一点是针对redux来说的。但凡使用React的工程,都会选择一个状态管理工具。Redux使用者较多,我们也是其中一员。Redux中使用action和reducer的概念进行事件分发和数据组装。对于开发同学来说,这个操作过于繁琐(不是复杂)了。例如,我们会创建若干看起来一模一样的action.js,其中写了无数看起来一模一样的action_type。然后创建对应的看起来差不多的reducer.js,引用action.js中的那个常量actiontype。这波操作从我最开始接触reducer的时候就觉得过于恶心。没错,保持事件分发有助于解耦,但是action和reducer的写法过于冗余,代码可读性缺失,一眼望去全是看起来一模一样的常量名。于是在这次组件化进程中我们简化了这种写法,抽象出来一个叫做fk-action-type的工具,最后我们的action-reducer语法变成了这样:

代码语言:javascript
复制
store.listen('fetch', {
    action: name => params => cgiAction(name, {
        url: params.url,
        data: params.data,
    }),
    reducer: name => ({
        [name+'_success']: (state, action) => {
            return newState;
        }
    })
})

如果一切都是透传,那么可以省略对应的action和reducer:

代码语言:javascript
复制
store.listen('setTab', {
    reducer: plainReducer((state, action)=>{
        return Object.assign({}, state, {tabIndex: action.data})
    })
})

以往一百行的代码,现在30行就能写完。

禁止依赖检查

在我们的组件化中,依赖并不是直接引用的。会存在一个完全解耦的依赖声明。例如,组件B依赖组件A拉取ajax数据后的返回结果,会这样声明:

代码语言:javascript
复制
storeB.listenOther('A.fetch_success', (action)=>{
    // do something with action dispatched by A
})

如果组件B被迁移到其他工程,以上代码可能跟着迁移过去。如果其他工程没有A,或者有A但是没有fetch_success事件,虽然这段代码不会引起任何错误,但我们希望这些无用的依赖声明越少越好,保持开发者良好的组件迁移习惯,记得删除无用的依赖声明很重要。要做到这一点其实也很容易,我们是可以通过静态的依赖检查给与开发者提示,“无用的监听B:当前工程并没有A”,或者“无用的监听B:A并不会发出fetch_success事件”,等等。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 不一样的React组件化
    • 我们做了什么?
      • 反向依赖
        • 无props化
          • 无actionType化
            • 禁止依赖检查
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档