若是你以为能够,请多点赞,鼓励我写出更精彩的文章🙏。
若是你感受有问题,也欢迎在评论区评论,三人行,必有我师焉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
中组件化,是一个编程思路,我感受,懒人才能够真正的领略到他的魅力,由于懒人才能够想出让本身书写代码,更快,更高,更强的方式。
这也是一种编程习惯和方式。用最短的时间,写最少的代码,干最漂亮的代码。
但愿我啰嗦这么多,能对你有所帮助。若是你们看到有哪里不是很明白的,能够评论区,留言。若是感受还不是很尽兴。不要紧,这些东西,有不少。
其实天天都在吵吵组件化,组件化,组件化是在实际开发项目中,在解决实际业务中,才会有灵感。他主要的目的是解决实际问题,而不是闭门造车。