React Hooks常见用法

背景

Reactv16中推出了一项重大更新: 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(() => {
// code
}, []);

componentWillReceiveProps

1
2
3
4
5
6
7
useMemo(() => {
// return new value
}, [props.name]);

useEffect(() => {
// code
}, [props.name]);

shouldComponentUpdate

1
2
3
4
5
6
7
useCallback(() => {
// return new value
}, [props.name]);

useMemo(() => {
// return new value
}, [props.name]);

componentWillUnmount

1
2
3
4
5
useEffect(() => {
return () => {
// code
};
}, []);

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) {
// do componentDidMount logic
mounted.current = true;
} else {
// do componentDidUpdate logic
}
});

// 或者实现自定义hooks
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 () => {
// code
},
}),
[],
);
});

获取内部节点

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) {
// sendErrorToRemote();
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;
作者

Mosby

发布于

2019-02-21

许可协议

评论