React組件封裝思路拓展

若是你以爲能夠,請多點贊,鼓勵我寫出更精彩的文章🙏。
若是你感受有問題,也歡迎在評論區評論,三人行,必有我師焉
javascript

TL;DR

  • 前言
  • 常規組件化思路
  • Hooks組件
  • 很是規組件(有奇特的效果,重點是這個)

前言

在個人一些文章中,不論是本身寫的仍是翻譯國外優秀開發人員博客的。其中一個主線就是,瞭解了JS基礎,來進行前端模塊化實踐。尤爲如今前端比較流行的框架ReactVue都是提倡進行對頁面利用組件進行拆分。這其實就是MVVM的設計思路。前端

而咱們今天以React項目開發,來聊聊如何在實際項目中實現組件化,或者如何進行高逼格的邏輯服用。java

如下所講內容,都是本人平時開發中用到的,而且都實踐過的(若是你們有更好的想法,也能夠留言討論,畢竟每一個人對一個事件的見解或多或少有一些認知的差別)。react

我信奉一句話,實踐出真知,沒有實踐就沒有發言權webpack

常規組件化思路

這裏起的標題是常規,是由於這個篇幅中所講的內容也是React官方畢竟推崇的常規方案(很是規方案,例如Hooks因爲API比較新穎對於一些開發中,比較陌生,同時受公司React版本的影響,只是停留在理論階段。我將Hooks會單獨拎出來,講一些小🌰)web

若是你們常常翻閱React官網,在ADVANCED GUIDES中明確指出了,兩個實現邏輯服用的方式:HOCRender Props算法

因此咱們來簡單介紹一下。編程

HOC

來看一下官方的解釋:api

A higher-order component (HOC) is an advanced technique in React for reusing component logic.數組

其實若是對JS高級函數有過了解的童鞋,看到這個其實不會感到很陌生。而HOC就是模仿高級函數,來對一些比較共用的邏輯,進行提煉。從而達到代碼複用的效果。

繼續給你們深挖一下,高階函數能夠接收函數作爲參數,同時將函數返回。

function hof(f){
    let name="北宸";
    return f(name);
}

function f(name){
    console.log(`${name} 是一個萌萌噠的漢子!`)
}

複製代碼

看到這點是否是有點閉包的身影。也就是說,被包裹的函數擁有hof中所定義變量的訪問權限。

若是你們想對閉包做用域有一個比較深入的瞭解能夠參考

  1. JS閉包(Closures)瞭解一下
  2. 帶你走進JS做用域(Scope)的世界

這題有點跑偏了。咱們進入正題。

const EnhancedComponent = higherOrderComponent(WrappedComponent);
複製代碼

這是對HOC的一個簡單公式的歸納。經過剛纔講解hof其實對這個就輕車熟路了吧。

Talk is cheap,show your the code

function EnhancedComponent(WrappedComponent){
    //其實,這裏也能夠定義一些比較共用的參數和邏輯
    return class extends React.Component{
        //
       state={
           //定義一些公共屬性
           name:'北宸'
       }

    componentDidMount() {
        //作一些數據初始化處理
    }

    componentWillUnmount() {
     //針對一些註冊事件例如輪詢事件的解綁
    }

    handleChange() {
     //自定義事件處理
    }
    render() {
        //這裏最好作一下結構處理
        const {name} = this.state;
      return <WrappedComponent 
      parentData={name} //將一些共用的數據傳入
      {...this.props} //簡單的傳入傳出
      />;
    }
    }
}
複製代碼

這就是一個簡單的HOC例子。 若是你們想對HOC有一個更深的瞭解能夠參考

  1. React-HOC瞭解一下(這是我早期寫的,可能有點Low)

Render Props

這也是一個常規邏輯複用的方式。

先來一個總結,其實就是在組件屬性中有一個屬性,而該屬性是一個函數,函數返回了一個組件。

<RenderProps renderRegion={(value)=><CommonConponenet value={value}/>}
複製代碼

稍微解釋一下RenderProps組件包含中一些共有邏輯和數據,而這些邏輯和數據,偏偏是CommonConponenet須要的。

Talk is cheap,show your the code

class RenderProps extends React.Component{
    state={
        name:'北宸'
    }
    //一堆生命週期方法
    //一堆自定義方法
    render(
        const {name} = this.state;
        return (
            <section>
                <>
                    //RenderProps本身的頁面結構
                </>
                //若是傳入的數據過多,這裏可使用一個對象把數據進行包裝
                {this.props.renderRegion(name)}
            </section>
        )
    )
}
複製代碼

想必,你們在平時開發中,或多或少都用到這些比較常規的方式。

上面只是簡單的介紹了一下。其實本文的重點是下面的一些很是規操做。

Hooks組件

想必你們在翻閱一些技術文檔,有不少,關於Hooks用法的介紹。本文就不在囉嗦了,而今天給你們準備說一些很是規操做。若是實在想了解能夠參考我翻譯的一篇關於Hooks的簡單應用。

  1. 後生,React-Hooks瞭解一下

首先,有一點須要明確,hooks的推出,是針對函數組件。是讓函數組件,也能享受state/生命週期帶來的愉悅感。

給你們一個應用場景,就是有一個組件,有一個定時功能,須要一個定時器,暫且定位15min內定時器走完,並在定時器完成以後進行額外的操做。

經過上面的需求,咱們來分析一波。

  1. 有一個定時功能,那勢必就是在組件初始化時,要觸發這個定時器,在觸發定時器以後,定時器會額外的開出一個線程進行數據處理。
  2. 每次時間變化,都須要顯示到頁面中
  3. 咱們須要監聽定時器是否到達要求,(15min以後,須要額外操做)
  4. 觸發額外操做
  5. 若是在15min以內,把組件銷燬,還須要將定時器銷燬

Talk is cheap,show your the code

import React,{useState,useEffect} from 'react';
function Test(props){
    //從父組件來的數據
    const {name}=props;
    const [msg, setMsg] = useState("二維碼有效時間:15分00秒");
    let timer, maxtime = 15 * 60;
    //1 初始化一個定時器,(在組件初始化時),
    useEffect(()=>{
         timer = setInterval(() => {
      if (maxtime >= 0) {
        let minutes = Math.floor(maxtime / 60);
        let seconds = Math.floor(maxtime % 60);
        let msg = "二維碼有效時間:" + minutes + "分" + seconds + "秒";
        --maxtime;
        //2. 這裏定義一個state,變量,用於頁面顯示
        setMsg(msg)
      } else {
        clearInterval(timer);
        //若是在正常狀況下時間走完,進行額外操做
        //3. 這裏能夠是更新頁面,也能夠進行事件回調
      }
    }, 1000)
    return ()=>{
        clearInterval(timer); //在組件銷燬時,將定時器銷燬
    }
    },[])//這裏爲何須要第二個參數?能夠參考我剛纔給的連接,尋找緣由
    
    function stopTimer(){
        clearInterval(timer);
    }
    
    return(
        <section>
            <Button onClick={()=>{this.stopTimer()}}>中止計時</Button>
            {mag}
        </section>
    )
}
複製代碼

很是規組件

該部分,是本人在平時項目開發中,有時候會用到的組件封裝思路,若是你們有好意見和建議,能夠互相討論。總之,想到達的目的就是取其精華,去其槽粕

由於在項目開發中用的UI庫是Antd,有些使用方式也是基於它的使用規則去使用的。

RandomKey

若是你在React項目中,經過遍歷一個數組來渲染一些組件,例如:li。若是你不給加一個key在控制檯會有警告出現。這個key也是React隱藏屬性。是爲了Diff算法用的。

可是,有一些場景,比方說,一些彈窗,是須要用戶填寫一些信息的,而有一些用戶在填寫的時候,有取消的操做,常規的處理方式是否是,須要遍歷,並經過對比原來的信息進行數據的恢復。

而,若是你可以在組件調用處監聽到用戶的取消事件,那提供一個比較方便,可是這也是屬於無奈之舉的方式。RandomKey

<Test key={Math.random() />}
複製代碼

State組件

這個名詞是本人,擅自命名的,若是你們感受有好的名字,在評論區告訴我。

若是在React開發項目中,你前期可能規劃的很好,循序漸進的去寫頁面,可是,可是,可是,你架不住,產品天天不停的改需求,而改來改去,發現本身原先規劃的東西本身都看不懂了。

或者,大家老大讓你去維護別人寫的代碼,追加一個新的需求,而當你看到別人寫的代碼時候,心涼了。這TM是人能看懂的代碼嗎,不說邏輯是否,負責,TM一個頁面的全部功能都堆砌在一個文件中。(這裏說堆砌一點都不爲過,我看過別人寫一個頁面1000行代碼,20多個state)。

比方說,如今有以下的組件,須要新增一個彈窗,而新增一個彈窗,咱們來簡單的細數一下須要的變量

  1. visibleForXX
  2. valueXX

暫且就按最少的來

class StateComponent extends React.Component{
    state={
        //這裏忽略7-10個參數
        visibleForXX:false,
        valueXX:'北宸',
    }
    
    handleVisible=(visible)=>{
        this.setState({
            visibleForXX:visible
        })
    }
    
    render(
    const {visibleForXX,valueXX}= this.state;
    const {valueFromPorpsA,valueFromPorpsB} = this.props;
    return (
        <section>
            //這裏是700多行的魔鬼代碼
             <Button onClick=(()=>this.handleVisible(true))>我叫一聲你敢答應嗎</Button>
             <Button onClick=(()=>this.handleVisible(false))>我不敢</Button>
            {visibleForXX?
                <Modal
                    visible={visibleForXX}
                    valueXX={valueXX}
                    valueFromPorpsA={valueFromPorpsA}
                    //....若是邏輯負責,可能還不少
                >
                 //bala bala 一大推
                </Modal>
                :null
            }
        </section>
    )
    )
}
複製代碼

這樣寫有毛病嗎,一點毛病都沒有,能實現功能嗎,。能交差嗎,。能評優嗎,評優和代碼質量有毛關係。後面維護你的代碼的人,能問候你親戚嗎,我感受也能

同時你們發現一個問題嗎,雖然我在寫代碼的時候,特地用了解構,可是每次不論是否和該組件相關的有關的渲染,都會進行一次按做用域鏈查找。有沒有必要,這個尚未。

那有啥能夠解決呢,有人會說,那你不會拆一個組件出來啊。能夠,若是按我平時開發,這個新功能通常都是一個組件。可是,把上述Modal代碼拆出去,其實仍是會有每次render做用域鏈查找問題

其實,咱們能夠換種思考思路。

直接上菜吧。

import Modal from '../某個文件路徑,固然也能夠用別名'
class StateComponent extends React.Component{
    state={
        //這裏忽略7-10個參數
        ModalNode:null
    }
    
    handleClick=()=>{
        const {valueFromPorpsA,valueFromPorpsB} = this.props;
        this.setState(
        ModalNode:<Modal 
                     visible={visibleForXX}
                    valueXX={valueXX}
                    valueFromPorpsA={valueFromPorpsA}
                    //....若是邏輯負責,可能還不少
        />
        )
    }
    
    render(
    const {ModalNode}= this.state;
    return (
        <section>
            //這裏是700多行的魔鬼代碼
            <Button onClick=(this.handleClick)>我叫一聲你敢答應嗎</Button>
            {ModalNode?
                ModalNode
                :null
            }
        </section>
    )
    )
}
複製代碼

把組件換一個位置,他不香嗎。用最密集的代碼作相關的事。雖然,有可能接收你代碼的人,可能會罵街,可是等他替換一個簡單的參數。就不須要在1000行代碼中遨遊了

上述例子,只是簡單說了一下思路,可是確定能實現,因爲篇幅問題,就不CV又臭又長的代碼了。

自我控制顯隱的Modal

有沒有遇到這麼一個需求,須要在某一個頁面中,新增一個彈窗,而這個新增的任務又雙叒叕交給你了。

而你欣然接受,發現須要新增的地方,又是一堆XX代碼。(你懂我說的意思)

咱們繼續分析一下常規Modal(在已經將Modal封裝成一個組件前提下)具有的條件(在調用處須要準備的東西)

  1. visible
  2. this.handleVisible(false)
  3. this.handleOk(這裏可能有額外操做)

列舉的,是最簡單的,可能有比這還複雜的。this.handleVisible(false)是控制Modal關閉的。

而後你又繼續bala bala的寫。若是一個頁面存在1-2個modal仍是能夠接受,可是若是是4-5個呢,你仍是用這種方式,個人天,恭喜你成爲了CV俱樂部高級會員。

我能夠弱弱的卑微的提出兩點

  1. Modal的殼子封裝成組件,你只負責content的書寫
  2. 封裝的Modal本身去控制顯隱

咱們着重說一下2,由於這個篇幅是2的主場。

Talk is cheap,show your the code

組件調用方式,會發現,只要引入對應的組件,而且定義一個孩子節點,就能夠實現一個控制顯隱的組件。

<OperatingModal>
    <Button>點擊</Button>
</OperatingModal>
複製代碼

Modal簡單實現思路

import React from 'react';
import { Modal } from 'antd';
export class OperatingModal extends React.Component {
    static getDerivedStateFromProps(nextProps, state) {
        if ('visibleFromParent' in nextProps && nextProps.visibleFromParent && !state.visible) {
            return {
                visible: nextProps.visibleFromParent
            }
        }
        return null;
    }
    state = {
        visible: false
    }

    handleVisible = (visible) => {
        this.setState({
            visible: visible
        });
    }

    handleOk = (secondNode) => {
       //控制顯示隱藏
    }

    renderFooter = () => {
        const { footer, footerPosition } = this.props;
        let footerNode = footer;
        if (footer != null) {
            let footerChidren = footerNode.props.children;
            footerNode = <div >
                {footerChidren.length > 1 ?
                    <React.Fragment>
                       //克隆處理
                    </React.Fragment>
                    : footer}
            </div>
        }
        return footerNode;

    }

    render() {
        const { title, children, content, width, closable, zIndex } = this.props;
        const { visible } = this.state;
        return (
            <section>
                {children ?
                    React.cloneElement(children, { onClick: () => this.handleVisible(true) })
                    : null
                }
                <Modal
                   //常規配置
                >
                    {content}
                </Modal>
            </section>
        )
    }
}
OperatingModal.defaultProps = {
    title: '提示',
    content: '我是內容',
    children: null,//須要包裹的孩子節點
    footer: null,
    visibleFromParent: false,
    width: 300,
    closable: true,
    footerPosition: 'center',
    zIndex: 1000,
}

複製代碼

這是我簡單的一個版本,你若是正好用antd,你能夠嘗試用一下。

有一點須要說明,這裏用到了,一個React屬性React.cloneElement。具體API講解能夠參考官網。

自帶from提交的Modal

這個例子是基於2(自我控制顯隱的Modal)的升級版本,只提供一個思路。主要代碼以下。

該組件具有的功能:

  1. form表單和非form表單
  2. 自控顯隱
  3. 防重複提交
  4. from 提交以後,可以獲取到接口結果,進行其餘操做
  5. 父組件也能夠調用顯示隱藏
  6. ....

組件調用方式

<AdaptationFromModal>
    <From.Item>
        //.....
    </From.Item>
    //不少From.Item
</AdaptationFromModal>

複製代碼

組件實現大體思路

import React, { Component } from 'react';
import {
  Modal, Form, Button, message,
} from 'antd';
import throttle from 'lodash.throttle';

class AdaptationFromModal extends Component {
  constructor(props, context) {
    super(props, context);
    this.state = {
      visible: false,
      isSubmitLoading: false,
      randomKey: 1,
    };
    this.handleOK = this.handleOK.bind(this);
    this.handleOKThrottled = throttle(this.handleOK, 4000);
  }

  componentWillUnmount() {
    this.handleOKThrottled.cancel();
  }

  renderFormCotent = () => {
    let formContentNode = null;
    const { formItemLayout } = this.props;
    formContentNode = (
      <Form >
       //對renderCotent進行遍歷
      </Form>
    );
    return formContentNode;
  }

  renderNormalContent = () => {
    let normalContentNode = null;
    normalContentNode = this.props.renderCotent(//能夠將組件值拋出去);
    return normalContentNode;
  }

  webpackModalVisibleStatus = (visible = false) => {
    this.setState({
      visible: visible,
    }, () => {
     //根據不一樣的visible處理相關的代碼
    });
  }

  handleOK() {
    const { isIncludeForm, callbackForOK, callbackForOKAfter } = this.props;
    this.setState({
      isSubmitLoading: true,
    });
    if (isIncludeForm) {
      // 進行接口數據的處理
        
    }
  }

  renderFooter = () => {
    const {
      isHasPersonFooter, defineFooterNode, callbackForOK, callbackForOKAfter,
    } = this.props;
    const { isSubmitLoading } = this.state;
    let footerNode = null;
    if (isHasPersonFooter) {
      if (defineFooterNode !== null) {
        footerNode = defineFooterNode(//這裏能夠將組件內部的值,拋出去  );
      }
    } else {
      footerNode = (
       //常規渲染
      );
    }
    return footerNode;
  }


  render() {
    const { visible, randomKey } = this.state;
    const {
      width, isIncludeForm, title, children, style,
    } = this.props;
    return (
      <section style={style || {}}>
        {children
          ? React.cloneElement(children, { onClick: () => this.webpackModalVisibleStatus(true) })
          : null}
        {visible
          ? (
            <Modal
             //常規Modal配置
            >
              {
                isIncludeForm ? this.renderFormCotent() : this.renderNormalContent()
              }
            </Modal>
          )
          : null}
      </section>
    );
  }
}
AdaptationFromModal.defaultProps = {
  title: 'xxx', //
  width: 600,
  isIncludeForm: true,
  isHasPersonFooter: false,
  callbackForOkClick: null, // 點擊肯定的回調函數
  formItemLayout: {
    labelCol: { span: 6 },
    wrapperCol: { span: 14 },
  },
  style: undefined,
};


export default Form.create()(AdaptationFromModal);

複製代碼

Antd From殼子

有些公司業務中,可能有表單啊,相似的功能,就是簡單的form處理。可是,antd的在進行表單處理的時候,須要不少冗餘的配置。以下:

<Form layout="inline" onSubmit={this.handleSubmit}>
        <Form.Item validateStatus={usernameError ? 'error' : ''} help={usernameError || ''}>
          {getFieldDecorator('username', {
            rules: [{ required: true, message: 'Please input your username!' }],
          })(
            <Input
              prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
              placeholder="Username"
            />,
          )}
        </Form.Item>
        <Form.Item validateStatus={passwordError ? 'error' : ''} help={passwordError || ''}>
          {getFieldDecorator('password', {
            rules: [{ required: true, message: 'Please input your Password!' }],
          })(
            <Input
              prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
              type="password"
              placeholder="Password"
            />,
          )}
        </Form.Item>
      </Form>
複製代碼

比方說有些Form.ItemgetFieldDecorator這些看起來很礙眼。是否是有一種處理方式,就是簡單的配置一些頁面須要的組件,而這些處理放在一個殼子裏呢。

<SearchBoxFactory
        callBackFetchTableList={this.callBackFetchTableList}
        callBackClearSearchCondition={this.callBackClearSearchCondition}
      >
        <Input
          placeholder={'請輸入你的姓名'}
          formobj={{
            apiField: 'XXA', initialValue: '', maxLength: 50, label: '北宸',
          }}
        />
        <Input
          placeholder={'請輸入你的性別'}
          formobj={{
            apiField: 'XXB', initialValue: '', maxLength: 50, label: '南蓁',
          }}
        />
        <Select
          style={{ width: 120 }}
          formobj={{
            apiField: 'status', initialValue: '1',
          }}
        >
          <Option value="1">我帥嗎?</Option>
          <Option value="2">那必須滴。(錦州語氣)</Option>
        </Select>
      </SearchBoxFactory>
複製代碼

這種處理方式他不香嗎。就問你香不香。

總結

其實React中組件化,是一個編程思路,我感受,懶人才能夠真正的領略到他的魅力,由於懶人才能夠想出讓本身書寫代碼,更快,更高,更強的方式。

這也是一種編程習慣和方式。用最短的時間,寫最少的代碼,幹最漂亮的代碼。

但願我囉嗦這麼多,能對你有所幫助。若是你們看到有哪裏不是很明白的,能夠評論區,留言。若是感受還不是很盡興。不要緊,這些東西,有不少。

其實天天都在吵吵組件化,組件化,組件化是在實際開發項目中,在解決實際業務中,纔會有靈感。他主要的目的是解決實際問題,而不是閉門造車

相關文章
相關標籤/搜索