在cc里用class和function实现counter

674 阅读5分钟

前言

随着CcFragment支持hook了,私底下有小伙伴问我,在 什么 场景下使用hook,才能体现出hook的精髓,以及什么时候支持useStoreuseReducer

这里我分开回答一下,解开小伙伴的疑惑:

  • 1 什么时候使用hook

我们知道hook仅限与在CcFragment内部使用,那么这里先就先解释一下CcFragment的出现缘由,当你定义了不少storereducer,在此基础上写了不少cc class业务组件了,随着功能的迭代,你将要实现的组件需要跨多个模块消费数据,当组件本身交互逻辑和渲染逻辑都复杂的时候,我们可以使用register或者connect去装饰一个新的类来达到此目的。


但是组件本身的渲染逻辑不复杂而且很轻量的时候,没有必要去抽象一个class来完成此组件,CcFragment的出现让你可以快速实现类似的组件。


CcFragment标签通过提供connect属性让你能够共享多个模块的state,但是如果用户在CcFragment里需要保管和操作自己的一些localState时,看起来就没法了,难道又要将CcFragment的逻辑写为cc class组件吗?hook的出现本质上和react hook是一个目的,都是为了解决localState的管理问题,目前CcFragment里可以使用hook的两个函数,分别是useStateuseEffect,使用的效果和react hook是完全一样的。

  1. useState返回一个值和setter组成的元组。
  2. useEffect让你执行副作用,如果不传递第二位参数,useEffect在每一次CcFragment渲染完毕都会执行,如果传递第二位参数为空数组,useEffect仅仅是在CcFragment挂载完毕执行,如果传递第二位参数为包含元素的数组,useEffect是否需要执行取决于数组里的元素是否发生了变化,当然useEffect返回的函数是在CcFragment卸载时会被执行。
  • 2 什么时候支持useStoreuseReducer

其实这个问题是因为小伙伴没有彻底理解CcFragment才会有此疑问,上面我们提到了:CcFragment标签通过提供connect属性让你能够共享多个模块的state,理所当然的CcFragment也提供对应的函数让你直接复用现有的reducer函数,实际上CcFragment提供的内置函数是和cc class完全一致的,所以CcFragment同样能给你和cc class一样的使用体验,下面这个例子展示了CcFragment提供给你的函数,所以聪明的你是不是醒悟过来,useStoreuseReducerCcFragment里已经是多余的存在了^_^

<CcFragment connect={{'foo/*'}} render={({hook,propState,dispatch,emit,emitIndentity,invoke,effect,xeffect})=>{
    //your logic code here
    render <div>see the method above?</div>
}}/>

实现Counter

目标

为了进一步解释CcFragmenthook,我们来用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,注入storereducerreducer里包含一个自增、自减和随机自增函数

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了,设定ClazzCounterccClassKeyCounter,在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有趣写法。