首頁  >  文章  >  web前端  >  React進階設計與控制權的內容介紹

React進階設計與控制權的內容介紹

不言
不言原創
2018-09-13 16:31:221290瀏覽

這篇文章帶給大家的內容是關於React進階設計與控制權的內容介紹 ,有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

控制權-這個概念在程式設計中至關重要。例如,“輪子”封裝層與業務消費層對於控制權的“爭奪”,就是一個很有意思的話題。這在 React 世界裡也不例外。表面上看,我們當然希望「輪子」掌控的事情越多越好:因為抽象層處理的邏輯越多,業務呼叫時關心的事情就越少,使用就越方便。可是有些設計卻「不敢越雷池一步」。 「輪子」與業務在控制權上的拉鋸,就非常有意思了。

同時,控制能力與元件設計也息息相關:Atomic components 這樣的原子元件設計被受推崇;在原子元件這個概念之上,還有分子元件:Molecules components。不管是分子還是原子,在解決商業問題上都有存在的理由。

這篇文章將以 React 框架為背景,談談我在開發當中對於控制權的一些想法和總結。如果你不使用 React,原則上仍不妨礙閱讀

在文章開始之前,我想先向大家介紹一本書。

從去年起,我和知名科技大佬顏海鏡開始了合著之旅,今年我們共同打磨的書籍《React 狀態管理與同構實戰》終於正式出版了!本書以 React 技術堆疊為核心,在介紹 React 用法的基礎上,從原始碼層面分析了 Redux 思想,同時著重介紹了服務端渲染和同構應用的架構模式。書中包含許多專案實例,不僅為使用者打開了 React 技術堆疊的大門,更能提升讀者對前沿領域的整體認知。

從受控與非受控元件說起

初入 React 大門,關於控制權概念,我們最先接觸到的就是受控元件與非受控元件。這兩個概念往往與表單關聯在一起。在大部分情況下,建議使用受控元件來實現表單、輸入框等狀態控制。在受控元件中,表單等資料都由 React 元件自行處理。而非受控元件,是指表單的資料由 Dom 自己控制。以下就是一個典型的非受控元件:


     

對於 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 (
      
                        
    )   } }

這時候表單值和行為都由 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>
        <toggle></toggle>
      </p>
    )
  }
}

效果如圖:

React進階設計與控制權的內容介紹

##我們可以透過輸入方塊來控制Toggle 元件狀態切換(輸入「on「 啟動狀態,輸入」off「 狀態置灰),同時也可以透過滑鼠來點選切換,此時輸入方塊內容也會隨之變更。

請思考:對於 UI 元件 Toggle 來說,它的狀態可以由業務呼叫方來控制其狀態,這就賦予了使用層面上的消費便利。在業務程式碼中,不管是 Input 或其他任何元件都可以控制其狀態,呼叫時我們具有完全的控制權掌控能力。

同時,如果在呼叫 Toggle 元件時,就不去傳 props 值,則該元件仍然可以正常發揮。如下:

  <toggle>
    {({on, getTogglerProps}) => (
      <p>
        <button>Toggle me</button>
        </p>
<p>{on ? 'Toggled On' : 'Toggled Off'}</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:

React進階設計與控制權的內容介紹

以上四点都是相关类库基于 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進階設計與控制權的內容介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn