进入Hooks时代写出高质量react及vue组件详解

目录
  • 概述
  • 1.组件什么时候拆?怎么拆?
  • 2.如何组织拆分出的组件文件?
  • 3.如何用hooks抽离组件逻辑?
  • 题外话:全局状态的管理

概述

vue和react都已经全面进入了hooks时代(在vue中也称为组合式api,为了方便后面统一称为hooks),然而受到以前react中类组件和vue2写法的影响,很多开发者都不能及时转换过来,以致于开发出一堆面条式代码,整体的代码质量反而不如改版以前了。

hooks组件到底应该如何写,我也曾为此迷惘过一段时间。特别我以前以react开发居多,但在转到新岗位后又变成了使用vue3开发,对于两个框架在思维方式和写法的不同上,很是花了一段时间适应。好在几个月下来,我发现二者虽然在写法上有区别之处,但思想上却大同小异。

所以在比较了两个框架的异同后,我总结出了一套通用的hooks api的抽象方式,在这里分享给大家。如果您有不同意见欢迎在评论区指正。

一个组件内部的所有代码——无论vue还是react——都可以抽象成以下几个部分:

  • 组件视图,组件中用来描述视觉效果的部分,如css和html、react的jsx或者vue的template代码
  • 组件相关逻辑,如组件生命周期,按钮交互,事件等
  • 业务相关逻辑,如登录注册,获取用户信息,获取商品列表等与组件无关的业务抽象

单独拆分这三块并不难,难的是一个组件可能写得特别复杂,里面可能包含了多个视图,每个视图相互之间又有交互;同时又可能包含多个业务逻辑,多个业务的函数和变量杂乱无章地随意放置,导致后续维护的时候要在代码之间反复横跳。

要写出高质量的组件,可以思考以下几个问题:

1.组件什么时候拆?怎么拆?

一个常见的误区是,只有需要复用的时候才去拆分组件,这种看法显然过于片面了。你可以思考一下,自己是如何抽象一个函数的,你只会在代码需要复用的时候才抽出一个函数吗?显然不是。因为函数不仅有代码复用的功能,还具有一定的描述性质以及代码封闭性。这种特性使得我们看到一个函数的时候,不必关注代码细节,就能大概知道这部分代码是干啥的。

我们还可以再用函数将一部分函数组合起来,形成更高层级的抽象。按国内流行的说法,高层级的抽象被称为粗粒度,低层级的抽象被称为细粒度,不同粗细粒度的抽象可以称它们为不同的抽象层级。并且一个理想的函数内部,一般只会包含同一抽象层级的代码。

组件的拆分也可以遵循同样的道理。我们可以按照当前的结构或者功能、业务,将组件拆分为功能清晰且单一、与外部耦合程度低的组件(即所谓高内聚,低耦合)。如果一个组件里面干了太多事,或者依赖的外部状态太多,那么就不是一个容易维护的组件了。

然而,为了保持组件功能单一,我们是不是要将组件拆分得特别细才可以呢?事实并非如此。因为上面说过,抽象是有粗细粒度之分的,也许一个组件从较细的粒度来讲功能并不单一,但是从较粗的粒度来说,可能他们的功能就是单一的了。例如登录和注册是两个不同的功能,但是你从更高层级的抽象来看,它们都属于用户模块的一部分。

所以是否要拆分组件,最关键还是得看复杂度。如果一个页面特别简单,那么不进行拆分也是可以,有时候拆分得过于细可能反而不利于维护。

如何判断一个组件是否复杂?恐怕这里不能给出一个准确的答案,毕竟代码的实现方式千奇百怪,很难有一个机械的标准评判。但是我们不妨站在第三方角度看看自己的代码,如果你是一个工作一年的程序员,是否能比较容易地看懂这里的代码?如果不能就要考虑进行拆分了。如果你非要一个机械的判断标准,我建议是代码控制在200行内。

总结一下,拆分组件的时候可以参考下面几个原则:

  • 拆分的组件要保持功能单一。即组件内部代码的代码都只跟这个功能相关;
  • 组件要保持较低的耦合度,不要与组件外部产生过多的交互。如组件内部不要依赖过多的外部变量,父子组件的交互不要搞得太复杂等等。
  • 用组件名准确描述这个组件的功能。就像函数那样,可以让人不用关心组件细节,就大概知道这个组件是干嘛的。如果起名比较困难,考虑下是不是这个组件的功能并不单一。

2.如何组织拆分出的组件文件?

拆分出来的组件应该放在哪里呢?一个常见的错误做法是一股脑放在一个名为components文件夹里,最后搞得这个文件夹特别臃肿。我的建议是相关联的代码最好尽量聚合在一起。

为了让相关联的代码聚合到一起,我们可以把页面搞成文件夹的形式,在文件夹内部存放与当前文件相关的组成部分,并将表示页面的组件命名为index放在文件夹下。再在该文件夹下创建components目录,将组成页面的其他组件放在里面。

如果一个页面的某个组成部分很复杂,内部还需要拆分成更细的多个组件,那么就把这个组成部分也做成文件夹,将拆分出的组件放在这个文件夹下。

最后就是组件复用的问题。如果一个组件被多个地方复用,就把它单独提取出来,放到需要复用它的组件们共同的抽象层级上。 如下:

  • 如果只是被页面内的组件复用,就放到页面文件夹下。
  • 如果只是在当前业务场景下的不同页面复用,就放到当前业务模块的文件夹下。
  • 如果可以在不同业务场景间通用,就放到最顶层的公共文件夹,或者考虑做成组件库。

关于项目文件的组织方式已经超过本文讨论的范畴,我打算放到以后专门出一篇文章说下如何组织项目文件。这里只说下页面级别的文件如何进行组织。下面是我常用的一种页面级别的文件的组织方式:

homePage // 存放当前页面的文件夹
    |-- components // 存放当前页面组件的文件夹
        |-- componentA // 存放当前页面的组成部分A的文件夹
            |-- index.(vue|tsx) // 组件A
            |-- AChild1.(vue|tsx) // 组件a的组成部分1
            |-- AChild2.(vue|tsx) // 组件a的组成部分2
            |-- ACommon.(vue|tsx) // 只在componentA内部复用的组件
        |-- ComponentB.(vue|tsx) // 当前页面的组成部分B
        |-- Common.(vue|tsx) // 组件A和组件B里复用的组件
    |-- index.(vue|tsx) // 当前页面

实际上这种组织方式,在抽象意义上并不完美,因为通用组件和页面组成部分的组件并没有区分开来。但是一般来说,一个页面也不会抽出太多组件,为了方便放到一起也不会有太大问题。但是如果你的页面实在复杂,那么再创建一个名为common的文件夹也未尝不可。

3.如何用hooks抽离组件逻辑?

在hooks出现之前,曾流行过一个设计模式,这个模式将组件分为无状态组件和有状态组件(也称为展示组件和容器组件),前者负责控制视觉,后者负责传递数据和处理逻辑。但有了hooks之后,我们完全可以将容器组件中的代码放进hooks里面。后者不仅更容易维护,而且也更方便把业务逻辑与一般组件区分开来。

在抽离hooks的时候,我们不仅应该沿用一般函数的抽象思维,如功能单一,耦合度低等等,还应该注意组件中的逻辑可分为两种:组件交互逻辑与业务逻辑。如何把文章开头说的视图、交互逻辑和业务逻辑区分开来,是衡量一个组件质量的重要标准。

以一个用户模块为例。一个包含查询用户信息,修改用户信息,修改密码等功能的hooks可以这样写:

// 用户模块hook
const useUser = () => {
    // react版本的用户状态
    const user = useState({});
    // vue版本的用户状态
    const userInfo = ref({});
    // 获取用户状态
    const getUserInfo = () => {}
    // 修改用户状态
    const changeUserInfo = () => {};
    // 检查两次输入的密码是否相同
    const checkRepeatPass = (oldPass,newPass) => {}
    // 修改密码
    const changePassword = () => {};
    return {
        userInfo,
        getUserInfo,
        changeUserInfo,
        checkRepeatPass,
        changePassword,
    }
}

交互逻辑的hook可以这么写(为了方便只写vue版本的,大家应该也都看得懂):

// 用户模块交互逻辑hooks
const useUserControl = () => {
    // 组合用户hook
    const { userInfo, getUserInfo, changeUserInfo, checkRepeatPass, changePassword } = useUser();
    // 数据查询loading状态
    const loading = ref(false);
    // 错误提示弹窗的状态
    const errorModalState = reactive({
        visible: false, // 弹窗显示/隐藏
        errorText: '',  // 弹窗文案
    });
    // 初始化数据
    const initData = () => {
        getUserInfo();
    }
    // 修改密码表单提交
    const onChangePassword = ({ oldPass, newPass ) => {
        // 判断两次密码是否一致
        if (checkRepeatPass(oldPass, newPass)) {
            changePassword();
        } else {
            errorModalState.visible = true;
            errorModalState.text = '两次输入的密码不一致,请修改'
        }
    };
    return {
        // 用户数据
        userInfo,
        // 初始化数据
        initData: getUserInfo,
        // 修改密码
        onChangePassword,
        // 修改用户信息
        onChangeUserInfo: changeUserInfo,
    }
}

然后只要在组件里面引入交互逻辑的hook即可:

vue版本:

<template>
    <!-- 视图部分省略,在对应btn处引用onChangePassword和onChangeUserInfo即可 -->
</template>
<script setup>
import useUserControl from './useUserControl';
import { onMounted } from 'vue';
const { userInfo, initData, onChangePassword, onChangeUserInfo } = useUserControl();
onMounted(initData);
<script>

react版本:

import useUserControl from './useUserControl';
import { useEffect } from 'react';
const UserModule = () => {
    const { userInfo, initData, onChangePassword, onChangeUserInfo } = useUserControl();
    useEffect(initData, []);
    return (
        // 视图部分省略,在对应btn处引用onChangePassword和onChangeUserInfo即可
    )
}

而拆分出的三个文件放在组件同级目录下即可;如果拆出的hooks较多,可以单独开辟一个hooks文件夹。如果有可以复用的hooks,参考组件拆分里面分享的方法,放到需要复用它的组件们共同的抽象层级上即可

可以看到抽离出hooks逻辑后,组件变得十分简单、容易理解,我们也实现了各个部分的分离。不过这里还有一个问题,那就是上面的业务场景实在太过简单,有必要拆分得这么细,搞出三个文件这么复杂吗?

针对逻辑并不复杂的组件,我个人觉得和组件放到一起也未尝不可。为了简便,我们可以只把业务逻辑封装成hooks,而组件的交互逻辑就直接放在组件里面。如下:

<template>
    <!-- 视图部分省略,在对应btn处引用changePassword和changeUserInfo即可 -->
</template>
<script setup>
import { onMounted } from 'vue';
// 用户模块hook
const useUser = () => {
    // 代码省略
}
const { userInfo, getUserInfo, changeUserInfo, checkRepeatPass, changePassword } = useUser();
// 数据查询loading状态
const loading = ref(false);
// 错误提示弹窗的状态
const errorModalState = reactive({
    visible: false, // 弹窗显示/隐藏
    errorText: '', // 弹窗文案
});
// 初始化数据
const initData = () => { getUserInfo(); }
// 修改密码表单提交
const onChangePassword = ({ oldPass, newPass ) => {};
onMounted(initData);
<script>

但是如果逻辑比较复杂,或者一个组件里面包含多个复杂业务或者复杂交互,需要抽离出多个hooks的情况,还是单独抽出一个个文件比较好。总而言之,依据代码复杂度,选择相对更容易理解的写法。

也许单独一个组件,你并不能体会出hooks写法的优越性。但当你封装出更多的hooks之后,你会逐渐发现这样写的好处。正因为不同的业务和功能被封装在一个个hooks里面,彼此互不干扰,业务才能更容易区分和理解。对于项目的可维护性和可读性提升是非常之大的。

下图展示了vue2写法和vue3 hooks写法的区别。图中相同颜色的代码块代表这些代码是属于同一个功能的,但vue2的写法导致本来是相同功能的代码,却被拆散到了不同地方(react其实也容易有相同的问题,例如当一个组件有多个功能时,不同功能的代码也很容易混杂到一起)。而通过封装成一个个hooks,相关联的代码就很容易被聚合到了一起,且和其他功能区分开了。

题外话:全局状态的管理

现在的前端项目还有一个较为常见的误区,那就是全局状态管理库(即redux、vuex等)的滥用。依据抽象层级的思维,实际上很多项目并不需要放较多的状态到全局,这种情况利用react和vue自身的状态管理就足够了。

如果非要用状态管理库,也要警惕放较多状态和函数到全局。一个状态是否要放到全局,我一般有两个判断标准:

  • 状态是否在多个页面间共享;
  • 跳转页面后又返回该页面,是否需要还原跳转之前的状态(仅对react而言,vue有keep-alive)

而全局状态管理库中的函数,则只放置与全局状态有关的逻辑。除此之外的状态,一律交由react和vue组件本身进行管理。

以上就是Hooks中写出高质量的react和vue组件的详细内容,更多关于Hooks react和vue组件的资料请关注我们其它相关文章!

(0)

相关推荐

  • 基于React Hooks的小型状态管理详解

    目录 实现基于 React Hooks 的状态共享 使用感受 本文主要介绍一种基于 React Hooks 的状态共享方案,介绍其实现,并总结一下使用感受,目的是在状态管理方面提供多一种选择方式. 实现基于 React Hooks 的状态共享 React 组件间的状态共享,是一个老生常谈的问题,也有很多解决方案,例如 Redux.MobX 等.这些方案很专业,也经历了时间的考验,但私以为他们不太适合一些不算复杂的项目,反而会引入一些额外的复杂度. 实际上很多时候,我不想定义 mutation 和

  • vue3使用自定义hooks获取dom元素的问题说明

    目录 使用自定义hooks获取dom元素问题 分享下楼主自己的观点 vue获取/操作组件的dom元素 下面是我的代码内容(非全部内容) 最后总结 使用自定义hooks获取dom元素问题 在自定义hooks的onMounted事件里面 获取ref元素,组件调用hooks的时候必须要传递响应式对象. 分享下楼主自己的观点 代码如下 <script> // demo.vue import { defineComponent, ref } from 'vue' import useBars from

  • React Hooks与setInterval的踩坑问题小结

    目录 一.需求 二.解决方案 1.函数式更新 2.使用useRef 3.用useReducer 4.自定义的hooks 一.需求 我们希望有一个每一秒自动+1的定时器 function Counter() { let [count, setCount] = useState(0); useEffect(() => { let id = setInterval(() => { setCount(count + 1); }, 1000); return () => clearInterval

  • 教你在react中创建自定义hooks

    一.什么是自定义hooks 逻辑复用 简单来说就是使用自定义hook可以将某些组件逻辑提取到可重用的函数中. 自定义hook是一个从use开始的调用其他hook的Javascript函数. 二.不使用自定义hook时 例1:当我们整个页面需要获取用户鼠标移动的坐标时,不使用hook的代码,我们可以这样写 const [position, setPosition] = useState({ x: 0, y: 0 }) useEffect(() => { const move = (e) => {

  • vue3中的hooks总结

    目录 vue3的hooks总结 计数器的hook vue3自定义hooks vue3的hooks总结 vue3中的hooks其实是函数的写法,就是将文件的一些单独功能的js代码进行抽离出来,放到单独的js文件中.这样其实和我们在vue2中学的mixin比较像.下面我们总结一下如何去书写hooks. 首先应该先建立一个hooks文件夹:其目的是为了存放hook文件. 建立相关的hook文件:一般使用use开头. 计数器的hook useTitle的hooks useScrollPostion用来监

  • Hooks对于Vue作用意义详解

    目录 前言 问题背景 Vue Hooks 传递状态 来源清晰 小结 前言 本篇主体译自:https://css-tricks.com/what-hooks-mean-for-vue/ 作者:Sarah Drasner OS:虽然这是一篇 19 年 4 月的文章,但是对于 Hooks 说的非常清晰,作者也是请到尤大进行了原文的订正,对于了解 Vue Hooks 的设计及发展,还是有很好的阅读性的. 本文要谈到的 Hooks,不同于 Lifecycle Hooks(生命周期钩子),它是在 v16.7

  • 进入Hooks时代写出高质量react及vue组件详解

    目录 概述 1.组件什么时候拆?怎么拆? 2.如何组织拆分出的组件文件? 3.如何用hooks抽离组件逻辑? 题外话:全局状态的管理 概述 vue和react都已经全面进入了hooks时代(在vue中也称为组合式api,为了方便后面统一称为hooks),然而受到以前react中类组件和vue2写法的影响,很多开发者都不能及时转换过来,以致于开发出一堆面条式代码,整体的代码质量反而不如改版以前了. hooks组件到底应该如何写,我也曾为此迷惘过一段时间.特别我以前以react开发居多,但在转到新岗

  • 写出高质量软件的75条体会

    如何用正确的方法写出高质量软件的75条体会 1. 你们的项目组使用源代码管理工具了么? MVM:应该用.VSS.CVS.PVCS.ClearCase.CCC/Harvest.FireFly都可以.我的选择是VSS. 2. 你们的项目组使用缺陷管理系统了么? MVM:应该用.ClearQuest太复杂,我的推荐是BugZilla. 3. 你们的测试组还在用Word写测试用例么? MVM:不要用Word写测试用例(Test Case).应该用一个专门的系统,可以是Test Manager,也可以是自

  • MySQL优化之如何写出高质量sql语句

    前言 关于数据库优化,网上有不少资料和方法,但是不少质量参差不齐,有些总结的不够到位,内容冗杂.这篇文章就来给大家详细介绍了26条优化建议,下面来一起看看吧 1. 查询SQL尽量不要使用全查 select *,而是 select + 具体字段. 反例: select * from student; 正例: select id,name, age from student; 理由: 只取需要的字段,可以节省资源.减少CPU和IO以及网络开销. select * 进行查询时,无法使用到覆盖索引,就会

  • 12条写出高质量JS代码的方法

    书写出高质量的JS代码不仅让程序员看着舒服,更加能够提高程序的运行速度,以下就是我们的小编整理方法: 一.如何书写可维护性的代码 当出现bug的时候如果你能立马修复它是最好的,此时解决问题的四路在你脑中还是很清晰的.否则,你转移到其他任务或者bug是经过一定的时间才出现的,你忘了那个特定的代码,一段时间后再去查看这些代码就 需要: 1.花时间学习和理解这个问题 2.化时间是了解应该解决的问题代码 还有个问题,特别对于大的项目或是公司,修复bug的这位伙计不是写代码的那个人(且发现bug和修复bu

  • 写出高质量的PHP程序

    一.安全 无论程序写的如何,首先安全是第一位的,没有安全保障的程序根本不能谈高质量. 二.稳定 无论你代码写的再烂,必须要能稳定运行. 三.用户体验 用户的体验直接决定着一个程序的命运,根本不懂用户体验的程序高质量便无从谈起. 四.商业体验 开发应用的目的自然是为了赚钱,我认为,再优秀的程序,不赚钱也等于是一个废物. 五.效率 这是最后一个了,一直认为性能是最次要的,PHP程序本身的效率就不是太高,只所以能如此流行主要是开源和开发成本低而已.这个问题不想再去说,有的程序员想从PHP代码上来提高性

  • Swift中的高阶函数功能作用示例详解

    目录 高阶函数的作用 1. 简化代码 2. 提高可读性 3. 支持函数式编程 4. 提高代码的可重用性 常见的高阶函数 1. map() 2. filter() 3. reduce() 4. sorted() 5. forEach() 6. compactMap() 7. flatMap() 8. zip() 9. first() 10. contains() 高阶函数的作用 Swift中的高阶函数是指那些参数或返回值是函数的函数.它们的存在使得我们可以用非常简洁和优雅的代码来解决许多问题. 1

  • J2ee 高并发情况下监听器实例详解

    J2ee 高并发情况下监听器实例详解 引言:在高并发下限制最大并发次数,在web.xml中用过滤器设置参数(最大并发数),并设置其他相关参数.详细见代码. 第一步:配置web.xml配置,不懂的地方解释一下:参数50通过参数名maxConcurrent用在filter的实现类中获取,filter-class就是写的实现类, url-pattern就是限制并发时间的url,结束! <filter> <filter-name>ConcurrentCountFilter</filt

  • Android开发高仿课程表的布局实例详解

    先说下这个demo,这是一个模仿课程表的布局文件,虽然我是个菜鸟,但我还是想留给学习的人一些例子,先看下效果 然后再来看一下我们学校的app 布局分析 先上一张划分好了的布局图 首先整个页面放在一个LinearLayout布局下面,分为上面和下面两个部分,下面一个是显示课程表的详细信息 1:这个没什么好讲的,就是直接一个LinearLayout布局,然后将控件一个TextView用来显示年份,一个View用来当作竖线,一个Spinner用来显示选择周数 2:这个是显示星期几的部件,是我自定义的V

  • vue和react中关于插槽详解

    目录 简述Slot 基本插槽 vue基本插槽 react基本插槽 具名插槽 vue具名插槽 react具名插槽的讨论 模仿具名插槽 属性插槽 插槽传参 vue插槽传参 react:render-props 简述Slot slot插槽是Vue对组件嵌套这种扩展机制的称谓,在react可以也这样称呼,但是并不很常见.不过叫slot确实很形象. 这样的形式就是slot插槽: vue <template> <container-comp> <content></conte

  • 提高团队代码质量利器ESLint及Prettier详解

    目录 正文 ESLint VUE 项目的规则 Prettier ESLint 与 Prettier 正文 每个开发人员都有独特的代码编写风格和不同的文本编辑器.在团队项目开发过程,不能强迫每个团队成员都写一样的代码风格. 可能会看到以下部分(或全部)内容: 缺少分号: 有单引号.双引号,风格不一致: 一些行之间有大量的空格,而其他行之间没有空格: 在使向右滚动多年以查看其中包含的所有内容的行上运行: 看似随意的缩进: 注释掉代码块: 初始化但未使用的变量: 一些使用“严格”JS 的文件和其他不使

随机推荐