沐光

记录在前端之路的点点滴滴

React 通用开发模式小记

前言

近期花了差不多 1 个月的时间开发项目,包含看板、列表两种类型,从早期磕磕绊绊的页面设计,中间的反复改版,到最终的完成开发,也体会到了很多不一样的点,感受特别深的是与 Vue 开发时的区别,这里简单记一下我的心得体会。

开发方式的改变

对于 Vue 来说,因为有双向绑定,因此习惯将大多数逻辑写在一个组件里,通过 computed 钩子监听多个依赖项的变动,双向数据流通拿到需要的参数值,必要时再配合上 Vuex,便能很好的解决大多数通用表单的问题。

但这个在 React 中是不太吃香的,因为 React 是单向数据流,先不论比较麻烦的值传递问题,仅看 Hook 对参数的依赖,也会造成一些比较麻烦的问题,比如:

折线图列表,每个列表有自己的查询条件,同时也依赖外部传入的查询条件,为它们封装一个组件的话则会有死循环问题,必须拆成俩组件才能较好的处理此情况,而 Vue 则因为不用担心函数刷新触发查询,因此写在一个组件内也没问题

因此对于 React 来说,组件拆的越细,耦合程度越低,那么使用 Hook 或者 HOC 来拼凑起来会更为方便,而且对于组件优化、逻辑梳理也会更为容易一些。

页面开发模式

对于顶部 Form 表单,底部多个 折线图表格 这样常见的表单页面(底部图表可能自带子表单项),我总结了一点开发心得,按照这种方式开发,能减少很多写代码时的心智负担,同时也能便捷的解决很多表单场景。

类比 Vue 的 Vuex + Components 的模式,在 React 开发时使用的模式为 CreateContext + Components ,Store 存储的基础内容如下:

1
2
3
4
5
6
7
// CreateContext 的对象内容
{
form: {}, // form 表单项
formLoading: true, // form 表单是否查询中(初始化&查询状态控制)
}

// 组件中直接使用 Context 来更新存储,还能根据情况依赖 Context 或 Context.value ,来控制函数的更新频次

此外,还可以使用 CreateContext + useReducer 的模式来做一些复杂的控制逻辑,当然这也是 React 官网推崇的方式。

组件部分的内容可分为以下三步来看:

Form 表单的初始化

顶部 Form 表单部分推荐单独使用一个组件封装,仅向外暴露最终的 form.getFieldsValue 的内容(无需做任何参数处理),这样的好处是给外部留下预查询的一些处理、以及表单内容的处理和缓存。内部初始化可考虑如下步骤:

  • 表单设置初始化 loading 状态(Context 中获取),通过 useRef 创建一个空的表单初始化值 initValue;
  • 根据项目需要,使用 useEffect 获取表单的异步依赖项(如下拉菜单)
  • 待所有异步依赖获取完后更新 ref.current 的初始化值,然后使用 form.resetFields 更新默认选中项目;

  • 最后关闭 loading 状态,并根据情况设置是否默认查询(向外触发 onChange);

查询处于 loading 状态时可以考虑使用一些动画来让页面不显得那么突兀。

表单查询&缓存

Form 表单查询得分为两部:预查询&表单查询

预查询 的目的其一是为了查询前的表单校验,此外则是部分条件的初始化和格式化(如 table 的初始分页信息表单查询条件格式化),这和分页查询等的逻辑是分开的,最后就是查询条件的缓存了;后续的 表单查询 则使用传入的查询条件即可,因为如其它的分页查询、重置查询等都需要依赖缓存内容进行查询,这样整体的逻辑也就拆开了。

查询的逻辑列举如下:

  • 点击表单的查询按钮,触发表单查询函数,向外部 emit 出来的参数,并调用 beforeSearch 函数;
  • 处理 form 表单参数,初始化 分页排序 等参数信息,缓存格式化后的查询条件,设置 loading 状态;
  • 根据情况触发对应的查询函数(调用查询 或 监听缓存条件查询);

接口参数还依赖自身组件的情况

除了表单查询外,部分接口可能还有自身的查询条件(如:自带日期筛选的图表组件),这种情况其接口触发查询的情况有两种:Form 表单查询 以及 自身筛选条件变更查询。如果直接监听两者则会出现“初始化查询两次”的问题,因此需要做一些优化,这里我总结了一些方法:

  1. 使用 Ref 设置初始化状态,初始化时不做处理,待后续变更时再做调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { useRef, useEffect } from 'react';

const demo = () => {
const initRef = useRef(true);

// 初始化时阻碍触发
useEffect(() => {
if (initRef.current) {
initRef.current = false;
return;
}

// 其它操作
}, [initRef]);
};
  1. 将俩情况拆分为两种处理方式,并通过 useRef 来存储子查询条件:
  • Form 表单项变更,通过触发 useEffect 来触发接口函数;(副作用函数)
  • 子查询条件变更,触发 onChange 事件,并更新 ref 的自身查询依赖,手动调用接口函数;(仅事件变动触发)

注意:子查询条件仅根据 onChange 事件触发,初始化参数均在子组件设置的时候初始化即可。

参考文章