背景
React
在v16
中推出了一项重大更新: Hooks
. 以往在React
组件开发中, 往往要区分函数式组件和类组件的定义和使用, 函数式组件纯展示无副作用, 类组件里面可以保存状态
但在Hooks
推出后, 函数式组件除了在某些特殊场景下无法使用(ErrorBoundary
/getSnapshotBeforeUpdate
), 其他情况下都可以完美替代Class
组件了, 今天来看看它是如何替换的
生命周期
先来看看React 16
的最新生命周期, 和之前版本相比有些小的调整, 旧版的可以参考这一份文章, 主要是去掉了部分方法和和并了一些获取props
更新的方法
去掉的方法
componentWillMount
componentWillUpdate
componentWillReceiveProps
这些方法的功能都被统一集中到了getDerivedStateFromProps
中, 同时还新增了getSnapshotBeforeUpdate
来解决异步渲染时的dom
操作问题, 总体大的生命周期还是一致的
Hooks
先根据React
官方文档简单介绍一下各Hooks
函数的作用
Basic Hooks
useState
: 保存状态, 状态变化时会触发重新渲染
useEffect
: 监听值变化
useContext
: 跨组件传值, 状态变化时会触发重新渲染
Additional Hooks
useReducer
: useState
的高级版, 更适合管理复杂状态
useCallback
: 局部缓存, 往往用于缓存函数
useMemo
: 局部缓存, 往往用于缓存值
useRef
: 获取一个脱离于生命周期的指针, 可以设置为任何值, 修改不会触发重新渲染
useImperativeHandle
: 用于包装函数式组件的父组件获取到的引用值(父组件调用子组件方法), 需要配合forwardRef
一起使用
useLayoutEffect
: useEffect
的升级版, 在React
实际渲染之前触发, 会阻塞渲染, 用于特殊情况下减少页面闪烁
useDebugValue
: 与React
开发者工具配合调试时使用, 可以用来显示状态等
模拟生命周期
constructor
一般在constructor
里都是初始化state
或者从props
里读取某些值, 在函数式组件里可以直接使用, 不需要特殊处理, 初始化state
可以使用setState
1
| const [nameState, setNameState] = useState(props.name);
|
componentDidMount
1 2 3
| useEffect(() => { }, []);
|
componentWillReceiveProps
1 2 3 4 5 6 7
| useMemo(() => { }, [props.name]);
useEffect(() => { }, [props.name]);
|
shouldComponentUpdate
1 2 3 4 5 6 7
| useCallback(() => { }, [props.name]);
useMemo(() => { }, [props.name]);
|
componentWillUnmount
1 2 3 4 5
| useEffect(() => { return () => { }; }, []);
|
componentDidUpdate
这是实现起来比较复杂的一个方法, 需要结合多个Hooks
才能满足条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const mounted = useRef(); useEffect(() => { if (!mounted.current) { mounted.current = true; } else { } });
export const useComponentDidUpdate = (effect, dependencies) => { const mounted = useRef(false);
useEffect(() => { if (!mounted.current) { mounted.current = true; return; } effect(); }, dependencies); };
|
其他场景
和上一次值对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { useEffect, useRef, DependencyList } from 'react';
const useUpdate: (effect: (deps?: DependencyList) => void | ((deps?: DependencyList) => void | undefined), deps?: DependencyList) => void = ( effect, deps, ) => { const mountedRef = useRef<{ data: DependencyList; init: boolean }>({ data: null, init: false }); useEffect(() => { const preDeps = mountedRef.current.data; mountedRef.current.data = deps; if (!mountedRef.current.init) { mountedRef.current.init = true; } else { const returnFn = effect(preDeps); return () => returnFn && returnFn(preDeps); } }, deps); };
export default useUpdate;
|
提供方法给父组件调用
1 2 3 4 5 6 7 8 9 10 11
| const C = forwardRef((props, ref) => { useImperativeHandle( ref, () => ({ func: async () => { }, }), [], ); });
|
获取内部节点
1 2
| const divRef = useRef < HTMLDivElement > null; return <div ref={divRef}></div>;
|
错误处理
错误处理只有类组件才能实现, 在项目中往往在最外层和各容器层包裹一个通用错误处理组件即可
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
| class ErrorBoundary extends Component<{}, { hasError: boolean }> { constructor(props: {}) { super(props); this.state = { hasError: false }; }
static getDerivedStateFromError(error: any) { console.error(error); return { hasError: true }; }
componentDidCatch(error: Error, info: ErrorInfo) { console.error(error, info); }
render() { if (this.state.hasError) { return <Result status='500' title='500' subTitle='Sorry, something went wrong.' />; } return this.props.children; } }
export default ErrorBoundary;
|