前端组件化

在前端开发中,模块化和组件化是提升代码质量和开发效率的两大重要概念。在理解它们之前,了解软件工程中的高内聚、低耦合原则是非常有帮助的。这些原则能够帮助我们编写更易维护、更灵活的代码。

高内聚,低耦合

高内聚指的是一个模块或函数内部的代码紧密相关,完成单一职责。也就是说,一个函数尽量只做一件事。低耦合则意味着模块或函数之间的依赖关系尽量减少,使得一个模块的变化不会对其他模块产生过大的影响。

// math.js
export function add(a, b) {return a + b
}export function mul(a, b) {return a * b
}
// test.js
import { add, mul } from 'math'
add(1, 2)
mul(1, 2)
mul(add(1, 2), add(1, 2))

上面的 math.js 就是高内聚,低耦合的典型示例。add()、mul() 一个函数只做一件事,它们之间也没有直接联系。如果要将这两个函数联系在一起,也只能通过传参和返回值来实现。

既然有好的示例,那就有坏的示例,下面再看一个不好的示例。

// 母公司
class Parent {getProfit(...subs) {let profit = 0subs.forEach(sub => {profit += sub.revenue - sub.cost})return profit}
}// 子公司
class Sub {constructor(revenue, cost) {this.revenue = revenuethis.cost = cost}
}const p = new Parent()
const s1 = new Sub(100, 10)
const s2 = new Sub(200, 150)
console.log(p.getProfit(s1, s2)) // 140

上面的代码是一个不太好的示例,因为母公司在计算利润时,直接操作了子公司的数据。更好的做法是,子公司直接将利润返回给母公司,然后母公司做一个汇总。

class Parent {getProfit(...subs) {let profit = 0subs.forEach(sub => {profit += sub.getProfit()})return profit}
}class Sub {constructor(revenue, cost) {this.revenue = revenuethis.cost = cost}getProfit() {return this.revenue - this.cost}
}const p = new Parent()
const s1 = new Sub(100, 10)
const s2 = new Sub(200, 150)
console.log(p.getProfit(s1, s2)) // 140

这样改就好多了,子公司增加了一个 getProfit() 方法,母公司在做汇总时直接调用这个方法。

高内聚,低耦合在业务场景中的运用

理想很美好,现实很残酷。刚才的示例是高内聚、低耦合比较经典的例子。但在业务场景中写代码不可能做到这么完美,很多时候会出现一个函数要处理多个逻辑的情况。

举个例子,用户注册。一般注册会在按钮上绑定一个点击事件回调函数 register(),用于处理注册逻辑。

function register(data) {// 1. 验证用户数据是否合法/*** 验证账号* 验证密码* 验证短信验证码* 验证身份证* 验证邮箱*/// 省略一大堆串 if 判断语句...// 2. 如果用户上传了头像,则将用户头像转成 base64 码保存/*** 新建 FileReader 对象* 将图片转换成 base64 码*/// 省略转换代码...// 3. 调用注册接口// 省略注册代码...
}

这个示例属于很常见的需求,点击一个按钮处理多个逻辑。从代码中也可以发现,这样写的结果就是三个功能耦合在一起。

按照高内聚、低耦合的要求,一个函数应该尽量只做一件事。所以我们可以将函数中的另外两个功能:验证和转换单独提取出来,封装成一个函数。

function register(data) {// 1. 验证用户数据是否合法verifyUserData()// 2. 如果用户上传了头像,则将用户头像转成 base64 码保存toBase64()// 3. 调用注册接口// 省略注册代码...
}function verifyUserData() {/*** 验证账号* 验证密码* 验证短信验证码* 验证身份证* 验证邮箱*/// 省略一大堆串 if 判断语句...
}function toBase64() {/*** 新建 FileReader 对象* 将图片转换成 base64 码*/// 省略转换代码...
}

这样修改以后,就比较符合高内聚、低耦合的要求了。以后即使要修改或移除、新增功能,也非常方便。

模块化、组件化

模块化

模块化,就是把一个个文件看成一个模块,它们之间作用域相互隔离,互不干扰。一个模块就是一个功能,它们可以被多次复用。另外,模块化的设计也体现了分治的思想。什么是分治?维基百科的定义如下:

字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

从前端方面来看,单独的 JavaScript 文件、CSS 文件都算是一个模块。

例如一个 math.js 文件,它就是一个数学模块,包含了和数学运算相关的函数:

// math.js
export function add(a, b) {return a + b
}export function mul(a, b) {return a * b
}export function abs() { ... }
...

一个 button.css 文件,包含了按钮相关的样式:

/* 按钮样式 */
button {...
}

组件化

那什么是组件化呢?我们可以认为组件就是页面里的 UI 组件,一个页面可以由很多组件构成。例如一个后台管理系统页面,可能包含了 Header、Sidebar、Main 等各种组件。

一个组件又包含了 template(html)、script、style 三部分,其中 script、style 可以由一个或多个模块组成。

一个页面可以分解成一个个组件,每个组件又可以分解成一个个模块,充分体现了分治的思想。

由此可见,页面成为了一个容器,组件是这个容器的基本元素。组件与组件之间可以自由切换、多次复用,修改页面只需修改对应的组件即可,大大的提升了开发效率。

最理想的情况就是一个页面元素全部由组件构成,这样前端只需要写一些交互逻辑代码。虽然这种情况很难完全实现,但我们要尽量往这个方向上去做,争取实现全面组件化。

Web Components

得益于技术的发展,目前三大框架在构建工具(例如 webpack、vite…)的配合下都可以很好的实现组件化。例如 Vue,使用 *.vue 文件就可以把 template、script、style 写在一起,一个 *.vue 文件就是一个组件。

<template><div>{{ msg }}</div>
</template><script>
export default {data() {return {msg: 'Hello World!'}}
}
</script><style>
body {font-size: 14px;
}
</style>

如果不使用框架和构建工具,还能实现组件化吗?

答案是可以的,组件化是前端未来的发展方向,Web Components 就是浏览器原生支持的组件化标准。使用 Web Components API,浏览器可以在不引入第三方代码的情况下实现组件化。

实战

现在我们来创建一个 Web Components 按钮组件,点击它将会弹出一个消息 Hello World!。点击这可以看到 DEMO 效果。

Custom elements(自定义元素)

浏览器提供了一个 customElements.define() 方法,允许我们定义一个自定义元素和它的行为,然后在页面中使用。

class CustomButton extends HTMLElement {constructor() {// 必须首先调用 super方法 super()// 元素的功能代码写在这里const templateContent = document.getElementById('custom-button').contentconst shadowRoot = this.attachShadow({ mode: 'open' })shadowRoot.appendChild(templateContent.cloneNode(true))shadowRoot.querySelector('button').onclick = () => {alert('Hello World!')}}connectedCallback() {console.log('connected')}
}customElements.define('custom-button', CustomButton)

上面的代码使用 customElements.define() 方法注册了一个新的元素,并向其传递了元素的名称 custom-button、指定元素功能的类 CustomButton。然后我们可以在页面中这样使用:

<custom-button></custom-button>

这个自定义元素继承自 HTMLElement(HTMLElement 接口表示所有的 HTML 元素),表明这个自定义元素具有 HTML 元素的特性。

使用 <template> 设置自定义元素内容
<template id="custom-button"><button>自定义按钮</button><style>button {display: inline-block;line-height: 1;white-space: nowrap;cursor: pointer;text-align: center;box-sizing: border-box;outline: none;margin: 0;transition: .1s;font-weight: 500;padding: 12px 20px;font-size: 14px;border-radius: 4px;color: #fff;background-color: #409eff;border-color: #409eff;border: 0;}button:active {background: #3a8ee6;border-color: #3a8ee6;color: #fff;}</style>
</template>

从上面的代码可以发现,我们为这个自定义元素设置了内容 button自定义按钮以及样式,样式放在 <style> 标签里。可以说 <template> 其实就是一个 HTML 模板。

Shadow DOM(影子DOM)

设置了自定义元素的名称、内容以及样式,现在就差最后一步了:将内容、样式挂载到自定义元素上。

// 元素的功能代码写在这里
const templateContent = document.getElementById('custom-button').content
const shadowRoot = this.attachShadow({ mode: 'open' })shadowRoot.appendChild(templateContent.cloneNode(true))shadowRoot.querySelector('button').onclick = () => {alert('Hello World!')
}

元素的功能代码中有一个 attachShadow() 方法,它的作用是将影子 DOM 挂到自定义元素上。DOM 我们知道是什么意思,就是指页面元素。那“影子”是什么意思呢?“影子”的意思就是附加到自定义元素上的 DOM 功能是私有的,不会与页面其他元素发生冲突。

attachShadow() 方法还有一个参数 mode,它有两个值:

open 代表可以从外部访问影子 DOM。
closed 代表不可以从外部访问影子 DOM。

// open,返回 shadowRoot
document.querySelector('custom-button').shadowRoot
// closed,返回 null
document.querySelector('custom-button').shadowRoot
生命周期

自定义元素有四个生命周期:

connectedCallback: 当自定义元素第一次被连接到文档 DOM 时被调用。
disconnectedCallback: 当自定义元素与文档 DOM 断开连接时被调用。
adoptedCallback: 当自定义元素被移动到新文档时被调用。
attributeChangedCallback: 当自定义元素的一个属性被增加、移除或更改时被调用。

生命周期在触发时会自动调用对应的回调函数,例如本次示例中就设置了 connectedCallback() 钩子。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/460673.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

信息安全数学基础(35)同态和同构

一、同态 定义&#xff1a; 设(M,)和(S,)是两个群&#xff08;或更一般的代数系统&#xff09;&#xff0c;如果存在一个映射σ:M→S&#xff0c;使得对于M中的任意两个元素a、b&#xff0c;都有σ(ab)σ(a)σ(b)&#xff0c;则称σ为M到S的同态或群映射。 性质&#xff1a; 同…

微信小程序中点击搜素按钮没有反应,可能是样式问题(按钮被其他元素覆盖或遮挡)

文章目录 1. 确认 bindtap 绑定在正确的元素上2. 检查是否有遮挡或重叠元素3. 检查 this 上下文绑定问题4. 清除微信小程序开发者工具的缓存5. 用微信开发者工具查看事件绑定6. 确保 handleSearch 没有拼写错误进一步调试 1、searchResults.wxml2、searchResults.wxss3、search…

实验干货|电流型霍尔传感器采样设计03-信号调理

在前两篇博客中&#xff0c;将霍尔输出的电流信号转换成了有正有负的电压信号&#xff0c;但是DSP需要采集0~3V的电压信号&#xff0c;因此需要对信号缩放并抬升至全部为正的信号。 常见的方法是&#xff0c;通过比例放大(缩小)电路对信号进行放缩&#xff0c;通过加法电路抬升…

SQLI LABS | Less-20 POST-Cookie Injections-Uagent field-error based

关注这个靶场的其它相关笔记&#xff1a;SQLI LABS —— 靶场笔记合集-CSDN博客 0x01&#xff1a;过关流程 输入下面的链接进入靶场&#xff08;如果你的地址和我不一样&#xff0c;按照你本地的环境来&#xff09;&#xff1a; http://localhost/sqli-labs/Less-20/ 可以看到…

爬虫+数据保存2

爬取数据保存到MySQL数据库 这篇文章, 我们来讲解如何将我们爬虫爬取到的数据, 进行保存, 而且是把数据保存到MySQL数据库的方式去保存。 目录 1.使用pymysql连接数据库并执行插入数据sql代码(insert) 2.优化pymysql数据库连接以及插入功能代码 3.爬取双色球网站的数据并保…

echarts 遍历多个图表,并添加resize缩放

数据结构&#xff1a; data() { return { charts: [ { title: Chart 1, xAxisData: [Mon, Tue, Wed, Thu, Fri, Sat, Sun], yAxisData: [120, 200, 150, 80, 70, 110, 130], }, { title: Chart 2, xAxisData: [Jan, Feb, Mar, Apr, May, Jun, Jul], yAxisData: [22…

Linux 中,flock 对文件加锁

在Linux中&#xff0c;flock是一个用于对文件加锁的实用程序&#xff0c;它可以帮助协调多个进程对同一个文件的访问&#xff0c;避免出现数据不一致或冲突等问题。以下是对flock的详细介绍&#xff1a; 基本原理 flock通过在文件上设置锁来控制多个进程对该文件的并发访问。…

(五)Web前端开发进阶2——AJAX

目录 2.Axios库 3.认识URL 4.Axios常用请求方法 5.HTTP协议——请求报文/响应报文 6.前后端分离开发 7.Element组件库 1.Ajax概述 AJAX 是异步的 JavaScript和XML(Asynchronous JavaScript And XML)。简单点说&#xff0c;就是使用XMLHttpRequest 对象与服务器通信。它可…

使用C#学习Office文件的处理(pptx docx xlsx)

Office文件 是指PPT 、word、Excel 这些常用工具生成的文件 &#xff0c;例如 pptx docx xlsx。 这些文件的读取和生成有很多很多库 例如 NOPI 、DevExpress、C1、Aspose、Teleric 等等&#xff0c;各有各的优缺点。俺今天不讲这个&#xff0c;俺只是讲讲如何了解Office文件的…

2020年下半年网络规划设计师上午真题及答案解析

1.在支持多线程的操作系统中&#xff0c;假设进程P创建了线程T1&#xff0c;T2&#xff0c;T3&#xff0c;那么下列说法中正确的是&#xff08; &#xff09;。 A.该进程中已打开的文件是不能被T1&#xff0c;T2和T3共享的 B.该进程中T1的栈指针是不能被T2共享&#xff0c;但…

Java 使用Maven Surefire插件批量运行单元测试

在基于Maven的Java项目中可以使用Maven 的 mvn test 命令来运行单元测试。 示例 有一个简单的Maven 项目&#xff0c; pom.xml 只导入了JUnit 5 的相关依赖&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://m…

Linux CentOS7下创建SFTP服务器

本文详细介绍了在Linux CentOS上部署安全文件传输协议&#xff08;SFTP&#xff09;服务器的全过程。SFTP基于SSH&#xff08;安全壳层协议&#xff09;提供文件传输服务&#xff0c;继承了SSH的安全特性&#xff0c;如数据加密、完整性验证和服务器认证等&#xff0c;确保数据…

电科金仓(人大金仓)更新授权文件(致命错误: XX000: License file expired.)

问题:电科金仓(人大金仓)数据库链接异常,重启失败,查看日志如下: 致命错误: XX000: License file expired. 位置: PostmasterMain, postmaster.c:725 解决方法: 一、下载授权文件 根据安装版本在官网下载授权文件(电科金仓-成为世界卓越的数据库产品与服务提供商)…

3D Gaussian Splatting代码详解(二):模型构建

3 模型构建 gaussians GaussianModel(dataset.sh_degree) 3.1 初始化函数 __init__ 构造函数 构造函数 __init__ 的主要作用是初始化 3D 高斯模型的各项参数和激活函数&#xff0c;用于生成 3D 空间中的高斯表示。 初始化球谐函数的参数&#xff1a; self.active_sh_degre…

一文讲明白大模型分布式逻辑(从GPU通信原语到Megatron、Deepspeed)

1. 背景介绍 如果你拿到了两台8卡A100的机器&#xff08;做梦&#xff09;&#xff0c;你的导师让你学习部署并且训练不同尺寸的大模型&#xff0c;并且写一个说明文档。你意识到&#xff0c;你最需要学习的就是关于分布式训练的知识&#xff0c;因为你可是第一次接触这么多卡…

Python毕业设计选题:基于协同过滤的校园音乐推荐系统小程序-django+uniapp

开发语言&#xff1a;Python框架&#xff1a;djangouniappPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录界面 管理员功能界面 用户管理 音乐信息管理 音乐类型管理 留言板管…

【python】OpenCV—findContours(4.4)

文章目录 1、功能描述2、代码实现3、完整代码4、结果展示5、涉及到的库函数6、参考 1、功能描述 找出物体轮廓&#xff0c;根据 PCA 计算特征值和特征向量&#xff0c;绘制特征值和特征向量&#xff0c;来初步展示物体的方向 2、代码实现 导入库函数&#xff0c;读入图片&am…

Jmeter实际应用

环境准备 JDK1.8Jmeter 5.6.3 下载地址Jmeter 插件 下载地址 放到lib/ext下 常用命令 # 启动 sh jmeter# 集群模式下启动节点&#xff0c;不启动用不了集群 sh jmeter-server#生成ssl需要的证书, 这里会要求输入个密码&#xff0c;是要在jmeter中用的 keytool -import -ali…

Javaweb 实验4 xml

我发现了有些人喜欢静静看博客不聊天呐&#xff0c; 但是ta会点赞。 这样的人呢帅气低调有内涵&#xff0c; 美丽大方很优雅。 说的就是你&#xff0c; 不用再怀疑哦 实验四 XML 目的&#xff1a; 安装和使用XML的开发环境认识XML的不同类型掌握XML文档的基本语法了解D…

C#界面设计--9--fatal error C1083: 无法打开包括文件:“jruparse.h”: No such file or directory

1、VS2008-编译时报错“fatal error C1083: 无法打开包括文件:“jruparse.h”: No such file or directory” 2、问题出现的原因及解决方法 1、如果要引入的这些,h文件跟.cpp在同一个目录下&#xff0c;就不会出现这种问题&#xff0c;检査在工程的include目录下是不是真的存…