1.hooks使用规则
Hooks can only be called inside the body of a function component
a.只在最顶层使用 Hook
b.不要在循环,条件或嵌套函数中调用 Hook
c.只在 React 函数中调用 Hook, 不要在普通的 JavaScript 函数中调用 Hook。
✅ 在 React 的函数组件中调用 Hook
✅ 在自定义 Hook 中调用其他 Hook
遵循此规则,确保组件的状态逻辑在代码中清晰可见。
2.hooks原理
-
为什么只能在函数最外层调用 Hook,不要在循环、条件判断或者子函数中调用?
-
为什么 useEffect 第二个参数是空数组,就相当于 ComponentDidMount ,只会执行一次?
-
自定义的 Hook 是如何影响使用它的函数组件的?
-
Capture Value 特性是如何产生的?
3.测试
react-dom/test-utils,react-test-renderer,我们在测试中应该怎么选择测试工具呢。
再做选择前,首先我们聊聊,什么是renderer。react最早是被用来开发网页的,所以早期React库中还包含了大量和DOM相关的逻辑。后来React的设计思想慢慢被迁移到其它场景,最被人们熟知的莫过于React Native了。为了灵活性和扩展性,React的代码被分拆为React核心代码与各种renderer。React自带了3个renderer,前两个是大家常见的:
- react-dom 负责将组件渲染到浏览器页面中。
- react-native-renderer 负责将组件渲染成原生场景中的各种View
- react-test-renderer 负责将组件输出成JSON对象以方便我们便利、断言或是进行snapshot测试
这里有一个各种renderer列表
react-dom/test-utils从名称可以看出这个库是包含在react-dom中的。所以他只是react-dom的辅助测试工具。该库中的方法主要是帮助我们遍历ReactDom生成的DOM树,方便我们编写断言。使用该库时必须提供一个DOM环境,这个DOM环境可以是jsdom模拟环境
如何选择:简单来说
- 如果需要测试事件(click,change,blur等),使用react-dom/test-utils
- 其他时候使用更简单、更灵活的react-test-renderer
react-test-renderer分为:
- shallow render:组件只会被render一层(children中的React组件不会被render)
- full render:组件会被完全render
其中Enzyme的底层其实也是基于react-test-renderer和react-dom/test-utils的,但它在二者的基础上进行了分装提供了更加简单易用的查询、断言方法。在Enzyme中提供了三种render模式:
- Shadow Rendering 对应 react-test-renderer/shallow
- Full DOM Rendering 对应 react-dom/test-utils
- Static Rendering 对应 react-test-renderer
4.useState同步异步
异步更新,同步执行
5.合成事件
6.自定义hook
1. useDebounce
import { useRef, useCallback, useEffect } from 'react';interface IPoint<S> { func: (args: S) => void; timer: NodeJS.Timeout;}// 当事件触发后,一定时间内不再触发该事件,而如果重新触发,就会重新开始延长,debounce也分为两种,在延长周期前生效,和周期末生效function useDebounce<S>(func: (args: S) => void, wait: number, isImmediate = false) { const a: IPoint<S> = { func, timer: null }; const { current } = useRef(a); useEffect(() => { current.func = func; }, [current, func]); return useCallback( (...args) => { if (current.timer) clearTimeout(current.timer); // 清除之前的时间延迟执行 if (isImmediate) { if (!current.timer) { func.apply(this, args); current.timer = null; } current.timer = setTimeout(() => { current.timer = null; }, wait); } else { current.timer = setTimeout(() => { func.apply(this, args); }, wait); } }, [current, func, isImmediate, wait], );}export default useDebounce;
2. useSyncState
import { useState, useRef, useCallback } from 'react'
const useSyncState = ( initVal ) => {
// 状态值
const [ state, setState ] = useState(initVal)
// 变量值
let current = useRef(initVal)
// 同步 state 和 current 的值
const setState = useCallback(( changeVal ) => {
current.current = changeVal
setState(changeVal)
}, [])
// 返回一个数组
return [ state, setState, current ]
}
3. useCountdown
import { useCallback, useEffect, useReducer, useState } from 'react';const useCountdown = (initCount: number, interval: number, callback?: () => void) => { const [count, setCount] = useState(initCount - Date.now()); // 一个增长的计时器,用于重置倒计时 const [ignored, forceUpdate] = useReducer(x => x + 1, 0); const reset = useCallback(() => { setCount(initCount - Date.now()); forceUpdate(); }, [initCount]); useEffect(() => { const time = setInterval(() => { setCount(preCount => { if (initCount <= Date.now()) { clearInterval(time); if (callback) { const t = setTimeout(() => { callback(); // NOTE:魔法数字100,延迟执行,防止组件销毁的比组件内部操作还早,从而出现警告 clearTimeout(t); }, 100); } return 0; } // BUGFIX:解决后台运行的时候,倒计时停止 return initCount - Date.now(); }); }, interval); return () => { clearInterval(time); }; }, [interval, ignored]); return [count, reset] as const;};