react組件抽象通識篇

前言

爲何會提到一個抽象組件的概念,其實咱們稱其爲高複用組件更好,由於其實在業務開發中不少時候會有這樣的場景,咱們的某部分功能是能夠共用給其餘部分的,但這部分又不太可能脫離組件或者某個基準數據存在。因而,咱們須要將這部分代碼進行必定的抽象或者說設計。javascript

mixin

混入在其餘編程語言中很是常見,在es6的語法中已經提到了裝飾器的語法,其實裝飾器就是混入的基本實現。下面咱們實現下js版本的mixin。vue

function mixins(obj,mixins){
  let newObj = obj;
  newObj.prototype = Object.create(obj.prototype);
  for(let p in mixins){
    if(mixins.hasOwnProperty(p)){
    	newObj[p] = mixins[p];
    }
  }
  return newObj;
}
複製代碼

看完以後,發現其實現其實和lodash的assign以及underscore的extend方法很是相似。那麼結合react,以前的方式是咱們在react的中能夠定義一個mixins數組共享一些方法。在vue中也有相似的方式。不過因爲這種方式會致使不靈活的使用,已經被高級組件所代替。java

class App extends React.creatClass({
	mixins:[fn1],
  render(){
  }
})
複製代碼

高級組件

點擊跳轉查看個人另外一篇文章: 連接react

屬性代理

屬性代理是咱們最多見的使用方式,它能夠將指定的屬性傳入,並返回帶有這些屬性的任意組件。點擊查看個人codesanbox地址:連接es6

// 包裝組件的容器
import React from "react";

export const MyContainer = WrappedComponent =>
  class extends React.PureComponent {
    componentWillMount() {
      this.setState({ type: 1 });
    }

    render() {
      return <WrappedComponent type={this.state.type} />; } }; // 具體使用 直接用函數封裝傳遞 import React from "react"; import { MyContainer } from "./MyContainer"; class Hoc extends React.PureComponent { constructor(props) { super(props); this.state = {}; } render() { let { type } = this.props; return <div>高階組件{type}</div>; } } export default MyContainer(Hoc); 複製代碼

控制props

不管咱們刪除仍是編輯屬性的部分,咱們都應該儘量最高階組件的props作新的命名來防止混淆。例如咱們須要添加一個新的prop.因而咱們須要保留原有的屬性,這是必要的。這樣使用高級組件就可使用新的屬性,而原有組件不使用的時候仍然是無損的。(其中對象的拓展符是很方便的,在不肯定有哪些屬性或者屬性很是多的時候,很是建議使用這個語法特性)。編程

// 包裝組件的容器
import React from "react";

export const MyContainer = WrappedComponent =>
  class extends React.PureComponent {
    componentWillMount() {
    }
    render() {
      const newProps = {
      	text:1
      }
      return <WrappedComponent {...this.props} {...newProps} />; } }; 複製代碼

經過refs使用引用

在高階組件中,咱們能夠經過refs來使用WrappedComponent的引用。看上去與上面的控制屬性麼有什麼差異,實際上,每當子組件執行的時候,refs的回調函數就會執行,它能夠方便的調用或者讀取實例的props.換一句說法,這裏能夠實現調用子組件的方法,除了實現部分組件鉤子,還能夠根據需求靈活的進行一些方法調用。數組

以爲很沒有想法,找不到什麼場景下會有這種需求,給你們舉個例子,好比容器組件想主動調用子組件的某個方法或者讀取其某個值的狀態,在我作業務開發的時候,就有一種場景,用戶在容器組件的某個操做,須要主動刷新子組件的一些數據,還有執行子組件的一些事件,按照常規方式,是沒有主動觸發這一條的。由於咱們的通常的通信是經過子組件使用父組件的回調函數來實現的。那麼假如是這種場景,咱們直接封裝一個這種需求的高級組件即可,而後在根據不斷變動的需求,去維護固定的一個或者多個高階組件。bash

// 包裝組件的容器
import React from "react";

export const MyContainer = WrappedComponent =>
  class extends React.PureComponent {
    proc(WrappedComponentInstance) {
     WrappedComponentInstance &&  WrappedComponentInstance.method();
    }
    render() {
      const props = Object.assign({},this.props,{ref:this.proc.bind(this)});
      return <WrappedComponent {...props} />; } }; 複製代碼

抽象state

這一層設計的緣由是咱們在考慮設計我函數組件仍是狀態組件時常常考量的一點,在react的組件設計思想中,咱們判斷的核心標準是組件自己是否有狀態,是否須要根據數據的狀態靈活的變化,也就是是否對setState的更新視圖操做有強依賴,是不是屢次渲染,若是有,那麼是建議的使用帶狀態組件,不然建議你使用無狀態組件,也就是函數組件。架構

可是咱們在開發某些業務時,發現耦合了太多交互邏輯以及狀態邏輯在組件中,而這些代碼設計是可重用的。好比咱們都是展現用戶信息,都是點擊某個位置,更新用戶信息,只是展現的位置以及渲染有差別。那麼咱們該如何作?那就是抽象出這部分state,原來的組件變爲函數組件。(若是你只有一個組件中這樣,能夠沒必要提取,若是出現多個,建議這樣使用高階組件抽象一次)。app

// 包裝組件的容器
import React from "react";

export const MyContainer = WrappedComponent =>
// 在這個組件中完成全部的數據變動 和 交互邏輯,完成後屬性傳遞給渲染組件便可
  class extends React.PureComponent {
    constructor(props){
      super(props);
      this.state ={
      //xxx
      }
    }
    method1(){
    }
     method2(){
    }
    render() {
      const newProps = {};
      return <WrappedComponent {...this.props} {...newProps} />; } }; 複製代碼

使用其餘元素包裹組件

實際上咱們除了上面的用途,還能夠根據本身的須要去靈活的對組件的樣式,外層空間,等任意的自定義。好比咱們常常須要對一些組件規定它的大小位置,或者就是指定一些有規律的className.剩下的空間自行發揮,這裏只是提醒你們高階組件有如此的一個使用場景。

// 封裝函數裏的返回class render函數裏
render(){
 return <div className="side-bar">
 <WrappedComponent  className="side-bar-content" {...this.props} {...newProps} />
 </div>;
}
複製代碼

反向繼承

說的簡單一點就是在封裝高級組件的時候對包裝組件使用繼承。其基本的寫法以下:

const MyContainer = WrapperComp =>(
	class extends WrapperComp{
  	render(){
    return super.render()
    }
  }
)
複製代碼

這種方法與屬性代理不一樣,它能夠經過super方法來獲取組件的屬性以及方法。下面會詳細說明其帶來的兩個特色:渲染劫持以及state控制。在瞭解這個以前,咱們有必要了解下其生命週期以及其會帶來的影響。

備註:同名的方法以及生命週期,若是你再次申明會被覆蓋。你能夠經過我寫的hocSuper的例子查看這個問題。

渲染劫持

說的直白一點就是控制如何渲染原來已經肯定好的輸出渲染的某部分,咱們在許多業務中其實已經加入了相似的代碼,好比 hasRight && .只不過如今咱們的場景是把這部分的代碼用在反向繼承中的組件上。它的代碼多是下面這樣的。

render(){
  return(
  <div> {hasRight ? return super.render(): <span>無權限提示文本</span>} </div>
  )
}
複製代碼

固然上面的控制看起來很是簡單,沒有什麼華麗的技巧,咱們更須要的多是下面這樣的渲染劫持。拿到渲染的樹以後,咱們改變其某些節點的狀態。

render(){
  const elementsTree = super.render();
  let newProps = {};
  const props = Object.assign({},elementsTree.props,newProps);
  const newElementsTree= React.cloneElement(elementsTree,props,props.children);
  return newElementsTree
}
複製代碼

控制state

咱們可在高階組件中刪除或者修改組件的state,但爲了不一些低級的問題,**咱們不建議直接修改甚至刪除其原具備的state,更建議的方式是新建以及重命名。**若是你不確認原來的組件具備哪些屬性以及方法,能夠嘗試着用JSON.stringify來序列化展現,固然更好的方式你能夠經過開發工具好比devTool去查看這些。

組件命名

當咱們用高級組件時,咱們失去了原來組件的名字,咱們能夠經過簡單的命名規則爲HOC${getDisplayName(WrappedComponent)}來實現,其中getDisplayName函數寫法能夠參考下面的方式:或可使用 recompose 庫,它已經幫咱們實現了相應的方法。

function getDisplayName(WrappedComponent){
	return WrappedComponent.dispalyName || WrappedComponent.name || 'Component';
}

// recompose 方法
// Any Recompose module can be imported individually
import getDisplayName from 'recompose/getDisplayName'
ConnectedComponent.displayName = `connect(${getDisplayName(BaseComponent)})`

// Or, even better:
import wrapDisplayName from 'recompose/wrapDisplayName'
ConnectedComponent.displayName = wrapDisplayName(BaseComponent, 'connect')
複製代碼

組件參數

有不少時候,咱們給高級組件添加一些靈活的參數,而不只僅是使用組件做爲參數,那麼咱們多一層嵌套便可實現。

import React, { Component } from 'React';
function HOCFactoryFactory(...params) {
// 能夠作一些改變 params 的事
	return function HOCFactory(WrappedComponent) {
        return class HOC extends Component { render() {
          return <WrappedComponent {...this.props} />; } } } //當你使用的時候,能夠這麼寫: HOCFactoryFactory(params)(WrappedComponent) // 或者 @HOCFatoryFactory(params) class WrappedComponent extends React.Component{} 複製代碼

混合與高階組件的對比

image.png

高階組件

高階組件屬於函數式編程(functional programming)思想,對於被包裹的組件時不會感知到高階組件的存在,而高階組件返回的組件會在原來的組件之上具備功能加強的效果。

mixin 退出的緣由

雖然咱們知道mixin被慢慢的廢棄,可是咱們仍是有必要了解下用這個的問題是那些?而顯然新的高級組件是能解決這些問題的。這也將有益於咱們理解一些高級組件設計的優點。

  • 破壞了原有組件的封裝,可能會增長其餘的狀態侵入以及可能的混入依賴關係
  • 不一樣的mixins存在的命名衝突
  • 增長了組件的複雜性:混入了各類方法以及爲生命週期能夠添加了不一樣方法

組合組件開發

它指的是當咱們進行一些高階組件的開發的時候,發現不少時候不斷的去調整屬性,同時爲了減小對已經在使用的部分,通常是高級組件的屬性都是增長,累加下去會致使配置了不少可能無用的屬性。

組件再分離

也就是將組件進一步細分,每個組件均可以儘量的原子化,而後稍高階的組件經過組裝完成咱們所看到的一個基本組件。好比下圖理解下:

image.png

實際上這種思想,咱們也偶爾會使用,只不過沒有造成一些固定的思惟設計思路。實際上,無論咱們是設計的可重用的組件,仍是說就是寫業務組件,頁面組件,咱們都應該考慮組件的拆分。讓每一個組件內部儘量的細化,拆分紅若干具備單獨解耦的獨立渲染的邏輯或者子組件。

咱們在ant的input組件中,能夠看到其組件目錄每個文檔上基本的組件都是有入口文件,若干的小組件拼裝而成。

image.png

咱們在寫組件的時候也要有這樣的思惟模式,好比一個帳單的顯示,本來是這樣的:

// old way 
render (){
		return (
    <div>
        <h2>標題</h2>
        // 列表數據的渲染
        {list.map(item)=>(<div className="m-docItem">
            <img src={item.headimg}/>
            <span>{item.docName}</span>
            <p>{item.resume}</p>          
          </div>)}
      </div>
    )
}

//new way as a class fun
renderDocItem(list){
  return ({
  list.map(item)=>(<div className="m-docItem">
            <img src={item.headimg}/>
            <span>{item.docName}</span>
            <p>{item.resume}</p>          
          </div>)
  })
}
// new way as a single fun comp
export const RenderDocItem(props){
  const {list}= props;
  return ({
  list.map(item)=>(<div className="m-docItem">
            <img src={item.headimg}/>
            <span>{item.docName}</span>
            <p>{item.resume}</p>          
          </div>)
  })
}
render(){
	return(
   <div>
        <h2>標題</h2>
      // 方法的方式
      	{this.renderDocItem(list)}
      // 函數組件的方式
      <RenderDocItem list={list}/>
    </div>  
  )
}
複製代碼

咱們在庫中也常常看到這樣的代碼維護方式:養成這樣的編碼習慣,會讓你的代碼可維護性大大的加強。

image.png

邏輯再抽象

好比咱們針對輸入框的值進行監聽以後執行某個特定的事件,而這個事件自己發現可重用的位置不少,和輸入框自己是沒有重度關聯的,那麼針對這個場景,若是你有強迫症,能夠抽象一波。

// 完成 SearchInput 與 List 的交互
const searchDecorator = WrappedComponent => {
    class SearchDecorator extends Component { 
      constructor(props) {
        super(props);
        this.handleSearch = this.handleSearch.bind(this); 
      }
      handleSearch(keyword) { 
        this.setState({
          data: this.props.data,
          keyword, });
        this.props.onSearch(keyword); 
        }
      render() {
        const { data, keyword } = this.state;
        return (
        <WrappedComponent {...this.props} data={data} keyword={keyword} onSearch={this.handleSearch} /> ); } } return SearchDecorator; } 複製代碼

圖解組合組件開發的架構

image.png

小結

經過本文但願咱們能瞭解到高階組件的一些基本設計思路,能解決的組件組合的痛點。使用react越久咱們越會發現,對於一個比較複雜的系統,若是你有特別思考過組件的可重用這個問題,而不只僅是一個頁面一個組件,附加基本的ui框架,設計好這些組件的組合方式,如何抽離等都是一個很考驗你能力的部分。

相關文章
相關標籤/搜索