视图与业务,好一对冤家
业务型model
model
是需要精心的设计和合理的划分的,这是我们之前开发大型的redux
+react
单页面应用,大家都认同的真理,同样的,在react-control-center
+react
的开发里也适用这条黄金规则,通常,我们在接到需求,定制开发计划的时候,会抽象出很多业务相关的关键词,这些关键词慢慢经过进一步整理,将成为我们划分功能或者模块的有效依据,这些模块最终在前端这里会沉淀为model
,每一个model
定义了自己的state
、reducer
,当然如果有需要,还可以为其定义computed
、init
,通过精心的目录组织和规范的约定,视图的渲染逻辑和我们书写的业务逻辑被有效的解耦到component
里和reducer
里,这样当我们需要重构UI组件,可以放心的对其重构或者新增一个组件,复用相同的state
和reducer
参考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
组件需要修改model
的state
,所以我们在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
组件Foo1
、Foo2
、Foo3
,Foo1
嵌套了Foo2
,Foo2
嵌套了Foo3
,看起来要一层一层传递下去了。
同时视图组件调整的时间占比会远大于reducer
函数的书写,我们有时候为了那个某个model
的state
,不停的传递下去或者慢慢的将某些比较重的function
组件又提升为class
组件
react hooks解决了什么呢?
这里复制一段facebook引出hooks
要解决的问题所在之处
- 难以重用和共享组件中的与状态相关的逻辑
- 逻辑复杂的组件难以开发与维护,当我们的组件需要处理多个互不相关的 local state * 时,每个生命周期函数中可能会包含着各种互不相关的逻辑在里面。
- 类组件中的this增加学习成本,类组件在基于现有工具的优化上存在些许问题。
- 由于业务变动,函数组件不得不改为类组件等等。
可是如果我们的function
组件如果是需要共享或者修改model
的state
呢,有什么更优雅的办法解决吗?
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
标记一个ccKey
,connect
- 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.register
、cc.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
参数观察的参数是否发生变化,所以它的渲染依然是高效的。