之前的文章中总结过 React Hooks常用的钩子useState、useContext、useReducer、useEffect的用法,今日趁着工作不忙,再次详细了解下 Hooks
简介
什么是Hooks
Hooks是一个新的React特性提案,组件尽量写成纯函数,如果需要外部React特性(比如状态管理,生命周期),就用钩子把外部特性"钩"进来,通常函数名字都是以use开头。首次在v16.7.0-alpha版本中添加,在v16.8.0中正式发布。
Hooks产生的背景
- 跨组件复用stateful logic十分困难
使用Hooks,你可以在将含有state的逻辑从组件中抽象出来,这将可以让这些逻辑容易被测试。同时,Hooks可以帮助你在不重写组件结构的情况下复用这些逻辑。
- 复杂的组件难以理解
Hooks允许您根据相关部分(例如设置订阅或获取数据)将一个组件分割成更小的函数,而不是强制基于生命周期方法进行分割
- 不止是用户,机器也对Classes难以理解
Hooks让你可以在classes之外使用更多React的新特性
常用Hooks
-
useState
1
2
3 1let [count, setCount] = useState(100);
2
3
-
useEffect
1
2
3
4
5 1useEffect(()=>{
2 return ()=>{}
3}, [])
4
5
-
useLayoutEffect
-
useEffect 在全部渲染完毕后才会执行
- useLayoutEffect 会在 浏览器 layout 之后,painting 之前执行
- 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect
- 可以使用它来读取 DOM 布局并同步触发重渲染
- 在浏览器执行绘制之前 useLayoutEffect 内部的更新计划将被同步刷新
- 会阻塞页面的渲染,如果在里面执行耗时任务的话,页面就会卡顿
-
useContext
直接获取祖先元素通过createContext创建的context
-
useReducer
1
2
3 1const [state, dispatch] = useReducer(reducer, initialState, init);
2
3
-
memo
-
useMemo
-
useCallback
-
memo,useMemo和useCallback在优化组件的应用场景
-
在子组件不需要父组件值和函数的情况下,使用memo包裹即可
- 如果是函数作为props,可以使用useCallback保证不会反复修改
- 如果是值作为props,可以使用useMemo保证值不会反复修改
-
useRef
不符合capture values,本身不会变化,存储的.current会变化
- useImperativeHandle
配合forwardRef使用,用于自定义通过ref给父组件暴露的值
- useDebugValue
用于开发者工具调试
封装自定义Hooks
封装hooks获取窗口大小
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 1// useWinSize
2import React, { useState ,useEffect ,useCallback } from 'react';
3
4export default function useWinSize(){
5 const [ size , setSize] = useState({
6 width:document.documentElement.clientWidth,
7 height:document.documentElement.clientHeight
8 })
9
10 const onResize = useCallback(()=>{
11 setSize({
12 width: document.documentElement.clientWidth,
13 height: document.documentElement.clientHeight
14 })
15 },[])
16
17 useEffect(()=>{
18 window.addEventListener('resize',onResize)
19 return ()=>{
20 window.removeEventListener('resize',onResize)
21 }
22 },[])
23
24 return size;
25}
26
27// demo.js
28import useWinSize from './useWinSize'
29
30export default function(){
31 const size = useWinSize()
32 return (
33 <div>页面Size:{size.width}x{size.height}</div>
34 )
35}
36
37
延迟设置值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 1import React, {useState, useRef, useEffect} from 'react'
2
3export const useDelayState = (initialState)=>{
4 const [state, setState] = useState(initialState);
5 const ref = useRef();
6 const delaySetState = (value, delay)=>{
7 ref.current = setTimeout(()=>{
8 setState(value)
9 }, delay)
10 }
11 useEffect(()=>{
12 return ()=>{
13 clearTimeout(ref.current)
14 }
15 }, [])
16 return [state, delaySetState, setState]
17}
18
19
GeoLocation获取地理定位
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 1import { useEffect, useState } from 'react';
2
3const useGeolocation = options => {
4 const [state, setState] = useState({
5 loading: true,
6 accuracy: null,
7 altitude: null,
8 altitudeAccuracy: null,
9 heading: null,
10 latitude: null,
11 longitude: null,
12 speed: null,
13 timestamp: Date.now(),
14 });
15 let mounted = true;
16 let watchId: any;
17
18 const onEvent = event => {
19 if (mounted) {
20 setState({
21 loading: false,
22 accuracy: event.coords.accuracy,
23 altitude: event.coords.altitude,
24 altitudeAccuracy: event.coords.altitudeAccuracy,
25 heading: event.coords.heading,
26 latitude: event.coords.latitude,
27 longitude: event.coords.longitude,
28 speed: event.coords.speed,
29 timestamp: event.timestamp,
30 });
31 }
32 };
33 const onEventError = (error) =>
34 mounted && setState(oldState => ({ ...oldState, loading: false, error }));
35
36 useEffect(() => {
37 navigator.geolocation.getCurrentPosition(onEvent, onEventError, options);
38 watchId = navigator.geolocation.watchPosition(onEvent, onEventError, options);
39
40 return () => {
41 mounted = false;
42 navigator.geolocation.clearWatch(watchId);
43 };
44 }, []);
45
46 return state;
47};
48export default useGeolocation;
49
50
更多hooks
Hooks的使用注意事项
- 只能在顶层调用Hooks?
Hooks是使用数组或单链表串联起来,Hooks顺序修改会打乱执行结果
- useState在多个组件中引入,彼此之间会不会有影响?
在React中Hooks把数据存在fiber node上的,每个组件都有自己的currentlyRenderingFiber.memoizedState
Hooks的问题
- Hooks能解决组件状态的复用问题,但没有很好的解决JSX复用问题
- React Hooks模糊了生命周期的概念,但也带来了更高门槛的学习(Hooks生命周期的理解、Hooks Rules的理解、useEffect依赖项的判断等)
- 类拥有比函数更丰富的表达能力(OOP),Function Component容易使代码逻辑混乱
Hooks的原理
- 单向链表通过next把hooks串联起来
- memoizedState存在fiber node上,组件之间不会相互影响
- useState和useReducer中通过dispatchAction调度更新任务
React Fiber介绍
触发React重新渲染
- ReactDOM.render
- setState
- forceUpdate
- React Hooks的dispatchAction
重新渲染机制
React 框架内部的运作可以分为3层:
- Virtual DOM层,描述页面长什么样
- Reconciler层,负责调用组件生命周期方法,进行Diff运算
- Renderer层,根据不同的平台,渲染出相应的页面,比较常见的是ReactDOM和ReactNative
React 16.0前后Reconciler层的改动:
- 之前:React会遍历应用的所有节点,计算出差异,然后再更新UI,整个过程不能被打断,一旦用的时机超过16毫秒,就容易出现掉帧的现
- 之后:引入Fiber Reconciler,依赖requestIdleCallback
把调和过程分成两个阶段
阶段一,生成 Fiber 树,得出需要更新的节点信息。这一步是一个渐进的过程,可以被打断
阶段二,将需要更新的节点一次过批量更新,这个过程不能被打断
图解两者的区别
stack reconciler
fiber reconciler
Fiber Reconciler 在阶段一进行 Diff 计算的时候,会生成一棵 Fiber 树。这棵树是在 Virtual DOM 树的基础上增加额外的信息来生成的(hooks依赖的memoizedState链表),它本质来说是一个链表。
延展思考
React Hooks与Vue Composition API有什么异曲同工之妙