若是你以爲能夠,請多點贊,鼓勵我寫出更精彩的文章🙏。
若是你感受有問題,也歡迎在評論區評論,三人行,必有我師焉javascript
在個人一些文章中,不論是本身寫的仍是翻譯國外優秀開發人員博客的。其中一個主線就是,瞭解了JS基礎,來進行前端模塊化實踐。尤爲如今前端比較流行的框架React
、Vue
都是提倡進行對頁面利用組件進行拆分。這其實就是MVVM的設計思路。前端
而咱們今天以React
項目開發,來聊聊如何在實際項目中實現組件化,或者如何進行高逼格的邏輯服用。java
如下所講內容,都是本人平時開發中用到的,而且都實踐過的(若是你們有更好的想法,也能夠留言討論,畢竟每一個人對一個事件的見解或多或少有一些認知的差別)。react
我信奉一句話,實踐出真知,沒有實踐就沒有發言權webpack
這裏起的標題是常規,是由於這個篇幅中所講的內容也是React
官方畢竟推崇的常規方案(很是規方案,例如Hooks
因爲API比較新穎對於一些開發中,比較陌生,同時受公司React
版本的影響,只是停留在理論階段。我將Hooks
會單獨拎出來,講一些小🌰)web
若是你們常常翻閱React
官網,在ADVANCED GUIDES中明確指出了,兩個實現邏輯服用的方式:HOC
和Render Props
。算法
因此咱們來簡單介紹一下。編程
來看一下官方的解釋: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
中所定義變量的訪問權限。
若是你們想對閉包、做用域有一個比較深入的瞭解能夠參考
這題有點跑偏了。咱們進入正題。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
複製代碼
這是對HOC
的一個簡單公式的歸納。經過剛纔講解hof
其實對這個就輕車熟路了吧。
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
有一個更深的瞭解能夠參考
這也是一個常規邏輯複用的方式。
先來一個總結,其實就是在組件屬性中有一個屬性,而該屬性是一個函數,函數返回了一個組件。
<RenderProps renderRegion={(value)=><CommonConponenet value={value}/>}
複製代碼
稍微解釋一下RenderProps
組件包含中一些共有邏輯和數據,而這些邏輯和數據,偏偏是CommonConponenet
須要的。
class RenderProps extends React.Component{
state={
name:'北宸'
}
//一堆生命週期方法
//一堆自定義方法
render(
const {name} = this.state;
return (
<section>
<>
//RenderProps本身的頁面結構
</>
//若是傳入的數據過多,這裏可使用一個對象把數據進行包裝
{this.props.renderRegion(name)}
</section>
)
)
}
複製代碼
想必,你們在平時開發中,或多或少都用到這些比較常規的方式。
上面只是簡單的介紹了一下。其實本文的重點是下面的一些很是規操做。
想必你們在翻閱一些技術文檔,有不少,關於Hooks
用法的介紹。本文就不在囉嗦了,而今天給你們準備說一些很是規操做。若是實在想了解能夠參考我翻譯的一篇關於Hooks
的簡單應用。
首先,有一點須要明確,hooks
的推出,是針對函數組件。是讓函數組件,也能享受state
/生命週期帶來的愉悅感。
給你們一個應用場景,就是有一個組件,有一個定時功能,須要一個定時器,暫且定位15min內定時器走完,並在定時器完成以後進行額外的操做。
經過上面的需求,咱們來分析一波。
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,有些使用方式也是基於它的使用規則去使用的。
若是你在React
項目中,經過遍歷一個數組來渲染一些組件,例如:li
。若是你不給加一個key
在控制檯會有警告出現。這個key
也是React
中隱藏屬性。是爲了Diff
算法用的。
可是,有一些場景,比方說,一些彈窗,是須要用戶填寫一些信息的,而有一些用戶在填寫的時候,有取消的操做,常規的處理方式是否是,須要遍歷,並經過對比原來的信息進行數據的恢復。
而,若是你可以在組件調用處監聽到用戶的取消事件,那提供一個比較方便,可是這也是屬於無奈之舉的方式。RandomKey
。
<Test key={Math.random() />}
複製代碼
這個名詞是本人,擅自命名的,若是你們感受有好的名字,在評論區告訴我。
若是在React開發項目中,你前期可能規劃的很好,循序漸進的去寫頁面,可是,可是,可是,你架不住,產品天天不停的改需求,而改來改去,發現本身原先規劃的東西本身都看不懂了。
或者,大家老大讓你去維護別人寫的代碼,追加一個新的需求,而當你看到別人寫的代碼時候,心涼了。這TM是人能看懂的代碼嗎,不說邏輯是否,負責,TM一個頁面的全部功能都堆砌在一個文件中。(這裏說堆砌一點都不爲過,我看過別人寫一個頁面1000行代碼,20多個state
)。
比方說,如今有以下的組件,須要新增一個彈窗,而新增一個彈窗,咱們來簡單的細數一下須要的變量
暫且就按最少的來
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又臭又長的代碼了。
有沒有遇到這麼一個需求,須要在某一個頁面中,新增一個彈窗,而這個新增的任務又雙叒叕交給你了。
而你欣然接受,發現須要新增的地方,又是一堆XX代碼。(你懂我說的意思)
咱們繼續分析一下常規Modal
(在已經將Modal封裝成一個組件前提下)具有的條件(在調用處須要準備的東西)
列舉的,是最簡單的,可能有比這還複雜的。this.handleVisible(false)
是控制Modal
關閉的。
而後你又繼續bala bala的寫。若是一個頁面存在1-2個modal仍是能夠接受,可是若是是4-5個呢,你仍是用這種方式,個人天,恭喜你成爲了CV俱樂部高級會員。
我能夠弱弱的卑微的提出兩點
Modal
的殼子封裝成組件,你只負責content
的書寫Modal
本身去控制顯隱咱們着重說一下2
,由於這個篇幅是2
的主場。
組件調用方式,會發現,只要引入對應的組件,而且定義一個孩子節點,就能夠實現一個控制顯隱的組件。
<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講解能夠參考官網。
這個例子是基於2
(自我控制顯隱的Modal)的升級版本,只提供一個思路。主要代碼以下。
該組件具有的功能:
組件調用方式
<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);
複製代碼
有些公司業務中,可能有表單啊,相似的功能,就是簡單的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.Item
,getFieldDecorator
這些看起來很礙眼。是否是有一種處理方式,就是簡單的配置一些頁面須要的組件,而這些處理放在一個殼子裏呢。
<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
中組件化,是一個編程思路,我感受,懶人才能夠真正的領略到他的魅力,由於懶人才能夠想出讓本身書寫代碼,更快,更高,更強的方式。
這也是一種編程習慣和方式。用最短的時間,寫最少的代碼,幹最漂亮的代碼。
但願我囉嗦這麼多,能對你有所幫助。若是你們看到有哪裏不是很明白的,能夠評論區,留言。若是感受還不是很盡興。不要緊,這些東西,有不少。
其實天天都在吵吵組件化,組件化,組件化是在實際開發項目中,在解決實際業務中,纔會有靈感。他主要的目的是解決實際問題,而不是閉門造車。