摘要:盤點(diǎn)一下,模式反應(yīng)了典型的控制權(quán)問題。異步狀態(tài)管理與控制權(quán)提到控制權(quán)話題,怎能少得了這樣的狀態(tài)管理工具。狀態(tài)管理中的控制主義和極簡主義了解了異步狀態(tài)中的控制權(quán)問題,我們再從全局角度進(jìn)行分析。
控制權(quán)——這個(gè)概念在編程中至關(guān)重要。比如,“輪子”封裝層與業(yè)務(wù)消費(fèi)層對于控制權(quán)的“爭奪”,就是一個(gè)很有意思的話題。這在 React 世界里也不例外。表面上看,我們當(dāng)然希望“輪子”掌控的事情越多越好:因?yàn)槌橄髮犹幚淼倪壿嬙蕉?,業(yè)務(wù)調(diào)用時(shí)關(guān)心的事情就越少,使用就越方便??墒怯行┰O(shè)計(jì)卻“不敢越雷池一步”?!拜喿印迸c業(yè)務(wù)在控制權(quán)上的拉鋸,就非常有意思了。
同時(shí),控制能力與組件設(shè)計(jì)也息息相關(guān):Atomic components 這樣的原子組件設(shè)計(jì)被受推崇;在原子組件這個(gè)概念之上,還有分子組件:Molecules components。不管是分子還是原子,在解決業(yè)務(wù)問題上都有存在的理由。
這篇文章將以 React 框架為背景,談?wù)勎以陂_發(fā)當(dāng)中對于控制權(quán)的一些想法和總結(jié)。如果你并不使用 React,原則上仍不妨礙閱讀。
在文章開始之前,我想先向大家介紹一本書。
從去年起,我和知名技術(shù)大佬顏海鏡開始了合著之旅,今年我們共同打磨的書籍《React 狀態(tài)管理與同構(gòu)實(shí)戰(zhàn)》終于正式出版了!這本書以 React 技術(shù)棧為核心,在介紹 React 用法的基礎(chǔ)上,從源碼層面分析了 Redux 思想,同時(shí)著重介紹了服務(wù)端渲染和同構(gòu)應(yīng)用的架構(gòu)模式。書中包含許多項(xiàng)目實(shí)例,不僅為用戶打開了 React 技術(shù)棧的大門,更能提升讀者對前沿領(lǐng)域的整體認(rèn)知。
如果各位對圖書內(nèi)容或接下來的內(nèi)容感興趣,還望多多支持!文末有詳情,不要走開!
初入 React 大門,關(guān)于控制權(quán)概念,我們最先接觸到的就是受控組件與非受控組件。這兩個(gè)概念往往與表單關(guān)聯(lián)在一起。在大部分情況下,推薦使用受控組件來實(shí)現(xiàn)表單、輸入框等狀態(tài)控制。在受控組件中,表單等數(shù)據(jù)都由 React 組件自己處理。而非受控組件,是指表單的數(shù)據(jù)由 Dom 自己控制。下面就是一個(gè)典型的非受控組件:
對于 React 來說,非受控組件的狀態(tài)和用戶輸入都無法直接掌控,只能依賴 form 標(biāo)簽的原生能力進(jìn)行交互。如果使上例非受控組件變?yōu)橐粋€(gè)受控組件,代碼也很簡單:
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 () } }
這時(shí)候表單值和行為都由 React 組件控制,使得開發(fā)更加便利。
這當(dāng)然是很基礎(chǔ)的概念,借此拋出控制權(quán)的話題,請讀者繼續(xù)閱讀。
UI “輪子”與 Control Props 模式前文介紹的樣例,我稱之為“狹義受控和非受控”組件。廣義來說,我認(rèn)為完全的非受控組件是指:不含有內(nèi)部 states,只接受 props 的函數(shù)式組件或無狀態(tài)組件。它的渲染行為完全由外部傳入的 props 控制,沒有自身的“自治權(quán)”。這樣的組件在很好地實(shí)現(xiàn)了復(fù)用性,且具有良好的測試性。
但在 UI “輪子”設(shè)計(jì)當(dāng)中,“半自治”或者“不完全受控”組件,有時(shí)也會(huì)是一個(gè)更好的選擇。我們將此稱之為 “control props” 模式。簡單來說就是:組件具有自身 state,當(dāng)沒有相關(guān) porps 傳入時(shí),使用自身狀態(tài) statea 完成渲染和交互邏輯;當(dāng)該組件被調(diào)用時(shí),如果有相關(guān) props 傳入,那么將會(huì)交出控制權(quán),由業(yè)務(wù)消費(fèi)層面控制其行為。
在研究大量社區(qū) UI “輪子” 之后,我發(fā)現(xiàn)由 Kent C. Dodds 編寫的,在 paypal 使用的組件庫 downshift 便廣泛采用了這樣的模式。
簡單用一個(gè) Toogle 組件舉例,這個(gè)組件由業(yè)務(wù)方調(diào)用時(shí):
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 () } }
效果如圖:
我們可以通過輸入框來控制 Toggle 組件狀態(tài)切換(輸入 “on“ 激活狀態(tài),輸入 ”off“ 狀態(tài)置灰),同時(shí)也可以通過鼠標(biāo)來點(diǎn)擊切換,此時(shí)輸入框內(nèi)容也會(huì)相應(yīng)變化。
請思考:對于 UI 組件 Toggle 來說,它的狀態(tài)可以由業(yè)務(wù)調(diào)用方來控制其狀態(tài),這就賦予了使用層面上的消費(fèi)便利。在業(yè)務(wù)代碼中,不管是 Input 還是其他任何組件都可以控制其狀態(tài),調(diào)用時(shí)我們具有完全的控制權(quán)掌控能力。
同時(shí),如果在調(diào)用 Toggle 組件時(shí),不去傳 props 值,該組件仍然可以正常發(fā)揮。如下:
{({on, getTogglerProps}) => ( )}{on ? "Toggled On" : "Toggled Off"}
Toggle 組件在狀態(tài)切換時(shí),自己維護(hù)內(nèi)部狀態(tài),實(shí)現(xiàn)切換效果,同時(shí)通過 render prop 模式,對外輸出本組件的狀態(tài)信息。
我們看 Toggle 源碼(部分環(huán)節(jié)已刪減):
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
關(guān)鍵的地方在于組件內(nèi) isOnControlled 方法判斷是否有命名為 on 的屬性傳入:如果有,則使用 this.props.on 作為本組件狀態(tài),反之用自身 this.state.on 來管理狀態(tài)。同時(shí)在 render 方法中,使用了 render prop 模式,關(guān)于這個(gè)模式本文不再探討,感興趣的讀者可以在社區(qū)中找到很多資料,同時(shí)也可以在我新書中找到相關(guān)內(nèi)容。
盤點(diǎn)一下,control props 模式反應(yīng)了典型的控制權(quán)問題。這樣的“半自治”能夠完美適應(yīng)業(yè)務(wù)需求,在組件設(shè)計(jì)上也更加靈活有效。
Redux 異步狀態(tài)管理與控制權(quán)提到控制權(quán)話題,怎能少得了 Redux 這樣的狀態(tài)管理工具。Redux 的設(shè)計(jì)在方方面面都體現(xiàn)出來良好的控制權(quán)處理,這里我們把注意力集中在異步狀態(tài)上,更多的內(nèi)容還請讀者關(guān)注我的新書。
Redux 處理異步,最為人熟知的就是 Redux-thunk 這樣的中間件,它由 Dan 親自編寫,并在 Redux 官方文檔上被安利。它與其他所有中間件一樣,將 action 到 reducer 中間的過程進(jìn)行掌控,使得業(yè)務(wù)使用時(shí)可以直接 dispatch 一個(gè)函數(shù)類型的 action,實(shí)現(xiàn)代碼也很簡單:
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;
但是很快就有人認(rèn)為,這樣的方案因?yàn)樵谥虚g件實(shí)現(xiàn)中的控制不足,導(dǎo)致了業(yè)務(wù)代碼不夠精簡。我們還是需要遵循傳統(tǒng)的 Redux 步驟:八股文似的編寫 action,action creactor,reducer......于是,控制粒度更大的中間件方案應(yīng)運(yùn)而生。
Redux-promise 中間件控制了 action type,它限制業(yè)務(wù)方在 dispatch 異步 action 時(shí),action的 payload 屬性需要是一個(gè) Promise 對象時(shí),執(zhí)行 resolve,該中間件觸發(fā)一個(gè)類型相同的 action,并將 payload 設(shè)置為 promise 的 value,并設(shè) 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); }; }
這樣的設(shè)計(jì)與 Redux-thunk 完全不同,它將 thunk 過程控制在中間件自身中,這樣一來,第三方輪子做的事情更多,因此在業(yè)務(wù)調(diào)用時(shí)更加簡練方便。我們只需要正常編寫 action 即可:
dispatch({ type: GET_USER, payload: http.getUser(userId) // payload 為 promise 對象 })
我們對比一下 Redux-thunk,相對于“輪子”控制權(quán)較弱,業(yè)務(wù)方控制權(quán)更多的 Redux-thunk,實(shí)現(xiàn)上述三行代碼,就得不得不需要:
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 }) }) } )
當(dāng)然,Redux-promise 控制權(quán)越多,一方面帶來了簡練,但是另一方面,業(yè)務(wù)控制權(quán)越弱,也喪失了一定的自主性。比如如果想實(shí)現(xiàn)樂觀更新(Optimistic updates),那就很難做了。具體詳見 Issue #7
為了平衡這個(gè)矛盾,在 Redux-thunk 和 Redux-promise 這兩個(gè)極端控制權(quán)理念的中間件之間,于是便存在了中間狀態(tài)的中間件:Redux-promise-middleware,它與 Redux-thunk 類似,掌控粒度也類似,但是在 action 處理上更加溫和和漸進(jìn),它會(huì)在適當(dāng)?shù)臅r(shí)機(jī) dispatch XXX_PENDING、XXX_FULFILLED 、XXX_REJECTED 三種類型的 action,也就是說這個(gè)中間件在掌控更多邏輯的基礎(chǔ)上,增加了和外界第三方的通信程度,不再是直接高冷地觸發(fā) XXX_FULFILLED 、XXX_REJECTED,請讀者仔細(xì)體會(huì)其中不同。
狀態(tài)管理中的控制主義和極簡主義了解了異步狀態(tài)中的控制權(quán)問題,我們再從 Redux 全局角度進(jìn)行分析。在內(nèi)部分享時(shí),我將基于 Redux 封裝的狀態(tài)管理類庫共同特性總結(jié)為這一頁 slide:
以上四點(diǎn)都是相關(guān)類庫基于 Redux 所進(jìn)行的簡化,其中非常有意思的就是后面三點(diǎn),它們無一例外地與控制權(quán)相關(guān)。以 Rematch 為代表,它不再是處理 action 到 reducer 的中間件,而是完全控制了 action creator,reducer 以及聯(lián)通過程。
具體來看:
業(yè)務(wù)方不再需要顯示申明 action type,它由類庫直接函數(shù)名直接生成,如果 reducer 命名為 increment,那么 action.type 就是 increment;
同時(shí)控制 reducer 和 action creator 合二為一,態(tài)管理從未變得如此簡單、高效。
我把這樣的實(shí)踐稱為控制主義或者極簡主義,相比 Redux-actions 這樣的狀態(tài)管理類庫,這樣的做法更加徹底、完善。具體思想可參考 Shawn McKay 的文章,介紹的比較充分,這里我不再贅述。
總結(jié):碼農(nóng)和控制權(quán)控制權(quán)說到底是一種設(shè)計(jì)思想,是第三方類庫和業(yè)務(wù)消費(fèi)的交鋒和碰撞。它與語言和框架無關(guān),本文只是以 React 舉例,實(shí)際上在編程領(lǐng)域控制權(quán)的爭奪隨處可見;他與抽象類別無關(guān),本文已經(jīng)在 UI 抽象和狀態(tài)抽象中分別例舉分析;控制權(quán)與碼農(nóng)息息相關(guān),它直接決定了我們的編程體驗(yàn)和開發(fā)效率。
可是在編程的初期階段,優(yōu)秀的控制權(quán)設(shè)計(jì)難以一蹴而就。只有投身到一線開發(fā)當(dāng)中,真正了解自身業(yè)務(wù)需求,進(jìn)而總結(jié)大量最佳實(shí)踐,同時(shí)參考社區(qū)精華,分析優(yōu)秀開源作品,相信我們都會(huì)得到成長。
最后,前端學(xué)習(xí)永無止境,希望和每一位技術(shù)愛好者共同進(jìn)步,大家可以在知乎找到我!
Happy coding!
Happy coding!
《React 狀態(tài)管理與同構(gòu)實(shí)戰(zhàn)》這本書由我和前端知名技術(shù)大佬顏海鏡合力打磨,凝結(jié)了我們在學(xué)習(xí)、實(shí)踐 React 框架過程中的積累和心得。除了 React 框架使用介紹以外,著重剖析了狀態(tài)管理以及服務(wù)端渲染同構(gòu)應(yīng)用方面的內(nèi)容。同時(shí)吸取了社區(qū)大量優(yōu)秀思想,進(jìn)行歸納比對。
本書受到百度公司副總裁沈抖、百度資深前端工程師董睿,以及知名 JavaScript 語言專家阮一峰、Node.js 布道者狼叔、Flarum 中文社區(qū)創(chuàng)始人 justjavac、新浪移動(dòng)前端技術(shù)專家小爝、百度資深前端工程師顧軼靈等前端圈眾多專家大咖的聯(lián)合力薦。
有興趣的讀者可以點(diǎn)擊這里,了解詳情。也可以掃描下面的二維碼購買。再次感謝各位的支持與鼓勵(lì)!懇請各位批評指正!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/54641.html
摘要:盤點(diǎn)一下,模式反應(yīng)了典型的控制權(quán)問題。異步狀態(tài)管理與控制權(quán)提到控制權(quán)話題,怎能少得了這樣的狀態(tài)管理工具。狀態(tài)管理中的控制主義和極簡主義了解了異步狀態(tài)中的控制權(quán)問題,我們再從全局角度進(jìn)行分析。 控制權(quán)——這個(gè)概念在編程中至關(guān)重要。比如,輪子封裝層與業(yè)務(wù)消費(fèi)層對于控制權(quán)的爭奪,就是一個(gè)很有意思的話題。這在 React 世界里也不例外。表面上看,我們當(dāng)然希望輪子掌控的事情越多越好:因?yàn)槌橄髮?..
摘要:課程制作和案例制作都經(jīng)過精心編排。對于開發(fā)者意義重大,希望對有需要的開發(fā)者有所幫助。是從提案轉(zhuǎn)為正式加入的新特性。并不需要用繼承,而是推薦用嵌套。大型項(xiàng)目中模塊化與功能解耦困難。從而更加易于復(fù)用和獨(dú)立測試。但使用會(huì)減少這種幾率。 showImg(https://segmentfault.com/img/bVbpNRZ?w=1920&h=1080); 講師簡介 曾任職中軟軍隊(duì)事業(yè)部,參與...
摘要:特意對前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯(cuò)誤的地方,還請斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會(huì)及時(shí)更新,平時(shí)業(yè)務(wù)工作時(shí)也會(huì)不定期更...
摘要:根本原因在于,并不是真正意義上的異步操作,它只是模擬了異步的行為。而合成事件和生命周期函數(shù)中,是受控制的,其會(huì)將設(shè)置為,從而走的是類似異步的那一套。總結(jié)此處總結(jié)是直接引用了只在合成事件和鉤子函數(shù)中是異步的,在原生事件和中都是同步的。 如何使用setState 在 React 日常的使用中,一個(gè)很重要的點(diǎn)就是,不要直接去修改 state。例如:this.state.count = 1是無...
閱讀 3599·2023-04-26 02:55
閱讀 2866·2021-11-02 14:38
閱讀 4146·2021-10-21 09:39
閱讀 2856·2021-09-27 13:36
閱讀 3967·2021-09-22 15:08
閱讀 2657·2021-09-08 10:42
閱讀 2811·2019-08-29 12:21
閱讀 678·2019-08-29 11:22