前言
随着CcFragment支持hook了,私底下有小伙伴问我,在 什么 场景下使用hook,才能体现出hook的精髓,以及什么时候支持useStore和useReducer。
这里我分开回答一下,解开小伙伴的疑惑:
- 1 什么时候使用
hook?
我们知道
hook仅限与在CcFragment内部使用,那么这里先就先解释一下CcFragment的出现缘由,当你定义了不少store和reducer,在此基础上写了不少cc class业务组件了,随着功能的迭代,你将要实现的组件需要跨多个模块消费数据,当组件本身交互逻辑和渲染逻辑都复杂的时候,我们可以使用register或者connect去装饰一个新的类来达到此目的。
但是组件本身的渲染逻辑不复杂而且很轻量的时候,没有必要去抽象一个
class来完成此组件,CcFragment的出现让你可以快速实现类似的组件。
CcFragment标签通过提供connect属性让你能够共享多个模块的state,但是如果用户在CcFragment里需要保管和操作自己的一些localState时,看起来就没法了,难道又要将CcFragment的逻辑写为cc class组件吗?hook的出现本质上和react hook是一个目的,都是为了解决localState的管理问题,目前CcFragment里可以使用hook的两个函数,分别是useState和useEffect,使用的效果和react hook是完全一样的。
useState返回一个值和setter组成的元组。useEffect让你执行副作用,如果不传递第二位参数,useEffect在每一次CcFragment渲染完毕都会执行,如果传递第二位参数为空数组,useEffect仅仅是在CcFragment挂载完毕执行,如果传递第二位参数为包含元素的数组,useEffect是否需要执行取决于数组里的元素是否发生了变化,当然useEffect返回的函数是在CcFragment卸载时会被执行。
- 2 什么时候支持
useStore和useReducer?
其实这个问题是因为小伙伴没有彻底理解
CcFragment才会有此疑问,上面我们提到了:CcFragment标签通过提供connect属性让你能够共享多个模块的state,理所当然的CcFragment也提供对应的函数让你直接复用现有的reducer函数,实际上CcFragment提供的内置函数是和cc class完全一致的,所以CcFragment同样能给你和cc class一样的使用体验,下面这个例子展示了CcFragment提供给你的函数,所以聪明的你是不是醒悟过来,useStore和useReducer在CcFragment里已经是多余的存在了^_^
<CcFragment connect={{'foo/*'}} render={({hook,propState,dispatch,emit,emitIndentity,invoke,effect,xeffect})=>{
//your logic code here
render <div>see the method above?</div>
}}/>
实现Counter
目标
为了进一步解释CcFragment和hook,我们来用cc完成一个有意思的counter示例,让大家进一步了解,为什么cc宣传了这样一句话:让你书写优雅的react代码。
需求
- 基于
cc class实现一个组件名为ClazzCounter。 - 基于
CcFragment实现一个组件名为FnCounter。 ClazzCounter对数操作增加,并存储到counter这个模块的state里。FnCounter对数的操作维护在自己实例内部名为localCount,当localCount为10的整数倍的时候,同步到counter里。FnCounter实现一个自增按钮,点击一次后,自动对counter模块的state里的count值随机增加1~10以内的数,加10次,每增加一次,暂停500ms。FnCounter的实例卸载时使用alert弹一句提示语unmount FnCounter。
store和reducer定义
我们使用cc.startup函数启动cc,注入store和reducer,reducer里包含一个自增、自减和随机自增函数
function sleep(ms=600){
return new Promise(resolve=>setTimeout(resolve,ms));
}
function ran(max=10){
return Math.floor(Math.random()*10);
}
startup({
isModuleMode:true,// startup in module mode
store:{
counter:{
count:0,
}
},
reducer:{
counter:{
inc({moduleState, payload:count}){
let toAdd = 0;
if(count!==undefined)toAdd = count ;
else toAdd = 1;
return {count: moduleState.count + toAdd};
},
dec({moduleState, payload:count}){
return {count:moduleState.count-1};
},
async randomInc({dispatch}){
for(let i=0;i<10;i++){
await dispatch('inc', ran());
await sleep();
}
alert('randomInc finished');
}
}
},
middlewares:[
(context, next)=>{console.log(context);next()}
]
});
ClassCounter定义
因为cc会自动注入state,我们这里就不写constructor了,设定ClazzCounter的ccClassKey为Counter,在react dom tree上将会与<CC(Counter)>的方式展示,设定ClazzCounter属于counter模块,共享counter的所有key的状态变化。
@register('Counter', {module:'counter', sharedStateKeys:'*'})
class ClazzCounter extends React.Component {
inc = ()=>{
this.$$dispatch('inc');
}
render(){
const {count} = this.state;
return (
<div style={{border:'1px solid lightgrey',padding:'9px'}}>
count: {count}
<div>
<button onClick={this.inc}>+</button>
<button data-cct="dec" onClick={this.$$domDispatch}>-</button>
</div>
</div>
);
}
}
FnCounter定义
把FnCounter定义为一个function component,
- 内部使用
CcFragment,连接上counter模块的所有数据, - 使用
useState返回的setter函数包装一个mySetCount函数,完成当localCount为10的整数倍的时候,同步到counter里这个功能 - 使用
useState返回一个函数,完成需求:FnCounter的实例卸载时使用alert弹一句提示语unmount FnCounter - 定义一个
<button>的onClick事件调用dispatch('counter/randomInc'),触发随机增加的需求
function FnCounter({label}){
function FnCounter({label}){
return (
<CcFragment connect={{'counter/*':''}} render={({propState, hook, dispatch})=>{
const [count, setCount] = hook.useState(0);
const mySetCount = (count)=>{
setCount(count);
if(count%10===0){
dispatch('counter/inc', count);
}
};
hook.useEffect(()=>()=>alert('unmount FnCounter'));
return (
<div style={{border:'1px solid blue',padding:'9px',marginTop:'12px'}}>
<div>{label}</div>
counter count:{propState.counter.count}<br/>
local count: {count}
<div>
<button onClick={()=>mySetCount(count+1)}>+</button>
<button onClick={()=>mySetCount(count-1)}>-</button>
<button onClick={()=>dispatch('counter/randomInc')}>randomInc</button>
</div>
</div>
);
}}/>
);
}
渲染它们,看看效果吧
我们可以的多放几个,看看它们之间的数据同步效果,以及证明FnCounter的实例是各自维护自己的localCount值哦。
class App extends React.Component{
render(){
return(
<div>
<ClazzCounter />
<ClazzCounter />
<FnCounter label="FnCounter1"/>
<FnCounter label="FnCounter2"/>
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('root'));

总结
CcFragment结合hook,能够让你更加从容的复用已有的代码,以及更优雅的写function component,组合hook函数可以玩出更多的新花样,等待你去发现cc的更多的react有趣写法。