专栏首页腾讯IVWEB团队的专栏不一样的React组件化

不一样的React组件化

不一样的React组件化

我们做了什么?

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

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

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

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

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

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

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

“差不多是这样的。”

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

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

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

反向依赖

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

import B from './B'
//...
render(){
    return <div>
        <B />
    </div>
}

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

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

// 伪代码高能注意!!!
B.require('A', 'data_list', (list)=>{
    // .....
})

无props化

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

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

return <div>
    <B name={this.props.name}>
</div>

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

return <div>
    <B />
</div>

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

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语法变成了这样:

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

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

store.listen('setTab', {
    reducer: plainReducer((state, action)=>{
        return Object.assign({}, state, {tabIndex: action.data})
    })
})

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

禁止依赖检查

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

storeB.listenOther('A.fetch_success', (action)=>{
    // do something with action dispatched by A
})

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【译】选择Bit.dev构建组件库的15个理由

    2019年的时候,UI组件库在普及度上有了巨大的飞跃。当然这并不多么令人惊奇,因为像Uber、Airbnb、Booking等等公司都在通过共享的UI组件来保证其...

    腾讯IVWEB团队
  • 为你的React工程添加异步组件支持

    首先,要明白组件的概念。React中所有继承React.Component的类都是一个React组件,React组件可大可小,功能多样。React组件一般情况下...

    腾讯IVWEB团队
  • 【译】以接口为中心,让接口来接管,组件作为服务。重用组件和服务

    现如今,云服务在每个月都会大概收到1x10e16次的接口调用。这一巨大的变化改变了软件的开发模式。我们已经通过复用软件中的接口和开源来完成我们一部分的梦想。对我...

    腾讯IVWEB团队
  • CreatorPrimer|组件编码心得(上)

    Cocos Creator的核心是组件化,如何编写出高质量的组件代码值得程序员们不断探索,Shawn今天分享一点组件编码的心得供大家参考:“怎样才是一个合格的组...

    张晓衡
  • 微信小程序组件调用和传值

    微信小程序像Vue和React一样赋于了组件的开发能力,支持组件的调用和传值,同时由于小程序上传时限制在2MB以内,对于稍微大一点的小程序组件的使用就特别重要了...

    越陌度阡
  • vue父组件传字符串到子组件不需要写 v-bind 绑定或语法糖 :

    跟着阿笨一起玩NET
  • 代码组件 | 我的代码没有else

    前端大行组件化的当今,我们在写后端接口代码的时候还是按照业务思路一头写到尾吗?我们是否可以思索,「后端接口业务代码如何可以简单快速组件化?」,答案是肯定的,这就...

    用户1093396
  • vuejs中的组件以及父子组件间通信传值

    您将在本文当中了解到,往网页中添加数据,从传统的dom操作过渡到数据层操作,实现同一个目标,两种不同的方式.以及什么是组件,如何定义和使用组件,父子组件之间如何...

    itclanCoder
  • 在小程序中调用API在小程序中自定义弹窗组件

    表明它是一个组件,我们称之为“子组件” 3. 注意:在组件wxss中不应使用ID选择器、属性选择器和标签名选择器。(只使用class)

    九旬大爷
  • vue父子组件通信以及非父子组件通信的方法

    组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。一般来说,组件可以有以下几种关系,父子关系、兄弟...

    青年码农

扫码关注云+社区

领取腾讯云代金券