在react-control-center里玩转无状态组件
发布于 17 天前 作者 fantasticsoul 218 次浏览 来自 分享

视图与业务,好一对冤家

业务型model

model是需要精心的设计和合理的划分的,这是我们之前开发大型的redux+react单页面应用,大家都认同的真理,同样的,在react-control-center+react的开发里也适用这条黄金规则,通常,我们在接到需求,定制开发计划的时候,会抽象出很多业务相关的关键词,这些关键词慢慢经过进一步整理,将成为我们划分功能或者模块的有效依据,这些模块最终在前端这里会沉淀为model,每一个model定义了自己的statereducer,当然如果有需要,还可以为其定义computedinit,通过精心的目录组织和规范的约定,视图的渲染逻辑和我们书写的业务逻辑被有效的解耦到component里和reducer里,这样当我们需要重构UI组件,可以放心的对其重构或者新增一个组件,复用相同的statereducer
参考cc-antd-pro的划分

|________layouts
|     |________BasicLayout.js
|     |________BasicLayout.less
|     |________BlankLayout.js
|     |________PageHeaderLayout.js
|     |________PageHeaderLayout.less
|     |________UserLayout.js
|     |________UserLayout.less
|________models
|     |________activities.js
|     |________chart.js
|     |________form.js
|     |________global.js
|     |________index.js
|     |________list.js
|     |________login.js
|     |________monitor.js
|     |________profile.js
|     |________project.js
|     |________register.js
|     |________rule.js
|     |________user.js

视图型model

有一些状态,我们开发的过程中,发现和视图紧密相关,不同的组件在不同的生命周期阶段,都需要使用他们或者感知到他们的变化,例如右上角用户勾选的主题色,影响左下角一个抽屉的弹出策略或效果,这些状态同样需要交个状态管理框架集中管理起来,所以我们也会这些需求设计相应的model,这一类和主要业务逻辑不想管,但是我们依然需要精心管理起来的model,我们称之为视图型model.

视图代码膨胀之困惑

通常,我们已开始精心设计好各种model后,开始信心满满的进入开发流程,随着功能迭代越来越块,需求变动越来频繁,我们的model会不停的调整或者扩展,按照class组件和function组件比例2:8开的原则,我们总是想抽出更多的function组件,class组件负责和和model打通,然后从model里拿到的数据层层派发它的所以孩子function组件里,但是function组件通常都不是只负责展示,还是有不少function组件需要修改modelstate,所以我们在ant-design-pro里或者别的地方,依然会看到不少类似代码

@connect(state => ({
  register: state.register,
}))
class Foo extends Component {
    render(){
        return (
            <MyStatelessFoo {...this.props}/>
        );
    }
}

const MyStatelessFoo = ({dispatch}){
    return <div onClick={dispatch('foo/changeSomething')}>whaterver</div>
}

如果有function组件Foo1Foo2Foo3,Foo1嵌套了Foo2,Foo2嵌套了Foo3,看起来要一层一层传递下去了。
同时视图组件调整的时间占比会远大于reducer函数的书写,我们有时候为了那个某个modelstate,不停的传递下去或者慢慢的将某些比较重的function组件又提升为class组件

react hooks解决了什么呢?

这里复制一段facebook引出hooks要解决的问题所在之处

  • 难以重用和共享组件中的与状态相关的逻辑
  • 逻辑复杂的组件难以开发与维护,当我们的组件需要处理多个互不相关的 local state * 时,每个生命周期函数中可能会包含着各种互不相关的逻辑在里面。
  • 类组件中的this增加学习成本,类组件在基于现有工具的优化上存在些许问题。
  • 由于业务变动,函数组件不得不改为类组件等等。

可是如果我们的function组件如果是需要共享或者修改modelstate呢,有什么更优雅的办法解决吗?

CcFragment为你带来全新的无状态组件书写体验

一个典型的CcFragment使用方式如下

import {CcFragment} from 'react-control-center';

//在你的普通的react组件或者cc组件里,都可以写如下代码
  render() {
    <div>
      <span>another jsx content</span>
      <hr/>
      <CcFragment ccKey="toKnowWhichFragmentChangeStore" connect={{ 'foo/*': '', 'bar/a': 'a', 'bar/b': 'alias_b' }}>
        {
          ({ propState, setState, dispatch, emit, effect, xeffect, lazyEffect, lazyXeffect }) => (
            <div onClick={() => setState('foo', { name: 'cool, I can change foo module name' })}>
              {/* 以上方法,你可以像在cc类组件一样的使用它们,没有区别 */}
              {propState.foo.name}
              {propState.bar.a}
              {propState.bar.alias_b}
            </div>
          )
        }
      </CcFragment>
    </div>
  }

上面代码里,CcFragment标记一个ccKeyconnect

  • cc默认是会为所有CcFragment自动生成ccKey的,但是我们推荐你书写一个有意义的ccKey,因为CcFragment允许无状态组件直接使用setState, dispatch, emit, effect, xeffect, lazyEffect, lazyXeffect方法去修改状态或者发起通知,这些函数的使用体验是和cc class一摸一样,加上ccKey,你可以在你的中间件函数里看到某一次的状态变化是由哪一个ccKey触发的,这样未来你可以在还在计划开发中的cc-dev-tool里查看具体的状态变迁历史,当然目前你需要查看状态变化的话,可以写一个简单的中间件函数来log
function myMiddleware(params, next) {
  //params 里你可以看到本次状态变化提交的状态是什么,由什么方法触发,由那个ccKey的引用触发等
  console.log('myMiddleware', params);
  next();
}

cc.startup({
  //...
  middlewares: [myMiddleware]
});

  • connect和cc.registercc.connect一样,表示该CcFragment关注那些模块,哪些值的变化,上述示例的效果会是

1 只要bar模块的a或者b变化了,都会触发该CcFragment的渲染
2 只要foo模块的任意key变化了,都会触发该CcFragment的渲染
3 点击了div,会去修改foo模块的name值,关注foo模块name值变化的所有cc组件或者CcFragment组件都会触发渲染

所以CcFragment解决了用户在无状态组件里共享了model数据的问题,你写的无状态组件很容易和cc store打通,而无需在考虑抽取为cc class组件,CcFragment本质上和hooks不存在冲突管理,也和现有cc class不冲突,只是作为cc世界里更重要的补充,让你可以无损的使用现有的function组件。
注意一点哦,CcFragment本身是不会因为父组件的更新而被更新的哦,仅仅受控制于connect参数观察的参数是否发生变化,所以它的渲染依然是高效的。

那么可爱的各位看官,还不赶紧使用起来

回到顶部