本篇文章给大家带来的内容是关于react进阶设计与控制权的内容介绍 ,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。
控制权——这个概念在编程中至关重要。比如,“轮子”封装层与业务消费层对于控制权的“争夺”,就是一个很有意思的话题。这在 react 世界里也不例外。表面上看,我们当然希望“轮子”掌控的事情越多越好:因为抽象层处理的逻辑越多,业务调用时关心的事情就越少,使用就越方便。可是有些设计却“不敢越雷池一步”。“轮子”与业务在控制权上的拉锯,就非常有意思了。
同时,控制能力与组件设计也息息相关:atomic components 这样的原子组件设计被受推崇;在原子组件这个概念之上,还有分子组件:molecules components。不管是分子还是原子,在解决业务问题上都有存在的理由。
这篇文章将以 react 框架为背景,谈谈我在开发当中对于控制权的一些想法和总结。如果你并不使用 react,原则上仍不妨碍阅读
在文章开始之前,我想先向大家介绍一本书。
从去年起,我和知名技术大佬颜海镜开始了合著之旅,今年我们共同打磨的书籍《react 状态管理与同构实战》终于正式出版了!这本书以 react 技术栈为核心,在介绍 react 用法的基础上,从源码层面分析了 redux 思想,同时着重介绍了服务端渲染和同构应用的架构模式。书中包含许多项目实例,不仅为用户打开了 react 技术栈的大门,更能提升读者对前沿领域的整体认知。
从受控与非受控组件说起初入 react 大门,关于控制权概念,我们最先接触到的就是受控组件与非受控组件。这两个概念往往与表单关联在一起。在大部分情况下,推荐使用受控组件来实现表单、输入框等状态控制。在受控组件中,表单等数据都由 react 组件自己处理。而非受控组件,是指表单的数据由 dom 自己控制。下面就是一个典型的非受控组件:
<form> <label> name: <input type="text" name="name" /> </label> <input type="submit" value="submit" /></form>
对于 react 来说,非受控组件的状态和用户输入都无法直接掌控,只能依赖 form 标签的原生能力进行交互。如果使上例非受控组件变为一个受控组件,代码也很简单:
class nameform extends react.component { state= {value: ''} handlechange = event => { this.setstate({value: event.target.value}); } handlesubmit = event => { alert('a name was submitted: ' + this.state.value); event.preventdefault(); } render() { return ( <form onsubmit={this.handlesubmit}> <label> name: <input type="text" value={this.state.value} onchange={this.handlechange} /> </label> <input type="submit" value="submit" /> </form> ) }}
这时候表单值和行为都由 react 组件控制,使得开发更加便利。
这当然是很基础的概念,借此抛出控制权的话题,请读者继续阅读。
ui “轮子”与 control props 模式前文介绍的样例,我称之为“狭义受控和非受控”组件。广义来说,我认为完全的非受控组件是指:不含有内部 states,只接受 props 的函数式组件或无状态组件。它的渲染行为完全由外部传入的 props 控制,没有自身的“自治权”。这样的组件在很好地实现了复用性,且具有良好的测试性。
但在 ui “轮子”设计当中,“半自治”或者“不完全受控”组件,有时也会是一个更好的选择。我们将此称之为 “control props” 模式。简单来说就是:组件具有自身 state,当没有相关 porps 传入时,使用自身状态 statea 完成渲染和交互逻辑;当该组件被调用时,如果有相关 props 传入,那么将会交出控制权,由业务消费层面控制其行为。
在研究大量社区 ui “轮子” 之后,我发现由 kent c. dodds 编写的,在 paypal 使用的组件库 downshift 便广泛采用了这样的模式。
简单用一个 toogle 组件举例,这个组件由业务方调用时:
class example extends react.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}) } else if (value === 'off') { this.setstate({on: false}) } this.setstate({inputvalue: value}) } render() { const {on} = this.state return ( <p> <input value={this.state.inputvalue} onchange={this.handlechange} /> <toggle on={on} ontoggle={this.handletoggle} /> </p> ) }}
效果如图:
我们可以通过输入框来控制 toggle 组件状态切换(输入 “on“ 激活状态,输入 ”off“ 状态置灰),同时也可以通过鼠标来点击切换,此时输入框内容也会相应变化。
请思考:对于 ui 组件 toggle 来说,它的状态可以由业务调用方来控制其状态,这就赋予了使用层面上的消费便利。在业务代码中,不管是 input 还是其他任何组件都可以控制其状态,调用时我们具有完全的控制权掌控能力。
同时,如果在调用 toggle 组件时,不去传 props 值,该组件仍然可以正常发挥。如下:
<toggle> {({on, gettogglerprops}) => ( <p> <button {...gettogglerprops()}>toggle me</button> <p>{on ? 'toggled on' : 'toggled off'}</p> </p> )} </toggle>
toggle 组件在状态切换时,自己维护内部状态,实现切换效果,同时通过 render prop 模式,对外输出本组件的状态信息。
我们看 toggle 源码(部分环节已删减):
const callall = (...fns) => (...args) => fns.foreach(fn => fn && fn(...args))class toggle extends component { static defaultprops = { defaulton: false, ontoggle: () => {}, } state = { on: this.geton({on: this.props.defaulton}), } geton(state = this.state) { return this.isoncontrolled() ? this.props.on : state.on } isoncontrolled() { return this.props.on !== undefined } gettogglerstateandhelpers() { return { on: this.geton(), seton: this.seton, setoff: this.setoff, toggle: 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 = this.setonstate.bind(this, false) toggle = this.setonstate.bind(this, undefined) render() { const renderprop = unwraparray(this.props.children) return renderprop(this.gettogglerstateandhelpers()) }}function unwraparray(arg) { return array.isarray(arg) ? arg[0] : arg}export default toggle
关键的地方在于组件内 isoncontrolled 方法判断是否有命名为 on 的属性传入:如果有,则使用 this.props.on 作为本组件状态,反之用自身 this.state.on 来管理状态。同时在 render 方法中,使用了 render prop 模式,关于这个模式本文不再探讨,感兴趣的读者可以在社区中找到很多资料,同时也可以在我新书中找到相关内容。
盘点一下,control props 模式反应了典型的控制权问题。这样的“半自治”能够完美适应业务需求,在组件设计上也更加灵活有效。
redux 异步状态管理与控制权提到控制权话题,怎能少得了 redux 这样的状态管理工具。redux 的设计在方方面面都体现出来良好的控制权处理,这里我们把注意力集中在异步状态上,更多的内容还请读者关注我的新书。
redux 处理异步,最为人熟知的就是 redux-thunk 这样的中间件,它由 dan 亲自编写,并在 redux 官方文档上被安利。它与其他所有中间件一样,将 action 到 reducer 中间的过程进行掌控,使得业务使用时可以直接 dispatch 一个函数类型的 action,实现代码也很简单:
function createthunkmiddleware(extraargument) { return ({ dispatch, getstate }) => next => action => { if (typeof action === 'function') { return action(dispatch, getstate, extraargument); } return next(action); };}const thunk = createthunkmiddleware();export default thunk;
但是很快就有人认为,这样的方案因为在中间件实现中的控制不足,导致了业务代码不够精简。我们还是需要遵循传统的 redux 步骤:八股文似的编写 action,action creactor,reducer......于是,控制粒度更大的中间件方案应运而生。
redux-promise 中间件控制了 action type,它限制业务方在 dispatch 异步 action 时,action的 payload 属性需要是一个 promise 对象时,执行 resolve,该中间件触发一个类型相同的 action,并将 payload 设置为 promise 的 value,并设 action.status 属性为 success。
export default function promisemiddleware({ dispatch }) { return next => action => { if (!isfsa(action)) { return ispromise(action) ? action.then(dispatch) : next(action); } return ispromise(action.payload) ? action.payload .then(result => dispatch({ ...action, payload: result })) .catch(error => { dispatch({ ...action, payload: error, error: true }); return promise.reject(error); }) : next(action); };}
这样的设计与 redux-thunk 完全不同,它将 thunk 过程控制在中间件自身中,这样一来,第三方轮子做的事情更多,因此在业务调用时更加简练方便。我们只需要正常编写 action 即可:
dispatch({ type: get_user, payload: http.getuser(userid) // payload 为 promise 对象})
我们对比一下 redux-thunk,相对于“轮子”控制权较弱,业务方控制权更多的 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 控制权越多,一方面带来了简练,但是另一方面,业务控制权越弱,也丧失了一定的自主性。比如如果想实现乐观更新(optimistic updates),那就很难做了。具体详见 issue #7
为了平衡这个矛盾,在 redux-thunk 和 redux-promise 这两个极端控制权理念的中间件之间,于是便存在了中间状态的中间件:redux-promise-middleware,它与 redux-thunk 类似,掌控粒度也类似,但是在 action 处理上更加温和和渐进,它会在适当的时机 dispatch xxx_pending、xxx_fulfilled 、xxx_rejected 三种类型的 action,也就是说这个中间件在掌控更多逻辑的基础上,增加了和外界第三方的通信程度,不再是直接高冷地触发 xxx_fulfilled 、xxx_rejected,请读者仔细体会其中不同。
状态管理中的控制主义和极简主义了解了异步状态中的控制权问题,我们再从 redux 全局角度进行分析。在内部分享时,我将基于 redux 封装的状态管理类库共同特性总结为这一页 slide:
以上四点都是相关类库基于 redux 所进行的简化,其中非常有意思的就是后面三点,它们无一例外地与控制权相关。以 rematch 为代表,它不再是处理 action 到 reducer 的中间件,而是完全控制了 action creator,reducer 以及联通过程。
具体来看:
业务方不再需要显示申明 action type,它由类库直接函数名直接生成,如果 reducer 命名为 increment,那么 action.type 就是 increment;
同时控制 reducer 和 action creator 合二为一,态管理从未变得如此简单、高效。
我把这样的实践称为控制主义或者极简主义,相比 redux-actions 这样的状态管理类库,这样的做法更加彻底、完善。具体思想可参考 shawn mckay 的文章,介绍的比较充分,这里我不再赘述。
总结:码农和控制权控制权说到底是一种设计思想,是第三方类库和业务消费的交锋和碰撞。它与语言和框架无关,本文只是以 react 举例,实际上在编程领域控制权的争夺随处可见;他与抽象类别无关,本文已经在 ui 抽象和状态抽象中分别例举分析;控制权与码农息息相关,它直接决定了我们的编程体验和开发效率。
可是在编程的初期阶段,优秀的控制权设计难以一蹴而就。只有投身到一线开发当中,真正了解自身业务需求,进而总结大量最佳实践,同时参考社区精华,分析优秀开源作品,相信我们都会得到成长。
相关推荐:
react router4+redux控制路由权限步骤详解
怎样使用react router4+redux实现路由权限控制
以上就是react进阶设计与控制权的内容介绍的详细内容。