精读《React Hooks 最佳实践》
简介
React 16.8 于 2019.2 正式发布,这是一个能提升代码质量和开发效率的特性,笔者就抛砖引玉先列出一些实践点,希望得到大家进一步讨论。
然而需要理解的是,没有一个完美的最佳实践规范,对一个高效团队来说,稳定的规范比合理的规范更重要,因此这套方案只是最佳实践之一。
精读
环境要求
- 拥有较为稳定且理解函数式编程的前端团队。
- 开启 ESLint 插件:eslint-plugin-react-hooks。
组件定义
Function Component 采用 const
+ 箭头函数方式定义:
const App: React.FC<{ title: string }> = ({ title }) => {return React.useMemo(() => <div>{title}</div>, [title]);
};App.defaultProps = {title: 'Function Component'
}
上面的例子包含了:
- 用
React.FC
申明 Function Component 组件类型与定义 Props 参数类型。 - 用
React.useMemo
优化渲染性能。 - 用
App.defaultProps
定义 Props 的默认值。
FAQ
为什么不用 React.memo?
推荐使用 React.useMemo
而不是 React.memo
,因为在组件通信时存在 React.useContext
的用法,这种用法会使所有用到的组件重渲染,只有 React.useMemo
能处理这种场景的按需渲染。
没有性能问题的组件也要使用 useMemo 吗?
要,考虑未来维护这个组件的时候,随时可能会通过 useContext
等注入一些数据,这时候谁会想起来添加 useMemo
呢?
为什么不用解构方式代替 defaultProps?
虽然解构方式书写 defaultProps
更优雅,但存在一个硬伤:对于对象类型每次 Rerender 时引用都会变化,这会带来性能问题,因此不要这么做。
局部状态
局部状态有三种,根据常用程度依次排列: useState
useRef
useReducer
。
useState
const [hide, setHide] = React.useState(false);
const [name, setName] = React.useState('BI');
状态函数名要表意,尽量聚集在一起申明,方便查阅。
useRef
const dom = React.useRef(null);
useRef
尽量少用,大量 Mutable 的数据会影响代码的可维护性。
但对于不需重复初始化的对象推荐使用 useRef
存储,比如 new G2()
。
useReducer
局部状态不推荐使用 useReducer
,会导致函数内部状态过于复杂,难以阅读。 useReducer
建议在多组件间通信时,结合 useContext
一起使用。
FAQ
可以在函数内直接申明普通常量或普通函数吗?
不可以,Function Component 每次渲染都会重新执行,常量推荐放到函数外层避免性能问题,函数推荐使用 useCallback
申明。
函数
所有 Function Component 内函数必须用 React.useCallback
包裹,以保证准确性与性能。
const [hide, setHide] = React.useState(false);const handleClick = React.useCallback(() => {setHide(isHide => !isHide)
}, [])
useCallback
第二个参数必须写,eslint-plugin-react-hooks 插件会自动填写依赖项。
发请求
发请求分为操作型发请求与渲染型发请求。
操作型发请求
操作型发请求,作为回调函数:
return React.useMemo(() => {return (<div onClick={requestService.addList} />)
}, [requestService.addList])
渲染型发请求
渲染型发请求在 useAsync
中进行,比如刷新列表页,获取基础信息,或者进行搜索, 都可以抽象为依赖了某些变量,当这些变量变化时要重新取数 :
const { loading, error, value } = useAsync(async () => {return requestService.freshList(id);
}, [requestService.freshList, id]);
组件间通信
简单的组件间通信使用透传 Props 变量的方式,而频繁组件间通信使用 React.useContext
。
以一个复杂大组件为例,如果组件内部拆分了很多模块, 但需要共享很多内部状态 ,最佳实践如下:
定义组件内共享状态 - store.ts
export const StoreContext = React.createContext<{state: State;dispatch: React.Dispatch<Action>;
}>(null)export interface State {};export interface Action { type: 'xxx' } | { type: 'yyy' };export const initState: State = {};export const reducer: React.Reducer<State, Action> = (state, action) => {switch (action.type) {default:return state;}
};
根组件注入共享状态 - main.ts
import { StoreContext, reducer, initState } from './store'const AppProvider: React.FC = props => {const [state, dispatch] = React.useReducer(reducer, initState);return React.useMemo(() => (<StoreContext.Provider value={{ state, dispatch }}><App /></StoreContext.Provider>), [state, dispatch])
};
任意子组件访问/修改共享状态 - child.ts
import { StoreContext } from './store'const app: React.FC = () => {const { state, dispatch } = React.useContext(StoreContext);return React.useMemo(() => (<div>{state.name}</div>), [state.name])
};
如上解决了 多个联系紧密组件模块间便捷共享状态的问题 ,但有时也会遇到需要共享根组件 Props 的问题,这种不可修改的状态不适合一并塞到 StoreContext
里,我们新建一个 PropsContext
注入根组件的 Props:
const PropsContext = React.createContext<Props>(null)const AppProvider: React.FC<Props> = props => {return React.useMemo(() => (<PropsContext.Provider value={props}><App /></PropsContext.Provider>), [props])
};
结合项目数据流
参考 react-redux hooks。
debounce 优化
比如当输入框频繁输入时,为了保证页面流畅,我们会选择在 onChange
时进行 debounce
。然而在 Function Component 领域中,我们有更优雅的方式实现。
其实在 Input 组件
onChange
使用debounce
有一个问题,就是当 Input 组件 受控 时,debounce
的值不能及时回填,导致甚至无法输入的问题。
我们站在 Function Component 思维模式下思考这个问题:
- React scheduling 通过智能调度系统优化渲染优先级,我们其实不用担心频繁变更状态会导致性能问题。
- 如果联动一个文本还觉得慢吗?
onChange
本不慢,大部分使用值的组件也不慢,没有必要从onChange
源头开始就debounce
。 - 找到渲染性能最慢的组件(比如 iframe 组件),对一些频繁导致其渲染的入参进行
useDebounce
。
下面是一个性能很差的组件,引用了变化频繁的 text
(这个 text
可能是 onChange
触发改变的),我们利用 useDebounce
将其变更的频率慢下来即可:
const App: React.FC = ({ text }) => {// 无论 text 变化多快,textDebounce 最多 1 秒修改一次const textDebounce = useDebounce(text, 1000)return useMemo(() => {// 使用 textDebounce,但渲染速度很慢的一堆代码}, [textDebounce])
};
使用 textDebounce
替代 text
可以将渲染频率控制在我们指定的范围内。
useEffect 注意事项
事实上,useEffect
是最为怪异的 Hook,也是最难使用的 Hook。比如下面这段代码:
useEffect(() => {props.onChange(props.id)
}, [props.onChange, props.id])
如果 id
变化,则调用 onChange
。但如果上层代码并没有对 onChange
进行合理的封装,导致每次刷新引用都会变动,则会产生严重后果。我们假设父级代码是这么写的:
class App {render() {return <Child id={this.state.id} onChange={id => this.setState({ id })} />}
}
这样会导致死循环。虽然看上去 <App>
只是将更新 id 的时机交给了子元素 <Child>
,但由于 onChange
函数在每次渲染时都会重新生成,因此引用总是在变化,就会出现一个无限死循环:
新 onChange
-> useEffect
依赖更新 -> props.onChange
-> 父级重渲染 -> 新 onChange
…
想要阻止这个循环的发生,只要改为 onChange={this.handleChange}
即可,useEffect
对外部依赖苛刻的要求,只有在整体项目都注意保持正确的引用时才能优雅生效。
然而被调用处代码怎么写并不受我们控制,这就导致了不规范的父元素可能导致 React Hooks 产生死循环。
因此在使用 useEffect
时要注意调试上下文,注意父级传递的参数引用是否正确,如果引用传递不正确,有两种做法:
- 使用 useDeepCompareEffect 对依赖进行深比较。
- 使用
useCurrentValue
对引用总是变化的 props 进行包装:
function useCurrentValue<T>(value: T): React.RefObject<T> {const ref = React.useRef(null);ref.current = value;return ref;
}const App: React.FC = ({ onChange }) => {const onChangeCurrent = useCurrentValue(onChange)
};
onChangeCurrent
的引用保持不变,但每次都会指向最新的 props.onChange
,从而可以规避这个问题。
总结
如果还有补充,欢迎在文末讨论。
如需了解 Function Component 或 Hooks 基础用法,可以参考往期精读:
- 精读《React Hooks》
- 精读《怎么用 React Hooks 造轮子》
- 精读《useEffect 完全指南》
- 精读《Function Component 入门》
讨论地址是:精读《React Hooks 最佳实践》 · Issue #202 · dt-fe/weekly
相关文章:
精读《React Hooks 最佳实践》
简介 React 16.8 于 2019.2 正式发布,这是一个能提升代码质量和开发效率的特性,笔者就抛砖引玉先列出一些实践点,希望得到大家进一步讨论。 然而需要理解的是,没有一个完美的最佳实践规范,对一个高效团队来说&#x…...

varFormatter 数据格式化库 以性能优先的 快速的 内存对象格式转换
varFormatter 数据格式化 技术 开源技术栏 对象/变量格式化工具库,其支持将一个对象进行按照 JSON XML HTML 等格式进行转换,并获取到结果字符串! 目录 文章目录 varFormatter 数据格式化 技术目录介绍获取方式 使用实例格式化组件的基本使…...

基于PHP的在线英语学习平台
有需要请加文章底部Q哦 可远程调试 基于PHP的在线英语学习平台 一 介绍 此在线英语学习平台基于原生PHP开发,数据库mysql。系统角色分为学生,教师和管理员。(附带参考设计文档) 技术栈:phpmysqlphpstudyvscode 二 功能 学生 1 注册/登录/…...
基于微信小程序电影院订票选座系统 (后台JSP+JDBC+Mysql)答辩常规问题和如何回答(答辩指导)
博主介绍:黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程ÿ…...
C++知识点总结(22):模拟算法真题 ★★★☆☆《安全警报》
安全警报 1. 审题 题目描述 Z市最大的金融公司:太平洋金融遭到了入侵,一名黑客潜入到了公司中,公司紧急启动安保程序,将大楼封锁,并安排作为安全主管的你对楼层进行搜查。所以你准备写一个程序,输入搜查楼…...

蓝桥杯练习系统(算法训练)ALGO-993 RP大冒险
资源限制 内存限制:64.0MB C/C时间限制:200ms Java时间限制:600ms Python时间限制:1.0s 问题描述 请尽情使用各种各样的函数来测试你的RP吧~~~ 输入格式 一个数N表示测点编号。 输出格式 一个0~9的数。 样例输入 0 样…...

Unity的相机跟随和第三人称视角
Unity相机跟随和第三人称视角 介绍镜头视角跟随人物方向进行旋转的镜头视角固定球和人的镜头视角 思路跟随人物方向进行旋转的镜头视角固定球和人的镜头视角 镜头旋转代码人物移动的参考代码注意 介绍 最近足球项目的镜头在做改动,观察了一下实况足球的视角&#x…...

哪个超声波清洗机品牌值得入手?销量榜品牌值得选购!
在科技日益发展的今天,超声波清洗技术以其高效、便捷和深度清洁的特点,已经深入到生活的诸多领域,从精密仪器到珠宝首饰,从眼镜框到假牙,甚至是厨房用品的日常护理,都能见到超声波清洗机的身影。面对市场上…...

Stwo:基于Circle STARK和M31的下一代STARK证明系统
1. 引言 StarkWare团队和Polygon Labs团队,历时数月,构造了基于Mersenne素数域M31的Circle STARK协议,通过使用M31 over a circle,可基于任意有限域构造高效STARKs,具体见2024年2月19日论文《Circle STARKs》。 基于…...
笔记本以太网集线器Hub充电可能导致网络异常(貌似是我把服务器网关写错了)
文章目录 笔记本以太网集线器(Hub)充电导致网络异常概述原理分析电源与信号干扰设备热度设备兼容性问题 解决方案升级固件提高设备散热效率选择兼容性好的设备 总结 今天用笔记本以太网直连服务器,一开始能连通,结果以太网hub插上…...
npm ERR! code ETIMEDOUT
在新建vue项目的时候报错 npm ERR! code ETIMEDOUT npm ERR! syscall connect npm ERR! errno ETIMEDOUT npm ERR! network request to https://registry.npmjs.org/vue%2fcli failed, reason: connect ETIMEDOUT 104.16.2.35:443 npm ERR! network This is a problem relate…...

动态规划(算法竞赛、蓝桥杯)--分组背包DP
1、B站视频链接:E16 背包DP 分组背包_哔哩哔哩_bilibili #include <bits/stdc.h> using namespace std; const int N110; int v[N][N],w[N][N],s[N]; // v[i,j]:第i组第j个物品的体积 s[i]:第i组物品的个数 int f[N][N]; // f[i,j]:前i组物品,能放…...

太阳能供电井盖-物联网智能井盖监测系统-旭华智能
在这个日新月异的科技时代,城市的每一个角落都在悄然发生变化。而在这场城市升级的浪潮中,智能井盖以其前瞻性的科技应用和卓越的安全性能,正悄然崭露头角,变身马路上的智能“眼睛”,守护城市安全。 传统的井盖监测系统…...
贪心 Leetcode 455 分发饼干
分发饼干 Leetcode 455 学习记录自代码随想录 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸࿱…...
策略开发:EMA如何计算
EMA的计算原理 EMA 是MA(平滑移动平均线)的另一种形式。全名“加权指数移动平均线”。 2/13就是12日移动平均线的平滑因子,他的意思是指:给予新价格 2/13的权重,给予过去的EMA 11/13的权重。 在计算的时候第一天的M…...
学习Android的第二十天
目录 Android Toast 吐司 常量 常用方法 例子 Android Notification 状态栏通知 Notification 的基本布局 扩展布局 Notification ( 状态栏通知 ) 相关的方法 例子: 参考文档 Android AlertDialog 弹出框 Android Dialog 继承图谱 AlertDialog 几种常…...

Linux技巧|centos7|重新认识和学习egrep和grep命令
前言: 相信提高文本检索工具,大家脑海里肯定有很多工具会自动跳出来,比如,grep,egrep,sed,cat,more,less,cut,awk,vim,vi…...

css实现背景渐变叠加
线性渐变效果图: .box{width: 100vw;height: 100vh;background:linear-gradient(to bottom,transparent,#fff 30%),linear-gradient(to right,pink,skyblue);}径像渐变效果图: .box{width: 100vw;height: 100vh;background:linear-gradient(to bottom,transparent,#…...

Unity(第二十四部)UI
在游戏开发中,用户界面(UI)是至关重要的一部分。它负责与玩家进行交互,提供信息,并增强游戏的整体体验。Unity 提供了强大的工具和功能来创建和管理 UI。 ui的底层就是画布,创建画布的时候会同时创建一个事…...

VSCode通过SSH连接Docker环境进行开发
文章目录 VSCode 插件Docker 镜像构建镜像部署环境 VSCode 连接本地Docker容器VSCode SSH连接Docker容器VSCode 打开容器内目录文件 VSCode 插件 Remote - SSH Docker 镜像 https://hub.docker.com/_/golang # Golang 镜像 docker pull golang:1.22构建镜像 Dockerfile F…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...
React---day11
14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store: 我们在使用异步的时候理应是要使用中间件的,但是configureStore 已经自动集成了 redux-thunk,注意action里面要返回函数 import { configureS…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...

基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...

数据结构:递归的种类(Types of Recursion)
目录 尾递归(Tail Recursion) 什么是 Loop(循环)? 复杂度分析 头递归(Head Recursion) 树形递归(Tree Recursion) 线性递归(Linear Recursion)…...

【Post-process】【VBA】ETABS VBA FrameObj.GetNameList and write to EXCEL
ETABS API实战:导出框架元素数据到Excel 在结构工程师的日常工作中,经常需要从ETABS模型中提取框架元素信息进行后续分析。手动复制粘贴不仅耗时,还容易出错。今天我们来用简单的VBA代码实现自动化导出。 🎯 我们要实现什么? 一键点击,就能将ETABS中所有框架元素的基…...

使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...
OCR MLLM Evaluation
为什么需要评测体系?——背景与矛盾 能干的事: 看清楚发票、身份证上的字(准确率>90%),速度飞快(眨眼间完成)。干不了的事: 碰到复杂表格(合并单元…...