Control-这个概念在编程中至关重要。比如“轮子”封装层和业务消费层对控制权的“竞争”,就是一个很有意思的话题。这在React世界中也不例外。从表面上看,我们当然希望“轮子”能控制的东西越多越好:因为抽象层处理的逻辑越多,调用业务时关心的东西越少,使用起来越方便。但是,有些设计“不敢逾越一步”。“轮子”和业务控制的拉锯战很有意思。同时,控制能力也与组件设计密切相关:Atomic组件等原子组件设计备受推崇;在原子成分的概念之上,还有分子成分:Moleculescomponents。分子和原子都有存在的理由来解决业务问题。本文将以React框架为背景,谈谈我在开发过程中对控制权的一些思考和总结。如果不使用React,原则上还是不影响阅读。在文章开始之前,先给大家介绍一本书。从去年开始,我和颜海静这个知名科技大佬就开始了合着之旅。今年,我们共同打磨的一本书《React 状态管理与同构实战》终于正式出版了!本书以React技术栈为核心。在介绍React用法的基础上,从源码层面分析了Redux的思想,同时着重介绍了服务端渲染和同构应用的架构模式。书中包含众多项目实例,不仅为用户打开了React技术栈的大门,也提升了读者对前沿领域的整体认知。如果您对本书内容或后续内容感兴趣,请多多支持!文末有详细介绍,别走开!从受控组件和非受控组件来看,我们首先进入了React的大门。关于控制权的概念,我们首先接触到的是受控组件和非受控组件。这两个概念通常与表单相关联。大多数情况下,推荐使用受控组件来实现表单、输入框等状态控制。在受控组件中,表单等数据由React组件自己处理。不是一个受控的组件,是指表单的数据由Dom自己控制。下面是一个典型的非受控组件:
对于React,无法直接控制非受控组件的状态和用户输入,只能依赖于form标签的原生能力进行交互。如果把上面的非受控组件变成受控组件,代码也很简单:classNameFormextendsReact.Component{state={value:''}handleChange=event=>{this.setState({value:event.target。价值});}handleSubmit=event=>{alert('Anamewassubmitted:'+this.state.value);事件.preventDefault();}render(){return(
)}}此时,表单值和行为由React组件控制,开发更方便。这当然是一个很基础的概念,至此抛开控制的话题,请读者继续阅读。UI“轮子”和ControlProps模式前面介绍过的示例,我称之为“狭义受控和非受控”组件。广义上讲,我认为完全非托管组件是:功能组件或无状态组件,不包含内部状态,只接受props。它的渲染行为完全由外部传入的props控制,没有自己的“自主权”。这样的组件很好地实现了可重用性,并且具有良好的可测试性。但在UI“轮子”设计中,“半自主”或“非完全控制”的组件有时是更好的选择。我们称之为“控制道具”模式。简单来说:组件有自己的状态。当没有传入相关的props时,会使用自己的状态statea来完成渲染和交互逻辑;调用组件时,如果有相关的props传入,就会交出控制权。其行为由业务消费级别控制。在研究了很多社区UI“轮子”之后,我发现由KentC.Dodds编写并在paypal使用的组件库downshift广泛使用了这种模式。简单的以一个Toogle组件为例。当业务方调用该组件时:classExampleextendsReact.Component{state={on:false,inputValue:'off'}handleToggle=on=>{this.setState({on,inputValue:on?'on':'off'})}handleChange=({target:{value}})=>{if(value==='on'){this.setState({on:true})}elseif(value==='off'){this.setState({on:false})}this.setState({inputValue:value})}render(){const{on}=this.statereturn(
)}}效果如图:我们可以输入框控制Toggle组件的状态切换(输入“on”为激活状态,输入“off”为灰色状态),也可以用鼠标点击切换,输入框内容会发生变化因此。请思考:对于UI组件Toggle,其状态可以由业务调用者控制,在使用层面赋予了消费便利性。在业务代码中,无论是Input还是其他任何一个组件,它的状态都是可以控制的,我们完全可以控制调用。同时,如果在调用Toggle组件时不传递props值,组件仍然可以正常运行。如下:{({on,getTogglerProps})=>({on?'ToggledOn':'ToggledOff'}
)}Toggle组件维护其内部状态,实现状态切换时的切换效果,同时通过render输出组件的状态信息道具模式。我们看Toggle源码(部分环节已删减):constcallAll=(...fns)=>(...args)=>fns.forEach(fn=>fn&&fn(...args))classToggle扩展组件{staticdefaultProps={defaultOn:false,onToggle:()=>{},}state={on:this.getOn({on:this.props.defaultOn}),}getOn(state=this.state){返回this.isOnControlled()?this.props.on:state.on}isOnControlled(){returnthis.props.on!==undefined}getTogglerStateAndHelpers(){返回{on:this.getOn(),setOn:this.setOn,setOff:this.setOff,切换:this.toggle,}}setOnState=(state=!this.getOn())=>{if(this.isOnControlled()){this.props.onToggle(state,this.getTogglerStateAndHelpers())}else{this.setState({on:state},()=>{this.props.onToggle(this.getOn(),this.getTogglerStateAndHelpers())})}}setOn=this.setOnState.bind(this,true)setOff=锡s.setOnState.bind(this,false)toggle=this.setOnState.bind(this,undefined)render(){constrenderProp=unwrapArray(this.props.children)返回renderProp(this.getTogglerStateAndHelpers())}}functionunwrapArray(arg){返回Array.isArray(arg)?arg[0]:arg}exportdefaultToggle关键点在于组件中的isOnControlled方法判断是否有一个名为on的属性传入:如果有则使用this.props。on用作此组件的状态,否则它使用自己的this.state.on来管理状态。同时在render方法中,使用了renderprop模式。本文不讨论这种模式。有兴趣的读者可以在社区里找到很多资料,同时也可以在我的新书中找到。总结一下,控制道具模式反映了典型的控制问题。这样的“半自治”可以完美适应业务需求,在组件设计上更加灵活有效。Redux异步状态管理和控制说到控制这个话题,怎么能少不了Redux这样的状态管理工具呢。Redux的设计在各个方面都体现了良好的控制权处理。这里我们关注异步状态。更多内容请读者关注我的新书。Redux处理异步,最著名的中间件是Redux-thunk,它是Dan自己写的,在Redux官方文档上被安利了。和其他所有的中间件一样,它控制着从action到reducer的流程,这样在业务使用的时候可以直接dispatch一个function类型的action,实现代码也很简单:functioncreateThunkMiddleware(extraArgument){return({dispatch,getState})=>next=>action=>{if(typeofaction==='function'){returnaction(dispatch,getState,extraArgument);}返回下一个(动作);};}constthunk=createThunkMiddleware();exportdefaultthunk;但很快有人认为这样的解决方案不足以精简业务代码,因为中间件实现缺乏控制。我们仍然需要按照传统的Redux步骤:写action、actioncreator、reducer之类的千篇一律的文章……于是,控制粒度更大的中间件解决方案应运而生。Redux-promise中间件控制action类型,限制业务方在派发异步action时执行resolve,action的payload属性需要是Promise对象。中间件触发相同类型的动作并将有效负载设置为承诺值,并将action.status属性设置为“成功”。exportdefaultfunctionpromiseMiddleware({dispatch}){returnnext=>action=>{if(!isFSA(action)){returnisPromise(action)?action.then(dispatch):next(动作);}返回isPromise(action.payload)?action.payload.then(result=>dispatch({...action,payload:result})).catch(error=>{dispatch({...action,payload:error,error:true});returnPromise.reject(error);}):next(action);};}这种设计和Redux-thunk完全不同,它把thunk过程控制在中间件本身,让第三方轮子做的东西更多,所以在调用业务的时候更加简洁方便。我们只需要正常写action:dispatch({type:GET_USER,payload:http.getUser(userId)//payloadisapromiseobject})我们来对比一下Redux-thunk,它的控制力弱于“轮子”和business控制方比较多的redux-thunk需要实现以上三行代码:dispatch(function(dispatch,getState){dispatch({type:GET_USERE,payload:userId})http.getUser(id)。then(response=>{dispatch({type:GET_USER_SUCCESS,payload:response})}).catch(error=>{dispatch({type:GET_DATA_FAILED,payload:error})})})当然,Redux-promise控制越多,一方面带来了简单性,但另一方面,业务控制力越弱,失去了一定的自主权。例如,如果要实现乐观更新(Optimisticupdates),就很难做到。有关详细信息,请参阅第7期。为了平衡这种矛盾,在Redux-thunk和Redux-promise这两个极端控制概念的中间件之间,有一个中间状态中间件:Redux-promise-middleware,它与Redux-thunk的关系相似,控制粒度类似,但在动作处理上更加温和渐进。它会适时派发三种类型的action:XXX_PENDING、XXX_FULFILLED和XXX_REJECTED,这意味着这个中间件控制了更多的逻辑。基本上是增加了与外部第三方沟通的程度,不再是直接冷冰冰的直接触发XXX_FULFILLED和XXX_REJECTED。请读者仔细理解其中的区别。状态管理中的控制主义和极简主义了解了异步状态下的控制权问题后,我们就从Redux的全局角度来分析一下。在内部分享的时候,我把基于Redux封装的状态管理类库的共性总结为这张pageslide:以上四点都是对基于Redux的相关类库的简化,最后三点很有意思。一个例外与控制权有关。Rematch,以Rematch为代表,不再是一个从action到reducer的中间件,而是完全控制了action的创建者、reducer和连接的过程。具体来说:业务方不再需要显示和声明动作类型,直接由类库的直接函数名生成。如果reducer命名为increment,那么action.type就是increment;同时,controlreducer和actioncreator合二为一,状态管理从未如此简单高效。我称这种实践为控制主义或极简主义。与Redux-actions这样的状态管理库相比,这种做法更加彻底和完整。具体思路可以参考ShawnMcKay的文章,介绍的比较全面,这里不再赘述。总结:码农与控制权控制说到底是一种设计思想,是第三方类库与业务消费的对抗与碰撞。它与语言和框架无关。本文仅以React为例。事实上,控制权的争夺在编程领域随处可见;它与抽象类别无关。本文分别分析了UI抽象和状态抽象;控制权和coders是息息相关的,它直接决定了我们的编程经验和开发效率。但是在编程的初期,好的控件设计是很难一蹴而就的。只有投身于一线开发,真正了解自己的业务需求,然后总结出大量的最佳实践,同时参考社区的精华,分析优秀的开源作品,相信我们都会有所成长。最后,前端学习无止境,希望和每一位技术爱好者一起进步。你可以在知乎找到我!编码愉快!编码愉快!《React 状态管理与同构实战》本书由我和知名前端技术大牛严海静共同打磨,浓缩了我们在学习和实践React框架过程中的积累和经验。除了介绍React框架的使用,重点分析了同构应用的状态管理和服务端渲染。同时,吸取了社区大量的优秀思想,进行了归纳对比。本书灵感来源于百度副总裁沉斗,百度高级前端工程师董锐,知名JavaScript语言专家阮一峰,Node.js浪叔。前端工程师谷依玲和众多前端圈高手联名推荐。感兴趣的读者可以点击这里了解详情。您也可以扫描下方二维码进行购买。再次感谢您的支持与鼓励!恳请大家批评指正!